しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Build 2023 で発表されて Windows 11 Insider Preview で利用可能になった Dev Drive を試した

先月の時点で Windows 11 Insider Preview の Dev Channel では利用可能になっていて、パフォーマンス面で気になっていた点を検証していたのですが、ようやく Dev Drive について簡単にまとめる気になりました。

Build 2023 ではオフライン限定の Dev Drive ディスカッション部屋もあったので覗いてきましたが、意外に注目されていない気配を感じました。Dev Drive については記事とドキュメントが地味に充実しているので、こちらを読んでおくと大体は理解できます。

記事とドキュメントは充実していますが、とりあえず試せる環境を作らないことには始まらないので、適当な Windows 11 マシンを Dev Channel に変更すると Dev Drive が作成可能になります。セットアップ方法は以下のドキュメントが分かりやすいです。

Dev Channel っぽく日本語と英語が混在した、ローカライズが不完全な画面が多いですが利用には問題ありません。VHD を利用するか、パーティションを作成するかを選べるようになっていますが、今回はパーティションを作成することにします。

パーティションを作成する方を選ぶと、既存パーティションのリサイズが出来るので、それを使って Dev Drive 用のパーティションを新しく確保します。この後に既存パーティションのサイズをいくつにリサイズするか入力する流れです。

リサイズしてしまえば、確保した領域を Dev Drive としてフォーマットしていきます。とりあえず D ドライブとしてマウントするのが一般的になる気がしています。

Dev Drive の作成が完了すると ReFS かつ Dev Drive として作成されたことがストレージの一覧から確認できるようになります。今回は 200GB を割り当ててあるはずです。

これで Dev Drive の作成が完了したので、Git のリポジトリなど一通りコピーすれば最低限の Dev Drive の恩恵に預かれます。更に Dev Drive を活用するためには、各言語のパッケージマネージャのキャッシュディレクトリを Dev Drive に移すことが推奨されています。

今回は NuGet と npm だけで試したので、以下の 2 つのコマンドを叩きこんでキャッシュの場所を Dev Drive に変更しておきました。事前にディレクトリを作っておいた方が安全です。

setx /M NUGET_PACKAGES D:\packages\nuget
setx /M npm_config_cache D:\packages\npm

これで Dev Drive を利用する準備も出来たので、適当に Nuxt 3 のプロジェクトを作成して npm ci の実行時間を確認してみました。npm i だとネットワーク周りに依存する部分が大きくなるので、キャッシュのある状態で npm ci を実行することでストレージ周りの違いを確認したいという目論見です。

以下が試してみた結果ですが、Dev Drive 上で実行した方が 2 倍近く早く完了しています。ちなみに Dev Drive ではない方では、キャッシュのディレクトリもデフォルトのままにしています。

Dev Drive は通常のドライブよりもパフォーマンスは改善されるのは確実のようですが、WSL 2 と比べるとまだ少し遅いようです。Node.js のアプリケーション開発は Dev Drive でも頑張れるレベルになってそうですが、WSL 2 の方が未だ優勢なのは変わらないようです。

次は NuGet 周りでの確認を行った結果ですが、正直なところ大きな違いは得られませんでした。用意できたプロジェクトがそこまで大きくないこともありますが、恐らく .NET Runtime などかなり規模の大きいプロジェクトぐらいで実感できる気がします。

ただし Dev Drive で利用可能な Copy-on-Write を MSBuild や .NET CLI で有効化すると、ビルド時にコピーされるアセンブリといった中間生成物などに効果的に作用するので、トータルでのストレージ使用量を大幅に改善できるようでした。有効化するには以下のドキュメントにあるように、専用の NuGet パッケージを追加する必要があります。

簡単な適用方法はドキュメントにもあるように Directory.Packages.Props を作成して CPM を有効化し、GlobalPackageReference として参照を追加する方法です。以下のような内容のファイルを作成すれば、ビルド時に Copy-on-Write が有効になります。

<Project>
  <ItemGroup>
    <GlobalPackageReference Include="Microsoft.Build.CopyOnWrite" Version="1.0.263" />
  </ItemGroup>
</Project>

そもそも CPM が気になる方は、以下のエントリで紹介しているのでこちらを参照してください。

パッケージを追加後、C# プロジェクトのビルドで Copy-on-Write を有効にするには、MSBuild や .NET CLI のパラメータとして /p:EnableCopyOnWriteWin=true を追加するだけです。プロパティの有無によってビルド後の消費ストレージサイズが 約 1GB から 20MB にまで大幅に削減されました。

内部では以下のリポジトリの実装が使われているようですが、今年リリース予定の .NET 8 には Linux と macOS 向けには組み込みの IO に Copy-on-Write の実装が入っているようです。

Windows 向けの Copy-on-Write 実装は含まれていないので、必要な場合はこのライブラリを利用できそうですが、やはり理想的には .NET の実装として透過的に利用できるようになっていて欲しいですね。

NuGet 6.4 の Central Package Management を使って複数プロジェクト間のバージョン管理を改善する

