Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,33 @@ When reviewing documentation, do the following:
- Review the public interface defined in `DirectXMesh.h`, as well as the headers in the `Utilities` folder.
- Read the documentation on the wiki located in [this git repository](https://github.com/microsoft/DirectXMesh.wiki.git).
- Report any specific gaps in the documentation compared to the public interface.

## Release Process

1. Ensure all changes are merged into the `main` branch and that all tests pass.
2. Git pull the local repository to ensure it is up to date with the `main` branch.
3. Run the PowerShell script `build\preparerelease.ps1` which will generate a topic branch for the release, update the version number in `CMakeLists.txt`, the `README.md` file, the release notes in the nuspec files, and create a stub in the `CHANGELOG.md` file for the new release.
4. Edit the `CHANGELOG.md` file to update it with a summary of changes.
5. Submit the topic branch for review and merge into `main` once approved. Allow the GitHub Actions workflows and the Azure DevOps pipelines to complete successfully before proceeding.
6. Run the PowerShell script `build\completerelease.ps1` which will set a tag on the project repo and the test repo, and create a release on GitHub with the release notes from `CHANGELOG.md`. Ensure you have set up GPG signing for your GitHub account so that the tags will be verified.
7. Git pull the local repository to ensure it is up to date with the `main` branch. Be sure to include `--tags`.
8. Push the `main` branch to the MSCodeHub mirror repository. Be sure to include `--tags`.
9. Create a PR on MSCodeHub from the `main` branch to the `release` branch.
10. Merge the PR on MSCodeHub to update the release branch, which will trigger the Azure DevOps pipeline to build signed binaries and the NuGet packages.
11. Run the PowerShell script `build\downloadartifacts.ps1` to download the signed binaries from the Azure DevOps pipeline artifacts.
12. Edit the GitHub release and upload the signed binaries to the release assets.
13. Download the GitHub source .zip archive from the release. Unzip and compare to the local repo to ensure it matches — keep in mind there may be some CR/LF differences. Run minisign on the .zip to generate a signature file, and upload the signature file to the release assets.
14. Validate the NuGet packages with <https://github.com/walbourn/contentexporter> by pushing the NuGet packages to a local Packages Source folder, updating the NuGet packages from that folder, and then build the project.
15. Run the PowerShell script `build\promotenuget.ps1` with the `-Release` parameter to promote the version to the Release view on the project-scoped ADO feed.
16. Run the MSCodeHub pipeline to publish the NuGet packages to nuget.org. The pipeline will automatically push the most recent package promoted to the Release view to nuget.org.
17. Git pull a local repository of VCPKG to `d:\vcpkg` in sync with the `main` branch of the VCPKG repository.
18. Run the PowerShell script `build\updatevcpkg.ps1` to update the DirectXMesh port in VCPKG with the new release version. This will edit the files in `ports\directxmesh`.
19. Test the VCPKG port using all appropriate triplets and features.
20. Run `.\vcpkg --x-add-version directxmesh` to update the VCPKG versioning history.
21. Submit a PR to the VCPKG repository to update the DirectXMesh port back to the main GitHub repo. The PR will be reviewed and merged by the VCPKG maintainers.
22. If relevant changes were made to the `meshconvert` tool, update the winget manifests for that tool in the `winget` repository.
- Git pull a local repository to `D:\winget-pkgs` in sync with the `master` branch of the WinGet repository.
- Run the PowerShell script `build\updatewinget.ps1` to update the winget manifests for the tool with the new release version.
- Submit a PR to the `winget` repository to update the manifests for the tool.

> When fully completed, be sure to update the GitHub release with links to the matching NuGet packages, the VCPKG port, and the winget manifests for the tools.
2 changes: 1 addition & 1 deletion .github/linters/.powershell-psscriptanalyzer.psd1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# PSScriptAnalyzerSettings.psd1
@{
Severity=@('Error','Warning')
ExcludeRules=@('PSAvoidUsingWriteHost')
ExcludeRules=@('PSAvoidUsingWriteHost', 'PSUseShouldProcessForStateChangingFunctions')
}
74 changes: 74 additions & 0 deletions .github/workflows/wsl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,77 @@ jobs:
- name: 'Build (-shared)'
working-directory: ${{ github.workspace }}
run: cmake --build out/build/${{ matrix.build_type }}

buildclang:
runs-on: ubuntu-latest

strategy:
fail-fast: false

matrix:
build_type: [x64-Debug-Linux, x64-Release-Linux]
clang: [16, 17, 18]

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: seanmiddleditch/gha-setup-ninja@3b1f8f94a2f8254bd26914c4ab9474d4f0015f67 # v6

- name: Get vcpkg commit hash
shell: pwsh
run: |
if ($Env:vcpkgRelease) {
echo "Using vcpkg commit from repo variable..."
$VCPKG_COMMIT_ID = $Env:vcpkgRelease
}
else {
echo "Fetching latest vcpkg commit hash..."
$commit = (git ls-remote https://github.com/microsoft/vcpkg.git HEAD | Select-String -Pattern '([a-f0-9]{40})').Matches.Value
$VCPKG_COMMIT_ID = $commit
}
Write-Host "VCPKG_COMMIT_ID=$VCPKG_COMMIT_ID"
echo "VCPKG_COMMIT_ID=$VCPKG_COMMIT_ID" >> $env:GITHUB_ENV
env:
vcpkgRelease: '${{ vars.VCPKG_COMMIT_ID }}'

- uses: lukka/run-vcpkg@305c06bd4dee21e23dcf142c85c657a993f7aa1a # v11
with:
runVcpkgInstall: true
vcpkgJsonGlob: '**/build/vcpkg.json'
vcpkgGitCommitId: '${{ env.VCPKG_COMMIT_ID }}'

- name: 'Configure CMake'
working-directory: ${{ github.workspace }}
run: >
cmake --preset=${{ matrix.build_type }}
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake"
-DVCPKG_MANIFEST_DIR="${{ github.workspace }}/build"
-DVCPKG_TARGET_TRIPLET="x64-linux"

env:
CC: clang-${{ matrix.clang }}
CXX: clang++-${{ matrix.clang }}

- name: 'Build'
working-directory: ${{ github.workspace }}
run: cmake --build out/build/${{ matrix.build_type }}

- name: 'Clean up'
working-directory: ${{ github.workspace }}
run: rm -rf out

- name: 'Configure CMake (-shared)'
working-directory: ${{ github.workspace }}
run: >
cmake --preset=${{ matrix.build_type }}
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake"
-DVCPKG_MANIFEST_DIR="${{ github.workspace }}/build"
-DVCPKG_TARGET_TRIPLET="x64-linux" -DBUILD_SHARED_LIBS=ON

env:
CC: clang-${{ matrix.clang }}
CXX: clang++-${{ matrix.clang }}

- name: 'Build (-shared)'
working-directory: ${{ github.workspace }}
run: cmake --build out/build/${{ matrix.build_type }}
275 changes: 275 additions & 0 deletions build/completerelease.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
<#

.NOTES
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.

.SYNOPSIS
Completes the release process by creating verified GitHub tags and a release.

.DESCRIPTION
Creates GPG-signed annotated tags on both the DirectXMesh repository and the
DirectXMesh test suite repository (Tests/), then publishes a GitHub release on
the DirectXMesh repository using the signed tag.

Tags are signed locally with 'git tag -s', which requires GPG signing to be
configured in git and the signing key to be registered with GitHub. This
produces the Verified badge in the GitHub UI.

Run this script after the release PR (prepared by preparerelease.ps1) has been
merged into the main branch.

.PARAMETER PAT
GitHub Personal Access Token with 'repo' scope, used to publish the GitHub
release on microsoft/DirectXMesh. Can also be provided via the GITHUB_TOKEN
environment variable. If neither is provided, the script attempts to obtain a
token from the 'gh' CLI.

.PARAMETER SkipTestRepo
If set, skips creating a tag on the test suite repository (Tests/).

.PARAMETER WhatIf
Shows what would happen without creating tags, pushing, or publishing a release.

.LINK
https://github.com/microsoft/DirectXMesh/wiki

#>

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '')]
param(
[string]$PAT = "",
[switch]$SkipTestRepo,
[switch]$WhatIf
)

