tech.guitarrapc.cóm

Technical updates

.NET 7 で WPF を Linux ビルドする

.NET 7 のリリースノートを見ているとふとWPFがあったので覗いてみると気になるリリースが含まれています。

devblogs.microsoft.com

引用: https://devblogs.microsoft.com/dotnet/wpf-on-dotnet-7/

Improve Linux build とは。.NET 7 で WPF がビルドできるようになったのかしら?

ということで見てみましょう。

tl;dr;

.NET 7 から、WPF アプリ/ライブラリを Linux でビルドできるようになっています。(vcxproj はコンパイルできない)

Linux ビルドには3つの条件を満たす必要があるようです。

  • csproj の TargetFramework は <TargetFramework>net7.0-windows</TargetFramework> (デフォルトのまま)
  • csproj に <EnableWindowsTargeting>true</EnableWindowsTargeting> のエントリーをPropertyGroup に追加する
  • .NET 7 SDK がインストールされた Linux 環境を用意する。

あとは、dotnet build を行えばビルドできます。

ただし、このままLinux でビルドしても成果物は Linux向けなので、Windowsアーキテクチャ向けにランタイム識別子 もビルド時に指定しましょう。

  • dotnet publish -r win-x64 --no-self-contained (--self-contained でも必要な方で)

夢にまで見た Linux で WPF をビルドして、Windows で実行する環境がそこにあります。

サンプルリポジトリ

リポジトリおいておきます。

github.com

GitHub Ations のUbuntu-22.04 環境でビルドしたWPF 成果物は Actions からダウンロードできます。(成果物による一切に関して責任は取りません。といういつものは前置きしておきます)

Windows で実行できるのが確認できるでしょう。

.NET 6 では WPF を Linuxビルドできたことがない

私の認識と.NET 6 検証用 GitHub Actions/WSL2/Docker 環境では WPF アプリ/ライブラリを Linux ビルド成功したことはなくできない認識でした。

おおむねこのIssue と同じです。

github.com

.NET 7 で導入された WPF の Linux ビルド対応

次の PR で対応がされています。

github.com

内容だけ見ると .NET6 でも実はできてたのでは 1 という感じですが、とりあえずパッと作ってビルドできないのでそういうものなんでしょう。現実です。

.NET 7 で WPFアプリ をLinuxビルドする

WPF アプリを生成してサクッとやってみましょう。2

以降、誰でも試せるように docker を用いて実際の作業を示します。成果物を Windows に持ってきたいなら適当にボリュームマウントしてください。

docker run -it mcr.microsoft.com/dotnet/sdk:7.0 /bin/bash

まずは WPF のプロジェクトを .NET 7 で作成します。 このまま、 Linux の .NET 7 SDK でビルドしようとしてもエラーが出ます。

$ mkdir -p /home/WpfApp
$ cd /home/WpfApp
$ dotnet new wpf
$ ls -l
total 4
-rwxrwxrwx 1 foo foo  366 Nov 11 02:10 App.xaml
-rwxrwxrwx 1 foo foo  339 Nov 11 02:10 App.xaml.cs
-rwxrwxrwx 1 foo foo  604 Nov 11 02:10 AssemblyInfo.cs
-rwxrwxrwx 1 foo foo  491 Nov 11 02:10 MainWindow.xaml
-rwxrwxrwx 1 foo foo  658 Nov 11 02:10 MainWindow.xaml.cs
drwxrwxrwx 1 foo foo 4096 Nov 11 02:10 obj
-rwxrwxrwx 1 foo foo  245 Nov 11 02:10 WpfApp.csproj

$ dotnet build
... 省略...
Error: /home/runner/.dotnet/sdk/7.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(90,5): error NETSDK1100: To build a project targeting Windows on this operating system, set the EnableWindowsTargeting property to true. [/home/WpfApp/WpfApp.csproj]

エラーには、EnableWindowsTargeting プロパティを true で設定しろとあります。実際今の csproj を見てみると初期状態にはないプロパティですね。

$ cat WpfApp.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net7.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

csproj に、EnableWindowsTargeting を true と設定してから、もう一度ビルドしてみましょう。 今回は sed で適当に追加しちゃいますが、VS Code や Vim でもなんでもいいでしょう。

$ sed -i '/<UseWPF>.*/a \ \ \ \ <EnableWindowsTargeting>true<\/EnableWindowsTargeting>' WpfApp.cspr
oj
$ cat WpfApp.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net7.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
    <EnableWindowsTargeting>true</EnableWindowsTargeting>
  </PropertyGroup>

</Project>

$ dotnet build
... 省略...
WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/WpfApp.dll

$ dotnet publish
MSBuild version 17.4.0+18d5aef85 for .NET
  Determining projects to restore...
  Restored /src/WpfApp.csproj (in 1.65 sec).
  WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/WpfApp.dll
  WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/publish/

ビルドがうまくいきましたね。しかし、成果物は次のように.exe ではないようです。

$ ls -l /home/WpfApp/bin/Release/net7.0-windows/publish
total 212
-rwxr-xr-x 1 foo foo 181016 Nov 10 17:32 WpfApp
-rw-r--r-- 1 foo foo    388 Nov 10 17:32 WpfApp.deps.json
-rw-r--r-- 1 foo foo   7168 Nov 10 17:32 WpfApp.dll
-rw-r--r-- 1 foo foo  13860 Nov 10 17:32 WpfApp.pdb
-rw-r--r-- 1 foo foo    355 Nov 10 17:32 WpfApp.runtimeconfig.json

Windows 向けにビルドするように、ランタイムに win-x64 を指定しましょう。これで .exe が生成されます。

$ dotnet publish -r win-x64 --no-self-contained
/usr/share/dotnet/sdk/7.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.targets(574,5): warning NETSDK1074: The application host executable will not be customized because adding resources requires that the build be performed on Windows (excluding Nano Server). [/home/WpfApp.csproj]
  WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/WpfApp.dll
  WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/publish/

$ ls -l /home/WpfApp/bin/Debug/net7.0-windows/win-x64/publish/
total 170
-rwxr-xr-x 1 foo foo    440 Nov 10 17:48 WpfApp.deps.json
-rwxr-xr-x 1 foo foo   6656 Nov 10 17:48 WpfApp.dll
-rwxr-xr-x 1 foo foo 152064 Nov 10 17:48 WpfApp.exe
-rwxr-xr-x 1 foo foo  13808 Nov 10 17:48 WpfApp.pdb
-rwxr-xr-x 1 foo foo    252 Nov 10 17:48 WpfApp.runtimeconfig.json

Windows で WpfApp.exe も実行できますね、やったー。

Linux でビルドした WpfApp を Windows で実行

--self-contained でも同様にLinuxでビルド、成果物をWindowsで実行できます。

$ dotnet publish -r win-x64 --self-contained
/usr/share/dotnet/sdk/7.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.targets(574,5): warning NETSDK1074: The application host executable will not be customized because adding resources requires that the build be performed on Windows (excluding Nano Server). [/home/WpfApp.csproj]
  WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/WpfApp.dll
  WpfApp -> /home/WpfApp/bin/Release/net7.0-windows/publish/