Visual Studio 2022 17.4 以降に含まれている NuGet 6.4 では、複数プロジェクトでのバージョン管理を一元化出来る Central Package Management が実運用可能なレベルとして提供されています。

NuGet などのパッケージマネージャを利用する際の共通の悩みが、プロジェクト間でバージョンがバラバラになってしまいやすく、それぞれが参照しているパッケージのバージョンを上げること自体に手間がかかることですが、NuGet 6.4 以降では CPM を有効化すると解消可能です。

最新の Visual Studio 2022 か .NET SDK をインストールしていれば CPM が利用可能なので、GitHub Actions などで利用する際には最新バージョンを使うように指定するのが安全です。

SDK スタイル以前の古い csproj でも利用可能ですが、PackageReference の利用が最低要件なので ASP.NET プロジェクトでは利用出来ません。その場合は ASP.NET Core への移行を検討する形になります。

実際に Central Package Management を有効にする方法ですが、シンプルに以下のような内容で Directory.Packages.props という名前のファイルをリポジトリやソリューションのルートに作成するだけです。このファイルでは ManagePackageVersionsCentrallytrue になっていることが重要になります。

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.31.2" />
    <PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
    <PackageVersion Include="Microsoft.NET.Sdk.Functions" Version="4.1.3" />
  </ItemGroup>
</Project>

実際のパッケージ情報は ItemGroup 内で PackageVersion を使って NuGet のパッケージ ID とバージョンを記述します。このファイルを作成した時点で既存の csproj は NU1008NU1010 が出てビルドが出来なくなるため、同時に csproj 側も対応する必要があります。

csproj 側での対応は PackageReference から Version を削除するだけなので簡単に終わります。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Cosmos" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

この状態になると Visual Studio や .NET SDK を使ったビルドが正しく通るようになります。

正直なところ CPM が必要な規模のソリューションでは手動での対応は無理なはずなので、以下のようなツールを使って機械的に対応するのが良いです。

CPM を有効化した後の NuGet パッケージの追加や更新ですが、Visual Studio の NuGet パッケージマネージャーを使うと自動的に Directory.Packages.props に対して更新を行ってくれるので、特別な対応は必要ありません。追加した場合でも以下のように csproj にバージョン無しで追加してくれるので、パッケージマネージャーでの対応は実運用で問題ないレベルです。

自分が確認している範囲では dotnet-outdated ツールが CPM に完全に対応できていないようでした。最低限の実装として Directory.Packages.props ファイルは認識してくれますが、一部のパッケージしか表示されないなど挙動が怪しかったです。

後は GitHub の Dependency Graph 上はバージョンが正しく認識されていませんでした。今後の対応に期待したいですが、現時点ではバージョンが >= 0 という扱いになります。

Dependabot を利用する際の注意点

GitHub とパッケージマネージャーと言えば Dependabot の存在は外せないので、CPM を有効にした状態で複数プロジェクトを追加して動作を確認しました。

結論としては問題なく動作しますが、最初は Directory.Packages.props をリポジトリのルートに置いておくと読み取ってくれませんでした。以下のようにソリューションと同じディレクトリに置くと正しく認識されたので、親ディレクトリを辿る部分に問題がありそうでした。

あえて古いバージョンを参照した状態で Dependabot を動かすと、以下のように Directory.Packages.props に対する Pull Request が作成されました。これは完全に意図した動作になるのと、複数の csproj があっても更新ファイルは 1 つだけなので変更点もシンプルです。

公式からの Production Ready との宣言通りに、CPM は実運用可能なレベルに達していると感じました。プロジェクト数が少ない場合はメリットを感じにくそうですが、大規模になるとパッケージ管理にもガバナンスが必要になるため CPM は良いツールとなるはずです。

.NET 6.0 にアップグレードすると ReadyToRun ビルドが失敗するようになったのを直す

.NET Core 3.0 からビルド時に Tier 0 のネイティブコードを生成する ReadyToRun が利用可能になっています。実行時に Tier 0 のコード生成を行わない分、スタートアップの高速化が期待できる機能です。

.NET 6.0 は Windows x64 環境なら全てのクロスコンパイルも出来るので、使い勝手がよくなっています。

起動速度が重要となるデスクトップアプリケーションでは基本的に ReadyToRun は有効化するようにしているのですが、WPF を使って MSIX でパッケージングしているアプリケーションを .NET 6.0 に移行すると GitHub Actions で行っているビルドが通らなくなりました。

使用しているのは Windows Server 2022 環境なので x64 の .NET SDK が使われているのですが、何故か R2R ビルドがサポートされないプラットフォームと言われてしまいます。

f:id:shiba-yan:20220211002057p:plain

error NETSDK1095: Optimizing assemblies for performance is not supported for the selected target platform or architecture. Please verify you are using a supported runtime identifier, or set the PublishReadyToRun property to false.

エラーメッセージにあるように PublishReadyToRunfalse にすればビルドが通るようになりますが、出来る限り有効化した状態で WPF アプリケーションはリリースしたいです。