$reporoot = Split-Path -Path $PSScriptRoot -Parent
$readme = Join-Path $reporoot "README.md"
$history = Join-Path $reporoot "CHANGELOG.md"
$testsroot = Join-Path $reporoot "Tests"

#--- Validate script location ---

if ((-Not (Test-Path $readme)) -Or (-Not (Test-Path $history))) {
Write-Error "ERROR: Unexpected location of script file!" -ErrorAction Stop
}

#--- Validate local repo state ---

$branch = git -C $reporoot branch --show-current
if ($branch -ne "main") {
Write-Error "ERROR: Must be on the 'main' branch (currently on '$branch')!" -ErrorAction Stop
}

Write-Host "Fetching from origin..."
git -C $reporoot fetch -q origin
if ($LastExitCode -ne 0) {
Write-Error "ERROR: Failed to fetch from origin!" -ErrorAction Stop
}

$headHash = git -C $reporoot rev-parse HEAD
$remoteHash = git -C $reporoot rev-parse "origin/main"
if ($headHash -ne $remoteHash) {
Write-Error "ERROR: Local 'main' is not in sync with origin. Run 'git pull' first." -ErrorAction Stop
}

#--- Derive release info from README.md ---

$rawreleasedate = $(Get-Content $readme) | Select-String -Pattern "^## [A-Z][a-z]+ \d+,?\s*\d{4}" | Select-Object -First 1
if ([string]::IsNullOrEmpty($rawreleasedate)) {
Write-Error "ERROR: Failed to find a release date header in README.md!" -ErrorAction Stop
}

