32bit・64bitのインストーラをまとめて作る

環境:Visual Studio 2012 Professional、Wix Toolset 3.6

状況:

  1. 開発しているアプリ群は複数のソリューションで構成される。
  2. 複数のアプリを1つのインストーラで配布する。
  3. 1度のビルドで32bitOS向けと64bitOS向けの両方のインストーラを作成する。
  4. 一部のソリューションにのみ32bit向けと64bit向けで分けるコンポーネントがあり、大半は64bit向けでも32bitと同じコンポーネントをインストールする。

まず1と2は簡単。

全てのソリューションにWixライブラリプロジェクトを追加して、それぞれ必要なコンポーネントをwxsファイルに定義する。生成されたwixlibファイルを最後にまとめてビルド後イベントでlight.exeでリンクして完成。

ビルド後イベントはこんな感じ:

set light="<light.exeのパス>"
set MsiName="<出力するMSIインストーラのファイル名>"
set SampleLib="<リンクするwixlibのパス>"

%light% -ext WixFirewallExtension -sice:ICE09 -sice:ICE80 -out %MsiName% %SampleLib% …

サーバビルドだとローカルビルドとは出力されるwixlibのパスが違ったりするので注意。必要に応じてファイアウォールとかの拡張やらICE警告の抑制とかもlightのオプションで指定する。

Product要素を含むメインのwxsファイル(私はProduct.wxsとしている)と、どのアプリにも属さないようなファイルの配置・設定・共通で使用するディレクトリの定義については、インストーラのソリューションとしてアプリとは別にした。ほかのアプリのソリューションを全てビルドし終わってから、最後にインストーラのソリューションをビルドし、ビルド後イベントで全wixlibをリンクするという流れ。

尚、以後で述べる32bit向けと64bit向けで分ける必要のあるコンポーネントインストーラのソリューションに含まれているとする。

 

問題は3と4。

できるだけ同じwxsファイルをそのまま32bitと64bit両方で使いたいので、Package要素のPlatformは指定せずにcandle.exeの-archオプションで切り替えることになる。VSを使っているので、Wixプロジェクトのプロパティを開いて、コンパイル時の追加オプションで書いておくとcandle.exeに渡る。

まず試したのは(うまく行かなかったけど)、32bitと64bitで分けるコンポーネントを含むプロジェクト(Installer.wixprojとする)について、ビルド時のプラットフォームの指定がx86かx64かで、-archの指定を切り替える方法。現状サーバビルドはRelease|Any CPU指定でビルドしているので、ソリューションを2つ用意して構成マネージャでビルドするプラットフォームを以下のように切り替えてみた。

つまり

Installer.sln

Release|Any CPU

 

Installer.wixproj

Release|x86

-arch x86

Installer64.sln

Release|Any CPU

 

Installer.wixproj

Release|x64

-arch x64

と設定した。

これでサーバビルドでInstaller.slnとInstaller64.slnを両方ビルドすれば、ビルド結果のInstaller\Installer.wixlibは32bit用、Installer64\Installer.wixlibは64bit用になるのではと考えた。(ビルド結果はソリューション名のフォルダに格納する設定にしてある)

しかし、最終的にビルド結果はソリューションごとに分けられて格納されても、ビルド時にはプロジェクトごとのbin\Releaseフォルダにまず出力されるので、1度ビルドしたプロジェクトは2度目以降はビルド済みだからとスキップされてしまい両方のwixlibが同じファイルになってしまった。

プロジェクトのプロパティで-archを指定するためには32bit用と64bit用でWixプロジェクトを分ける必要があることが分かったので、方針を変更してInstaller.slnに32bit用のInstaller.x86.wixprojとInstaller.x64.wixprojを作成して、それぞれ-arch x86と-arch x64を指定。

Installer.sln

Release|Any CPU

 

Installer.wixproj

Release|x86(Any CPUとするほうが適切かも)

-arch指定なし

Installer.x86.wixproj

Release|x86

-arch x86

Installer.x64.wixproj

Release|x64

-arch x64

と設定した。それぞれのwixプロジェクトは分かりやすいようにx86かx64のプラットフォームの設定しか持たないようにしてある。

wxsファイルは共通にしたいので、Installer.x86.wixprojが代表してwxsファイルを持つようにして、Installer.x64.wixprojにはInstaller.x86.wixprojの全てのwxsファイルを「リンクとして追加」して持つようにした。分かりやすくするため、32bit/64bitで分けなくていいファイルは共通で使うInstaller.wixprojに入れておく。

こうすると、32bit向けの作成時はInstaller.wixlibとInstaller.x86.wixlibをリンクし、64bit向けではInstaller.wixlibとInstaller.x64.wixlibをリンクすればよい。

 