最初は GitHub Actions 固有の問題かと思っていましたが、調べていると以下の Issue が見つかりました。どうやら .NET 6.0 から ReadyToRun ビルドを行うときの挙動が変更になっていたようです。

少し前に Breaking change としてドキュメントも公開されていました。.NET 6.0 からは ReadyToRun ビルドを行う際には復元時にも PublishReadyToRun プロパティを指定して、必要となる NuGet パッケージ*1をインストールする必要があるようです。

従って GitHub Actions や MSIX に関係なく、シンプルな WPF アプリケーションでもビルド・発行時に --no-restore を指定していると発生します。回避方法は dotnet restore 時に PublishReadyToRun を指定するか、dotnet publish で暗黙的に NuGet パッケージの復元を実行する必要があります。

.NET CLI で ReadyToRun ビルドを行う場合は以下のどちらかになることを覚えておけば良さそうです。

# --no-restore を指定する場合は restore 時に R2R を使う宣言をしないと crossgen2 がインストールされない
dotnet restore -p:PublishReadyToRun=true
dotnet publish -c Release --no-restore

# --no-restore を付けなければ自動で restore が行われるので問題なく動作する
dotnet publish -c Release -p:PublishReadyToRun=true

大きなソリューションでは最初に一回だけ dotnet restore を実行するようにして、暗黙的な復元を無効化した方がビルド時間が短縮できるので、--no-restore を使っているケースもあると思います。その場合は R2R との組み合わせに注意が必要です。

.NET CLI を使ったビルドの場合はここまでの方法で問題ないのですが、MSIX を使ったデスクトップアプリケーションのパッケージングには Visual Studio とインストールされた MSBuild が必要になります。具体的には wapprojdotnet msbuild を使ってもビルドは行えません。

最初に .NET CLI を使って dotnet restore だけ実行すれば良さそうではありますが、それもエラーになって上手く動きませんでした。なので MSBuild で NuGet パッケージの復元を行うことで対応します。

MSBuild は .NET CLI のように自動的に復元は実行してくれないですが -restore オプションを指定すると、ビルド前に NuGet パッケージの復元を行ってくれるようになります。その際に PublishReadyToRun を指定することで、必要なパッケージのインストールが行われるようになります。

# -restore を付けると dotnet build/publish のように自動で restore が行われる
msbuild -restore -p:Configuration=Release -p:PublishReadyToRun=true

Windows App SDK を使ったプロジェクトなど、最近の新しいプロジェクトでは各 RID 向けの Publish Profile が作成されて、それを wapproj から参照するようになっていますが、復元時にはその設定が考慮されないので個別に R2R の設定を指定しています。

実際にプロジェクトで使っている GitHub Actions のワークフローを修正した PR が以下になります。

GitHub 上で新しく Release を作成して MSIX のビルドを行わせると、ReadyToRun が有効な状態でも問題なくビルドが完了するようになりました。ARM64 向けの R2R ビルドも問題なく実行されています。

f:id:shiba-yan:20220211002212p:plain

原因が分からなかったので、一旦はローカル開発環境でビルドしたものをストアに申請していて非常に面倒でしたが、これで .NET 6.0 でも GitHub Actions を使った MSIX の自動化が利用出来るようになりました。

*1:crossgen2 が含まれているパッケージ

Azure Functions への Zip Deploy を Publish Profile を使って行う方法

普段は Visual Studio からデプロイ先の Azure Functions を選んで Zip Deploy 用のプロファイルを作成していますが、たまに Visual Studio で Publish Profile を使った Zip Deploy を行う場合に、その手順を毎回忘れてしまうのでメモとして残します。

おまけとして Publish Profile についても少し触れています。Zip Deploy については何回も書いているはずなので、以前書いたエントリを参照してください。公式ドキュメントもあります。

最近は Visual Studio に色々統合されているので Publish Profile を使う機会が減ってしまったので、基本的なところも軽く紹介しておきます。

ダウンロードボタンは App Service / Azure Functions の Overview ツールバーにあります。

f:id:shiba-yan:20201226004221p:plain

Publish Profile に含まれている認証情報は再生成も出来ますが、場所が昔から大きく変わっています。今は Deployment Center (Preview) の Manage publish profile から行います。

f:id:shiba-yan:20201226004228p:plain

ここまで理解しておけば大体 OK です。特に Publish Profile の再生成は分かりにくいので注意。

Publish Profile の仕組み

ダウンロードした Publish Profile は Minify されていて読みにくいのですが、フォーマットしなおすと以下のような構造になっています。

昔は Web Deploy と FTP しかなかったのですが、最近は Zip Deploy と ReadOnly FTP が増えていました。

<publishData>
  <publishProfile profileName="serverlesstest-1 - Web Deploy" publishMethod="MSDeploy" publishUrl="***.scm.azurewebsites.net:443" msdeploySite="***" userName="$***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
  <publishProfile profileName="serverlesstest-1 - FTP" publishMethod="FTP" publishUrl="ftp://***.ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="***\***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
  <publishProfile profileName="serverlesstest-1 - Zip Deploy" publishMethod="ZipDeploy" publishUrl="***.scm.azurewebsites.net:443" userName="***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
  <publishProfile profileName="serverlesstest-1 - ReadOnly - FTP" publishMethod="FTP" publishUrl="ftp://***.ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="***\***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