$releasename = ($rawreleasedate.ToString() -replace '^## ', '').Trim()

try {
$releaseDateTime = [datetime]::Parse($releasename)
}
catch {
Write-Error "ERROR: Failed to parse release date '$releasename': $_" -ErrorAction Stop
}

$releasetag = (Get-Date -Date $releaseDateTime -Format "MMMyyyy").ToLower()

Write-Host " Release Name: $releasename"
Write-Host " Release Tag: $releasetag"

#--- Extract release notes from CHANGELOG.md ---

$changelog = Get-Content $history
$notesStart = -1
$notesEnd = $changelog.Count - 1

for ($i = 0; $i -lt $changelog.Count; $i++) {
if ($changelog[$i] -match "^### $([regex]::Escape($releasename))") {
$notesStart = $i + 1
}
elseif ($notesStart -ge 0 -and $changelog[$i] -match "^### ") {
$notesEnd = $i - 1
break
}
}

if ($notesStart -lt 0) {
Write-Error "ERROR: Could not find release notes for '$releasename' in CHANGELOG.md!" -ErrorAction Stop
}

$releaseNotes = (($changelog[$notesStart..$notesEnd] | Where-Object { $_ -ne "" }) -join "`n").Trim()

Write-Host "Release Notes:"
Write-Host $releaseNotes
Write-Host ""

#--- Acquire GitHub token ---

if ($PAT.Length -eq 0) {
$PAT = [string]$env:GITHUB_TOKEN
}

if ($PAT.Length -eq 0) {
try {
$ghToken = & gh auth token 2>$null
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($ghToken)) {
$PAT = $ghToken.Trim()
Write-Host "Using token from 'gh' CLI."
}
}
catch {
# gh CLI not available
}
}

if ($PAT.Length -eq 0) {
Write-Error "ERROR: No GitHub token found. Provide -PAT, set GITHUB_TOKEN, or sign in with 'gh auth login'." -ErrorAction Stop
}

$apiHeaders = @{
"Accept" = "application/vnd.github+json"
"Authorization" = "Bearer $PAT"
"X-GitHub-Api-Version" = "2022-11-28"
}

#--- Helper: create a GPG-signed tag locally and push it ---