ちなみにプラットフォームがx86かx64かによってSystemFolder/System64FolderだとかProgramFilesFolder/ProgramFiles64Folderを切り替えたり、コンポーネントWin64属性をno/yesで切り替えたりする必要があるが、以下のようにプリコンパイラで変数の値を切り替えるようにする。

<?if $(var.Platform) = x86 ?>
   <?define SystemDir = "SystemFolder" ?>
   <?define ProgramFilesDir = "ProgramFilesFolder" ?>
   <?define Win64 = "no" ?>
<?elseif $(var.Platform) = x64 ?>
   <?define SystemDir = "System64Folder" ?>
   <?define ProgramFilesDir = "ProgramFiles64Folder" ?>
   <?define Win64 = "yes" ?>
<?endif ?>

またはコンパイル時のオプションで変数を定義してしまう。WiXプロジェクトのプロパティに変数を定義できる箇所があるのでそこに書くとcandle.exeに渡る。

上に書いたプリコンパイラへの命令は、変数を使うwxsファイル全てで書くかまたはwxiファイルにしてしまってそれをincludeしないといけないが、コンパイル時のオプションで定義すればそのプロジェクト内では全てのwxsファイルで使える。お好みで。

変数が定義されていれば、コンポーネントでは

<Component Win64="$(var.Win64)" Directory="$(var.ProgramFilesDir)" … />

などとしておけば、動的に値が切り替わった状態でコンパイルされる。

以下のページが参考になった。

x64 - Using Wix to create 32bit and 64bit installers from one .wxs file - Stack Overflow

 

ちょっと横道にそれるが、32bit環境と64bit環境ではパスの関係がややこしくて

 

32bit

64bit

SystemFolder

system32

sysWOW64

System64Folder

-

System32

ProgramFilesFolder

Program Files

Program Files (x86)

ProgramFiles64Folder

-

Program Files

Property Reference (Windows)

という対応になっているので、インストールしたいディレクトリを適切に指定する必要がある。64bit環境で32bitプロセスからsystem32にアクセスしたら勝手にsysWOW64にリダイレクトされたりもするので64bit対応は色々めんどい。ちゃんと検証する必要がある。

System64FolderやProgramFiles64Folderのように64bit専用の場所にファイルをインストールする場合はWin64属性がyesになっていないと警告がでる。32bitのコンポーネントだけど64bit用のディレクトリが指定されているよ、と。ディレクトリ指定もWin64属性指定も固定ならベタ書きだし、32bitと64bitで普通に変えるなら変数で。もうちょっと複雑なら<?if ?>でプリコンパイラに上手いことやらせましょう。

もう1つ、Win64属性に関連して注意事項。

おそらくプラットフォームとしてx64を指定していると、そのプロジェクト内のコンポーネントWin64="yes"として扱われるように思う。Win64属性のリファレンスをみると既定はnoと書いてあるのだけど、実際x64を指定しているプロジェクトからWin64属性を指定しないでSystemFolderにファイルをインストールしようとすると、64bitのコンポーネントだけど32bit用のディレクトリが指定されているよと警告されてしまい、Win64="no"を明示的に指定することで警告はでなくなったのでそういうことだと思う。

Component Element

 

さて、これでもう行けるだろうと思っていたのにまだ詰まる。
-arch x64と指定しているのに、以下のICE80のエラーが出てビルドが失敗する。
This package contains 64 bit component '[1]' but the Template Summary Property does not contain Intel64 or x64.

ICE80 (Windows)

要はプラットフォームとしてx64が指定されていないよ、と。確かにPackageのPlatformは指定していないがPlatformのドキュメントをみると-arch x64でも同じ意味になると書かれているし、ググってもみんな普通に-arch x64指定で上手くいってる書き込みしか見つからない。かなり悩んだ。

いろいろ試したところ、Product要素を含むwxsをビルドするプロジェクトのプロパティで-arch x64を指定しないといけないことが分かった。Installer.wixprojにProduct.wxsを入れていたのが悪かったらしい。Product.wxsもInstaller.x86.wixprojに移動し、Installer.x64.wixprojにも「リンクとして追加」することで解決。

 

最後に、ビルド後イベントで以下のように両方のリンクを行うようにして完成!よかった!帰れる!

set light="<light.exeのパス>"
set MsiNameX86="<出力するx86版のMSIインストーラのファイル名>"
set MsiNameX64="<出力するx64版のMSIインストーラのファイル名>"
set InstallerLib="<Installer.wixlibのパス>"
set InstallerLibX86="<Installer.x86.wixlibのパス>"
set InstallerLibX64="<Installer.x64.wixlibのパス>"

%light% -ext WixFirewallExtension -sice:ICE09 -sice:ICE80 -out %MsiNameX86% %InstallerLib% %InstallerLibX86% …
%light% -ext WixFirewallExtension -sice:ICE09 -sice:ICE80 -out %MsiNameX64% %InstallerLib% %InstallerLibX64% …