</publishData>

Publish Profile の中身としては userNameuserPWD が重要な情報です。それ以外はどうでも良いです。

この userNameuserPWD は Site Credential というものになり、FTP の接続情報として確認できるものと同じです。大昔に書いた気がしますが、App Service は User Credential と Site Credential が存在しています。

f:id:shiba-yan:20201226004516p:plain

Site Credential を持っていると Kudu の API を自由に叩けるようになるので、当然ながら扱いには注意が必要ですが、App Service のデプロイ API は Kudu 側に用意されているので便利です。

コマンドを使ってデプロイ

あまり使わないと思いますが、API を知るには都合が良いので curl を使って Zip Deploy を行ってみます。

デプロイ API は /api/zipdeploy という分かりやすいエンドポイントにあるので、そこに Site Credential で Zip ファイルをアップロードするだけです。

curl -X POST -u "$serverlesstest-1" --data-binary @"FunctionApp16.zip" https://serverlesstest-1.scm.azurewebsites.net/api/zipdeploy

他にも WAR 向けエンドポイントもあるのですが、将来的にはこの辺りが統合されそうです。今後は POST 時に特殊なヘッダーを付けることで、Deployment Center 向けのメタデータを送信できるようになりそうです。

Visual Studio を使ってデプロイ

Azure Functions のみで利用できる方法になりますが、Visual Studio を使ったデプロイ時に Azure サブスクリプションを紐づけておけば Zip Deploy を行えるようになっています。

しかし Publish Profile をインポートする場合には Web Deploy しか認識してくれません。

f:id:shiba-yan:20201226010214p:plain

Publish Profile 自体には Zip Deploy の定義は含まれていますが、今の Visual Studio では正しく認識してくれないため、手動で pubxml を弄って Zip Deploy 向けに書き換えます。

Visual Studio で Publish Profile をインポートすると Properties の中に pubxml が作成されます。単なる MSBuild 定義ですが、中にはデプロイに必要な情報が記載されています。

f:id:shiba-yan:20210105150333p:plain

この pubxml を以下のように修正することで、Web Deploy から Zip Deploy へ変更できます。

  • WebPublishMethodZipDeploy
  • PublishUrl に SCM の URL*1 を指定して追加
  • DeployIisAppPathMSDeploy が含まれている項目を一応削除

修正後にデプロイを行うと、以下のように Zip Deploy が行われていることが確認できます。

f:id:shiba-yan:20210105023255p:plain

多少手間はかかりますが、Visual Studio に複数の Azure サブスクリプションを追加したくないケースもあるので、Publish Profile でサクッとデプロイ出来るようになっておくと便利です。

GitHub Actions を使ってデプロイ

おまけ的に GitHub Actions を使う時の情報も載せておきます。とは言っても、以前に書いてあるエントリを紹介するぐらいに留めておきます。

App Service へのデプロイに使う azure/webapps-deploy@v2 は最初から Publish Profile に対応しているので、複数アプリケーションへのデプロイが必要ない場合には簡単に始められます。

Deployment Center (Preview) が良くなっている話

あまり内容とは関係ないですが、Deployment Center (Preview) が良い感じにアップデートされて、デプロイ履歴もしっかり確認出来るようになっています。

f:id:shiba-yan:20210105023454p:plain

GitHub Actions との連携が推されていましたが、個人的には履歴の確認やメタデータから該当のコミットに簡単に飛べる機能などのがありがたいので、今後もアップデートに期待しています。

ちなみにこのデプロイ履歴は Kudu の API で取れるので、Site Credential があれば自由にアクセス出来ます。

Visual Studio で Azure Functions を作成すると文字化けする問題を EditorConfig で直す

Visual Studio や Azure Functions Core Tools などで新しい Azure Functions を作成すると、一般的な C# クラスとは異なり UTF-8 BOM 無しのファイルが生成されます。

UTF-8 BOM が必要なのかどうかは議論しませんが、C# では UTF-8 BOM が無いとマルチバイト圏の人間は困るようになっています。具体的には以下のように日本語を書いた時です。

ファイルに UTF-8 BOM が付いていない場合、Visual Studio はデフォルトのエンコーディング、すなわち Shift_JIS として保存してしまいます。最悪な挙動ではあります。

Git で差分を確認しようとすると、まともに表示されないので不便です。GitHub や Azure Repos などで確認した時にも文字化けが発生するので、結構困ります。

全てを日本語の Windows 上で完結する場合には気が付きにくいですが、ビルドを GitHub Actions や Azure Pipelines を行っている場合には、デフォルトエンコーディングが異なるのでビルドしたバイナリに文字化けしたデータが含まれます。

この辺りは以前にぶちぞう RD が踏んで、ブログでブチギレていることで有名な挙動です。

これまで書いたように、Azure Functions のテンプレートが UTF-8 BOM 付きのファイルを生成しないのが根本原因ですが、Issue を上げても理解してもらえずにクローズされたので諦めています。

