From ec68e80e5fcb43e00e1242a7198d9bb6aab23f16 Mon Sep 17 00:00:00 2001 From: "Camille-Maxime Thea (MaxRev)" Date: Fri, 25 Sep 2026 11:12:56 +0300 Subject: [PATCH 1/4] fix(ci): gate nuget.org publish on all-platform success Previously each platform's PushPackages job pushed to nuget.org as soon as its own tests passed, independently of other platforms. When Windows failed in run 36090066720, Linux packages (MaxRev.Gdal.Core, LinuxRuntime.Minimal.x64/.arm64, CLI.linux-*) reached nuget.org at 3.13.3.573 with no matching Windows or macOS packages - an orphan partial release. Remove the per-platform nuget.org push from unix.yml, macos.yml, and windows.yml PushPackages jobs. The per-platform bundle build and artifact upload steps are preserved. main.yml TestAndPushPackages (which already gates on all three platform workflows) now downloads the per-platform bundle artifacts (packages-unix-final, packages-osx-final) and publishes the complete package set to nuget.org in one shot, after all platforms succeed and the final integration test passes. The existing --skip-duplicate flag keeps re-runs safe. --- .github/workflows/macos.yml | 4 ---- .github/workflows/main.yml | 16 ++++++++++++++++ .github/workflows/unix.yml | 5 ----- .github/workflows/windows.yml | 4 ---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 61d5b7a8c..a094eb3d3 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -278,7 +278,3 @@ jobs: path: nuget/*.nupkg overwrite: true - - name: Push packages - if: ${{ github.event.pull_request.merged == true || github.ref == 'refs/heads/main' || fromJson(inputs.is-version-branch) }} - run: | - make -f ../unix/push-packages-makefile BUILD_NUMBER_TAIL=${{ github.run_number }} API_KEY_GITHUB=${{ secrets.API_KEY_GITHUB }} API_KEY_NUGET=${{ secrets.API_KEY_NUGET }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 33961bc04..7f1f8a3b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -152,6 +152,22 @@ jobs: path: nuget merge-multiple: true + # Per-platform bundle packages (MaxRev.Gdal.LinuxRuntime.Minimal, + # MaxRev.Gdal.MacosRuntime.Minimal) are built by PushPackages-Linux + # and PushPackages-Macos in the reusable workflows. Download them so + # the centralised push below publishes the complete package set. + - uses: actions/download-artifact@v6.0.0 + with: + name: packages-unix-final + path: nuget + merge-multiple: true + + - uses: actions/download-artifact@v6.0.0 + with: + name: packages-osx-final + path: nuget + merge-multiple: true + - name: Remove old formats run: | rm -rf ${{ github.workspace }}/tests/gdal-formats diff --git a/.github/workflows/unix.yml b/.github/workflows/unix.yml index da68bf2d3..76c7984d8 100644 --- a/.github/workflows/unix.yml +++ b/.github/workflows/unix.yml @@ -305,8 +305,3 @@ jobs: path: nuget/*.nupkg overwrite: true - - name: Push packages - if: ${{ github.event.pull_request.merged == true || github.ref == 'refs/heads/main' || fromJson(inputs.is-version-branch) }} - run: | - make -f push-packages-makefile PRERELEASE=${{ inputs.is-pre-release }} INCLUDE_CORE=1 \ - BUILD_NUMBER_TAIL=${{ github.run_number }} API_KEY_GITHUB=${{ secrets.API_KEY_GITHUB }} API_KEY_NUGET=${{ secrets.API_KEY_NUGET }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6dd4c9437..ba6e12ec8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -224,7 +224,3 @@ jobs: api-user-nuget: ${{ secrets.API_USER_NUGET }} shell: pwsh - - name: Push packages - if: ${{ github.event.pull_request.merged == true || github.ref == 'refs/heads/main' || fromJson(inputs.is-version-branch) }} - run: | - make -f push-packages-makefile BUILD_NUMBER_TAIL=${{ github.run_number }} API_KEY_GITHUB=${{ secrets.API_KEY_GITHUB }} API_KEY_NUGET=${{ secrets.API_KEY_NUGET }} From 04da755d007bcde66c4d15828ed606ffafd4e7c3 Mon Sep 17 00:00:00 2001 From: "Camille-Maxime Thea (MaxRev)" Date: Fri, 25 Sep 2026 11:14:57 +0300 Subject: [PATCH 2/4] chore: add script to unlist a published version on nuget.org Finds every MaxRev.Gdal.* package that has the given version and unlists it with dotnet nuget delete. Dry run by default; --yes to apply. Used to clean up the orphaned 3.13.3.573 packages. --- shared/scripts/unlist-nuget-version.sh | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100755 shared/scripts/unlist-nuget-version.sh diff --git a/shared/scripts/unlist-nuget-version.sh b/shared/scripts/unlist-nuget-version.sh new file mode 100755 index 000000000..673d3c5f0 --- /dev/null +++ b/shared/scripts/unlist-nuget-version.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Unlist every MaxRev.Gdal.* package published at a given version on nuget.org. +# +# nuget.org never deletes packages: `dotnet nuget delete` unlists them, so they +# disappear from search and floating-version restores, but existing lock files +# that pin the exact version keep restoring. +# +# Usage: +# NUGET_API_KEY=... shared/scripts/unlist-nuget-version.sh 3.13.3.573 # dry run +# NUGET_API_KEY=... shared/scripts/unlist-nuget-version.sh 3.13.3.573 --yes # unlist +# shared/scripts/unlist-nuget-version.sh 3.13.3.573 --yes MaxRev.Gdal.Core # only these IDs +# +# Without explicit package IDs, the script searches nuget.org for MaxRev.Gdal.* +# packages and keeps the ones that actually have the requested version. + +set -euo pipefail + +SOURCE="https://api.nuget.org/v3/index.json" +SEARCH="https://azuresearch-usnc.nuget.org/query" +FLAT="https://api.nuget.org/v3-flatcontainer" + +usage() { + sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' + exit "${1:-1}" +} + +version="" +apply=0 +ids=() +for arg in "$@"; do + case "$arg" in + -h|--help) usage 0 ;; + -y|--yes) apply=1 ;; + -*) echo "unknown option: $arg" >&2; usage ;; + *) if [[ -z "$version" ]]; then version="$arg"; else ids+=("$arg"); fi ;; + esac +done + +[[ -n "$version" ]] || usage +[[ "$version" =~ ^[0-9]+(\.[0-9]+){2,3}(-[0-9A-Za-z.-]+)?$ ]] || { echo "not a version: $version" >&2; exit 1; } +for tool in curl jq dotnet; do + command -v "$tool" >/dev/null || { echo "missing required tool: $tool" >&2; exit 1; } +done + +if [[ ${#ids[@]} -eq 0 ]]; then + while IFS= read -r id; do ids+=("$id"); done < <( + curl -fsS "$SEARCH?q=MaxRev.Gdal&prerelease=true&semVerLevel=2.0.0&take=1000" | + jq -r '.data[].id | select(startswith("MaxRev.Gdal."))' | sort -u) + [[ ${#ids[@]} -gt 0 ]] || { echo "no MaxRev.Gdal.* packages found on nuget.org" >&2; exit 1; } +fi + +# Keep only IDs that have this exact version (the flat container lists unlisted versions too). +targets=() +for id in "${ids[@]}"; do + lower=$(tr '[:upper:]' '[:lower:]' <<<"$id") + if curl -fsS "$FLAT/$lower/index.json" 2>/dev/null | jq -e --arg v "$version" '.versions | index($v)' >/dev/null; then + targets+=("$id") + fi +done + +if [[ ${#targets[@]} -eq 0 ]]; then + echo "no packages found at version $version" + exit 0 +fi + +echo "packages at $version:" +printf ' %s\n' "${targets[@]}" + +if [[ $apply -eq 0 ]]; then + echo "dry run: re-run with --yes to unlist them" + exit 0 +fi + +[[ -n "${NUGET_API_KEY:-}" ]] || { echo "NUGET_API_KEY is not set" >&2; exit 1; } + +failed=0 +for id in "${targets[@]}"; do + if dotnet nuget delete "$id" "$version" -s "$SOURCE" -k "$NUGET_API_KEY" --non-interactive; then + echo "unlisted $id $version" + else + echo "FAILED to unlist $id $version" >&2 + failed=1 + fi +done +exit $failed From 33cd7b94b4f5e917d0ecaad70d875d239a885245 Mon Sep 17 00:00:00 2001 From: "Camille-Maxime Thea (MaxRev)" Date: Fri, 25 Sep 2026 11:18:05 +0300 Subject: [PATCH 3/4] fix(ci): re-download canonical Core after bundle artifacts packages-osx-final (and the raw osx/win artifacts) contain a macOS-built copy of MaxRev.Gdal.Core under the same filename. With merge-multiple the last download wins, so the Core published to nuget.org could silently become the macOS build instead of the canonical Linux x64 one from packages-core. Re-download packages-core as the final artifact step to guarantee it overwrites any duplicate. --- .github/workflows/main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f1f8a3b3..ea5457c79 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -168,6 +168,15 @@ jobs: path: nuget merge-multiple: true + # Re-download the canonical Linux-built Core last so it overwrites the + # copy that osx/publish-makefile (and win/) also pack under the same + # filename into their bundle artifacts. + - uses: actions/download-artifact@v6.0.0 + with: + name: packages-core + path: nuget + merge-multiple: true + - name: Remove old formats run: | rm -rf ${{ github.workspace }}/tests/gdal-formats From f00bcf6b5b722a1299fccac2f24d96f4c6250383 Mon Sep 17 00:00:00 2001 From: "Camille-Maxime Thea (MaxRev)" Date: Fri, 25 Sep 2026 22:26:50 +0300 Subject: [PATCH 4/4] chore: drop unlist script from this change --- shared/scripts/unlist-nuget-version.sh | 85 -------------------------- 1 file changed, 85 deletions(-) delete mode 100755 shared/scripts/unlist-nuget-version.sh diff --git a/shared/scripts/unlist-nuget-version.sh b/shared/scripts/unlist-nuget-version.sh deleted file mode 100755 index 673d3c5f0..000000000 --- a/shared/scripts/unlist-nuget-version.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env bash -# Unlist every MaxRev.Gdal.* package published at a given version on nuget.org. -# -# nuget.org never deletes packages: `dotnet nuget delete` unlists them, so they -# disappear from search and floating-version restores, but existing lock files -# that pin the exact version keep restoring. -# -# Usage: -# NUGET_API_KEY=... shared/scripts/unlist-nuget-version.sh 3.13.3.573 # dry run -# NUGET_API_KEY=... shared/scripts/unlist-nuget-version.sh 3.13.3.573 --yes # unlist -# shared/scripts/unlist-nuget-version.sh 3.13.3.573 --yes MaxRev.Gdal.Core # only these IDs -# -# Without explicit package IDs, the script searches nuget.org for MaxRev.Gdal.* -# packages and keeps the ones that actually have the requested version. - -set -euo pipefail - -SOURCE="https://api.nuget.org/v3/index.json" -SEARCH="https://azuresearch-usnc.nuget.org/query" -FLAT="https://api.nuget.org/v3-flatcontainer" - -usage() { - sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' - exit "${1:-1}" -} - -version="" -apply=0 -ids=() -for arg in "$@"; do - case "$arg" in - -h|--help) usage 0 ;; - -y|--yes) apply=1 ;; - -*) echo "unknown option: $arg" >&2; usage ;; - *) if [[ -z "$version" ]]; then version="$arg"; else ids+=("$arg"); fi ;; - esac -done - -[[ -n "$version" ]] || usage -[[ "$version" =~ ^[0-9]+(\.[0-9]+){2,3}(-[0-9A-Za-z.-]+)?$ ]] || { echo "not a version: $version" >&2; exit 1; } -for tool in curl jq dotnet; do - command -v "$tool" >/dev/null || { echo "missing required tool: $tool" >&2; exit 1; } -done - -if [[ ${#ids[@]} -eq 0 ]]; then - while IFS= read -r id; do ids+=("$id"); done < <( - curl -fsS "$SEARCH?q=MaxRev.Gdal&prerelease=true&semVerLevel=2.0.0&take=1000" | - jq -r '.data[].id | select(startswith("MaxRev.Gdal."))' | sort -u) - [[ ${#ids[@]} -gt 0 ]] || { echo "no MaxRev.Gdal.* packages found on nuget.org" >&2; exit 1; } -fi - -# Keep only IDs that have this exact version (the flat container lists unlisted versions too). -targets=() -for id in "${ids[@]}"; do - lower=$(tr '[:upper:]' '[:lower:]' <<<"$id") - if curl -fsS "$FLAT/$lower/index.json" 2>/dev/null | jq -e --arg v "$version" '.versions | index($v)' >/dev/null; then - targets+=("$id") - fi -done - -if [[ ${#targets[@]} -eq 0 ]]; then - echo "no packages found at version $version" - exit 0 -fi - -echo "packages at $version:" -printf ' %s\n' "${targets[@]}" - -if [[ $apply -eq 0 ]]; then - echo "dry run: re-run with --yes to unlist them" - exit 0 -fi - -[[ -n "${NUGET_API_KEY:-}" ]] || { echo "NUGET_API_KEY is not set" >&2; exit 1; } - -failed=0 -for id in "${targets[@]}"; do - if dotnet nuget delete "$id" "$version" -s "$SOURCE" -k "$NUGET_API_KEY" --non-interactive; then - echo "unlisted $id $version" - else - echo "FAILED to unlist $id $version" >&2 - failed=1 - fi -done -exit $failed