Skip to content

Merge pull request #76 from routersys/dev/ComputeWeave.ConstantBuffer… #12

Merge pull request #76 from routersys/dev/ComputeWeave.ConstantBuffer…

Merge pull request #76 from routersys/dev/ComputeWeave.ConstantBuffer… #12

Workflow file for this run

name: リリース
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-*"
concurrency:
group: computeweave-release
queue: max
permissions:
contents: read
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "1"
DOTNET_NOLOGO: "1"
jobs:
validate:
name: バージョン検証とテスト
runs-on: windows-2022
defaults:
run:
shell: bash
outputs:
version: ${{ steps.parse.outputs.version }}
version_prefix: ${{ steps.parse.outputs.version_prefix }}
version_suffix: ${{ steps.parse.outputs.version_suffix }}
is_prerelease: ${{ steps.parse.outputs.is_prerelease }}
# ComputeWeave.Dxc が参照する環境変数。
# https://github.com/actions/runner-images/issues/6531 への対処。
env:
CI_RUNNER_DOTNET_TEST_PLATFORM: x64
steps:
- name: ソースコードを取得
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: リリース元とバージョン番号を検証
id: parse
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
TAG: ${{ github.ref_name }}
run: |
if [ "${GITHUB_REF_TYPE}" != "tag" ]; then
echo "タグ以外からリリースすることはできません。" >&2
exit 1
fi
VERSION="${TAG#v}"
PREFIX="${VERSION%%-*}"
if [[ "${VERSION}" == *"-"* ]]; then
SUFFIX="${VERSION#*-}"
IS_PRERELEASE=true
else
SUFFIX=""
IS_PRERELEASE=false
fi
if [[ ! "${PREFIX}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "タグのバージョン接頭辞が正しくありません。" >&2
exit 1
fi
IFS='.' read -r MAJOR MINOR PATCH <<< "${PREFIX}"
for COMPONENT in "${MAJOR}" "${MINOR}" "${PATCH}"; do
if [ "${#COMPONENT}" -gt 5 ] || { [ "${#COMPONENT}" -eq 5 ] && (( 10#${COMPONENT} > 65534 )); }; then
echo "タグのバージョン接頭辞がアセンブリバージョンの範囲を超えています。" >&2
exit 1
fi
done
if [ "${IS_PRERELEASE}" = "true" ] && [[ ! "${SUFFIX}" =~ ^[0-9a-z-]+(\.[0-9a-z-]+)*$ ]]; then
echo "タグのバージョン接尾辞が正しくありません。" >&2
exit 1
fi
if [ -n "${SUFFIX}" ]; then
IFS='.' read -ra IDENTIFIERS <<< "${SUFFIX}"
for IDENTIFIER in "${IDENTIFIERS[@]}"; do
if [[ "${IDENTIFIER}" =~ ^[0-9]+$ ]] && [[ ! "${IDENTIFIER}" =~ ^(0|[1-9][0-9]*)$ ]]; then
echo "数値のプレリリース識別子に先頭の0は使用できません。" >&2
exit 1
fi
done
fi
if [ "${#VERSION}" -gt 64 ]; then
echo "バージョンは64文字以内で指定してください。" >&2
exit 1
fi
if [ "${TAG}" != "v${VERSION}" ]; then
echo "リリースタグが正しくありません。" >&2
exit 1
fi
git check-ref-format "refs/tags/${TAG}"
git check-ref-format --branch "${DEFAULT_BRANCH}"
git fetch --no-tags --force origin "refs/heads/${DEFAULT_BRANCH}:refs/remotes/origin/${DEFAULT_BRANCH}"
if ! git merge-base --is-ancestor HEAD "refs/remotes/origin/${DEFAULT_BRANCH}"; then
echo "リリース対象は既定ブランチに含まれていません。" >&2
exit 1
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "version_prefix=${PREFIX}" >> "$GITHUB_OUTPUT"
echo "version_suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "is_prerelease=${IS_PRERELEASE}" >> "$GITHUB_OUTPUT"
- name: 抽出したバージョン番号を確認
env:
TAG: ${{ github.ref_name }}
VERSION: ${{ steps.parse.outputs.version }}
VERSION_PREFIX: ${{ steps.parse.outputs.version_prefix }}
VERSION_SUFFIX: ${{ steps.parse.outputs.version_suffix }}
IS_PRERELEASE: ${{ steps.parse.outputs.is_prerelease }}
run: |
echo "タグ : ${TAG}"
echo "バージョン : ${VERSION}"
echo "接頭辞 : ${VERSION_PREFIX}"
echo "接尾辞 : ${VERSION_SUFFIX}"
echo "プレリリース : ${IS_PRERELEASE}"
- name: .NET SDK をセットアップ
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.x"
- name: ソリューションをビルド
env:
VERSION_PREFIX: ${{ steps.parse.outputs.version_prefix }}
VERSION_SUFFIX: ${{ steps.parse.outputs.version_suffix }}
run: |
dotnet build ComputeWeave.sln -c Release -p:Platform=x64 \
"-p:VersionPrefix=${VERSION_PREFIX}" \
"-p:VersionSuffix=${VERSION_SUFFIX}"
- name: ソースジェネレーターのテストを実行
run: dotnet test tests/ComputeWeave.Tests.SourceGenerators/ComputeWeave.Tests.SourceGenerators.csproj -c Release -p:Platform=x64
- name: 内部実装のテストを実行
run: dotnet test tests/ComputeWeave.Tests.Internals/ComputeWeave.Tests.Internals.csproj -c Release -p:Platform=x64
- name: 本体のテストを実行
run: dotnet test tests/ComputeWeave.Tests/ComputeWeave.Tests.csproj -c Release -p:Platform=x64
# D2D1 の本体のテストは、ハードウェアが取れなければ WARP へ退避する。アダプターの無い runner でも走る。
- name: D2D1の本体のテストを実行
run: dotnet test tests/ComputeWeave.D2D1.Tests/ComputeWeave.D2D1.Tests.csproj -c Release -p:Platform=x64
- name: D2D1のソースジェネレーターのテストを実行
run: dotnet test tests/ComputeWeave.D2D1.Tests.SourceGenerators/ComputeWeave.D2D1.Tests.SourceGenerators.csproj -c Release -p:Platform=x64
- name: D2D1のアセンブリ属性のテストを実行
run: dotnet test tests/ComputeWeave.D2D1.Tests.AssemblyLevelAttributes/ComputeWeave.D2D1.Tests.AssemblyLevelAttributes.csproj -c Release -p:Platform=x64
# 最上位ステートメントのテストは Exe でありビルドだけでは実行時の不一致を検出できないため、実際に走らせる。
- name: 最上位ステートメントのテストを実行
run: dotnet run --project tests/ComputeWeave.Tests.GlobalStatements/ComputeWeave.Tests.GlobalStatements.csproj -c Release -p:Platform=x64
# 発行と実行を繰り返すため CI では回さない。同梱ライブラリが実際に配られるかを見る唯一の試験なので、出荷前に通す。
- name: ネイティブライブラリ解決のテストを実行
run: dotnet test tests/ComputeWeave.Tests.NativeLibrariesResolver/ComputeWeave.Tests.NativeLibrariesResolver.csproj -c Release -p:Platform=x64
# デバイス消失のテストは実デバイスを破棄するため、他のテストとは別のプロセスで実行する。
validate-device-lost:
name: デバイス消失のテスト
runs-on: windows-2022
defaults:
run:
shell: bash
env:
CI_RUNNER_DOTNET_TEST_PLATFORM: x64
steps:
- name: ソースコードを取得
uses: actions/checkout@v4
- name: .NET SDK をセットアップ
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.x"
- name: デバイス消失のテストを実行
run: dotnet test tests/ComputeWeave.Tests.DeviceLost/ComputeWeave.Tests.DeviceLost.csproj -c Release -p:Platform=x64
pack:
name: NuGet パッケージの作成
runs-on: windows-2022
needs: [validate, validate-device-lost]
defaults:
run:
shell: bash
steps:
- name: ソースコードを取得
uses: actions/checkout@v4
- name: .NET SDK をセットアップ
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.x"
- name: パッケージを作成
env:
VERSION_PREFIX: ${{ needs.validate.outputs.version_prefix }}
VERSION_SUFFIX: ${{ needs.validate.outputs.version_suffix }}
run: |
for project in \
src/ComputeWeave.Core/ComputeWeave.Core.csproj \
src/ComputeWeave/ComputeWeave.csproj \
src/ComputeWeave.Dxc/ComputeWeave.Dxc.csproj \
src/ComputeWeave.D3D12MemoryAllocator/ComputeWeave.D3D12MemoryAllocator.csproj \
src/ComputeWeave.D2D1/ComputeWeave.D2D1.csproj
do
dotnet pack "$project" -c Release \
"-p:VersionPrefix=${VERSION_PREFIX}" \
"-p:VersionSuffix=${VERSION_SUFFIX}"
done
- name: 作成されたパッケージ一覧を確認
run: ls -la ./artifacts/
- name: パッケージ内容を検証
shell: pwsh
env:
EXPECTED_VERSION: ${{ needs.validate.outputs.version }}
EXPECTED_COMMIT: ${{ github.sha }}
run: |
$ErrorActionPreference = 'Stop'
$expectedPackageIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
[void]$expectedPackageIds.Add('ComputeWeave.Core')
[void]$expectedPackageIds.Add('ComputeWeave')
[void]$expectedPackageIds.Add('ComputeWeave.Dxc')
[void]$expectedPackageIds.Add('ComputeWeave.D3D12MemoryAllocator')
[void]$expectedPackageIds.Add('ComputeWeave.D2D1')
function Test-PackageSet([string]$filter, [string]$description) {
$actualPackageIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
$packageFiles = @(Get-ChildItem -LiteralPath artifacts -File -Filter $filter)
if ($packageFiles.Count -ne $expectedPackageIds.Count) {
throw "$descriptionは$($expectedPackageIds.Count)個でなければなりません。"
}
foreach ($packageFile in $packageFiles) {
$archive = [System.IO.Compression.ZipFile]::OpenRead($packageFile.FullName)
try {
$nuspecEntries = @($archive.Entries | Where-Object { $_.FullName -like '*.nuspec' -and $_.FullName -notlike '*/*' })
if ($nuspecEntries.Count -ne 1) {
throw "$($packageFile.Name)のnuspecを一意に特定できません。"
}
$reader = [System.IO.StreamReader]::new($nuspecEntries[0].Open())
try {
[xml]$document = $reader.ReadToEnd()
}
finally {
$reader.Dispose()
}
}
finally {
$archive.Dispose()
}
$namespaceManager = [System.Xml.XmlNamespaceManager]::new($document.NameTable)
$namespaceManager.AddNamespace('n', $document.DocumentElement.NamespaceURI)
$metadata = $document.SelectSingleNode('/n:package/n:metadata', $namespaceManager)
$packageId = $metadata.SelectSingleNode('n:id', $namespaceManager).InnerText
$packageVersion = $metadata.SelectSingleNode('n:version', $namespaceManager).InnerText
$repositoryCommit = $metadata.SelectSingleNode('n:repository', $namespaceManager).GetAttribute('commit')
if (!$expectedPackageIds.Contains($packageId) -or !$actualPackageIds.Add($packageId)) {
throw "$($packageFile.Name)のパッケージIDが正しくありません。"
}
if ($packageVersion -cne $env:EXPECTED_VERSION) {
throw "$($packageFile.Name)のバージョンが正しくありません。"
}
if ($repositoryCommit -cne $env:EXPECTED_COMMIT) {
throw "$($packageFile.Name)のコミットが正しくありません。"
}
}
if (!$actualPackageIds.SetEquals($expectedPackageIds)) {
throw "必要な$descriptionが揃っていません。"
}
}
Test-PackageSet '*.nupkg' 'NuGetパッケージ'
Test-PackageSet '*.snupkg' 'NuGetシンボルパッケージ'
# 上の検証はパッケージの同一性を見る。積荷が揃っていることは別に確かめる。
- name: パッケージの積荷を検証
run: pwsh build/verify-package-payload.ps1
# 文書だけの変更では CI が走らないため、同梱される README を出荷前にここで確かめる。
- name: Markdown の表を検証
run: pwsh build/verify-markdown-tables.ps1
- name: パッケージをアーティファクトとして保存
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: artifacts/
retention-days: 90
if-no-files-found: error
publish-nuget:
name: NuGet.org へ公開
runs-on: ubuntu-latest
needs: [validate, pack]
environment: nuget-release
permissions:
id-token: write
packages: write
defaults:
run:
shell: bash
steps:
- name: .NET SDK をセットアップ
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.x"
- name: パッケージをダウンロード
uses: actions/download-artifact@v4
with:
name: nuget-packages
path: ./artifacts
- name: ダウンロードしたファイルを確認
run: ls -la ./artifacts/
- name: NuGet Trusted Publishing でログイン
uses: NuGet/login@v1
id: nuget-login
with:
user: routersys
- name: NuGet.org へパッケージを発行
env:
ALLOW_DUPLICATES: ${{ github.run_attempt > 1 }}
EXPECTED_COMMIT: ${{ github.sha }}
NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
VERSION: ${{ needs.validate.outputs.version }}
run: |
get_package_status() {
curl --location --silent --show-error --output /dev/null --write-out '%{http_code}' \
"${PACKAGE_BASE}$1/${VERSION}/$1.nuspec"
}
get_package_commit() {
curl --fail --location --silent --show-error "${PACKAGE_BASE}$1/${VERSION}/$1.nuspec" \
| python -c 'import sys, xml.etree.ElementTree as ET; root = ET.parse(sys.stdin).getroot(); print(next((node.get("commit", "") for node in root.iter() if node.tag.rsplit("}", 1)[-1] == "repository"), ""))'
}
PACKAGE_BASE=$(curl --fail --location --silent --show-error https://api.nuget.org/v3/index.json \
| python -c 'import json, sys; resources = json.load(sys.stdin)["resources"]; print(next(item["@id"] for item in resources if item["@type"] == "PackageBaseAddress/3.0.0"))')
for PACKAGE_ID in computeweave.core computeweave computeweave.dxc computeweave.d3d12memoryallocator computeweave.d2d1; do
PACKAGE_STATUS=$(get_package_status "${PACKAGE_ID}")
if [ "${PACKAGE_STATUS}" = "404" ]; then
continue
fi
if [ "${PACKAGE_STATUS}" != "200" ]; then
echo "NuGet.orgのパッケージ状態を確認できませんでした。" >&2
exit 1
fi
if [ "${ALLOW_DUPLICATES}" != "true" ]; then
echo "同じバージョンのパッケージがNuGet.orgに存在します。" >&2
exit 1
fi
PACKAGE_COMMIT=$(get_package_commit "${PACKAGE_ID}")
if [ "${PACKAGE_COMMIT}" != "${EXPECTED_COMMIT}" ]; then
echo "NuGet.orgのパッケージは別のコミットから作成されています。" >&2
exit 1
fi
done
if [ "${ALLOW_DUPLICATES}" = "true" ]; then
dotnet nuget push ./artifacts/*.nupkg \
--api-key "${NUGET_API_KEY}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
else
dotnet nuget push ./artifacts/*.nupkg \
--api-key "${NUGET_API_KEY}" \
--source https://api.nuget.org/v3/index.json
fi
if [ "${ALLOW_DUPLICATES}" = "true" ]; then
for ATTEMPT in $(seq 1 60); do
ALL_PACKAGES_VERIFIED=true
for PACKAGE_ID in computeweave.core computeweave computeweave.dxc computeweave.d3d12memoryallocator computeweave.d2d1; do
PACKAGE_STATUS=$(get_package_status "${PACKAGE_ID}")
if [ "${PACKAGE_STATUS}" = "404" ]; then
ALL_PACKAGES_VERIFIED=false
continue
fi
if [ "${PACKAGE_STATUS}" != "200" ]; then
echo "NuGet.orgのパッケージ状態を確認できませんでした。" >&2
exit 1
fi
PACKAGE_COMMIT=$(get_package_commit "${PACKAGE_ID}")
if [ "${PACKAGE_COMMIT}" != "${EXPECTED_COMMIT}" ]; then
echo "NuGet.orgのパッケージは別のコミットから作成されています。" >&2
exit 1
fi
done
if [ "${ALL_PACKAGES_VERIFIED}" = "true" ]; then
exit 0
fi
sleep 10
done
echo "NuGet.orgで全パッケージを確認できませんでした。" >&2
exit 1
fi
- name: GitHub Packages へも発行
env:
GITHUB_PACKAGES_API_KEY: ${{ secrets.GITHUB_TOKEN }}
run: |
dotnet nuget push ./artifacts/*.nupkg \
--api-key "${GITHUB_PACKAGES_API_KEY}" \
--source https://nuget.pkg.github.com/routersys/index.json \
--skip-duplicate
continue-on-error: true
github-release:
name: GitHub Release の作成
runs-on: ubuntu-latest
needs: [validate, pack, publish-nuget]
permissions:
contents: write
defaults:
run:
shell: bash
steps:
- name: ソースコードを取得
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: パッケージをダウンロード
uses: actions/download-artifact@v4
with:
name: nuget-packages
path: ./artifacts
- name: コミット履歴からリリースノートを生成
env:
CURRENT_TAG: ${{ github.ref_name }}
VERSION: ${{ needs.validate.outputs.version }}
run: |
CURRENT_SHA=$(git rev-parse HEAD)
PREV=$(git tag --merged "${CURRENT_SHA}" --sort=-version:refname \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| grep -Fxv "${CURRENT_TAG}" \
| head -n 1 || true)
if [ -n "${PREV}" ]; then
LOG=$(git log "${PREV}..${CURRENT_SHA}" --pretty=format:"- %s (%h)" --no-merges --)
else
LOG=$(git log -n 30 --pretty=format:"- %s (%h)" --no-merges)
fi
{
echo "### 変更内容"
printf '%s\n' "${LOG:-変更履歴を取得できませんでした。}"
echo ""
echo "### インストール"
echo '```sh'
echo "dotnet add package ComputeWeave --version ${VERSION}"
echo '```'
echo ""
echo "### NuGet"
echo "https://www.nuget.org/packages/ComputeWeave/${VERSION}"
} > release-notes.md
- name: GitHub Release を作成してパッケージを添付
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body_path: release-notes.md
prerelease: ${{ needs.validate.outputs.is_prerelease }}
fail_on_unmatched_files: true
files: |
artifacts/*.nupkg
artifacts/*.snupkg