公式の対応が完全に期待できないので、ふと EditorConfig で強制できるのではないかと思って調べると、Visual Studio は charset に対応していました。

文字コードとしても utf-8-bom が使えるので、これを使ってファイルエンコーディングを強制します。

最低限の .editorconfig ファイルは以下のようなものを用意しました。これで *.cs ファイルは全て UTF-8 BOM として保存されるようになります。

# top-most EditorConfig file
root = true

[*.cs]
charset = utf-8-bom

作成した .editorconfig が読み込まれたことを確認し、適当に編集して保存すると UTF-8 BOM として保存されるので、Git の差分確認でも文字化けが発生しなくなります。

一応ファイルエンコーディングを確認しておきます。ちゃんと UTF-8 BOM になっています。

これで新しく作成した Function も常に UTF-8 BOM になるため、安心してコメントなどで日本語を使って書いていくことが出来るようになりました。

あまり関係ないですが、UTF-8 を Git でちゃんと扱うためには、環境変数 LANG の設定が必要です。

EditorConfig はファイルエンコーディングだけだともったいないので、もっと本格的な定義を作成して一緒に入れておくのが良いです。自分は CoreFx などを参考にしてカスタマイズしたものを使っています。

暇な時に近代的な Azure Functions なプロジェクト構成を、テンプレとして作っておこうかと思いました。

Visual Studio と ReSharper で EditorConfig を作成して Azure Pipelines でチェックする

GitHub で公開してるコードに Pull Request が来た時、思ってた以上にコーディングスタイルがバラバラで困ってたので EditorConfig を追加して CI でチェックするようにしました。

C# はみんなが Visual Studio を使っていて、大体は良い感じのコーディングスタイルになっていると思っていたらそんなことはなかったようです。Visual Studio では少し前から EditorConfig に対応しています。

リポジトリに EditorConfig を入れておけば、Visual Studio や Visual Studio Code ではいい感じに読み取ってくれて最高という世界になるはずですが、元になるファイルをどう作るかが悩みどころでした。

いろいろと試してみましたが、C# の世界では以下の 4 種類ぐらいの方法を選べそうです。

Visual Studio の設定からエクスポート

素の Visual Studio でサクッと使えるのが、設定から .editorconfig をエクスポートする方法です。

現状のコーディングスタイルは Visual Studio の設定に従っているはずなので、ここからエクスポートすれば同じ設定を適用できます。設定からエクスポートを選ぶだけなので簡単です。

f:id:shiba-yan:20200322180049p:plain

欠点としては設定項目が少ないかなという点ぐらいです。まずはここから始めるのがわかりやすそうです。

Visual Studio は .editorconfig の IntelliSense に対応しているので、編集は比較的やりやすいですがドキュメントを確認しながらじゃないと難しいかったです。Before / After が確認できる GUI が欲しくなります。

IntelliCode を使って自動生成

IntelliCode を使うと既存のコーディングスタイルから .editorconfig を作成することが出来ます。

この IntelliCode を使った .editorconfig の生成はプレビュー機能なので、設定から明示的にオンにしないと有効にならないです。まだ生成される項目も少なめという印象です。

f:id:shiba-yan:20200322194348p:plain

生成はソリューションエクスプローラーの追加サブメニューから行えます。ちょっとわかりにくい。

f:id:shiba-yan:20200322180101p:plain

古いコードや社内の独自コーディングスタイルがある場合には便利に使えるのかもしれません。個人的には Visual Studio 標準のコーディングスタイルに従ってきたので、あまり有用ではなかったです。

ReSharper の設定からエクスポート

長く ReSharper を使ってきた人は自分用のフォーマット設定を持っていると思いますが、最近の ReSharper は .editorconfig としてエクスポートする機能があるので簡単に設定を共有できます。

ReSharper の設定からエクスポートする場合は Tools サブメニューから辿っていけば行えます。

f:id:shiba-yan:20200322180117p:plain

この機能でエクスポートした .editorconfig には ReSharper の独自拡張項目も含まれているので、相手が ReSharper を使っている場合には更に細かいコーディングスタイルを強制できるようになってます。

ただし、ReSharper 拡張項目に関しては後述する dotnet-format では反映されないです。

.NET Core / Roslyn の EditorConfig を利用する

ここまで Visual Studio や ReSharper を使って .editorconfig を生成する方法を紹介してきましたが、自分が最終的に選んだのは .NET Core / Roslyn の EditorConfig でした。

C# の世界では一番標準的なコーディングスタイルが .NET Core / Roslyn といったランタイム・コンパイラのコードだと思っています。実際に自分が書いていたコードに対して自動フォーマットを実行しましたが、ファイル最後の改行以外の差異は出ませんでした。

ちなみに C# 以外のファイルタイプに対する設定もいくつか入っているので結構便利です。

EditorConfig でのチェックを CI に入れる

ここまでで .editorconfig は準備できているはずなので、最後に CI でチェックを行うようにしておきます。Pull Request の場合は CI で落ちるようにので、パスするまでマージ出来ないようになります。

