Linux 環境上で、C# のプロジェクトから NuGet パッケージを作成して他のプロジェクトに読み込ませる

自分が担当している [Azure Functions kafka Extension](https://github.com/Azure/azure-functions-kafka-extension)だが、最近 Pull Request がおおいので、C#以外の言語のE2Eテストを書きたくなってきた。大抵の人は、C#のところだけみて、いろいろ変えるわけだけど、自分からすると、ちゃんと他言語でも動くんかいな？というのを毎回テストするのはめんどくさいので、仕組みを作ることにした。

# 現在のパッケージをビルドして、他言語のパッケージに読み込ませる時の課題

このプロジェクトは、もともとの Extension が C# で書かれている。そこで、作成された NuGet パッケージを使って、ほかの言語が動作するという感じなので次のことが必要だ。

* Linux 上で、現在のソースから パッケージを作る。
* Java, Python 等のパッケージで使っている csproj ファイルにそのパッケージを読み込ませる
* サーバーを起動して、テストを行う

一見なんでもなさそうだがいくつかの考慮事項がある

* ビルドするバージョンをどうするか？現在のプロジェクトのバージョンで素直にパッケージすると、現在のバージョンでパッケージされる。そうなると、NuGetソースから現在のバージョンがPublishされている場合、そちらを参照される可能性があるので、FalsePositveが発生しやすそう
* Java, Python のパッケージ側から、読み込ませるバージョンを何にするか？バージョンが上がるたびに手動で変更するのはだるいので変更しなくていいようにしたい。
* パッケージをどのように読み込ませるか？csproj ではファイル指定でパッケージの読み込みはできなさそう

結局どうしたかというと、`100.100.100` などという、まぁ、そこまでアップデートしないだろう。ぐらいのバージョンを設定しておいて、ソースコードもそうしておくという単純な解決策にしておいた。もっと良いのがありそうだけど。

# C# のパッケージのビルド

dotnet が既にインストールされていると想定すると、私はWSL2上で次のようなコマンドを書いて実行した。
重要なポイントは `dotnet pack -o temp --include-symbols src/Microsoft.Azure.WebJobs.Extensions.Kafka/Microsoft.Azure.WebJobs.Extensions.Kafka.csproj /p:Version=100.100.100` パッケージを `100.100.100` というバージョンに指定して、`pack` を実行し、`temp` ディレクトリに出力している。その後、各言語用のディレクトリにコピーしている。それぞれの言語のディレクトリは `Docker` にパックされるように構成している。


```bash
#!/bin/bash

CURRENT_DIR=`pwd`
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $DIR/..

WORKING_DIR=temp
if [ -d "$WORKING_DIR" ]; then rm -rf $WORKING_DIR; fi

dotnet pack -o temp --include-symbols src/Microsoft.Azure.WebJobs.Extensions.Kafka/Microsoft.Azure.WebJobs.Extensions.Kafka.csproj /p:Version=100.100.100


PACKAGE_DIR=test/Microsoft.Azure.WebJobs.Extensions.Kafka.LangEndToEndTests/server/java8/packages
if [ -d "$PACKAGE_DIR" ]; then rm -rf $PACKAGE_DIR; fi
mkdir $PACKAGE_DIR
cp temp/* $PACKAGE_DIR

cd $CURRENT_DIR

```

# NuGet Source の指定

NuGetのファイルを読み込ませるためには、どうしたらいいだろう？と考えて、`Local Source` を設定して、読ませるという方法にした。現在は `dotnet nuget add source` というコマンドがあったので使ってみた。古い情報だと、mono をインストールして、nuget.exe を使うとかあったが、今は不要っぽい。

* [dotnet nuget add source](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-add-source)

```bash
RUN dotnet nuget add source /workspace/packages/
```

ちなみに、このコマンドを発行すると、デフォルトの `NuGet.Config` が次のように更新される。最初は、バージョン固定の方法と、nuget source の読み込みの優先順位を変えて、必ずLocalが先に読ませるようにするとも考えたのだが、それだとやっぱりFalsePositiveが気になるので、バージョンの固定にした。

```bash
# pwd
/root/.nuget/NuGet
# cat NuGet.Config 
﻿<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  <add key="Package source 1" value="/workspace/packages/" />
</packageSources>
```

# Java, Python 側の csproj ファイル

という仕様にしたので、Java, Python 側の csproj ファイルは、バージョンだけ、`100.100.100.100` になったものになっている。これで自動的に現在のソースコードをビルドしたものが組み込まれるようになっている。

```
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
	<WarningsAsErrors></WarningsAsErrors>
	<DefaultItemExcludes>**</DefaultItemExcludes>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Kafka" Version="100.100.100" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator" Version="1.1.7" />
  </ItemGroup>
  <ItemGroup>
    <None Update="confluent_cloud_cacert.pem">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
```

# Dockerfile

最終的な Dockerfile は次の通りになっている。簡単でござった。

```docker
FROM mcr.microsoft.com/azure-functions/java:3.0-java8-core-tools

# Copy Local File
RUN mkdir /workspace
COPY . /workspace
WORKDIR /workspace
# dotnet 

ENV LD_LIBRARY_PATH=/workspace/target/azure-functions/kafka-function-20190419163130420/bin/runtimes/linux-x64/native

RUN dotnet nuget add source /workspace/packages/

RUN mvn clean package

ENTRYPOINT [ "/usr/bin/mvn", "azure-functions:run" ]
```

# まとめ
C#の達人だったらもっといい方法思いつきそう。もしあったら是非教えてください。
この後、docker-compose で、Kafka サーバーと一緒に起動して動作するところまでいけたので、後は、E2E のフレームワークをC#で書いて、Azure DevOps に組み込んだら終了でござる。

# 補足　11/9/2020

しばやんさんからいいアドバイスをもらったので、パッケージリファレンスで実施することにしました。もうLocalNuget作らなくていいので、こちらの方がよさそうです。Dockerにパックするので、PackageReferenceは使えないと思い込んでいましたが、単純で、docker build のコンテキストをプロジェクトルートにしてしまえばよいです。ただ、コマンドのいい学びになったのでエントリは残しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/78cf1dd3-a79a-f7b8-f045-2a7cfb153ae8.png)