$ ls -l /home/WpfApp/bin/Debug/net7.0-windows/win-x64/publish/
total 144999
-rwxr-xr-x 1 root root    21120 Oct 19 01:23 Accessibility.dll
-rwxr-xr-x 1 root root  4916728 May  6  2022 D3DCompiler_47_cor3.dll
-rwxr-xr-x 1 root root   522416 Oct 19 14:40 DirectWriteForwarder.dll
-rwxr-xr-x 1 root root  1058976 Oct 18 17:01 Microsoft.CSharp.dll
-rwxr-xr-x 1 root root  1813896 Dec 16  2020 Microsoft.DiaSymReader.Native.amd64.dll
-rwxr-xr-x 1 root root  1296512 Oct 18 17:01 Microsoft.VisualBasic.Core.dll
-rwxr-xr-x 1 root root   247968 Oct 19 14:40 Microsoft.VisualBasic.Forms.dll
-rwxr-xr-x 1 root root    19072 Oct 19 01:24 Microsoft.VisualBasic.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 Microsoft.Win32.Primitives.dll
-rwxr-xr-x 1 root root    39072 Oct 19 14:40 Microsoft.Win32.Registry.AccessControl.dll
-rwxr-xr-x 1 root root   129168 Oct 18 17:01 Microsoft.Win32.Registry.dll
-rwxr-xr-x 1 root root   112800 Oct 19 14:40 Microsoft.Win32.SystemEvents.dll
-rwxr-xr-x 1 root root   158896 Oct 19 04:32 PenImc_cor3.dll
-rwxr-xr-x 1 root root  8616056 Oct 19 14:40 PresentationCore.dll
-rwxr-xr-x 1 root root    39088 Oct 19 14:40 PresentationFramework-SystemCore.dll
-rwxr-xr-x 1 root root    34952 Oct 19 14:40 PresentationFramework-SystemData.dll
-rwxr-xr-x 1 root root    34976 Oct 19 14:40 PresentationFramework-SystemDrawing.dll
-rwxr-xr-x 1 root root    34960 Oct 19 14:40 PresentationFramework-SystemXml.dll
-rwxr-xr-x 1 root root    30848 Oct 19 14:40 PresentationFramework-SystemXmlLinq.dll
-rwxr-xr-x 1 root root   456864 Oct 19 14:40 PresentationFramework.Aero.dll
-rwxr-xr-x 1 root root   460936 Oct 19 14:40 PresentationFramework.Aero2.dll
-rwxr-xr-x 1 root root   239776 Oct 19 14:40 PresentationFramework.AeroLite.dll
-rwxr-xr-x 1 root root   272544 Oct 19 14:40 PresentationFramework.Classic.dll
-rwxr-xr-x 1 root root   682120 Oct 19 14:40 PresentationFramework.Luna.dll
-rwxr-xr-x 1 root root   338080 Oct 19 14:40 PresentationFramework.Royale.dll
-rwxr-xr-x 1 root root 16226464 Oct 19 14:40 PresentationFramework.dll
-rwxr-xr-x 1 root root  1234088 Oct  7 03:04 PresentationNative_cor3.dll
-rwxr-xr-x 1 root root  1288352 Oct 19 14:40 PresentationUI.dll
-rwxr-xr-x 1 root root  1628288 Oct 19 14:40 ReachFramework.dll
-rwxr-xr-x 1 root root    15520 Oct 18 16:21 System.AppContext.dll
-rwxr-xr-x 1 root root    15520 Oct 18 16:21 System.Buffers.dll
-rwxr-xr-x 1 root root   493728 Oct 19 14:40 System.CodeDom.dll
-rwxr-xr-x 1 root root   264336 Oct 18 17:01 System.Collections.Concurrent.dll
-rwxr-xr-x 1 root root   694416 Oct 18 17:01 System.Collections.Immutable.dll
-rwxr-xr-x 1 root root   108720 Oct 18 17:02 System.Collections.NonGeneric.dll
-rwxr-xr-x 1 root root   108688 Oct 18 17:02 System.Collections.Specialized.dll
-rwxr-xr-x 1 root root   268464 Oct 18 17:01 System.Collections.dll
-rwxr-xr-x 1 root root   198824 Oct 18 17:02 System.ComponentModel.Annotations.dll
-rwxr-xr-x 1 root root    17536 Oct 18 16:21 System.ComponentModel.DataAnnotations.dll
-rwxr-xr-x 1 root root    47232 Oct 18 17:02 System.ComponentModel.EventBasedAsync.dll
-rwxr-xr-x 1 root root    84136 Oct 18 17:02 System.ComponentModel.Primitives.dll
-rwxr-xr-x 1 root root   755856 Oct 18 17:02 System.ComponentModel.TypeConverter.dll
-rwxr-xr-x 1 root root    30888 Oct 18 17:02 System.ComponentModel.dll
-rwxr-xr-x 1 root root  1087616 Oct 19 14:40 System.Configuration.ConfigurationManager.dll
-rwxr-xr-x 1 root root    20128 Oct 18 16:21 System.Configuration.dll
-rwxr-xr-x 1 root root   186536 Oct 18 17:02 System.Console.dll
-rwxr-xr-x 1 root root    24224 Oct 18 16:21 System.Core.dll
-rwxr-xr-x 1 root root  3016864 Oct 18 17:02 System.Data.Common.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Data.DataSetExtensions.dll
-rwxr-xr-x 1 root root    25712 Oct 18 16:21 System.Data.dll
-rwxr-xr-x 1 root root    21648 Oct 19 01:24 System.Design.dll
-rwxr-xr-x 1 root root    16504 Oct 18 16:21 System.Diagnostics.Contracts.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Diagnostics.Debug.dll
-rwxr-xr-x 1 root root   391344 Oct 18 17:02 System.Diagnostics.DiagnosticSource.dll
-rwxr-xr-x 1 root root   800896 Oct 18 16:17 System.Diagnostics.EventLog.Messages.dll
-rwxr-xr-x 1 root root   383104 Oct 19 14:40 System.Diagnostics.EventLog.dll
-rwxr-xr-x 1 root root    47264 Oct 18 17:02 System.Diagnostics.FileVersionInfo.dll
-rwxr-xr-x 1 root root   305312 Oct 19 14:40 System.Diagnostics.PerformanceCounter.dll
-rwxr-xr-x 1 root root   346288 Oct 18 17:02 System.Diagnostics.Process.dll
-rwxr-xr-x 1 root root    47248 Oct 18 17:02 System.Diagnostics.StackTrace.dll
-rwxr-xr-x 1 root root    75936 Oct 18 17:03 System.Diagnostics.TextWriterTraceListener.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Diagnostics.Tools.dll
-rwxr-xr-x 1 root root   149672 Oct 18 17:03 System.Diagnostics.TraceSource.dll
-rwxr-xr-x 1 root root    16512 Oct 18 16:21 System.Diagnostics.Tracing.dll
-rwxr-xr-x 1 root root  1083536 Oct 19 14:40 System.DirectoryServices.dll
-rwxr-xr-x 1 root root  1493120 Oct 19 14:40 System.Drawing.Common.dll
-rwxr-xr-x 1 root root    15488 Oct 19 01:24 System.Drawing.Design.dll
-rwxr-xr-x 1 root root   137344 Oct 18 17:03 System.Drawing.Primitives.dll
-rwxr-xr-x 1 root root    21112 Oct 19 01:24 System.Drawing.dll
-rwxr-xr-x 1 root root    16512 Oct 18 16:21 System.Dynamic.Runtime.dll
-rwxr-xr-x 1 root root   207008 Oct 18 17:03 System.Formats.Asn1.dll
-rwxr-xr-x 1 root root   272528 Oct 18 17:03 System.Formats.Tar.dll
-rwxr-xr-x 1 root root    16544 Oct 18 16:21 System.Globalization.Calendars.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.Globalization.Extensions.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Globalization.dll
-rwxr-xr-x 1 root root    92336 Oct 18 17:03 System.IO.Compression.Brotli.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.IO.Compression.FileSystem.dll
-rwxr-xr-x 1 root root   828064 Oct 18 16:21 System.IO.Compression.Native.dll
-rwxr-xr-x 1 root root    51376 Oct 18 17:03 System.IO.Compression.ZipFile.dll
-rwxr-xr-x 1 root root   276624 Oct 18 17:03 System.IO.Compression.dll
-rwxr-xr-x 1 root root   108720 Oct 18 17:03 System.IO.FileSystem.AccessControl.dll
-rwxr-xr-x 1 root root    55456 Oct 18 17:03 System.IO.FileSystem.DriveInfo.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.IO.FileSystem.Primitives.dll
-rwxr-xr-x 1 root root    88240 Oct 18 17:03 System.IO.FileSystem.Watcher.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.IO.FileSystem.dll
-rwxr-xr-x 1 root root    92320 Oct 18 17:03 System.IO.IsolatedStorage.dll
-rwxr-xr-x 1 root root    84144 Oct 18 17:03 System.IO.MemoryMappedFiles.dll
-rwxr-xr-x 1 root root   284816 Oct 19 14:40 System.IO.Packaging.dll
-rwxr-xr-x 1 root root    16544 Oct 18 16:21 System.IO.Pipes.AccessControl.dll
-rwxr-xr-x 1 root root   186544 Oct 18 17:03 System.IO.Pipes.dll
-rwxr-xr-x 1 root root    16016 Oct 18 16:21 System.IO.UnmanagedMemoryStream.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.IO.dll
-rwxr-xr-x 1 root root  3827872 Oct 18 17:04 System.Linq.Expressions.dll
-rwxr-xr-x 1 root root   850080 Oct 18 17:04 System.Linq.Parallel.dll
-rwxr-xr-x 1 root root   231568 Oct 18 17:04 System.Linq.Queryable.dll
-rwxr-xr-x 1 root root   505984 Oct 18 17:04 System.Linq.dll
-rwxr-xr-x 1 root root   161960 Oct 18 17:04 System.Memory.dll
-rwxr-xr-x 1 root root   108704 Oct 18 17:04 System.Net.Http.Json.dll
-rwxr-xr-x 1 root root  1751184 Oct 18 17:04 System.Net.Http.dll
-rwxr-xr-x 1 root root   706720 Oct 18 17:04 System.Net.HttpListener.dll
-rwxr-xr-x 1 root root   444544 Oct 18 17:04 System.Net.Mail.dll
-rwxr-xr-x 1 root root   116880 Oct 18 17:04 System.Net.NameResolution.dll
-rwxr-xr-x 1 root root   170160 Oct 18 17:04 System.Net.NetworkInformation.dll
-rwxr-xr-x 1 root root   100512 Oct 18 17:04 System.Net.Ping.dll
-rwxr-xr-x 1 root root   227488 Oct 18 17:05 System.Net.Primitives.dll
-rwxr-xr-x 1 root root   268432 Oct 18 17:05 System.Net.Quic.dll
-rwxr-xr-x 1 root root   358520 Oct 18 17:05 System.Net.Requests.dll
-rwxr-xr-x 1 root root   620688 Oct 18 17:05 System.Net.Security.dll
-rwxr-xr-x 1 root root    47264 Oct 18 17:05 System.Net.ServicePoint.dll
-rwxr-xr-x 1 root root   563360 Oct 18 17:05 System.Net.Sockets.dll
-rwxr-xr-x 1 root root   174256 Oct 18 17:05 System.Net.WebClient.dll
-rwxr-xr-x 1 root root    67744 Oct 18 17:05 System.Net.WebHeaderCollection.dll
-rwxr-xr-x 1 root root    47280 Oct 18 17:05 System.Net.WebProxy.dll
-rwxr-xr-x 1 root root   104608 Oct 18 17:05 System.Net.WebSockets.Client.dll
-rwxr-xr-x 1 root root   194696 Oct 18 17:05 System.Net.WebSockets.dll
-rwxr-xr-x 1 root root    18040 Oct 18 16:21 System.Net.dll
-rwxr-xr-x 1 root root    16016 Oct 18 16:21 System.Numerics.Vectors.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.Numerics.dll
-rwxr-xr-x 1 root root    84112 Oct 18 17:05 System.ObjectModel.dll
-rwxr-xr-x 1 root root  1001632 Oct 19 14:40 System.Printing.dll
-rwxr-xr-x 1 root root 11651216 Oct 18 16:19 System.Private.CoreLib.dll
-rwxr-xr-x 1 root root  2230432 Oct 18 17:05 System.Private.DataContractSerialization.dll
-rwxr-xr-x 1 root root   264352 Oct 18 17:06 System.Private.Uri.dll
-rwxr-xr-x 1 root root   411808 Oct 18 17:06 System.Private.Xml.Linq.dll
-rwxr-xr-x 1 root root  8435872 Oct 18 17:06 System.Private.Xml.dll
-rwxr-xr-x 1 root root    75936 Oct 18 17:06 System.Reflection.DispatchProxy.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Reflection.Emit.ILGeneration.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Reflection.Emit.Lightweight.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Reflection.Emit.dll
-rwxr-xr-x 1 root root    15488 Oct 18 16:21 System.Reflection.Extensions.dll
-rwxr-xr-x 1 root root  1149072 Oct 18 17:06 System.Reflection.Metadata.dll
-rwxr-xr-x 1 root root    16040 Oct 18 16:21 System.Reflection.Primitives.dll
-rwxr-xr-x 1 root root    43152 Oct 18 17:06 System.Reflection.TypeExtensions.dll
-rwxr-xr-x 1 root root    16528 Oct 18 16:21 System.Reflection.dll
-rwxr-xr-x 1 root root   129152 Oct 19 14:40 System.Resources.Extensions.dll
-rwxr-xr-x 1 root root    15520 Oct 18 16:21 System.Resources.Reader.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Resources.ResourceManager.dll
-rwxr-xr-x 1 root root    55472 Oct 18 17:06 System.Resources.Writer.dll
-rwxr-xr-x 1 root root    16048 Oct 18 16:21 System.Runtime.CompilerServices.Unsafe.dll
-rwxr-xr-x 1 root root    34952 Oct 18 17:06 System.Runtime.CompilerServices.VisualC.dll
-rwxr-xr-x 1 root root    18080 Oct 18 16:21 System.Runtime.Extensions.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Runtime.Handles.dll
-rwxr-xr-x 1 root root    51328 Oct 18 17:06 System.Runtime.InteropServices.JavaScript.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Runtime.InteropServices.RuntimeInformation.dll
-rwxr-xr-x 1 root root    63656 Oct 18 17:06 System.Runtime.InteropServices.dll
-rwxr-xr-x 1 root root    17016 Oct 18 16:21 System.Runtime.Intrinsics.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Runtime.Loader.dll
-rwxr-xr-x 1 root root   333960 Oct 18 17:06 System.Runtime.Numerics.dll
-rwxr-xr-x 1 root root   329888 Oct 18 17:06 System.Runtime.Serialization.Formatters.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Runtime.Serialization.Json.dll
-rwxr-xr-x 1 root root    43168 Oct 18 17:07 System.Runtime.Serialization.Primitives.dll
-rwxr-xr-x 1 root root    17024 Oct 18 16:21 System.Runtime.Serialization.Xml.dll
-rwxr-xr-x 1 root root    17576 Oct 18 16:21 System.Runtime.Serialization.dll
-rwxr-xr-x 1 root root    43136 Oct 18 16:21 System.Runtime.dll
-rwxr-xr-x 1 root root   239776 Oct 18 17:07 System.Security.AccessControl.dll
-rwxr-xr-x 1 root root   100512 Oct 18 17:07 System.Security.Claims.dll
-rwxr-xr-x 1 root root    17536 Oct 18 16:21 System.Security.Cryptography.Algorithms.dll
-rwxr-xr-x 1 root root    16544 Oct 18 16:21 System.Security.Cryptography.Cng.dll
-rwxr-xr-x 1 root root    16504 Oct 18 16:21 System.Security.Cryptography.Csp.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.Security.Cryptography.Encoding.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.Security.Cryptography.OpenSsl.dll
-rwxr-xr-x 1 root root   870528 Oct 19 14:40 System.Security.Cryptography.Pkcs.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Security.Cryptography.Primitives.dll
-rwxr-xr-x 1 root root    59528 Oct 19 14:40 System.Security.Cryptography.ProtectedData.dll
-rwxr-xr-x 1 root root    17568 Oct 18 16:21 System.Security.Cryptography.X509Certificates.dll
-rwxr-xr-x 1 root root   444576 Oct 19 14:40 System.Security.Cryptography.Xml.dll
-rwxr-xr-x 1 root root  1972368 Oct 18 17:07 System.Security.Cryptography.dll
-rwxr-xr-x 1 root root   186512 Oct 19 14:40 System.Security.Permissions.dll
-rwxr-xr-x 1 root root   190640 Oct 18 17:07 System.Security.Principal.Windows.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Security.Principal.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.Security.SecureString.dll
-rwxr-xr-x 1 root root    19104 Oct 18 16:21 System.Security.dll
-rwxr-xr-x 1 root root    17552 Oct 18 16:21 System.ServiceModel.Web.dll
-rwxr-xr-x 1 root root    16512 Oct 18 16:21 System.ServiceProcess.dll
-rwxr-xr-x 1 root root   882864 Oct 18 17:07 System.Text.Encoding.CodePages.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Text.Encoding.Extensions.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Text.Encoding.dll
-rwxr-xr-x 1 root root   137384 Oct 18 17:07 System.Text.Encodings.Web.dll
-rwxr-xr-x 1 root root  1493136 Oct 18 17:07 System.Text.Json.dll
-rwxr-xr-x 1 root root   972944 Oct 18 17:07 System.Text.RegularExpressions.dll
-rwxr-xr-x 1 root root    96416 Oct 19 14:40 System.Threading.AccessControl.dll
-rwxr-xr-x 1 root root   137336 Oct 18 17:07 System.Threading.Channels.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.Threading.Overlapped.dll
-rwxr-xr-x 1 root root   530608 Oct 18 17:07 System.Threading.Tasks.Dataflow.dll
-rwxr-xr-x 1 root root    16504 Oct 18 16:21 System.Threading.Tasks.Extensions.dll
-rwxr-xr-x 1 root root   145584 Oct 18 17:07 System.Threading.Tasks.Parallel.dll
-rwxr-xr-x 1 root root    17016 Oct 18 16:21 System.Threading.Tasks.dll
-rwxr-xr-x 1 root root    16040 Oct 18 16:21 System.Threading.Thread.dll
-rwxr-xr-x 1 root root    16048 Oct 18 16:21 System.Threading.ThreadPool.dll
-rwxr-xr-x 1 root root    15480 Oct 18 16:21 System.Threading.Timer.dll
-rwxr-xr-x 1 root root    88224 Oct 18 17:07 System.Threading.dll
-rwxr-xr-x 1 root root   596128 Oct 18 17:08 System.Transactions.Local.dll
-rwxr-xr-x 1 root root    17536 Oct 18 16:21 System.Transactions.dll
-rwxr-xr-x 1 root root    16032 Oct 18 16:21 System.ValueTuple.dll
-rwxr-xr-x 1 root root    63632 Oct 18 17:08 System.Web.HttpUtility.dll
-rwxr-xr-x 1 root root    15992 Oct 18 16:21 System.Web.dll
-rwxr-xr-x 1 root root  1464456 Oct 19 14:40 System.Windows.Controls.Ribbon.dll
-rwxr-xr-x 1 root root   112784 Oct 19 14:40 System.Windows.Extensions.dll
-rwxr-xr-x 1 root root    16512 Oct 19 01:24 System.Windows.Forms.Design.Editors.dll
-rwxr-xr-x 1 root root  5335168 Oct 19 14:40 System.Windows.Forms.Design.dll
-rwxr-xr-x 1 root root   960672 Oct 19 14:40 System.Windows.Forms.Primitives.dll
-rwxr-xr-x 1 root root 13342848 Oct 19 14:40 System.Windows.Forms.dll
-rwxr-xr-x 1 root root   137352 Oct 19 14:40 System.Windows.Input.Manipulations.dll
-rwxr-xr-x 1 root root    30880 Oct 19 14:40 System.Windows.Presentation.dll
-rwxr-xr-x 1 root root    16544 Oct 18 16:21 System.Windows.dll
-rwxr-xr-x 1 root root  1439872 Oct 19 14:41 System.Xaml.dll
-rwxr-xr-x 1 root root    16552 Oct 18 16:21 System.Xml.Linq.dll
-rwxr-xr-x 1 root root    22144 Oct 18 16:21 System.Xml.ReaderWriter.dll
-rwxr-xr-x 1 root root    17040 Oct 18 16:21 System.Xml.Serialization.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Xml.XDocument.dll
-rwxr-xr-x 1 root root    30888 Oct 18 17:08 System.Xml.XPath.XDocument.dll
-rwxr-xr-x 1 root root    16000 Oct 18 16:21 System.Xml.XPath.dll
-rwxr-xr-x 1 root root    16504 Oct 18 16:21 System.Xml.XmlDocument.dll
-rwxr-xr-x 1 root root    18040 Oct 18 16:21 System.Xml.XmlSerializer.dll
-rwxr-xr-x 1 root root    24704 Oct 18 16:21 System.Xml.dll
-rwxr-xr-x 1 root root    50816 Oct 18 16:21 System.dll
-rwxr-xr-x 1 root root   415872 Oct 19 14:41 UIAutomationClient.dll
-rwxr-xr-x 1 root root   882848 Oct 19 14:41 UIAutomationClientSideProviders.dll
-rwxr-xr-x 1 root root    59520 Oct 19 14:41 UIAutomationProvider.dll
-rwxr-xr-x 1 root root   309424 Oct 19 14:41 UIAutomationTypes.dll
-rwxr-xr-x 1 root root  2275472 Oct 19 14:41 WindowsBase.dll
-rwxr-xr-x 1 root root   211072 Oct 19 14:41 WindowsFormsIntegration.dll
-rwxr-xr-x 1 root root    35857 Nov 10 17:53 WpfApp.deps.json
-rwxr-xr-x 1 root root     6656 Nov 10 17:48 WpfApp.dll
-rwxr-xr-x 1 root root   152064 Nov 10 17:48 WpfApp.exe
-rwxr-xr-x 1 root root    13808 Nov 10 17:48 WpfApp.pdb
-rwxr-xr-x 1 root root      260 Nov 10 17:53 WpfApp.runtimeconfig.json
-rwxr-xr-x 1 root root   309408 Oct 18 16:08 clretwrc.dll
-rwxr-xr-x 1 root root   662656 Oct 18 16:01 clrgc.dll
-rwxr-xr-x 1 root root  1532592 Oct 18 16:03 clrjit.dll
-rwxr-xr-x 1 root root  5103280 Oct 18 16:11 coreclr.dll
-rwxr-xr-x 1 root root    61160 Oct 18 16:08 createdump.exe
drwxrwxrwx 1 root root     8192 Nov 10 17:53 cs
drwxrwxrwx 1 root root     8192 Nov 10 17:53 de
drwxrwxrwx 1 root root     8192 Nov 10 17:53 es
drwxrwxrwx 1 root root     8192 Nov 10 17:53 fr
-rwxr-xr-x 1 root root   383648 Oct 18 16:53 hostfxr.dll
-rwxr-xr-x 1 root root   394880 Oct 18 16:54 hostpolicy.dll
drwxrwxrwx 1 root root     8192 Nov 10 17:53 it
drwxrwxrwx 1 root root     8192 Nov 10 17:53 ja
drwxrwxrwx 1 root root     8192 Nov 10 17:53 ko
-rwxr-xr-x 1 root root  1315248 Oct 18 16:23 mscordaccore.dll
-rwxr-xr-x 1 root root  1315248 Oct 18 16:23 mscordaccore_amd64_amd64_7.0.22.51805.dll
-rwxr-xr-x 1 root root  1247136 Oct 18 16:22 mscordbi.dll
-rwxr-xr-x 1 root root    58488 Oct 18 16:21 mscorlib.dll
-rwxr-xr-x 1 root root   136880 Oct 18 16:08 mscorrc.dll
-rwxr-xr-x 1 root root   534416 Oct 18 16:21 msquic.dll
-rwxr-xr-x 1 root root   100992 Oct 18 16:21 netstandard.dll
drwxrwxrwx 1 root root     8192 Nov 10 17:53 pl
drwxrwxrwx 1 root root     8192 Nov 10 17:53 pt-BR
drwxrwxrwx 1 root root     8192 Nov 10 17:53 ru
drwxrwxrwx 1 root root     8192 Nov 10 17:53 tr
-rwxr-xr-x 1 root root    99216 Sep 21 10:03 vcruntime140_cor3.dll
-rwxr-xr-x 1 root root  1958000 Oct 19 04:34 wpfgfx_cor3.dll
drwxrwxrwx 1 root root     8192 Nov 10 17:53 zh-Hans
drwxrwxrwx 1 root root     8192 Nov 10 17:53 zh-Hant