チェックには dotnet-format を使います。プリインストールはされないので CI では都度入れる必要がありますが、Global Tool なのでコマンド 1 つで入るため簡単です。

既に dotnet-format と Azure Pipelines を使った定義を試している方がいたので、この定義をほぼそのまま組み込みました。--dry-run--check の組み合わせは一見違和感がありますが動きました。

まだ GitHub に公開していて Azure Pipelines での CI を導入しているリポジトリ全てには設定できていないですが、利用頻度の高いリポジトリからチェックするようにしています。

Azure Pipelines での実行結果は以下のようになります。コーディングスタイルに問題があれば失敗します。

f:id:shiba-yan:20200322185038p:plain

暇な時にでもテンプレートとして .gitignore / .editorconfig / azure-pipelines.yml といった必須ファイルをメンテナンスするリポジトリでも作ろうかと思っています。

Vcpkg と CMake を使って Windows 10 ARM64 向けクロスコンパイルを行う

Windows 10 の ARM64 向けビルドは実質 Surface Pro X 向けですが、Surface Pro X でも x86 エミュレーションのオーバーヘッドを調べると無視できるレベルではなかったので、やっぱりアプリケーションは ARM64 ネイティブで動かしたい気持ちが高まってます。

ちなみに Geekbench 4 で x86 と ARM64 を比較した結果は以下のようになります。Surface Go よりは速いのですが、ARM64 ネイティブと比べると差がかなり大きいです。

C# での ARM64 ネイティブ実行は .NET Core 3.0 のコンソールアプリケーション以外不可能なので、現時点で ARM64 ネイティブのデスクトップ向けアプリを作るためには Win32 か UWP になります。*1

ARM64 ネイティブのアプリが増えるにはライブラリがビルド出来る必要があるので、Vcpkg と CMake の 2 つでクロスコンパイルの方法を試しました。昔に比べるとビルドが簡単になってて驚いてます。

ARM64 ビルドツールをインストール

まずは Visual Studio Installer を使って ARM64 ビルドツールをインストールします。ARM64 で検索するとたくさん出てきますが、バージョンの新しいやつを入れておけば良いです。

f:id:shiba-yan:20191201181745p:plain

Win32 向けと UWP 向けの 2 つが出てくるので、両方インストールしておけばよいです。

Vcpkg を使ってビルド

C++ 向けのパッケージマネージャは C# と同じように、NuGet を使ってプリコンパイル済みのバイナリを拾ってくるのかと思ってましたが、最近はソースからビルドする方が流行っているようです。

マシンパワーが格段に上がった昨今では、環境依存を少なく出来るので確かに便利です。

Vcpkg の使い方は既にいろんな人が書いているのと、README に書いてあるコマンドをいくつか叩くだけでセットアップできるので丸っと省略します。

最初から ARM64 Windows に対応した Triplets が用意されているので、以下のようにインストール時に arm64-windows を指定するだけでビルドが行われます。

vcpkg install zlib:arm64-windows

vcpkg install boost-regex:arm64-windows

たまに先に x86-windows 向けのパッケージをインストールしてから再実行しろというエラーメッセージが出ますが、メッセージに従えば大体は問題なくビルドに成功します。

実際に zlib と boost-regex を ARM64 向けにビルドしてみましたが、問題なく完了しました。

f:id:shiba-yan:20191202020646p:plain

パッケージマネージャらしく vcpkg list を叩くと、ビルド済みのパッケージの管理が出来ます。

f:id:shiba-yan:20191201182431p:plain

Vcpkg でビルドしたバイナリをそのまま、いい感じに C# のプロジェクトから参照するような MSBuild Task を用意したい気持ちでいっぱいです。

P/Invoke で使いたいシチュエーションも結構あると思うので、もうちょっと調べてみるつもりです。*2

CMake を直接使ってビルド

Vcpkg のベースになっている CMake も最初から ARM64 向けクロスコンパイルに対応しているので、こっちの方法も試してみました。CMake に関してはドキュメントを読むのが特に重要な感じでした。

全てのパッケージが Vcpkg でビルド出来れば楽ですが、そういう都合のいいことは無いので CMake 力を多少なりとも上げておきます。非対応のパッケージは当然ながらあります。

今回は Vcpkg で対応していたのですが、ARM64 ビルドは出来なかった OpenCV で試しました。というか ARM64 に対応させるために CMake の定義にパッチを当てました。

ARM64 向けツールセットは -A ARM64 を指定すると自動的に選択されましたが、CMAKE_SYSTEM_PROCESSOR は自動設定されないようだったので ARM64 を明示的に追加して対応しました。

今回 ARM64 のクロスコンパイルに成功した CMake のパラメータは以下のような感じでした。

cmake -G "Visual Studio 16 2019" -A ARM64 -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION=10.0 \
           -DCMAKE_SYSTEM_PROCESSOR=ARM64 -DWITH_OPENCL=OFF -DWITH_FFMPEG=OFF -DWITH_CUDA=OFF \
           -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON C:\opencv4

