vcpkg で x86-windows-static-md なNuGetパッケージを生成して使おうとして嵌った件

# はじめに

WindowsのC/C++ネイティブ開発環境でオープンソースライブラリを利用する場合、NuGetから取ってるくるのが手っ取り早いが、Nuget上にあるものの大半が非公式な野良パッケージであり、バージョンが古かったり、信頼性に乏しく利用に躊躇してしまう。
そこで、Microsoft C++ チームが保守しているという触れ込みの vcpkg を利用して、オープンソースライブラリをNuGetパッケージ化(.nupkg)して利用することにした。

スマートで良い方法だと思ったのだが、vcpkgには罠が多数仕掛けられていた。それらに全て嵌った体験記である。 

## 作業環境
- Windows10 21H1
- Visual Studio Community 2019: Version 16.11.3

## vcpkgとは

https://vcpkg.io/en/index.html
> About vcpkg
vcpkg is a free C/C++ package manager for acquiring and managing libraries. Choose from over 1500 open source libraries to download and build in a single step or add your own private libraries to simplify your build process. Maintained by the Microsoft C++ team and open source contributors.

上記公式サイトの説明通り、C/C++ 用のパッケージマネージャである。
元々は Visual Studio(msbuild)用であったが、CMakeにも対応したことで linux,osx,mingw,android,ios 用にも使えるものになっているらしい。
有名なオープンソースを網羅しており、私が必要としていた zlib/minizip, sqlite3, libpng, freetype はすべて最新バージョンが揃っている。
対応パッケージが [オンライン](https://vcpkg.io/en/packages.html) で検索できるのも素晴らしい。
Microsoft C++ チーム保守で安心感もある。

## vcpkg本体のインストール

vcpkg公式サイトの[Get started](https://vcpkg.io/en/getting-started.html)の説明通り、CMDプロンプト上で github から clone して、指定のバッチを実行するだけ。

```
git clone https://github.com/microsoft/vcpkg
cd vcpkg
bootstrap-vcpkg.bat
```

あっさり成功した。
```vcpkg help``` でコマンド一覧が、
```vcpkg help コマンド名``` で各コマンドの詳細オプションがコンソールに表示されるので、つらつら眺めておく。

## zlib/minizip パッケージのインストール

vcpkg環境が出来上がったので、zlib/minizipパッケージを取得する。
まずは正確なパッケージ名を search コマンドで調べる。

```
E:\build\vcpkg>vcpkg search minizip
minizip                  1.2.11#9         Zip compression library
minizip[bzip2]                            Support compression using bzip2 library
minizip-ng               3.0.2#1          minizip-ng is a zip manipulation library written in C that is supported on...
minizip-ng[bzip2]                         Enables BZIP2 compression
minizip-ng[lzma]                          Enables LZMA compression
minizip-ng[openssl]                       Enables OpenSSL for encryption
minizip-ng[pkcrypt]                       Enables PKWARE traditional encryption
minizip-ng[signing]                       Enables zip signing support
minizip-ng[wzaes]                         Enables WinZIP AES encryption
minizip-ng[zlib]                          Enables ZLIB compression
minizip-ng[zstd]                          Enables ZSTD compression
quazip                   0.9.1#1          Qt/C++ wrapper over minizip
The search result may be outdated. Run `git pull` to get the latest results.

If your library is not listed, please open an issue at and/or consider making a pull request:
    https://github.com/Microsoft/vcpkg/issues
```

minizip で良いらしい。 minizip-ng という派生パッケージなら lzma圧縮 や zstd圧縮 も使えるようだ。
だがとりあえずは本家の minizip を install する。

```
E:\build\vcpkg>vcpkg install minizip
Computing installation plan...
A suitable version of cmake was not found (required v3.21.1). Downloading portable cmake v3.21.1...
Downloading cmake...
  https://github.com/Kitware/CMake/releases/download/v3.21.1/cmake-3.21.1-windows-i386.zip -> E:\build\vcpkg\downloads\cmake-3.21.1-windows-i386.zip
Extracting cmake...
A suitable version of 7zip was not found (required v19.0.0). Downloading portable 7zip v19.0.0...
Downloading 7zip...
  https://www.7-zip.org/a/7z1900-x64.msi -> E:\build\vcpkg\vcpkg\downloads\7z1900-x64.msi
Extracting 7zip...
The following packages will be built and installed:
    minizip[core]:x86-windows -> 1.2.11#9
  * zlib[core]:x86-windows -> 1.2.11#12
Additional packages (*) will be modified to complete this operation.
Warning: The following VS instances are excluded because the English language pack is unavailable.
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
Please install the English language pack.
No suitable Visual Studio instances were found
```

### ★嵌りポイントその１: 英語言語パック
パッケージのダウンロードは成功したが、**Please install the English language pack.** と文句を言われる。

### 英語言語パックの追加

なぜ英語の言語パックが必要なのか疑問を感じるが、指示通りに入れることにする。
Visual Studio インストーラを起動し、言語パック＞英語 をチェックしてインストール実行。

![vcpkg-install-1-add-enpack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130677/38410a6d-f0a3-b060-ce93-50295ddc6b0f.png)

### 英語の言語パックを入れた後に再び install 実行

```
E:\build\vcpkg>vcpkg install minizip
Computing installation plan...
The following packages will be built and installed:
    minizip[core]:x86-windows -> 1.2.11#9
  * zlib[core]:x86-windows -> 1.2.11#12
Additional packages (*) will be modified to complete this operation.
Detecting compiler hash for triplet x86-windows...
Restored 2 packages from C:\Users\hkuno\AppData\Local\vcpkg\archives in 164.4 ms. Use --debug to see more details.
Starting package 1/2: zlib:x86-windows
Installing package zlib[core]:x86-windows...
Elapsed time for package zlib:x86-windows: 175.9 ms
Starting package 2/2: minizip:x86-windows
Installing package minizip[core]:x86-windows...
Elapsed time for package minizip:x86-windows: 59.27 ms

Total elapsed time: 8.773 s

The package minizip provides CMake targets:

    find_package(minizip CONFIG REQUIRED)
    target_link_libraries(main PRIVATE minizip::minizip)
```

minizip 1.2.11#9 と同時に zlib 1.2.11#12 も入った。
minizip パッケージには zlib 機能が入っておらず、別途 zlibパッケージ を参照するようだ。
従来は minizip と zlib が一体化した zlibstat.lib を使っていたので、vcpkg利用に伴いライブラリファイル指定の修正が必要となる。

### ★嵌りポイントその２：triplet 指定が必要

従来使っていた zlibstat.lib は、minizipとzlibを束ねたスタティックライブラリであり、/MD オプション(MSVCRT)でビルドしていた。だが、今回 vcpkg で install できた zlib.lib はスタティックライブラリではなく zlib1.dll へのインポートライブラリであった。従来と同じビルド構成の zlib が欲しい。

vcpkgは、様々なアーキテクチャとビルド構成に対応している。
その組み合わせを triplet と呼ぶ。その一覧を見てみよう。

```
E:\build\vcpkg>vcpkg help triplet
Available architecture triplets
VCPKG built-in triplets:
  arm-uwp
  arm64-windows
  x64-linux
  x64-osx
  x64-uwp
  x64-windows-static
  x64-windows
  x86-windows

VCPKG community triplets:
  arm-android
  arm-ios
  arm-linux
  arm-mingw-dynamic
  arm-mingw-static
  arm-neon-android
  arm-windows-static
  arm-windows
  arm64-android
  arm64-ios
  arm64-linux
  arm64-mingw-dynamic
  arm64-mingw-static
  arm64-osx-dynamic
  arm64-osx
  arm64-uwp
  arm64-windows-static-md
  arm64-windows-static
  armv6-android
  ppc64le-linux
  s390x-linux
  wasm32-emscripten
  x64-android
  x64-freebsd
  x64-ios
  x64-mingw-dynamic
  x64-mingw-static
  x64-openbsd
  x64-osx-dynamic
  x64-windows-static-md
  x86-android
  x86-freebsd
  x86-ios
  x86-mingw-dynamic
  x86-mingw-static
  x86-uwp
  x86-windows-static-md
  x86-windows-static
  x86-windows-v120
```

従来使っていた zlibstat.lib と同等なのは、デフォルトの ```x86-windows``` ではなく、```x86-windows-static-md``` のようだ。
```vcpkg install パッケージ名:triplet名``` とすることで、インストールするtripletを指示する。
 

### triplet=x86-windows-static-md で install 実行

```
E:\test\vcpkg>vcpkg install minizip:x86-windows-static-md
Computing installation plan...
The following packages will be built and installed:
    minizip[core]:x86-windows-static-md -> 1.2.11#9
  * zlib[core]:x86-windows-static-md -> 1.2.11#12
Additional packages (*) will be modified to complete this operation.
Detecting compiler hash for triplet x86-windows-static-md...
Restored 2 packages from C:\Users\hkuno\AppData\Local\vcpkg\archives in 127.6 ms. Use --debug to see more details.
Starting package 1/2: zlib:x86-windows-static-md
Installing package zlib[core]:x86-windows-static-md...
Elapsed time for package zlib:x86-windows-static-md: 207.6 ms
Starting package 2/2: minizip:x86-windows-static-md
Installing package minizip[core]:x86-windows-static-md...
Elapsed time for package minizip:x86-windows-static-md: 56.58 ms

Total elapsed time: 3.799 s

The package minizip provides CMake targets:

    find_package(minizip CONFIG REQUIRED)
    target_link_libraries(main PRIVATE minizip::minizip)
```

ようやく、欲しいパッケージが手に入った。

## zlib/minizip パッケージのNuGet化

vcpkgでインストールしたライブラリパッケージは、```vcpkg integrate install``` にて、ホストマシンの MSBuild / Visual Studio にて、オプション設定なしにincludeとlinkが出来る、つまり標準ライブラリと同等の扱いになるのだが、各ソリューション毎に使うパッケージを分けたい場合には不都合である。
そこで、ライブラリパッケージをNuGet化して、各プロジェクトに登録する方法をとる。 

NuGet形式ファイルを作るコマンドは ```vcpkg export パッケージ名:Triplet名 --nuget``` だ。

```
E:\build\vcpkg>vcpkg export minizip --nuget
The following packages are already built and will be exported:
    minizip:x86-windows
  * zlib:x86-windows
Additional packages (*) need to be exported to complete this operation.
Exporting package zlib:x86-windows...
Exporting package minizip:x86-windows...
Packing nuget package...
NuGet package exported at: E:\test\vcpkg\vcpkg-export-20211004-181805.1.0.0.nupkg

With a project open, go to Tools->NuGet Package Manager->Package Manager Console and paste:
    Install-Package vcpkg-export-20211004-181805 -Source "E:\build\vcpkg"

```

※ 上記は triplet名を指定し忘れたので x86-windows 用が作成されてしまった。


### ★嵌りポイントその３：NuGetパッケージ名やバージョン番号がダメ

nupkg ファイルが出来上がったが、NuGet名が生成日付時刻で、バージョン番号も 1.0.0 固定である。
Visual Studio の NuGetパッケージ一覧でも、この日付時刻がパッケージ名として出てくるので使い辛い。

exportの詳細オプションを ```vcpkg help export``` で確認する。

```
E:\build\vcpkg>vcpkg help export
Example:
  vcpkg export zlib zlib:x64-windows boost --nuget

Options:
  --dry-run                       Do not actually export
  --raw                           Export to an uncompressed directory
  --nuget                         Export a NuGet package
  --ifw                           Export to an IFW-based installer
  --zip                           Export to a zip file
  --7zip                          Export to a 7zip (.7z) file
  --x-chocolatey                  Export a Chocolatey package (experimental feature)
  --prefab                        Export to Prefab format
  --prefab-maven                  Enable maven
  --prefab-debug                  Enable prefab debug
  --x-all-installed               Export all installed packages
  --output=...                    Specify the output name (used to construct filename)
  --output-dir=...                Specify the output directory for produced artifacts
  --nuget-id=...                  Specify the id for the exported NuGet package (overrides --output)
  --nuget-description=...         Specify a description for the exported NuGet package
  --nuget-version=...             Specify the version for the exported NuGet package
  --ifw-repository-url=...        Specify the remote repository URL for the online installer
  --ifw-packages-directory-path=...
                                  Specify the temporary directory path for the repacked packages
  --ifw-repository-directory-path=...
                                  Specify the directory path for the exported repository
  --ifw-configuration-file-path=...
                                  Specify the temporary file path for the installer configuration
  --ifw-installer-file-path=...   Specify the file path for the exported installer
  --x-maintainer=...              Specify the maintainer for the exported Chocolatey package
                                  (experimental feature)
  --x-version-suffix=...          Specify the version suffix to add for the exported Chocolatey
                                  package (experimental feature)
  --prefab-group-id=...           GroupId uniquely identifies your project according maven
                                  specifications
  --prefab-artifact-id=...        Artifact Id is the name of the project according maven
                                  specifications
  --prefab-version=...            Version is the name of the project according maven specifications
  --prefab-min-sdk=...            Android minimum supported sdk version
  --prefab-target-sdk=...         Android target sdk version
  --triplet=<t>                   Specify the target architecture triplet. See 'vcpkg help triplet'
                                  (default: %VCPKG_DEFAULT_TRIPLET%)
  --host-triplet=<t>              Specify the host architecture triplet. See 'vcpkg help triplet'
                                  (default: %VCPKG_DEFAULT_HOST_TRIPLET%)
  --overlay-ports=<path>          Specify directories to be used when searching for ports
                                  (also: %VCPKG_OVERLAY_PORTS%)
  --overlay-triplets=<path>       Specify directories containing triplets files
                                  (also: %VCPKG_OVERLAY_TRIPLETS%)
  --binarysource=<path>           Add sources for binary caching. See 'vcpkg help binarycaching'
  --x-asset-sources=<path>        Add sources for asset caching. See 'vcpkg help assetcaching'
  --downloads-root=<path>         Specify the downloads root directory
                                  (default: %VCPKG_DOWNLOADS%)
  --vcpkg-root=<path>             Specify the vcpkg root directory
                                  (default: %VCPKG_ROOT%)
  --x-buildtrees-root=<path>      (Experimental) Specify the buildtrees root directory
  --x-install-root=<path>         (Experimental) Specify the install root directory
  --x-packages-root=<path>        (Experimental) Specify the packages root directory
  --x-scripts-root=<path>         (Experimental) Specify the scripts root directory
  --x-builtin-ports-root=<path>   (Experimental) Specify the packages root directory
  --x-builtin-registry-versions-dir=<path>
                                  (Experimental) Specify the versions root directory
  --x-json                        (Experimental) Request JSON output
```

--nuget-id=...
--nuget-description=...
--nuget-version=...
この辺りのオプションを使えば良い感じだ。


### パッケージ名とバージョン番号、説明を指定してNuGet化する

```
E:\build\vcpkg>vcpkg export minizip:x86-windows-static-md --nuget --nuget-id=vcpkg-zlib-minizip --nuget-version=1.2.11 --nuget-description="zlib 1.2.11 and minizip. x86-windows-static-md built"
The following packages are already built and will be exported:
    minizip:x86-windows-static-md
  * zlib:x86-windows-static-md
Additional packages (*) need to be exported to complete this operation.
Exporting package zlib:x86-windows-static-md...
Exporting package minizip:x86-windows-static-md...
Packing nuget package...
NuGet package exported at: E:\build\vcpkg\vcpkg-zlib-minizip.1.2.11.nupkg

With a project open, go to Tools->NuGet Package Manager->Package Manager Console and paste:
    Install-Package vcpkg-zlib-minizip -Source "E:\build\vcpkg"
```

これで、望みのNuGetパッケージが出来た。
生成メッセージの最終行にあるように、Visual Studio の NuGetパッケージマネージャコンソールにて ```Install-Package vcpkg-zlib-minizip -Source "E:\build\vcpkg"``` と打ち込めば、プロジェクトに登録される。

作成するNuGetパッケージが一個だけなら以上で終了だが、複数に増える場合は Visual Studio の IDE 上で一覧選択して、プロジェクトに登録したい。

## vcpkg のフォルダをNuGetパッケージソースとして、Visual Studio に登録する

1. Visual Studio の IDE を起動して、ツール＞オプション＞NuGetパッケージマネージャー＞パッケージソース を開く
1. 左上の ＋ ボタンを押して、名前[vcpkg]、ソース[vcpkgインストールフォルダ] を設定する

![vcpkg-install-2-set-vcpkg-dir.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130677/3ab5c01d-9b1a-43d8-afcc-c7e968dd7663.png)

## vcpkgで生成したNuGetパッケージをプロジェクトにインストールする。

1. Visual Studio の IDE でソリューションを開く
1. ツール＞NuGetパッケージマネージャー＞ソリューションのNuGetパッケージマネージャの管理 を開く
1. 参照タブ＞パッケージソース：[vcpkg] を選ぶ
1. vcpkgフォルダ下の *.nupkg が一覧表示されるので、そこから選んでプロジェクトにインストールする
1. パッケージの名前と説明とバージョンは、*.nupkg作成時に指定した --nuget-id=、--nuget-description=、--nuget-version= の内容になっている 

![vcpkg-install-3-select-nuget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130677/26d72a5c-d67d-49bd-e714-26a8e25d4c8e.png)

### ★嵌りポイントその４：triplet=x86-windows-static-md なvcpkgを使うには、構成プロパティに設定が必要

NuGet化してインストールしたのだから一発でビルド成功、とはならなかった。
ヘッダが見つからずコンパイルエラー、libが見つからずにリンクエラーが出る。
デフォルトの triplet=x86-windows なら問題ないが、triplet=x86-windows-static-md の場合は、プロジェクトプロパティに **こっそり追加されたvcpkgプロパティ**に、Triplet: x86-windows-static-md を追加設定する必要がある。

![vcpkg-install-4-set-triplet-vcxproj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130677/9d78b90d-feb8-4fe7-1406-c4e5da2144a8.png)

プロジェクトフォルダの packages 下のファイルをしらみつぶしに調べたら、vcpkg.targets というファイルにて、ヘッダーサーチパス指定 AdditionalIncludeDirectories と ライブラリサーチパス指定 AdditionalLibraryDirectories へパッケージパスを追加登録する処理があり、そのパス名を決める元となる ```$(VcpkgTriplet)``` の自動設定が、```$(VcpkgPlatformTarget)-$(VcpkgOSTarget)``` と ```$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static``` にしか対応していなかった。つまり今回の場合は ```x86-windows``` と ```x86-windows-static``` である。
それ以外の triplet については vcxproj 内で ```$(VcpkgTriplet)``` マクロをユーザが手動設定して使う設計らしい。これ、エラーメッセージに明示して欲しかった。

```xml:scripts\buildsystems\msbuild\vcpkg.targets
  <PropertyGroup>
    <_ZVcpkgLinkage />
    <_ZVcpkgLinkage Condition="'$(VcpkgUseStatic)' == 'true'">-static</_ZVcpkgLinkage>
    <VcpkgTriplet Condition="'$(VcpkgTriplet)' == ''">$(VcpkgPlatformTarget)-$(VcpkgOSTarget)$(_ZVcpkgLinkage)</VcpkgTriplet>
  </PropertyGroup>

  <PropertyGroup>
    <_ZVcpkgCurrentInstalledDir>$(_ZVcpkgInstalledDir)\$(VcpkgTriplet)\</_ZVcpkgCurrentInstalledDir>
    <_ZVcpkgNormalizedConfiguration Condition="$(VcpkgConfiguration.StartsWith('Debug'))">Debug</_ZVcpkgNormalizedConfiguration>
    <_ZVcpkgNormalizedConfiguration Condition="$(VcpkgConfiguration.StartsWith('Release')) or '$(VcpkgConfiguration)' == 'RelWithDebInfo' or '$(VcpkgConfiguration)' == 'MinSizeRel'">Release</_ZVcpkgNormalizedConfiguration>

    <_ZVcpkgConfigSubdir Condition="'$(_ZVcpkgNormalizedConfiguration)' == 'Debug'">debug\</_ZVcpkgConfigSubdir>
    <_ZVcpkgExecutable>$(_ZVcpkgRoot)vcpkg.exe</_ZVcpkgExecutable>
  </PropertyGroup>


  <ItemDefinitionGroup Condition="'$(VcpkgEnabled)' == 'true'">
    <Lib>
      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories);$(_ZVcpkgCurrentInstalledDir)$(_ZVcpkgConfigSubdir)lib;$(_ZVcpkgCurrentInstalledDir)$(_ZVcpkgConfigSubdir)lib\manual-link</AdditionalLibraryDirectories>
    </Lib>
    <Link>
      <AdditionalDependencies Condition="'$(VcpkgAutoLink)' != 'false'">%(AdditionalDependencies);$(_ZVcpkgCurrentInstalledDir)$(_ZVcpkgConfigSubdir)lib\*.lib</AdditionalDependencies>
      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories);$(_ZVcpkgCurrentInstalledDir)$(_ZVcpkgConfigSubdir)lib;$(_ZVcpkgCurrentInstalledDir)$(_ZVcpkgConfigSubdir)lib\manual-link</AdditionalLibraryDirectories>
    </Link>
    <ClCompile>
      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(_ZVcpkgCurrentInstalledDir)include</AdditionalIncludeDirectories>
    </ClCompile>
    <ResourceCompile>
      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(_ZVcpkgCurrentInstalledDir)include</AdditionalIncludeDirectories>
    </ResourceCompile>
  </ItemDefinitionGroup>

```

## まとめ

vcpkg は良いオープンソースプロジェクトであるが、まだまだ情報が足らない。
ビルド成功に辿り着くまで長い時間を費やした。
この記事が、特殊な triplet でビルドが出来ずに悩んでいる諸氏の助けになれば幸いである。

## 参考記事
- https://qiita.com/jugemjugemu/items/fcb72bdca13dc06b0f37

## END