これで、WPF も Linux CI でビルド、成果物を配布して Windows で実行という当たり前の世界がやってきましたね。 何年も待ってあきらめてましたがついに来たようです。

まとめ

誰か Windows ARM 向けにビルドして実行できるか試してくれないかなぁ。

ちなみに、linux-x64 向けビルドは失敗しました。

$ dotnet publish -c Release -r linux-x64 --self-contained
Error: /usr/share/dotnet/sdk/7.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(448,5): error NETSDK1082: There was no runtime pack for Microsoft.WindowsDesktop.App.WPF available for the specified RuntimeIdentifier 'linux-x64'. [/home/runner/work/WpfAppLinuxBuild/WpfAppLinuxBuild/WpfApp/WpfApp.csproj]

  1. ちょっといじらないとだめそうかな、という気配もあるので、誰もやってない感がすごい。

  2. WPF ライブラリも同様の流れです。

新しいバージョンの .NET SDK と Visual Studio の更新

2022/11/8、.NET 7 がリリースされて、Visual Studio 2022 にも対応する更新 17.4.0 が降ってきました。ヤッター。

devblogs.microsoft.com

これに伴い、Visual Studio の更新でインストールされている .NET SDK が意図せず変わるのに出会うのが3度を超えたのでメモしておきます。