内部で参照されている CMAKE_CROSSCOMPILINGCMAKE_SYSTEM_NAMECMAKE_SYSTEM_VERSION を明示的に指定しないと設定されないらしく、結構な時間を溶かしてしまいました。

CMake でのクロスコンパイルで重要なのは CMAKE_SYSTEM_NAME / CMAKE_SYSTEM_VERSION / CMAKE_SYSTEM_PROCESSOR のようなので、忘れないようにしっかり残しておきます。Vcpkg は微妙にこの辺りの設定をミスっている気配ありますが…。

作業中にはまったポイントなど

C/C++ ランタイムのインストール

Vcpkg や CMake のデフォルトでは C/C++ ランタイムは動的リンクになるので、ビルドしたバイナリを直接 Surface Pro X にコピーしても動きません。忘れないように ARM64 向けのランタイムを入れておきます。

ひっそりと Visual Studio のページからダウンロード可能になっています。

f:id:shiba-yan:20191201222205p:plain

Visual Studio のインストールディレクトリの中にも C/C++ ランタイムのインストーラがあります。

f:id:shiba-yan:20191120215340p:plain

デバッグ用の C/C++ ランタイムは Visual Studio からリモートデバッグする時に、自動的にインストールがされるようなオプションがありましたが試せていません。

OpenGL ライブラリが存在しない

ARM64 Windows 10 の制約として書いてあるように OpenGL は基本的に使えないと考えてよいです。

使えないだけならまだ良いのですが、ARM64 向け SDK には opengl32.lib すら存在しないので、依存しているライブラリは軒並みビルド出来なくなっています。

代わりに ANGLE を使えと回答が付いていますが、本当に ANGLE が動くのかも少し謎です。

https://developercommunity.visualstudio.com/content/problem/164107/cannot-link-opengl-programs-on-arm64.html

Vcpkg に opengl が存在しているので、依存するライブラリは opengl (!arm64) のように CONTROL ファイルで定義しないとビルド出来ないのがかなり辛いです。

デフォルトで追加されるライブラリが少ない?

これはかなり謎な挙動なのですが、OpenCV の highgui をビルド中にリンクエラーが出たので調べたら、x86 / x64 の場合より ARM64 はデフォルトライブラリが少ないような挙動でした。

user32.libkernel32.lib はありますが、comdlg32.libadvapi32.lib が無かったのでリンクエラーになっていました。どうしようもないので CMake 側で対応をしました。

アセンブラを使っていると辛い

当然ながらコンパイルには ARM64 向けのアセンブラが必要ですが、地味に VC++ の ARM アセンブラにリグレッションが多かったので困りました。

OpenCV の場合は SIMD の利用にコンパイラ側の機能を使っていたので、今度はコンパイラ依存の問題もあったりでひとまず棚上げにしたりも。リベンジはしたいです。

*1:Electron という選択肢もあるが試していない

*2:無ければ多分書く

Visual Studio 2019 の IntelliSense が Enter で確定出来なくなって困った話

地味に長い期間困ったのでメモとして残しておきます。調べようにも何てキーワードで調べるべきか浮かばなくて辛かったですが、Twitter に書いたら解決しました。

完了モードの切り替えが有効になっていると、IntelliSense で出てきた項目を Enter で確定できないという最悪な挙動になるらしいです。切り替えた記憶はないです。

この設定がローミングされて、全ての Visual Studio で同じ挙動になって本当に辛かったです。

最初は ReSharper を死ぬほど疑いましたが、VS 側の問題でした。疑ってすまんかった。

Snippet の確定もおかしかったので直した

これもいつからか覚えていないですが、Snippet を使って入力中に Enter を押すと改行されてぐちゃぐちゃになる問題も発生していました。

上の問題を調べている時に IntelliSense のオプションを見ると、何故かラジオボタンが空になっている項目があったので、適切なものを選択しました。

この時にどんな動作になるのかわからないですが、手動だとラジオボタンを非選択に出来ないので、2017 => 2019 のローミングとかで壊れたのかなと思ってます。

設定することで Snippet の問題も直って快適にコードが書けるようになりました。

Azure Functions and Web Jobs Tools のアップデートが失敗するのを直した

Visual Studio 2017 の 15.5 が出たので、やっと Azure Functions の新しいツールがインストール出来ると楽しみにしてましたが、無情にも謎のエラーが出てアップデートできませんでした。

インストールの途中ぐらいで以下の画像のようなエラーが出てしまいます。

エラーログには Azure Functions のツールをアンインストールしろと書いてますが、Visual Studio Installer からアンインストールすると Azure 開発周りもごっそり削除する割に、結局直らなかったので最悪です。

どうしようもないので Visual Studio Gallery の Q&A を見たら、同じ状況の人と回答を発見しました。割と前から発生していたようで、GitHub の Issue が割と伸びていました。

正直この Issue も解決したのかしてないのかわからない感じですが、エラーログからアンインストール出来ない拡張のパスを拾ってきて、そのディレクトリを削除するとアップデート出来るようになりました。

以下のようにディレクトリ名はランダムなので、エラーログから間違えないように拾ってきます。