function Push-SignedTag {
param(
[Parameter(Mandatory)] [string]$RepoPath,
[Parameter(Mandatory)] [string]$TagName,
[Parameter(Mandatory)] [string]$Message
)

# Check whether the tag already exists locally
$existing = git -C $RepoPath tag -l $TagName
if (-not [string]::IsNullOrEmpty($existing)) {
Write-Error "ERROR: Tag '$TagName' already exists in '$RepoPath'!" -ErrorAction Stop
}

if ($WhatIf) {
Write-Host "[WhatIf] Would create signed tag '$TagName' in '$RepoPath' and push to origin"
return
}

Write-Host "Creating signed tag '$TagName'..."
git -C $RepoPath tag -s $TagName -m $Message
if ($LastExitCode -ne 0) {
Write-Error "ERROR: Failed to create signed tag '$TagName'. Ensure GPG signing is configured." -ErrorAction Stop
}

Write-Host "Pushing tag '$TagName' to origin..."
git -C $RepoPath push origin $TagName
if ($LastExitCode -ne 0) {
git -C $RepoPath tag -d $TagName 2>$null
Write-Error "ERROR: Failed to push tag '$TagName' to origin." -ErrorAction Stop
}
}

#--- Helper: create a GitHub release ---

function New-GitHubRelease {
param(
[Parameter(Mandatory)] [string]$Owner,
[Parameter(Mandatory)] [string]$Repo,
[Parameter(Mandatory)] [string]$TagName,
[Parameter(Mandatory)] [string]$ReleaseName,
[Parameter(Mandatory)] [string]$ReleaseBody
)

# Check whether a release already exists for this tag
$checkUri = "https://api.github.com/repos/$Owner/$Repo/releases/tags/$TagName"
$releaseExists = $false

try {
$null = Invoke-RestMethod -Uri $checkUri -Method Get -Headers $apiHeaders -ErrorAction Stop
$releaseExists = $true
}
catch {
$sc = $null
try { $sc = [int]$_.Exception.Response.StatusCode } catch { }
if ($sc -ne 404) {
Write-Error "ERROR: Failed to check for existing release '$TagName' on ${Owner}/${Repo}: $_" -ErrorAction Stop
}
# 404 = no release exists yet, which is expected
}

if ($releaseExists) {
Write-Error "ERROR: Release '$TagName' already exists on ${Owner}/${Repo}!" -ErrorAction Stop
}

if ($WhatIf) {
Write-Host "[WhatIf] Would create release '$TagName' on ${Owner}/${Repo}"
return
}

$payload = @{
tag_name = $TagName
name = $ReleaseName
body = $ReleaseBody
draft = $false
prerelease = $false
make_latest = "true"
} | ConvertTo-Json

Write-Host "Creating release '$TagName' on ${Owner}/${Repo}..."

try {
$result = Invoke-RestMethod -Uri "https://api.github.com/repos/$Owner/$Repo/releases" `
-Method Post -Headers $apiHeaders -Body $payload -ContentType "application/json" -ErrorAction Stop
Write-Host " Created: $($result.html_url)"
}
catch {
Write-Error "ERROR: Failed to create release '$TagName' on ${Owner}/${Repo}: $_" -ErrorAction Stop
}
}

#--- Create verified tag and release on microsoft/DirectXMesh ---

Push-SignedTag -RepoPath $reporoot -TagName $releasetag -Message $releasename

New-GitHubRelease `
-Owner "microsoft" `
-Repo "DirectXMesh" `
-TagName $releasetag `
-ReleaseName $releasename `
-ReleaseBody $releaseNotes

#--- Create verified tag on walbourn/directxmeshtest ---

if (-Not $SkipTestRepo) {
if (-Not (Test-Path $testsroot)) {
Write-Warning "WARNING: Tests/ folder not found at '$testsroot'. Skipping test suite tag."
}
else {
Push-SignedTag -RepoPath $testsroot -TagName $releasetag -Message $releasename
}
}

#--- Done ---

if (-Not $WhatIf) {
Write-Host ""
Write-Host "Release complete. Sync the new tags locally with:"
Write-Host " git pull --tags"
if (-Not $SkipTestRepo) {
Write-Host " git -C Tests pull --tags"
}
}
Loading
Loading