tl;dr;

  • Visual Studio 2022 (17.0.0 - 17.3.x) を使って .NET SDK 6.0 をインストールしていると、Visual Studio 2022 (17.4.0) 以降 に更新した際に、.NET SDK 6.0 はアンインストールされて .NET SDK 7.0 のみになります。
  • Visual Studio に依存せずに 特定の .NET SDK を維持したい場合は、winget や インストーラーを使って .NET SDK をインストールしておきましょう。(そして .NET SDK の更新どうする問題に出会う)

.NET SDK のリリースと Visual Studio の更新

Visual Studio を使って .NET SDK をインストールしている人は多いかと思います。この仕組みはいろいろ便利な側面があります。

  • Visual Studio 2017 以降、.NET SDK は Visual Studio を使用してインストールできるようになりました。(Visual Studio Installer でインストールする .NET SDK のこと)
  • 新しい .NET のバージョンがリリースされると、.NET SDK や .NET Runtime、そして Visual Studio にも更新が降ってきます。
  • Visual Studio を使用して .NET SDK をインストールしている場合、新しい .NET SDK への更新を伴う Visual Studio へ更新するとインストールされている .NET SDK が新しいものに差し変わります。

Visual Studio を使用してインストールするの一覧 (引用元: https://learn.microsoft.com/ja-jp/dotnet/core/install/windows?tabs=net60 )

Visual Studio の更新をするだけで.NET SDK も更新できる、これは Visual Studio を使って書いている人には非常に便利な仕組みです。

この仕組みが生きた直近の例として、.NET SDK 6.0.2 にはビルドで困るバグがありました、が、Visual Studio を更新しているだけでバグが解消された .NET SDK 6.0.3 を利用することができるようになりました。 多分何が困ったのか気づかなかった方も多かったのではないかと思います。いい仕組みですね。

Visual Studio を更新していくだけで最新の .NET SDK が利用できる、最高ですね!

.NET の RollForward ポリシーと .NET SDK のバージョン更新

なぜ Visual Studio による .NET SDK の更新で普段困っていないかというと、「RollForward ポリシーはlatestPatch」と 「.NET SDK の更新がパッチレベルである」と「TargetFramework の指定はマイナーレベル」の3者が合致しているため、なんの不自由もなく利用できるわけです。

  • .NET のアプリケーションは RollForward ポリシーという仕組みで異なる .NET SDK でも latestPatch 1 で解決できる限りは互換性があると認識して動作します。2
  • Visual Studio 17 (17.0.0 ~ 17.3.x) の間にあった .NET SDK のバージョン更新は 6.0.0 ~ 6.0.x とパッチレベルでした。
  • csproj の TargetFramework は net6.0 や net7.0 とマイナーバージョンレベルの指定。

パッチレベルの更新なので、副作用も考える必要がない3 ということもあり実際開発環境の更新をあまり意識せずに済んでいるでしょう。

仮に .NET SDK が 2.1 から 2.2 とマイナーバージョン更新されると、困るわけですね。(.NET の RollForward は .NET Core 3.1 からの紹介なので影響なかったですが) 同様に、RollForward を latestPatch ではなく、 patch と厳密に指定しても Visual Studio の更新で .NET SDK のパッチレベル更新が入ったときに困ることになります。

If no rollForward value is set, it uses latestPatch as the default rollForward policy. Otherwise, check each value and their behavior in the rollForward section.

引用: global.json overview - .NET CLI | Microsoft Learn

RollForward は実行時のランタイム指定なので、Visual Studio でビルドしてデバッグ実行する際はビルドしなおしているので困らないというのもあります。

このことを踏まえて、今回の .NET 7 のリリースを見てみましょう。

新しい .NET SDK のリリースと Visual Studio の更新

今回は、.NET 7 の GA に伴い、Visual Studio 2022 (17.4.0) の更新が配布されました。 従来の Visual Studio による .NET SDK の更新と違うのは、メジャーバージョンの更新 (.NET SDK 6.0 から .NET SDK 7.0) だったということです。

結果起こったのは、更新前は .NET SDK 6.0 がインストールされていたのに、VS2022 (17.4.0) に更新したら .NET SDK 7.0 のみになってビルドや実行で困るようになったのでした。 csproj での TargetFramework レベルで一致しないので、メジャーレベルの .NET SDK 6.0 が Visual Studio の更新で消えるのは影響高いのです。

ということで、こういうのに困る場合はいずれかがいいのですかねぇ。

  • プロジェクトで必要な .NET SDK は Visual Studio とは別でインストーラーやwinget でインストールしておく。(安パイ。.NET SDK の更新はwingetやscoopじゃないと困りますね)
  • Visual Studio の更新に合わせてTargetFramework も新しい.NET バージョンに更新する。(アグレッシブですね。更新タイミング揃える努力がそこそこ必要)
  • 先に Visual Studio Preview で 次の .NET SDK が rc リリース時点から TargetFramework を上げる。(チャレンジャーと言わざるを得ないが以外と安パイ)

個人的には、Visual Studio による .NET SDK の更新がメジャーバージョンだった場合は、入れ替えないで残しておいてほしいお気持ちです。 すべてはそれで解決する....


  1. この記事を書いている時まで、latestMinor だったと認識していましたがドキュメントを読み直したら latestPatch でした。

  2. この RollForward は .csproj や global.json で挙動を指定できます。

  3. ほとんどのケースでは、.NET SDK のマイナーアップデートで挙動が変わるような変更はないので安心してアップデートできているでしょう。私はCI以外では困ったことはないです。

Visual Studio 2022 で使っている拡張機能

VS2022 がリリースされて時間がたち、来月には .NET 7 もリリースされるのでVS2022 で利用している拡張機能を一度まとめておきます。

VS2022 から x64対応されたことで、VS2019 で利用できた拡張機能は非互換になりましたが、幸いにもVS2022 でも多くの拡張機能が利用ができます。この非互換を解消できないパッケージは VS2019 までとは別名で拡張機能が出ていることがあるので注意が必要です。

以前 VS2019 で使っている拡張機能についても書いています。

tech.guitarrapc.com

目次

tl;dr;

VS2022 で拡張の多くが非互換になったことで、かなり見直すきっかけになりました。 どんな環境でも入れておいていいなぁと思うのは、VSColorOutput64、Parallel Builds Monitor、Time Stamp Margin 2022、Copy Nice です。

いずれもコードを書くのを一切邪魔しませんが、ふとしたときにこれがやってくれていたのか、とありがたさに感謝する拡張です。 いい拡張ないか、気になったら試していくぞー。

Extensions一覧

Name Desc VS2019 対応
Git Diff Margin - Visual Studio Marketplace git 差分をエディタ画面に表示
SwitchStartupProject for VS 2022 - Visual Studio Marketplace Startup Project の変更と定義ができるのでWebなどで複数プロジェクトを同時に起動するのが楽になります 〇 別名
VSColorOutput64 - Visual Studio Marketplace Output Window 出力された文字に状態に応じて色が付きます。 〇 別名
Open on GitHub - Visual Studio Marketplace VSからGitHub へのジャンプ
Parallel Builds Monitor - Visual Studio Marketplace プロジェクトごとのビルド経過がチャート表示されます。そのマシンでビルドがどのように遅いのかがわかるのですごくいいです。BuildVision から乗り換えました。
Time Stamp Margin 2022 - Visual Studio Marketplace Output Window で行ごとにタイムスタンプが表示されます。 〇 別名
Solution Error Visualizer - Visual Studio Marketplace Solution Explorer にエラーや警告をハイライト表示します。
Copy Nice - Visual Studio Marketplace コードを他ツールに張り付けた時のインデントをいい感じに修正します ×

それぞれの拡張を簡単に紹介します。

Git Diff Margin

Git Diff Marginの差分表示

git の diff状態がコードの左に表示されます。未保存が黄色、追加行が緑、変更が青、削除が赤で表示されています。 削除内容も赤い三角から見えるので、VSから動かずにどんな変更だったかを見るのに重宝します。

SwitchStartupProject for VS 2022

SwitchStartupProject for VS 2022

ツールバーで デバッグ実行するプロジェクトの選択、設定ができます。Configure からマルチスタートアップ設定ができるので、デバッグ実行で複数プロジェクトを同時に起動することもできます。 設定はslnパスにファイルに書き出されるので、チームで共有もできるのが便利です。

VSColorOutput64

VSColorOutput64の出力例

VSColorOutput の VS2022 対応版です。

Visual Studio は出力ウィンドウ (Output Window) に色付け出力してくれませんが、この拡張をいれることでエラーを赤字にしたりといった色付けが有効になります。

Open On GitHub

Open on GitHub は右クリックコンテキストメニューからジャンプできる

Visual Studio から GitHub の該当コード行をブラウザで開くことができます。

Parallel Builds Monitor

Parallel Builds Monitor でビルド状況の可視化

BuildVision を使わなくする程度の能力です。

ビルドをしたときに、どのプロジェクトの順に、どれぐらいの時間がかかってビルドされたかが可視化されます。 ビルドはいつの間にか遅くなったり、使うマシンによってビルド時間が変わりますが、自分の環境だけの問題なのかどうかを客観的に測定できるようになります。

新しい CPU にしたり、メモリ積んだり、ストレージ変えたり、いろんなときに試すと面白いです。

Time Stamp Margin 2022

Time Stamp Margin でデバッグ実行時にOutput Window にタイムスタンプが付与されている

デバッグ実行時、出力ウィンドウ (Output Window)にたくさん出力する割に、いつ出たものはわからず後から困ることが多いです。 そんな時に行ごとにタイムスタンプが付いていると、便利というわけです。

いざ困ってからではなく事前に入れておいて損がない拡張です。

Solution Error Visualizer

Solution Error Visualizer でソリューションエクスプローラーからエラーがわかる図

私は Solution Explorer が大好きですが、ビルドしたときにエラーや警告が何となく出てくれると便利だと思うことがあります。 そんな人はこの拡張を入れると、エラーウィンドウだけでなく、Solution Explorer でもエラーが出てるファイルを確認できます。

インストールしたり、アンインストールしたりを繰り返している筆頭拡張かもしれません。

Copy Nice

VSで先頭行のインデントを含まずコピーしても

貼り付けたときにインデントが壊れない

Mads Kristensen 先生の新作です。(2022年10月25日)

主にコード片をSlack やREADME、スライドにコピペするときに助かります。 コードをコピーするときに、先頭行のインデントのずれストレスを回避してくれます。

とりあえず御大の記事を読むといいです。

devblogs.microsoft.com

VS2022で使わなくなった拡張

VS2019 まで使っていて VS2022 で使わなくなった拡張です。

  • BuildVision: 動作が微妙に怪しいまま使っていましたが、Parallel Builds Monitor に置き換えました。今までありがとう。
  • Open UserSecrets: 自作の拡張でしたがついにいらなくなった気がするので、VS2022 対応をしない方針です。
  • Fix Mixed Tabs 2022: .editorconfig で代用するようになったので使わなくなりました。今までありがとう。

GitHub Actions でモノレポ上の特定パスをチェックアウトスキップしたい

モノレポでリポジトリサイズが大きくチェックアウトが長くて困ることがあります。

今回はこういったときにどうすることができるのかを考えてみましょう。

tl;dr;

CIでは特定のパスしかいらないのに、ほかのパスもチェックアウトされるのを制御したい場合、スパースチェックアウトが使えます。

  • スパースチェックアウトを使えば、モノレポ上で、特定のパスをチェックアウトスキップしたり、特定のパスだけチェックアウトすることができる。
  • actions/checkout は スパースチェックアウトに対応していないので、自前でチェックアウトが必要。(自前チェックアウトはしたくないがやむなし。)
  • actions/checkout の スパースチェックアウト 対応PR はあるので、upvote してマージされてほしい声が多くなるといいかもしれない。

github.com

GitHub Actions でスパースチェックアウトをする

GitHub Actions でスパースチェックアウトをする例を2つ紹介します。(両方ともシャロークローンも行っています)

もともとのルート直下は次のディレクトリです。このうち、src/ を対象にスパースチェックアウトしてみましょう。

$ ls -la
.editorconfig
.git
.gitatributes
.github
.gitignore
LICENSE.md
README.md
mermaid
samples
src
  • 1つ目の例 git-sparse-checkout-exclude.yaml は、特定のパスを除外してチェックアウトする例です。
    • チェックアウト後、src/ がチェックアウトから除外されていることがわかります。
  • 2つ目の例 git-sparse-checkout-only.yaml は、特定のパスだけチェックアウトする例です。
    • チェックアウト後、src/ だけチェックアウトされていることがわかります。

gist.github.com

過去の経験では actions/checkout で 1m30s 程度かかるリポジトリで35s 程度まで高速化されています。 すでにしゃろークローンしているが、それでもチェックアウトに時間がかかっており、もっと高速化が強く望まれるときはスパースチェックアウトを検討してもいいかもしれません。

ただ、現在の GitHub Actions の actions/checkout はスパースチェックアウトに対応しておらず、自前チェックアウトが必要です。 チェックアウトのコードはメンテしにくくくなりやすいため、自前チェックアウトは避けたい気持ちもあります。

actions/checkout にスパースチェックアウト対応が入ったら、気兼ねなく使っていけると思うので期待したいです。

CI におけるチェックアウトの基本

さて、CI においてチェックアウトをどのように行うといいのかを自分のために文章にします。 よく知られていることだとは思うので、知ってる方はスルーでどうぞ。

CI でのチェックアウトの基本は、「高速であればあるほど良い」であり、これは多くの方が同意するところだと思います。

高速なチェックアウトをしようとして課題になりやすいのが、マネージドCIサービスでのビルドです。(GitHub Actionsなどのホストランナー環境をイメージしてください) マネージドCIサービスは、環境を起動する度にクリーンな環境が用意されます。このため、前回のチェックアウトを再利用できず、毎回 git clone をする必要があります。リポジトリサイズが大きい場合、何も考えず git clone をするとチェックアウト完了まで数分かかり、ビルド全体が遅くなるなるのです。

一方で、Jenkins のように前回のジョブ結果 = チェックアウト結果を再利用できるセルフホストランナー環境では、2回目のチェックアウトはgit差分で済むため高速に終わります。このため、オンプレや自社サーバー、EC2などでビルドしている場合はチェックアウト速度に悩む機会が少ないでしょう。

では、どのようにしたらマネージドCIサービスでチェックアウトを高速化できるでしょうか? よくある失敗が、前回のキャッシュを取って次の起動でリストアして git 差分で済ませようという案です。残念ながら、キャッシュはただのダウンロード/アップロードであり、リポジトリをキャッシュに突っ込んでも高速化しないことがほとんどです。

git clone をいかに高速に行うかが課題となります。

git clone を高速化する定番の方法

よく使われる方法が2つあります。

  • シャロークローン (shallow clone)
  • スパースチェックアウト (sparse checkout)

それぞれを見てみましょう。

シャロークローン

「git clone が重いので履歴をすべて持ってくるのではなく、直近履歴だけ取得する」というのがシャロークローンです。

シャロークローンは git clone --depth 1 のように --depth n を指定することで直近n件の履歴だけ持ってくることで、大量のコミット履歴があるリポジトリでもクローンが軽くなります。1

シャロークローンは、チェックアウトする履歴を直近から指定できると捉えることができます。 詳しい内容は GitHub ブログがいい記事を出しているのでここを見るといいでしょう。2

github.blog

git履歴は運用や開発が進むごとに積み重なる割に、CIではそのコミットしかビルドにいらないことが多く、フルクローンはCIにおいては無駄になりやすいです。そのため、CIにおけるチェックアウトはしゃろークローンにするのが定番になっていると認識しています。

履歴指定は必ずしも --depth 1 にする必要はありません。1件前のコミットログが欲しいから--depth 2 にしたり、直近100件ぐらいまではほぼ速度が変わらないことが多いので、--depth 100 にすることもあります。

スパースチェックアウト

「git clone が重いので全部のパスをチェックアウトするのではなく、チェックアウトアウトするパスを特定のものに絞り込む」というのがスパースチェックアウトです。

スパースチェックアウトは git clone --no-checkout --sparse のようにクローン時点ではチェックアウトしません。チェックアウト前にgit sparse-checkout set "パス" でチェックアウト対象パスを指定してから、チェックアウトをします。特定のパスだけチェックアウトができるので、大量にファイルがあるリポジトリでも、クローンやチェックアウトが軽くなります。

スパースチェックアウトは、チェックアウト対象のパスを管理できると言い換えてもいいでしょう。 詳しい内容は GitHub ブログがいい記事を出しているのでここを見るといいでしょう。3

github.blog

スパースチェックアウトを使って効果が出やすい例としては、モノレポでそれぞれの言語やサーバー/クライアントごとにパスを切ってビルドしている場合や、テストには特定のパスだけチェックアウトが必要というケースが考えられます。(参考にある Zenn はそれ)

私はゲーム開発が多いので、サーバーとクライアント(Unity)がモノレポで管理されているケースでほしくなる時があります。Unityは管理しているファイルごとに .meta が必ず必要になるためクライアントコードのファイル数が膨れやすい性質があります。このため、クライアントコードのファイル数が枷となって、サーバーサイドビルドのチェックアウトに影響がでることがあるのです。

GitHub Actions でスパースチェックアウトをしたい

スパースチェックアウトが便利なのはわかりましたが、GitHub Actions ではどうやればスパースチェックアウトができるのでしょうか?

Gactions/checkout はスパースチェックアウトに対応していない

GitHub Actions でリポジトリを チェックアウト = git clone するときは、actions/checkout を使うのが定番です。4

しかし、actions/checkout はスパースチェックアウトには対応していません。スパースチェックアウトをしたい場合は、自前チェックアウトが必要があります。

スパースチェックアウトを書いてみよう

自前でスパースチェックアウトするときは、いくつか参考を見るといいでしょう。(記事の末尾にリンクをおいておきます。) 今回は、冒頭にあげたコードを使って流れを説明します。

git clone --filter=blob:none --no-checkout --depth 1 --sparse "https://github.com/なんとか.git" .

スパースチェックアウトを行う場合、git clone 時点では checkout をしません。このため、--no-checkout --sparse がセットで登場します。ちなみに --filter=blob:none はパーシャルクローン(の一つであるブロブレスクローン)で、--depth 1 はシャロークローンで、一緒に使うことが多いです。

git sparse-checkout set --no-cone "${{ env.SPARSECHECKOUT_DIR }}" "/*"

スパースチェックアウトのパス指定を行います。 パス指定は .gitignore と同様のスタイルで記載できるのですが、固定パス指定ではなく パスパターンで指定する場合、--no-cone が必要です。 cone にするとパスパターン (/* みたいなやつ) が使えないの注意です。

.gitignore で否定パスを書いたことがある人は、順番に違和感を覚えるかもしれません。.gitignore では、否定を後に書いて打消しますが、スパースチェックアウトでは先に書きます、書くまで知らなかったです。

NOTE: ちなみに .git/info/sparse-checkout に直接書き込もうと、echo -e "/*\n!node_modules" >> .git/info/sparse-checkout のようにするのは私は避けています。git sparse-checkout set だとパス指定に問題があるときにエラーなどで教えてくれるけど、直接書き込むと git sparse-checkout list するまで問題があるか気づけないためです。

git sparse-checkout init

スパースチェックアウトを有効にします。先に git sparse-checkout set しておいてから有効にするほうが、警告とか見やすいのでおすすめです。 パスパターンで指定するなら、git sparse-checkout init --cone しないこと。

git sparse-checkout list

デバッグ用です。スパースチェックアウトのパス指定があってるか表示させているだけです。.git/info/sparse-checkout にパスパターンで直書きしているのに、git sparse-checkout init --cone にしてると、「問題があるよ」と警告が出てくれます、便利。

git checkout "${GITHUB_SHA}"

チェックアウトします。ここで初めてファイルが取得されます。スパースチェックアウトがちゃんと指定されていれば、指定したパスだけチェックアウトしたり、指定したパスを除外してチェックアウトできているのが確認できるでしょう。

サブモジュールは割愛します。プライベートリポジトリなサブモジュールをチェックアウトする時はPATが必要なので気を付けて。

自前チェックアウトの最後は git reset --hard SHA します。これは定番ですね。後続のステップで git diff が出ないように気を払っておきましょう。

まとめ

GitHub Actions の actions/checkout は シャロークローンされており十分高速なため、99%のケースではこの記事は不要のはずです。 さらなる高速化としてスパースチェックアウトを使えば、特定のパスだけチェックアウトしたりできます。

特定のフォルダの大きさがチェックアウトが重い原因と判明している場合は、スパースチェックアウトが効果を発揮することが期待できます。

ただ、自前チェックアウトは書いた瞬間から重めの負債だと思っているので、慎重になりますね。

参考

これは特定のパスをチェックアウトする例ですが、やってることは大体一緒です。(この記事は パスパターン指定でのexclude なので cone はダメなのが違う) GitHub Actions の事情というより、git sparse-checkout の基本がいくつか触れられています。

zenn.dev

特定のフォルダだけ除外するときのコツが書かれています。cone には特に注意です。

stackoverflow.com


  1. 併せて --single-branch --branch=<branch> を指定することも多いでしょう。

  2. CIにおいては、そのコミットでのビルドができることが重要なので、ほとんどのケースではパーシャルクローンよりもシャロークローンが適しています。

  3. スパースチェックアウトが効果を発揮するのは、対象のパスのファイル数が数万、数十万オーダーなどチェックアウトに著しく影響ある場合です。

  4. シャロークローン、サブモジュール、PAT認証、特定のブランチチェックアウトと幅広く対応しています。特別な理由がないのに自前で git clone コマンドを書いているなら actions/checkout を使うほうがいいでしょう。

Terraform に startswith と endswith が来る

Terraform はその言語である HCLがシンプルで分かりやすいのでとっつきやすい、と言われることがあります。 一方で、組み込み関数であれこれある割に、これがないの!? というのもあります。

そのなくてたびたび困るのが、startswithendswith 関数です。

tl;dr;

  • startswith と endswith をビルトイン関数に入れるPR が先ほどマージされたので、Terraform のリリースにいつ入るか楽しみ (ETAは不明)
  • これで startswith("hello world", "hello")endswith("hello world", "world") が書けるようになる。

Terraform の組み込み関数

Terraform には組み込み関数 がありますが、バージョンが積み重なっても関数が増えることが少ない印象があります。

どんな関数があるかというのは、 Build-in Functions を見るといいでしょう。

www.terraform.io

startswith や endswith は組み込み関数に存在しない

この中で String Functions = 文字列操作の関数を見ると、StartsWithEndsWith に相当する関数がないことに気づきます。 ないのでどうするかというと、regexsubstrtrimprefix/trimsuffix あたりでやられることが多いと思います。1

# regex での startswith 代わり
can(regex("^vpc-", var.vpc-id))

# substr での startswith 代わり
substr(var.domain2, 0, length(var.domain1) + 1) == "${var.domain1}."

# trimprefix での startswith 代わり
trimprefix(var.domain2, var.domain1) != var.domain2

これでもいいという意見もありそうですが、さすがに startswith で済むものを流し読みしにくい感じで書かれてもうれしくないものです。

# startswith があればこれでいい
startswith(var.vpc-id, "vpc-")
startswith(var.domain2, var.domain1)

ということで Issue が 2021年には立っていたのですが、そこそこスルーされていました。

github.com

別の Issueですが、開発チームとしては従来のやり方でできるなら足したくないという意向があるようです。 startswith でもそれが適用されるのは、あんまりな気もしますがそういう方針なのでしょう。この方針ならとあきらめてました。

We're being pretty cautious about adding new functions that overlap with existing use-cases because Terraform has grown quite an unwieldy collection of builtins over the years, and so we've been considering various ways to allow for externally-defined functions (e.g. #2771) to avoid continuing to grow that set. https://github.com/hashicorp/terraform/issues/28855#issuecomment-856345294

組み込み関数として入れる意向とPRマージ

突然、startswithendswith は組み込み関数に足すのはありという方向に変わります。

I took your comments back to the team, and we revisited this particular decision. We do not need to wait for a function provider framework for these functions. In this case, startswith and endswith meet a sufficiently wide use case need to be considered for built-in functions. https://github.com/hashicorp/terraform/issues/28209#issuecomment-1123022185 より引用

ということでPRが提案され、2022/July/15 にマージされています。

github.com

これにより、次か近いバージョンで startswithendswith が使えるようになりそうです。 ヤッター。

おまけ: カスタム関数という道

Issue で知ったんですが、プラグインでカスタム関数を入れるという提案が 2015年から放置されていたりするんですね。 あったとしても、HCL ではあんまり使いたくない気もしますがどうなんでしょう。

github.com


  1. 例を書いておいてなんですが、やっぱりひどい。