📝
C#で、.NET ランタイムのインストールが不要な単一実行ファイルを作成し、WindowsとmacOSで動作させる方法
はじめに
ツールを作成するときに便利な、単一実行ファイルを作成する方法です。
dotnet環境がインストールされていない環境でも実行できる設定を行なっています。
シェルスクリプトで対応できない規模のプログラムを作成するときに使用します。
また、Unityなどで使用しているソースコードやライブラリを共有して使うこともできます。
dotnet sdk 8.0を使用しています。
dotnet --info
% dotnet --info
.NET SDK:
Version: 8.0.401
Commit: 811edcc344
Workload version: 8.0.400-manifests.b6724b7a
MSBuild version: 17.11.4+37eb419ad
ランタイム環境:
OS Name: Mac OS X
OS Version: 14.6
OS Platform: Darwin
RID: osx-arm64
Base Path: /usr/local/share/dotnet/sdk/8.0.401/
インストール済みの .NET ワークロード:
新しいマニフェストをインストールするときに loose manifests を使用するように構成されています。
表示するインストール済みワークロードはありません。
Host:
Version: 8.0.8
Architecture: arm64
Commit: 08338fcaa5
.NET SDKs installed:
8.0.401 [/usr/local/share/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 8.0.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Other architectures found:
None
Environment variables:
Not set
global.json file:
Not found
Learn more:
https://aka.ms/dotnet/info
Download .NET:
https://aka.ms/dotnet/download
コンソールアプリ作成
- フォルダーを作る。(例ではconsole_testフォルダ)
- dotnet newコマンドでテンプレートを作成する。[1]
% mkdir console_test
% cd console_test
% dotnet new console --framework net8.0 --use-program-main
テンプレート "コンソール アプリ" が正常に作成されました。
作成後の操作を処理しています...
/Users/hogehoge/dotnet/console_test/console_test.csproj を復元しています:
復元対象のプロジェクトを決定しています...
/Users/hogehoge/dotnet/console_test/console_test.csproj を復元しました (33 ミリ秒)。
正常に復元されました。
% ls
Program.cs console_test.csproj obj
設定
- csprojに単一バイナリ設定を追加する。(PropertyGroupのコメントがある行)[2]
- 必要に応じてRuntimeIdentifierのターゲットアーキテクチャを変更してください。[3]
- また、リリースビルド時のみ同一バイナリが生成される設定を追加する。(<Choose>内の設定)[4]
console_test.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!--単一ファイルの発行を有効にします。 また、dotnet build 時の単一ファイルの警告を有効にします。-->
<PublishSingleFile>true</PublishSingleFile>
<!--アプリが自己完結型またはフレームワーク依存であるかを判断します。-->
<SelfContained>true</SelfContained>
<!-- ターゲットとする OS と CPU の種類を指定します。 また、既定で <SelfContained>true</SelfContained> が設定されます。-->
<RuntimeIdentifier>win-x64;osx-arm64</RuntimeIdentifier>
</PropertyGroup>
<Choose>
<!-- リリースビルド時のみ -->
<When Condition=" '$(Configuration)'=='Release' ">
<PropertyGroup>
<!-- C# で同じソースコードから常に同じバイナリを生成する -->
<!-- https://ufcpp.net/blog/2019/5/deterministicbuilds/ -->
<!-- https://gist.github.com/ufcpp/f3cc89e8c266997063eb2c633114668a -->
<!-- このオプションの指定で、タイムスタンプとかを決定論的なハッシュ値に置き換える -->
<Deterministic>true</Deterministic>
<!-- 「相対パス化」って機能は実はなくて、「このプロジェクトのパスを、この指定したパスに置き換える」みたいな処理を書くらしい -->
<DeterministicSourceRoot>/_/</DeterministicSourceRoot>
<RepoRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\'))</RepoRoot>
<!-- これが、「あるパスを別のあるパスに置き換える」をやるための設定 -->
<PathMap>$(RepoRoot)=$(DeterministicSourceRoot)</PathMap>
<!-- おまけ。PDB 中にソースコード埋め込み -->
<!-- <EnableSourceLink>true</EnableSourceLink>-->
</PropertyGroup>
</When>
</Choose>
</Project>
ビルド
dotnet publishコマンドで実行ファイルを作成する。
# Mac用ビルド
% dotnet publish console_test.csproj -c Release -r osx-arm64 -o ./Export/osx-arm64
# Windows用ビルド
% dotnet publish console_test.csproj -c Release -r win-x64 -o ./Export/win-x64
実行
Export/実行したいアーキテクチャー/コマンドを実行する。
% ./Export/osx-arm64/console_test
Hello, World!
おまけ
ビルドスクリプト
Mac向けのビルドシェルスクリプト
build.sh
#!/bin/bash
# 不要なビルドは#でコメントアウトする
rm -fr ./Export/osx-arm64
dotnet clean -r osx-arm64
dotnet publish console_test.csproj -c Release -r osx-arm64 -o ./Export/osx-arm64
rm -fr ./Export/win-x64
dotnet clean -r win-x64
dotnet publish console_test.csproj -c Release -r win-x64 -o ./Export/win-x64
Windows向けのビルドバッチファイル
build.bat
rem 不要なビルドは rem でコメントアウトする
Remove-Item .\Export\osx-arm64 -Recurse
dotnet clean -r osx-arm64
dotnet publish console_test.csproj -c Release -r osx-arm64 -o ./Export/osx-arm64
Remove-Item .\Export\win-x64 -Recurse
dotnet clean -r win-x64
dotnet publish console_test.csproj -c Release -r win-x64 -o ./Export/win-x64
実行ファイルのフォルダーを取得する
実行ファイルが置いてあるフォルダーに、設定ファイルやログファイルをなど置く場合にパスを取得する。
// Environment.GetCommandLineArgs メソッド (System) | Microsoft Learn
// https://learn.microsoft.com/ja-jp/dotnet/api/system.environment.getcommandlineargs?view=net-8.0
var envExePath = System.Environment.GetCommandLineArgs()[0];
// Path.GetDirectoryName メソッド (System.IO) | Microsoft Learn
// https://learn.microsoft.com/ja-jp/dotnet/api/system.io.path.getdirectoryname?view=net-8.0
var path = System.IO.Path.GetDirectoryName(envExePath);
System.Console.WriteLine($@"path = {path}");
コマンドラインのパラメーター解析ライブラリー
コマンドラインのパラメーター解析は、Environment.CommandLineやEnvironment.GetCommandLineArgs()を使用して解析することもできる。
複雑なオプション処理をする場合はライブラリーを使用した方が楽に実現できる。
ConsoleAppFramework
- Cysharp/ConsoleAppFramework: Zero Dependency, Zero Overhead, Zero Reflection, Zero Allocation, AOT Safe CLI Framework powered by C# Source Generator.
- NuGet Gallery | ConsoleAppFramework
- neue cc - ConsoleAppFramework v5 - ゼロオーバーヘッド・Native AOT対応のC#用CLIフレームワーク
Cocona
- mayuki/Cocona: Micro-framework for .NET console application. Cocona makes it easy and fast to build console applications on .NET.
- NuGet Gallery | Cocona
- 【C#】Coconaを利用してSystem.CommandLineなどを利用せずに簡単にConsoleアプリケーションを作成する - はなちるのマイノート
Discussion