M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

maxOS環境でNuGetパッケージをつくる

例によって見事なまでにエラーと警告の嵐だったのでメモしておきます。NuGetの公式リファレンスやGithubのIssues、この記事を含む日本語の記事もいくつか参照しましたが、何と言っても役に立ったのはstackoverflowのエントリーでした。。。

[2017/05/25追記]この記事を執筆している時点のNuGetでは、*.csprojファイルを引数に指定するとエラーになる状態でしたが、2017/05/25現在の最新バージョンであるNuGet v4.1ではすでにこの問題が解消されています。この点についてはこちらの記事もご参照ください。

環境

今回、作業をした環境は以下の通り。IDE(Xamarin)は基本的に関係ないはずですが、私の環境の場合このXamarinとともにセットでMonoがインストールされているのと、ソリューション・ファイルやプロジェクト・ファイルの構成が影響する可能性もあるかもということで、一応書いておきます:

OS macOS Sierra (v10.12.1)
.NET Mono v4.6.2
NuGet v3.4.4
IDE Xamarin Studio Community v6.1.4

手順

前提としてターミナルでmonoコマンドが実行できることを確認してください。Xamarinを導入済みであれば実行できるはずです。そうでない場合はMacPorts等でインストールする必要があるかもしれません。

次にNuGetの公式サイトからnuget.exeをダウンロードします。いろいろなバージョンが記載されていますが、macOSLinuxであれば迷う必要はありません。というか迷う余地がありません。「Windows x86 Commandline」を選びます(!)。

ダウンロードしたEXEはお好みのディレクトリに置いておきます。Windows環境以外ではEXEはそれ単体では実行可能ファイルとして利用できないので、それ自体をパスの通ったディレクトリに置いても無駄。mono path/to/nuget.exe ...みたいにして使います。

パッケージを作成するプロジェクトのディレクトリに移動し、nuget specコマンドでnuspecファイルを作成します。このファイルはNuGetパッケージのメタ情報を管理するものです。

$ cd path/to/MyProject
$ ls
MyProject.csproj
Properties
bin
obj
...
$ mono path/to/nuget.exe spec

するとMyProject.nuspecというような名前でnuspecファイルが作成されます。これをテキスト・エディタなどで開きます。中身のフォーマットはXML。初期状態では多くの項目に<id>$id$</id>というふうに「置換トークン」(プレースホルダ)が記述されています。このトークンはリファレンスによれば*.csprojAssemblyInfo.csで宣言された情報で自動的に置き換えられます。

ただし、公式リファレンスおよびGithubリポジトリのIssues、そしてstackoverflowの情報によれば、この自動置換は後述のパッケージングを行うためのコマンドであるnuget packの引数にcsprojファイルを指定した場合のみ有効になるもので、csprojファイルを指定する方法は少なくともMonoラインタイム上で実行した場合のNuGetではサポートされていません。つまりmacOSLinuxでは置換トークンは使えないので、それぞれの項目に適切な値を記入していきます(.NET Coreのdotnetコマンドならもしかしたら…)。

それぞれの項目に何を記載すべきかはリファレンスのこちらのセクションに対照表が載っています。この表に基づいて書き換えたnuspecファイルの例は次のようになります:

<?xml version="1.0"?>
<package >
  <metadata>
    <id>MyProject</id><!--id>$id$</id-->
    <version>1.0.0.0</version><!--version>$version$</version-->
    <!--title>$title$</title-->
    <authors>example.com</authors><!--authors>$author$</authors-->
    <!--owners>$author$</owners-->
    <!--licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl-->
    <projectUrl>https://github.com/example/MyProject</projectUrl>
    <!--iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl-->
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>A example for NuGret packaging on macOS</description><!--description>$description$</description-->
    <!--releaseNotes>Summary of changes made in this release of the package.</releaseNotes-->
    <copyright>Copyright 2016</copyright>
    <!--tags>Tag1 Tag2</tags-->
  </metadata>
  <files>
    <file src="bin/Release/MyProject.dll" target="lib"/>
  </files>
</package>

これができたらnuget packコマンドでパッケージします。前述の通りコマンドライン引数にはcsprojファイルではなくnuspecファイルを指定します。

$ ls
MyProject.csproj
MyProject.nuspec
Properties
bin
obj
...
$ mono path/to/nuget.exe spec MyProject.nuspec \
  -Verbosity detailed -Build -Properties Configuration=Release
'MyProject.nuspec' からパッケージをビルドしています。

Id: MyProject
Version: 1.0.0
Authors: example.com
Description: A example for NuGret packaging on macOS
Project Url: https://github.com/example/MyProect
Dependencies: None

ファイル 'lib/MyProject.dll' が追加されました。

パッケージ '/path/to/MyProject/MyProject.1.0.0.0.nupkg' が正常に作成されました。

警告: パッケージ 'MyProject'1 個の問題が見つかりました。

問題: Assembly not inside a framework folder.
説明: The assembly 'lib/MyProject.dll' is placed directly under 'lib' folder. It is recommended that assemblies be placed inside a framework-specific folder.
ソリューション: Move it into a framework-specific folder. If this assembly is targeted for multiple frameworks, ignore this warning.

これで終わりです。上記の例では「MyProject.1.0.0.0.nupkg」という名前のパッケージが作成されました。

-Buildフラグはコマンドのヘルプを読む限りパッケージに先立ちビルドを行う旨指定するもののようですが、実際には何もしてくれません。nuspecファイルに記載したDLLが見つからないとコマンド実行時にエラーになるので、ビルド前にXamarinでプロジェクトのビルドを行っておきましょう。

nuspecファイルに置換トークンが残っていると「Value cannot be null or an empty string. Parameter name: value」というようなわりと不親切なエラーが発生します。もし遭遇したらnuspecファイルを見直しましょう。

nuget packコマンドの引数にまちがってcsprojファイルを指定してしまうと「The method or operation is not implemented」という生々しいエラーが発生します。コマンドライン引数を見直しましょう。

ちなみに先程のコマンド実行例では「フレームワーク固有のフォルダにアセンブリを置け」云々の警告が出力されています。警告にもあるとおりとくに個々のフレームワーク・バージョン固有のビルドをしていないのであればこのままでも良いのかもしれません(それにしてもバージョンに下限はあるだろうと思いますが)。

NuGetのパッケージはその内部に複数のフレームワーク・バージョン向けのアセンブリを格納できます。NuGetパッケージは実態としてはZIPファイルで、複数バージョンを内包する場合はあらかじめ決められたディレクトリ構成でアセンブリを格納します。この構成についてリファレンスを参照しつつnuspecを修正してパッケージングを行うことで上述の警告は消えます。

例:

<?xml version="1.0"?>
<package >
  <metadata>
    ...
  </metadata>
  <files>
    <file src="bin/Release/net40/MyProject.dll" target="lib/net40"/>
    <file src="bin/Release/net45/MyProject.dll" target="lib/net45"/>
    <file src="bin/Release/net46/MyProject.dll" target="lib/net46"/>
  </files>
</package>

(私の環境にはmsbuildがどこからともなくインストールされており──おそらくこれもXamarin由来ですが──これをプロジェクト・ディレクトリ内で実行する際に/p:Configuration=Release /p:Platform=AnyCPU /p:TargetFrameworkVersion=v4.0 /p:OutputPath=bin/Release/net40といった一連のコマンドライン引数を指定することでターゲット・フレームワーク・バージョンを指定したビルドを行いました。)

m12i.hatenablog.com