_extension.csproj_

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
	<WarningsAsErrors></WarningsAsErrors>
	<DefaultItemExcludes>**</DefaultItemExcludes>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator" Version="1.1.7" />
  </ItemGroup>
  <ItemGroup>
    <None Update="confluent_cloud_cacert.pem">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\..\src\Microsoft.Azure.WebJobs.Extensions.Kafka\Microsoft.Azure.WebJobs.Extensions.Kafka.csproj" />
  </ItemGroup>
</Project>
```

_Dockerfile_

```docker
FROM mcr.microsoft.com/azure-functions/java:3.0-java8-core-tools

# Copy Local File
RUN mkdir /workspace
COPY . /workspace
WORKDIR /workspace/test/Microsoft.Azure.WebJobs.Extensions.Kafka.LangEndToEndTests/server/java8
# dotnet 

ENV LD_LIBRARY_PATH=/workspace/target/azure-functions/kafka-function-20190419163130420/bin/runtimes/linux-x64/native

RUN mvn clean package

ENTRYPOINT [ "/usr/bin/mvn", "azure-functions:run" ]

```

```bash
docker build -t testj8 -f test/Microsoft.Azure.WebJobs.Extensions.Kafka.LangEndToEndTests/server/java8/Dockerfile . 
```


# Resource
* [nuget.config reference](https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file)
* [Package version is always 1.0.0 with dotnet pack](https://stackoverflow.com/questions/42797993/package-version-is-always-1-0-0-with-dotnet-pack)
* [Dockerfile reference](https://docs.docker.com/engine/reference/builder/)