diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 23a457d3..dcefcc2e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -296,3 +296,33 @@ When reviewing documentation, do the following: - Review the public interface defined in `DirectXTex.h`. - Read the documentation on the wiki located in [this git repository](https://github.com/microsoft/DirectXTex.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 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 DirectXTex port in VCPKG with the new release version. This will edit the files in `ports\directxtex`. +19. Test the VCPKG port using all appropriate triplets and features. +20. Run `.\vcpkg --x-add-version directxtex` to update the VCPKG versioning history. +21. Submit a PR to the VCPKG repository to update the DirectXTex 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 `texassemble`, `texconv` or `texdiag` tools, update the winget manifests for those tools 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 tools with the new release version. +- Submit a PR to the `winget` repository to update the manifests for each tool — they must be done as distinct PRs. The PRs will be reviewed and merged by the winget maintainers. + +> 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. diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 index b7719936..27e65c80 100644 --- a/.github/linters/.powershell-psscriptanalyzer.psd1 +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -1,5 +1,5 @@ # PSScriptAnalyzerSettings.psd1 @{ Severity=@('Error','Warning') - ExcludeRules=@('PSAvoidUsingWriteHost') + ExcludeRules=@('PSAvoidUsingWriteHost', 'PSUseShouldProcessForStateChangingFunctions') } diff --git a/.github/workflows/wsl.yml b/.github/workflows/wsl.yml index 6e66a549..865079b4 100644 --- a/.github/workflows/wsl.yml +++ b/.github/workflows/wsl.yml @@ -111,3 +111,75 @@ 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 }} -DENABLE_OPENEXR_SUPPORT=ON -DENABLE_LIBJPEG_SUPPORT=ON -DENABLE_LIBPNG_SUPPORT=ON + -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 }} -DENABLE_OPENEXR_SUPPORT=ON -DENABLE_LIBJPEG_SUPPORT=ON -DENABLE_LIBPNG_SUPPORT=ON + -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 }} diff --git a/build/completerelease.ps1 b/build/completerelease.ps1 new file mode 100644 index 00000000..be83cd61 --- /dev/null +++ b/build/completerelease.ps1 @@ -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 DirectXTex repository and the +DirectXTex test suite repository (Tests/), then publishes a GitHub release on +the DirectXTex 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/DirectXTex. 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/DirectXTex/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/DirectXTex --- + +Push-SignedTag -RepoPath $reporoot -TagName $releasetag -Message $releasename + +New-GitHubRelease ` + -Owner "microsoft" ` + -Repo "DirectXTex" ` + -TagName $releasetag ` + -ReleaseName $releasename ` + -ReleaseBody $releaseNotes + +#--- Create verified tag on walbourn/directxtextest --- + +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" + } +} \ No newline at end of file diff --git a/build/preparerelease.ps1 b/build/preparerelease.ps1 index bae8312f..30949744 100644 --- a/build/preparerelease.ps1 +++ b/build/preparerelease.ps1 @@ -24,6 +24,7 @@ https://github.com/microsoft/DirectXTex/wiki #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '')] param( [string]$BaseBranch = "main", [string]$TargetBranch = $null, @@ -31,28 +32,28 @@ param( ) $reporoot = Split-Path -Path $PSScriptRoot -Parent -$cmake = $reporoot + "\CMakeLists.txt" -$header = $reporoot + "\DirectXTex\DirectXTex.h" -$readme = $reporoot + "\README.md" -$history = $reporoot + "\CHANGELOG.md" +$cmake = Join-Path $reporoot "CMakeLists.txt" +$header = Join-Path $reporoot "DirectXTex\DirectXTex.h" +$readme = Join-Path $reporoot "README.md" +$history = Join-Path $reporoot "CHANGELOG.md" if ((-Not (Test-Path $cmake)) -Or (-Not (Test-Path $header)) -Or (-Not (Test-Path $readme)) -Or (-Not (Test-Path $history))) { Write-Error "ERROR: Unexpected location of script file!" -ErrorAction Stop } -$branch = git branch --show-current +$branch = git -C $reporoot branch --show-current if ($branch -ne $BaseBranch) { Write-Error "ERROR: Must be in the $BaseBranch branch!" -ErrorAction Stop } -git pull -q +git -C $reporoot pull -q if ($LastExitCode -ne 0) { Write-Error "ERROR: Failed to sync branch!" -ErrorAction Stop } $version = Get-Content ($cmake) | Select-String -Pattern "set\(DIRECTXTEX_VERSION" -CaseSensitive if (-Not ($version -match "([0-9]?\.[0-9]?\.[0-9]?)")) { - Write-Error "ERROR: Failed to current version!" -ErrorAction Stop + Write-Error "ERROR: Failed to find current version!" -ErrorAction Stop } $version = $Matches.0 $rawversion = $version.replace('.','') @@ -69,11 +70,11 @@ else { $newversion = $newrawversion[0] + "." + $newrawversion[1] + "." + $newrawversion[2] -$rawreleasedate = $(Get-Content $readme) | Select-String -Pattern "\#\#\s.[A-Z][a-z]+\S.\d+,?\S.\d\d\d\d" +$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 current release date!" -ErrorAction Stop + Write-Error "ERROR: Failed to find current release date!" -ErrorAction Stop } -$releasedate = $rawreleasedate -replace '## ','' +$releasedate = ($rawreleasedate.ToString() -replace '^## ', '').Trim() if($releasedate -eq $newreleasedate) { Write-Error ("ERROR: Release "+$releasedate+" already exists!") -ErrorAction Stop @@ -84,7 +85,7 @@ if ($TargetBranch -ne 'none') { $TargetBranch = $newreleasetag + "release" } - git checkout -b $TargetBranch + git -C $reporoot checkout -b $TargetBranch if ($LastExitCode -ne 0) { Write-Error "ERROR: Failed to create new topic branch!" -ErrorAction Stop } @@ -102,9 +103,9 @@ if($UpdateVersion) { (Get-Content $header).Replace("#define DIRECTX_TEX_VERSION $rawversion","#define DIRECTX_TEX_VERSION $newrawversion") | Set-Content $header } -(Get-Content $readme).Replace("$rawreleasedate", "## $newreleasedate") | Set-Content $readme +(Get-Content $readme).Replace("## $releasedate", "## $newreleasedate") | Set-Content $readme -Get-ChildItem -Path ($reporoot + "\.nuget") -Filter *.nuspec | Foreach-Object { +Get-ChildItem -Path (Join-Path $reporoot ".nuget") -Filter *.nuspec | Foreach-Object { (Get-Content -Path $_.Fullname).Replace("$releasedate", "$newreleasedate") | Set-Content -Path $_.Fullname -Encoding utf8 } diff --git a/build/promotenuget.ps1 b/build/promotenuget.ps1 index 2e330c50..c505993f 100644 --- a/build/promotenuget.ps1 +++ b/build/promotenuget.ps1 @@ -74,7 +74,7 @@ foreach ($package in $packages) { try { Write-Host "Checking if $package version $Version exists..." - Invoke-RestMethod -Uri $uri -Method Get -Headers $headers + $null = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers } catch { @@ -87,19 +87,19 @@ if (-not $allPackagesSucceeded) { Write-Error "##[error]Not all packages found. Aborting promotion." -ErrorAction Stop } -# Promote package to Prerelease view +# Promote packages to Prerelease view +$allPackagesSucceeded = $true foreach ($package in $packages) { $uri = $uriFormat -f $package, $Version try { - # Promote to Prerelease view Write-Host "Promoting $package version $Version to Prerelease view..." - Invoke-RestMethod -Uri $uri -Method Patch -Headers $headers -Body $bodyPrerelease + $null = Invoke-RestMethod -Uri $uri -Method Patch -Headers $headers -Body $bodyPrerelease } catch { - Write-Error "##[error]Package $package version $Version failed to promote" -ErrorAction Continue + Write-Error "##[error]Package $package version $Version failed to promote to Prerelease!" -ErrorAction Continue $allPackagesSucceeded = $false } } @@ -108,20 +108,20 @@ if (-not $allPackagesSucceeded) { Write-Error "##[error]Not all packages promoted to Prerelease." -ErrorAction Stop } -# Optionally promote package to Release view +# Optionally promote packages to Release view if ($Release.IsPresent) { + $allPackagesSucceeded = $true foreach ($package in $packages) { $uri = $uriFormat -f $package, $Version try { - # Promote to Release view Write-Host "Promoting $package version $Version to Release view..." - Invoke-RestMethod -Uri $uri -Method Patch -Headers $headers -Body $bodyRelease + $null = Invoke-RestMethod -Uri $uri -Method Patch -Headers $headers -Body $bodyRelease } catch { - Write-Error "##[error]Package $package version $Version failed to promote" -ErrorAction Continue + Write-Error "##[error]Package $package version $Version failed to promote to Release!" -ErrorAction Continue $allPackagesSucceeded = $false } } diff --git a/build/updatevcpkg.ps1 b/build/updatevcpkg.ps1 new file mode 100644 index 00000000..3d77cea5 --- /dev/null +++ b/build/updatevcpkg.ps1 @@ -0,0 +1,185 @@ +<# + +.NOTES +Copyright (c) Microsoft Corporation. +Licensed under the MIT License. + +.SYNOPSIS +Updates the vcpkg port for DirectXTex to match a GitHub release. + +.DESCRIPTION +This script updates the vcpkg port at D:\vcpkg\ports\directxtex to match a specific +GitHub release by tag. It updates the version-date in vcpkg.json, the tag and SHA512 +hashes in portfile.cmake for the source archive, texconv.exe, texassemble.exe, +texdiag.exe, and their ARM64 variants. + +.PARAMETER Tag +The GitHub release tag (e.g., 'may2026', 'mar2026'). Defaults to the latest tag. + +.LINK +https://github.com/microsoft/DirectXTex/wiki + +#> + +param( + [string]$Tag = "" +) + +$repoRoot = Split-Path -Path $PSScriptRoot -Parent +$readme = Join-Path $repoRoot "README.md" +$portDir = "D:\vcpkg\ports\directxtex" +$vcpkgJson = Join-Path $portDir "vcpkg.json" +$portfile = Join-Path $portDir "portfile.cmake" + +if (-Not (Test-Path $readme)) { + Write-Error "ERROR: Cannot find README.md at $readme" -ErrorAction Stop +} + +if ((-Not (Test-Path $vcpkgJson)) -Or (-Not (Test-Path $portfile))) { + Write-Error "ERROR: Cannot find vcpkg port files at $portDir" -ErrorAction Stop +} + +# Determine tag from latest git tag if not provided +if ($Tag.Length -eq 0) { + $Tag = (git --no-pager -C $repoRoot tag --sort=-creatordate | Select-Object -First 1).Trim() + if ($Tag.Length -eq 0) { + Write-Error "ERROR: Failed to determine latest tag!" -ErrorAction Stop + } +} + +Write-Host "Release Tag: $Tag" + +# Parse release date from README.md (format: "## Month Day, Year") +$rawReleaseDate = (Get-Content $readme) | Select-String -Pattern "^##\s+[A-Z][a-z]+\s+\d+,?\s+\d{4}" | Select-Object -First 1 +if ([string]::IsNullOrEmpty($rawReleaseDate)) { + Write-Error "ERROR: Failed to find release date in README.md!" -ErrorAction Stop +} + +$releaseDateStr = ($rawReleaseDate -replace '^##\s+', '').Trim() +$releaseDate = [datetime]::Parse($releaseDateStr, [System.Globalization.CultureInfo]::InvariantCulture) +$versionDate = $releaseDate.ToString("yyyy-MM-dd") + +Write-Host "Release Date: $releaseDateStr" +Write-Host "Version Date: $versionDate" + +# --- Update vcpkg.json --- +Write-Host "`nUpdating vcpkg.json..." + +$jsonContent = Get-Content $vcpkgJson -Raw +$jsonContent = $jsonContent -replace '"version-date":\s*"[^"]*"', "`"version-date`": `"$versionDate`"" +$jsonContent = $jsonContent -replace ',\s*"port-version":\s*\d+', '' +$jsonContent = $jsonContent -replace '"port-version":\s*\d+,?\s*', '' +Set-Content -Path $vcpkgJson -Value $jsonContent -NoNewline + +Write-Host " version-date set to $versionDate" + +# --- Update portfile.cmake tag --- +Write-Host "`nUpdating portfile.cmake tag..." + +$portContent = Get-Content $portfile -Raw +$portContent = $portContent -replace 'set\(DIRECTXTEX_TAG\s+\S+\)', "set(DIRECTXTEX_TAG $Tag)" +Set-Content -Path $portfile -Value $portContent -NoNewline + +Write-Host " Tag set to $Tag" + +# --- Download and hash source archive --- +$ProgressPreference = 'SilentlyContinue' +$tempDir = Join-Path $Env:Temp $(New-Guid) +New-Item -Type Directory -Path $tempDir | Out-Null + +$sourceUrl = "https://github.com/Microsoft/DirectXTex/archive/refs/tags/$Tag.tar.gz" +$sourcePath = Join-Path $tempDir "$Tag.tar.gz" + +Write-Host "`nDownloading source archive from $sourceUrl..." +try { + Invoke-WebRequest -Uri $sourceUrl -OutFile $sourcePath -ErrorAction Stop +} +catch { + Write-Error "ERROR: Failed to download source archive!" -ErrorAction Stop +} + +$sourceHash = (Get-FileHash -Path $sourcePath -Algorithm SHA512).Hash.ToLower() +Write-Host " Source SHA512: $sourceHash" + +# Replace SHA512 in vcpkg_from_github block +$portContent = Get-Content $portfile -Raw +$portContent = $portContent -replace '(vcpkg_from_github\s*\([^)]*SHA512\s+)[0-9a-fA-F]+', "`${1}$sourceHash" +Set-Content -Path $portfile -Value $portContent -NoNewline + +# --- Download and hash tool binaries --- +$tools = @( + @{ Name = "texassemble"; Var = "TEXASSEMBLE_EXE" }, + @{ Name = "texconv"; Var = "TEXCONV_EXE" }, + @{ Name = "texdiag"; Var = "TEXDIAG_EXE" } +) + +$toolHashes = @{} + +foreach ($tool in $tools) { + $toolName = $tool.Name + + # x64 binary + $url = "https://github.com/Microsoft/DirectXTex/releases/download/$Tag/$toolName.exe" + $outPath = Join-Path $tempDir "$toolName.exe" + + Write-Host "`nDownloading $toolName.exe from $url..." + try { + Invoke-WebRequest -Uri $url -OutFile $outPath -ErrorAction Stop + } + catch { + Write-Error "ERROR: Failed to download $toolName.exe!" -ErrorAction Stop + } + + $hash = (Get-FileHash -Path $outPath -Algorithm SHA512).Hash.ToLower() + Write-Host " $toolName.exe SHA512: $hash" + $toolHashes["${toolName}_x64"] = $hash + + # ARM64 binary + $urlArm64 = "https://github.com/Microsoft/DirectXTex/releases/download/$Tag/${toolName}_arm64.exe" + $outPathArm64 = Join-Path $tempDir "${toolName}_arm64.exe" + + Write-Host "`nDownloading ${toolName}_arm64.exe from $urlArm64..." + try { + Invoke-WebRequest -Uri $urlArm64 -OutFile $outPathArm64 -ErrorAction Stop + } + catch { + Write-Error "ERROR: Failed to download ${toolName}_arm64.exe!" -ErrorAction Stop + } + + $hashArm64 = (Get-FileHash -Path $outPathArm64 -Algorithm SHA512).Hash.ToLower() + Write-Host " ${toolName}_arm64.exe SHA512: $hashArm64" + $toolHashes["${toolName}_arm64"] = $hashArm64 +} + +# --- Replace SHA512 hashes for all tool binaries in portfile --- +$portContent = Get-Content $portfile -Raw + +foreach ($tool in $tools) { + $toolName = $tool.Name + $varName = $tool.Var + + # Match the vcpkg_download_distfile block for x64 binary + $portContent = $portContent -replace ` + "(vcpkg_download_distfile\s*\(\s*\n\s*${varName}\s*\n\s*URLS\s+`"[^`"]*${toolName}\.exe`"\s*\n\s*FILENAME\s+`"[^`"]*`"\s*\n\s*SHA512\s+)[0-9a-fA-F]+", ` + "`${1}$($toolHashes["${toolName}_x64"])" + + # Match the vcpkg_download_distfile block for ARM64 binary + $portContent = $portContent -replace ` + "(vcpkg_download_distfile\s*\(\s*\n\s*${varName}\s*\n\s*URLS\s+`"[^`"]*${toolName}_arm64\.exe`"\s*\n\s*FILENAME\s+`"[^`"]*`"\s*\n\s*SHA512\s+)[0-9a-fA-F]+", ` + "`${1}$($toolHashes["${toolName}_arm64"])" +} + +Set-Content -Path $portfile -Value $portContent -NoNewline + +# --- Cleanup --- +Remove-Item -Recurse -Force $tempDir + +Write-Host "`nvcpkg port updated successfully!" +Write-Host "`nUpdated files:" +Write-Host " $vcpkgJson" +Write-Host " $portfile" + +$portContent = Get-Content $portfile -Raw +if ($portContent -match '\bPATCHES\b') { + Write-Warning "This port includes patches. Review them to either remove or update." +} diff --git a/build/updatewinget.ps1 b/build/updatewinget.ps1 new file mode 100644 index 00000000..6f9ee54c --- /dev/null +++ b/build/updatewinget.ps1 @@ -0,0 +1,215 @@ +<# + +.NOTES +Copyright (c) Microsoft Corporation. +Licensed under the MIT License. + +.SYNOPSIS +Updates the winget manifests for texassemble, texconv, and texdiag to match a GitHub release. + +.DESCRIPTION +This script creates new winget manifest versions for texassemble, texconv, and texdiag under +D:\winget-pkgs based on the most recent release date in README.md. It copies the previous +version's manifests, then updates PackageVersion, ReleaseDate, InstallerSha256, InstallerUrl, +and ReleaseNotesUrl to match the new release. + +.PARAMETER Tag +The GitHub release tag (e.g., 'may2026', 'mar2026'). Defaults to the latest tag. + +.LINK +https://github.com/microsoft/DirectXTex/wiki + +#> + +param( + [string]$Tag = "" +) + +$repoRoot = Split-Path -Path $PSScriptRoot -Parent +$readme = Join-Path $repoRoot "README.md" +$wingetPkgs = "D:\winget-pkgs" + +if (-Not (Test-Path $readme)) { + Write-Error "ERROR: Cannot find README.md at $readme" -ErrorAction Stop +} + +if (-Not (Test-Path $wingetPkgs)) { + Write-Error "ERROR: Cannot find winget-pkgs at $wingetPkgs" -ErrorAction Stop +} + +# Determine tag from latest git tag if not provided +if ($Tag.Length -eq 0) { + $Tag = (git --no-pager -C $repoRoot tag --sort=-creatordate | Select-Object -First 1).Trim() + if ($Tag.Length -eq 0) { + Write-Error "ERROR: Failed to determine latest tag!" -ErrorAction Stop + } +} + +Write-Host "Release Tag: $Tag" + +# Parse release date from README.md (format: "## Month Day, Year") +$rawReleaseDate = (Get-Content $readme) | Select-String -Pattern "^##\s+[A-Z][a-z]+\s+\d+,?\s+\d{4}" | Select-Object -First 1 +if ([string]::IsNullOrEmpty($rawReleaseDate)) { + Write-Error "ERROR: Failed to find release date in README.md!" -ErrorAction Stop +} + +$releaseDateStr = ($rawReleaseDate -replace '^##\s+', '').Trim() +$releaseDate = [datetime]::Parse($releaseDateStr, [System.Globalization.CultureInfo]::InvariantCulture) + +# winget version format: YYYY.M.D (no leading zeros) +$packageVersion = $releaseDate.ToString("yyyy.M.d") + +# winget ReleaseDate format: YYYY-MM-DD +$releaseDateYaml = $releaseDate.ToString("yyyy-MM-dd") + +Write-Host "Release Date: $releaseDateStr" +Write-Host "Package Version: $packageVersion" +Write-Host "Release Date (YAML): $releaseDateYaml" + +# --- Tool definitions --- +# Each tool has a manifest base path and a list of release assets (name + architecture) +$tools = @( + @{ + Name = "Texassemble" + ManifestBase = Join-Path $wingetPkgs "manifests\m\Microsoft\DirectXTex\Texassemble" + PackageId = "Microsoft.DirectXTex.Texassemble" + Assets = @( + @{ FileName = "texassemble.exe"; Arch = "x64" }, + @{ FileName = "texassemble_arm64.exe"; Arch = "arm64" } + ) + }, + @{ + Name = "Texconv" + ManifestBase = Join-Path $wingetPkgs "manifests\m\Microsoft\DirectXTex\Texconv" + PackageId = "Microsoft.DirectXTex.Texconv" + Assets = @( + @{ FileName = "texconv.exe"; Arch = "x64" }, + @{ FileName = "texconv_arm64.exe"; Arch = "arm64" } + ) + }, + @{ + Name = "Texdiag" + ManifestBase = Join-Path $wingetPkgs "manifests\m\Microsoft\DirectXTex\Texdiag" + PackageId = "Microsoft.DirectXTex.Texdiag" + Assets = @( + @{ FileName = "texdiag.exe"; Arch = "x64" }, + @{ FileName = "texdiag_arm64.exe"; Arch = "arm64" } + ) + } +) + +# --- Find previous version directory --- +function Get-LatestVersionDir { + param([string]$BasePath) + $dirs = Get-ChildItem -Path $BasePath -Directory | Sort-Object Name + if ($dirs.Count -eq 0) { + Write-Error "ERROR: No existing version directories found in $BasePath" -ErrorAction Stop + } + return $dirs[-1] +} + +# --- Download release assets and compute SHA256 hashes --- +$ProgressPreference = 'SilentlyContinue' +$tempDir = Join-Path $Env:Temp $(New-Guid) +New-Item -Type Directory -Path $tempDir | Out-Null + +$hashes = @{} + +# Collect all unique asset filenames across tools +$allAssets = $tools | ForEach-Object { $_.Assets } | ForEach-Object { $_ } + +foreach ($asset in $allAssets) { + $url = "https://github.com/microsoft/DirectXTex/releases/download/$Tag/$($asset.FileName)" + $outPath = Join-Path $tempDir $asset.FileName + Write-Host "`nDownloading $($asset.FileName) from $url..." + try { + Invoke-WebRequest -Uri $url -OutFile $outPath -ErrorAction Stop + } + catch { + Write-Error "ERROR: Failed to download $($asset.FileName)!" -ErrorAction Stop + } + $hash = (Get-FileHash -Path $outPath -Algorithm SHA256).Hash.ToLower() + $hashes[$asset.FileName] = $hash + Write-Host " SHA256: $hash" +} + +# --- Create new manifests for each tool --- +$newDirs = @() + +foreach ($tool in $tools) { + $prevDir = Get-LatestVersionDir $tool.ManifestBase + $newDir = Join-Path $tool.ManifestBase $packageVersion + + Write-Host "`nPrevious $($tool.Name) version: $($prevDir.Name)" + + if (Test-Path $newDir) { + Write-Error "ERROR: $($tool.Name) version $packageVersion already exists at $newDir" -ErrorAction Stop + } + + Write-Host "Creating $($tool.Name) $packageVersion manifests..." + Copy-Item -Path $prevDir.FullName -Destination $newDir -Recurse + + foreach ($file in Get-ChildItem -Path $newDir -Filter "*.yaml") { + $content = Get-Content $file.FullName -Raw + $content = $content -replace "PackageVersion:\s+\S+", "PackageVersion: $packageVersion" + + if ($file.Name -match "installer") { + $content = $content -replace "ReleaseDate:\s+\S+", "ReleaseDate: $releaseDateYaml" + + # Update installer URLs + foreach ($asset in $tool.Assets) { + $escapedFileName = [regex]::Escape($asset.FileName) + $content = $content -replace "(InstallerUrl:\s+).+$escapedFileName", "`${1}https://github.com/microsoft/DirectXTex/releases/download/$Tag/$($asset.FileName)" + } + + # Update SHA256 hashes per architecture block + $lines = $content -split "`n" + $currentArch = "" + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($lines[$i] -match "Architecture:\s+(\S+)") { + $currentArch = $Matches[1] + } + + if ($lines[$i] -match "InstallerSha256:") { + $matchingAsset = $tool.Assets | Where-Object { $_.Arch -eq $currentArch } + if ($matchingAsset) { + $lines[$i] = " InstallerSha256: $($hashes[$matchingAsset.FileName])" + } + } + } + $content = $lines -join "`n" + } + + if ($file.Name -match "locale") { + $content = $content -replace "(ReleaseNotesUrl:\s+).+", "`${1}https://github.com/microsoft/DirectXTex/releases/tag/$Tag" + } + + Set-Content -Path $file.FullName -Value $content -NoNewline + } + + foreach ($file in Get-ChildItem -Path $newDir -Filter "*.yaml") { + Write-Host " $($file.Name)" + } + + $newDirs += $newDir +} + +# --- Cleanup --- +Remove-Item -Recurse -Force $tempDir + +Write-Host "`nwinget manifests created successfully!" +Write-Host "`nNew manifest directories:" +foreach ($dir in $newDirs) { + Write-Host " $dir" +} +Write-Host "`nNext steps:" +Write-Host " 1. Review the generated manifest files" +foreach ($dir in $newDirs) { + Write-Host " 2. Validate with: winget validate $dir" +} +foreach ($dir in $newDirs) { + Write-Host " 3. Test with: winget install --manifest $dir" +} +Write-Host " 4. Submit PRs to the winget-pkgs repository" +Write-Host "" +Write-Host " NOTE: Each tool must be submitted as its own separate PR per winget-pkgs policy."