f:id:shiba-yan:20171207004220p:plain

削除後には Visual Studio を起動して、機能拡張のアップデートを確認すると良いです。そこでアップデートか、もしくはインストールを実行すれば最新版が正しく入りました。

少しイレギュラーな対応をしてしまったので、動作に問題がないか少し不安でしたが、ちゃんと .NET Core 向けの Azure Functions が作れるようになっていたので問題なさそうです。

3 回ぐらい Azure 開発周りの再インストールを繰り返してしまったので、忘れないように残します。

Surface Book 2 でもアップデートを試しましたが、こっちはすんなりとアップデートが行えたので、特定のバージョンが入っている環境でのみ発生する問題のようでした。

Visual Studio 2017 Update 3 (15.3) と ASP.NET Core 2.0 などがやっとリリース

いつまで時間がかかるのかと思うぐらい Visual Studio 2017 Update 3 のリリースに時間がかかっていましたが、ようやく正式版がリリースされたようです。

更新内容はこれまでで一番多い気がします。そしてアップデートは必須ですね。

注意点としてはアップデートにめっちゃ時間がかかります。Visual Studio 本体のインストールよりもかかった気がするので、寝る前とかにアップデートをすると寝れなくなりそうです。

f:id:shiba-yan:20170815005805p:plain

手元の MacBook Pro で 30 分ぐらいかかりました。時間のある時にやりましょう。

f:id:shiba-yan:20170815011355p:plain

色々と良くなっている気がします。関係ないけどスプラッシュとか変わってます。

.NET Core 2.0

Update 3 と同時に .NET Core 2.0 がリリースされてます。.NET Core SDK もアップデートされました。

.NET Core 2.0 と .NET Standard 2.0 は .NET Blog にまとめられてます。明日の朝には ufcpp にぃにがブログに詳細な記事を書いてくれるはずなので、リンクだけ紹介しておきます。

Announcing .NET Core 2.0 | .NET Blog
Announcing .NET Standard 2.0 | .NET Blog

ちゃんと Docker Image も更新されてます。今回から ARM32 向けリリースが追加されたみたいです。

https://hub.docker.com/r/microsoft/dotnet/

Raspberry Pi では Mono を使う必要がありましたが、.NET Core 2.0 を使うことも出来そうです。

ASP.NET Core 2.0

.NET Core 2.0 と同時に ASP.NET Core 2.0 と Entity Framework Core 2.0 もリリースされています。

Announcing ASP.NET Core 2.0 | .NET Web Development and Tools Blog
Announcing Entity Framework Core 2.0 | .NET Blog

例によって Breaking Changes 祭りなので、1.1 からのアップデートには少し気を付けたい部分です。

ドキュメントにマイグレーション方法もまとまってるので、なるはやでアップグレードしたいところです。既に ASP.NET Core 1.1 を使っているプロジェクトで試してみようかと思ってます。

新しく ASP.NET Core プロジェクトを作成しようとすると .NET Core 2.0 SDK のダウンロードを促す表示が出るので、先に SDK をインストールしておきます。15.3 と同時に入って欲しかった。

f:id:shiba-yan:20170815011942p:plain

2.0 向けのプロジェクトテンプレートとしては Razor Pages と React が増えたようです。Web アプリケーションは Razor Pages で、もう片方が Core MVC となっています。

f:id:shiba-yan:20170815012315p:plain

作成されたプロジェクトはこれまでとあまり変わっていません。全ての ASP.NET Core パッケージを参照するメタパッケージがインストールされているぐらいです。

f:id:shiba-yan:20170815012542p:plain

関係ないですけど、いつまで bower を使い続けるつもりなんだろう。

ASP.NET

発行プロファイルの作成時に名前を指定できないのは変わらずですが、後から名前を変えることは出来るようになりました。デザイン性のない適当感あるダイアログですけど。

f:id:shiba-yan:20170815013329p:plain

そしてちょっと前の Chrome から証明書の CN じゃなくて SAN を見るようになった影響で、IIS Express で HTTPS を有効にすると自己署名証明書で常に警告が出てましたが、それが解消されました。

f:id:shiba-yan:20170815013503p:plain

地味に改善されましたが、それ以外は特に目立った変更はなさそうです。

Azure Functions Tools

これまでプレビューとして公開されていた Azure Functions Tools も Visual Studio 2017 Update 3 と同時に正式版になったようです。とはいえ同時には入らないみたいなので、別にインストールします。

f:id:shiba-yan:20170815011528p:plain

インストールすると Azure Function プロジェクトが作れます。新しい Function の追加がちょっとわかりにくいですが、新規追加のダイアログに Azure Function が用意されてます。

f:id:shiba-yan:20170815014119p:plain

用意されているテンプレートから選べます。この辺りは Azure Portal と変わりませんね。

f:id:shiba-yan:20170815014225p:plain

作成すれば F5 を押すだけでデバッグ実行も出来ますし、Azure に直接デプロイも出来ます。NuGet 周りもちゃんと対応されたみたいですし、以前のバージョンより使い勝手は良くなってます。