2025-11-16

F# で Cmdlet を書いてる pt.81

Central Package Management と .NET 10

krymtkts/pocof の開発をした。 開発というかメンテ。

まずは Central Package Management を導入した話。

ちょっと前に .NET の Dependabot version updates の挙動が変わって壊れたとき、 dependabot.yml を書き換えた内容が間違ってたみたい。 それが原因で一部の package が更新されない状態が続いてたのに気付いた。 それは pocof に限った話じゃないのだけで、自作の PowerShell cmdlet の repository 全体的に直した。 #377

それで期待通り Dependabot が働き始めたのだけど、 pocof のような 1 solution に複数(pocof は 4) project ある package 管理が面倒に感じた。 いい機会なので Central Package Management(CPM) を導入してみた。 #382

もともと Directory.Build.propsIonide.Analyzer を全 project へ導入するのに使ってた。 CPM ではこれとは別に Directory.Packages.props を置く。 そこで <PackageVersion> を定義して package ごとの version を集中管理する。 配下の各 project では PackageReferenceVersion 属性を指定せず、 Directory.Packages.props で集中管理する。 これにより複数 project で参照される同一 package も同じ version を使うので手間が省けるってことみたい。

CPM を使うと、これまで project の管理に入れてなかった F# の package FSharp.Core も明示的に入れる必要があり、管理が厳密化された。 また CentralPackageTransitivePinningEnabled=true にしてる。 これは transitive dependencies に同じ package がいた場合も強制するための設定。 他に CentralPackageVersionOverrideEnabled というのもあって、これは中央管理してる version の上書きを許可するかを指定できる。 pocof は指定忘れたので既定値で動いてるはず(既定値が true false どちらかは文書に書いてなかった)。 ガチガチ管理を進めるなら明示的に false が良さそう。 これは後ほど変更しておきたい。

CPM だと package 更新は手で直すしかないのかなと気になって試してみた。 以下の通り dotnet CLI で CPM でもうまく Directory.Packages.props を更新できた。 あと地味に .NET 10 から sub command 体系が変わってるのをここで知った。 What's new in the SDK and tooling for .NET 10 | Microsoft Learn

# .NET 9 まで(.NET 10 でも動く)
dotnet add ./src/pocof package FSharp.Core --version 10.0.100
# .NET 10 から
dotnet package add FSharp.Core --version 10.0.100 --project ./src/pocof

ただ Directory.Packages.props に体裁を整えるための改行とか入れてると全部吹っ飛ぶので、まあ考えどころやな。

CPM 導入により package version がひとつのファイルにまとまっただけだが、結構 package の見通しが良くなったと気がするので、気に入っている。

次は .NET 10 に上げた話。

毎年恒例の最新 .NET 10 が出たので、 pocof は #389 で対応した。 やることは決まりきってるのでそんなに困ることもないが、いつもと少し手順を変えている。 今回はいつも使ってる Chocolatey では .NET 10 SDK がまだ承認されてなかったので、 winget を使うことにした。 いま自分の PC の package 管理で winget を使ってるのは OpenSSH だけなので、使い方を忘れるしメモっておく。 以下のコマンドで .NET 10 SDK を install した。

# これだと確認 prompt が面倒
winget install --id Microsoft.DotNet.SDK.10
# これで完全自動化できる(管理者権限で実行すると)
winget install --id Microsoft.DotNet.SDK.10 --silent --accept-package-agreements --accept-source-agreements --exact

コメントのとおりだが winget はデフォで確認プロンプトが面倒なのでゴテゴテと options をつけないといけないのが少し残念。 Chocolatey なら choco upgrade dotnet -y やからな。

あとは手で SDK version を書き換える感じだけど、今回は PowerShell で簡単に global.json を変更できる関数を作った(初版 AI に作らせて)。

function Set-DotnetGlobalJson {
[CmdletBinding()]
param(
# Path to global.json (default: ./global.json)
[Parameter(Position = 0)]
[Alias('PSPath')]
[ValidateNotNullOrEmpty()]
[string]
$Path = 'global.json',
# Target major version (e.g. 10, 9)
[int]
$Major = 10,
# rollForward value to write
[ValidateSet('disable', 'latestPatch', 'minor', 'latestMinor', 'major', 'latestMajor', 'latestFeature')]
[string]
$RollForward = 'latestFeature'
)

function Get-LatestDotnetSdkVersionForMajor {
param(
[int]$Major
)

$sdks = dotnet --list-sdks 2>$null
if (-not $sdks) {
throw 'No dotnet SDKs found. Make sure dotnet is on PATH.'
}

$versions = $sdks |
ForEach-Object {
# "10.0.100 [C:\...]" → "10.0.100"
($_ -split '\s+')[0]
} |
Where-Object {
$_ -match '^\d+\.\d+\.\d+' -and $_.StartsWith("$Major.")
} |
ForEach-Object {
[version]$_
}

if (-not $versions) {
throw "No .NET $Major SDKs found. Please install .NET $Major SDK first."
}

($versions | Sort-Object -Descending | Select-Object -First 1).ToString()
}

try {
$version = Get-LatestDotnetSdkVersionForMajor -Major $Major
Write-Host "Detected latest .NET $Major SDK: $version"

$jsonObject = @{
sdk = @{
version = $version
rollForward = $RollForward
}
}

$json = $jsonObject | ConvertTo-Json -Depth 3

$resolved = Resolve-Path -Path $Path -ErrorAction SilentlyContinue
if ($null -eq $resolved) {
$targetPath = Join-Path (Get-Location) $Path
Write-Host "Creating new global.json at: $targetPath"
$json | Set-Content -Encoding UTF8 $targetPath
}
else {
Write-Host "Updating existing global.json at: $($resolved.Path)"
$json | Set-Content -Encoding UTF8 $resolved.Path
}

Write-Host "global.json updated to use SDK $version (rollForward=$RollForward)." -ForegroundColor Green
}
catch {
Write-Error $_
}
}

この関数を作った動機は dotnet CLI に指定する full version をいちいち覚えてないから自動で設定したいというものだ。 dotnet new globaljson --force --sdk-version 10 でも full version が入ってほしいがそうはいかない。 あと dotnet new globaljson --forceglobal.json の末尾改行が削れるのも好きじゃない。 ちょっと安全じゃないかもなという気はすれど、自前でやるのがいいかなと考えた。 これで Set-DotnetGlobalJson -Major 10 のようにして簡単に 10.0.100 に変更できる。楽だ。 (SDK を事前に install 済みである必要があるが)

まだ pocof しか .NET SDK を更新してないから、他の repository でもちまちま更新していく予定。 その間 pocof は放置気味になるかも知れんが、できる限りぼちぼち進めたい。 Multi targeting にしたことで netstandard2.0net6.0 でパフォに差があるのかとか benchmark を見てないから測定するとか。 速くなってるなら、ここらで次の version として公開するのもいいしね。 Multi targeting した初回は怖いので alpha が妥当かも知れん。