From 787818c434358ac6fbc548d4929cb30320dd6dc4 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Sat, 1 Aug 2026 20:36:46 -0500 Subject: [PATCH 1/6] remove appveyor --- .github/CODEOWNERS | 1 - .../post_performance_test_comment.yml | 4 +- .github/workflows/publish_nuget.yml | 83 ++++++++++++++ ...run_all_tests_and_upload_code_coverage.yml | 2 +- ..._windows.yml => run_performance_tests.yml} | 63 ++++++----- CLAUDE.md | 12 +- README.md | 2 +- appveyor.yml | 42 ------- .../PhoneNumbers.Extensions.Test.csproj | 4 +- .../PhoneNumbers.Extensions.csproj | 4 +- .../PhoneNumbers.Test.csproj | 4 +- csharp/PhoneNumbers.sln.DotSettings | 1 - csharp/PhoneNumbers/PhoneNumbers.csproj | 6 +- lib/update.sh | 105 ------------------ 14 files changed, 138 insertions(+), 195 deletions(-) create mode 100644 .github/workflows/publish_nuget.yml rename .github/workflows/{run_performance_tests_windows.yml => run_performance_tests.yml} (68%) delete mode 100644 appveyor.yml delete mode 100755 lib/update.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3dd0603a8..7a9bab264 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,4 +9,3 @@ # Security-sensitive automation and supply-chain config. /.github/ @twcclegg /.github/workflows/ @twcclegg -/appveyor.yml @twcclegg diff --git a/.github/workflows/post_performance_test_comment.yml b/.github/workflows/post_performance_test_comment.yml index effbbbc0e..87fd3fbc4 100644 --- a/.github/workflows/post_performance_test_comment.yml +++ b/.github/workflows/post_performance_test_comment.yml @@ -6,7 +6,7 @@ name: post_performance_test_comment on: workflow_run: - workflows: [ "run_performance_tests_windows" ] + workflows: [ "run_performance_tests" ] types: [ completed ] permissions: @@ -78,7 +78,7 @@ jobs: '', '## 📊 Benchmark Results', '', - `> Commit: \`${sha}\` · [Full run](${runUrl}) · Windows \`windows-latest\``, + `> Commit: \`${sha}\` · [Full run](${runUrl}) · Linux \`ubuntu-24.04-arm\``, '', '
', 'PR branch', diff --git a/.github/workflows/publish_nuget.yml b/.github/workflows/publish_nuget.yml new file mode 100644 index 000000000..192bd3e92 --- /dev/null +++ b/.github/workflows/publish_nuget.yml @@ -0,0 +1,83 @@ +name: publish_nuget + +# Packs and publishes both packages. Fired by the vX.Y.Z tag that +# create_new_release_on_new_metadata_update.yml pushes; the tag is the version. +# Tests are not re-run here - a tag only points at a commit already on main. + +on: + push: + tags: [ 'v*' ] + # Lets a failed publish be retried by dispatching against the tag. + workflow_dispatch: + +# Never cancel a publish in flight; --skip-duplicate makes a repeat run harmless. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + publish_nuget: + runs-on: ubuntu-24.04-arm + timeout-minutes: 20 + permissions: + contents: read + # NuGet/login exchanges this workflow's OIDC token for a short-lived + # nuget.org api key, so no push secret is stored in the repo. + id-token: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Setup .NET + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + with: + dotnet-version: 10.x + + # Fail closed on a non-release ref rather than publishing the csproj placeholder. + - name: Resolve package version from tag + run: | + tag="${GITHUB_REF_NAME}" + if [[ ! "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] + then + echo "error: expected a vX.Y.Z tag, got '${tag}'" >&2 + exit 1 + fi + echo "VERSION=${tag#v}" >> "${GITHUB_ENV}" + echo "publishing version ${tag#v}" >> "${GITHUB_STEP_SUMMARY}" + + - name: Restore dependencies + run: dotnet restore csharp --source https://api.nuget.org/v3/index.json + + - name: Pack + run: | + dotnet pack csharp/PhoneNumbers -c Release --no-restore \ + -p:VersionPrefix="${VERSION}" -o artifacts + dotnet pack csharp/PhoneNumbers.Extensions -c Release --no-restore \ + -p:VersionPrefix="${VERSION}" -o artifacts + + - name: Upload packages + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: nuget-packages + path: artifacts/*.nupkg + if-no-files-found: error + + - name: NuGet login + id: nuget_login + uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0 + with: + user: twcclegg + + # --skip-duplicate so a re-run after a partial failure pushes only what is missing. + - name: Push to nuget.org + env: + NUGET_API_KEY: ${{ steps.nuget_login.outputs.NUGET_API_KEY }} + run: | + dotnet nuget push "artifacts/*.nupkg" \ + --api-key "${NUGET_API_KEY}" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate diff --git a/.github/workflows/run_all_tests_and_upload_code_coverage.yml b/.github/workflows/run_all_tests_and_upload_code_coverage.yml index 2d5b702a7..22674373a 100644 --- a/.github/workflows/run_all_tests_and_upload_code_coverage.yml +++ b/.github/workflows/run_all_tests_and_upload_code_coverage.yml @@ -15,7 +15,7 @@ permissions: jobs: run_all_tests_and_upload_code_coverage: - runs-on: windows-latest + runs-on: ubuntu-24.04-arm timeout-minutes: 20 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/.github/workflows/run_performance_tests_windows.yml b/.github/workflows/run_performance_tests.yml similarity index 68% rename from .github/workflows/run_performance_tests_windows.yml rename to .github/workflows/run_performance_tests.yml index 58b9e13a9..3e0257b3c 100644 --- a/.github/workflows/run_performance_tests_windows.yml +++ b/.github/workflows/run_performance_tests.yml @@ -1,4 +1,4 @@ -name: run_performance_tests_windows +name: run_performance_tests on: pull_request: @@ -22,8 +22,8 @@ permissions: contents: read jobs: - run_performance_tests_windows: - runs-on: windows-latest + run_performance_tests: + runs-on: ubuntu-24.04-arm timeout-minutes: 30 steps: - name: Checkout @@ -47,9 +47,11 @@ jobs: # actions/cache/restore requires `key`, but we never expect an exact hit: # save keys carry a unique - suffix, so this is a deliberate near-miss # and the prefix restore-key below always supplies the most recently created baseline. - key: benchmark-main- + # The prefix names the runner: timings are only comparable against a baseline + # measured on the same hardware, so changing runner must change the key. + key: benchmark-main-linux-arm64- restore-keys: | - benchmark-main- + benchmark-main-linux-arm64- - name: Run benchmarks run: dotnet run -c Release --framework net10.0 -- --filter "*" @@ -58,14 +60,14 @@ jobs: # On push to main (or the keep-warm schedule), publish this run's results as the # new baseline for future PRs. - name: Stage main baseline - if: github.event_name != 'pull_request' - shell: pwsh run: | - New-Item -ItemType Directory -Force -Path "main-baseline" | Out-Null - $resultsDir = "csharp/PhoneNumbers.PerformanceTest/BenchmarkDotNet.Artifacts/results" - if (Test-Path $resultsDir) { - Copy-Item "$resultsDir/*-report-github.md" "main-baseline/" -ErrorAction SilentlyContinue - } + mkdir -p main-baseline + results_dir="csharp/PhoneNumbers.PerformanceTest/BenchmarkDotNet.Artifacts/results" + if [ -d "${results_dir}" ] + then + cp "${results_dir}"/*-report-github.md main-baseline/ 2> /dev/null || true + fi + if: github.event_name != 'pull_request' - name: Save main benchmark baseline if: github.event_name != 'pull_request' @@ -76,33 +78,34 @@ jobs: # cannot overwrite an existing key, and the keep-warm cron never restores the # baseline, so reusing benchmark-main- would let the timer lapse when main # is unchanged. The PR prefix restore-key picks the most recently created entry. - key: benchmark-main-${{ github.sha }}-${{ github.run_id }} + key: benchmark-main-linux-arm64-${{ github.sha }}-${{ github.run_id }} # On a PR, bundle the freshly-measured branch results with the restored main # baseline so the follow-up workflow can post a side-by-side comparison. - name: Stage benchmark artifact if: github.event_name == 'pull_request' - shell: pwsh + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + # Head SHA, not the merge SHA in GITHUB_SHA. + HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | - $stage = "benchmark-artifact" - New-Item -ItemType Directory -Force -Path "$stage/branch-results" | Out-Null - New-Item -ItemType Directory -Force -Path "$stage/main-results" | Out-Null + stage="benchmark-artifact" + mkdir -p "${stage}/branch-results" "${stage}/main-results" - $prDir = "csharp/PhoneNumbers.PerformanceTest/BenchmarkDotNet.Artifacts/results" - if (Test-Path $prDir) { - Copy-Item "$prDir/*-report-github.md" "$stage/branch-results/" -ErrorAction SilentlyContinue - } - if (Test-Path "main-baseline") { - Copy-Item "main-baseline/*-report-github.md" "$stage/main-results/" -ErrorAction SilentlyContinue - } + pr_dir="csharp/PhoneNumbers.PerformanceTest/BenchmarkDotNet.Artifacts/results" + if [ -d "${pr_dir}" ] + then + cp "${pr_dir}"/*-report-github.md "${stage}/branch-results/" 2> /dev/null || true + fi + if [ -d "main-baseline" ] + then + cp main-baseline/*-report-github.md "${stage}/main-results/" 2> /dev/null || true + fi - # PR metadata for the follow-up workflow (use head SHA, not the merge SHA in GITHUB_SHA) - @{ - pr_number = ${{ github.event.pull_request.number }} - head_sha = "${{ github.event.pull_request.head.sha }}" - } | ConvertTo-Json | Set-Content "$stage/pr-info.json" + jq -n --argjson pr_number "${PR_NUMBER}" --arg head_sha "${HEAD_SHA}" \ + '{pr_number: $pr_number, head_sha: $head_sha}' > "${stage}/pr-info.json" - Get-ChildItem -Recurse $stage | Select-Object FullName + find "${stage}" -type f - name: Upload benchmark artifact if: github.event_name == 'pull_request' diff --git a/CLAUDE.md b/CLAUDE.md index 8ff3981ed..0d39c57d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ The library tracks upstream metadata releases (~every two weeks) via the `create - `csharp/PhoneNumbers.PerformanceTest/` — BenchmarkDotNet harness. - `csharp/PhoneNumbers.MetadataBuilder/` — build-time tool that converts XML metadata + geocoding/timezone text files into per-region binary files. Source-links a small set of files from `PhoneNumbers/` so it doesn't depend on (and can't cycle with) the main library at build time. - `resources/` — XML metadata (`PhoneNumberMetadata.xml`, `ShortNumberMetadata.xml`, `PhoneNumberAlternateFormats.xml`, `PhoneNumberMetadataForTesting.xml`), plus `geocoding/`, `carrier/`, `timezones/`. **These are copied verbatim from upstream** — do not hand-edit. The library no longer reads them at runtime: the build pipeline emits binary equivalents under `obj/metadata/`, `obj/geocoding/`, `obj/timezones/` which are embedded into the published assembly. -- `lib/update.sh` + `lib/DumpLocale.java` — automation that pulls upstream resources and regenerates `csharp/PhoneNumbers/LocaleData.cs`. +- `lib/github-actions-metadata-update.sh` + `lib/DumpLocale.java` — automation that pulls upstream resources and regenerates `csharp/PhoneNumbers/LocaleData.cs`. ## Common commands @@ -47,7 +47,7 @@ dotnet test csharp/PhoneNumbers.Test --filter "FullyQualifiedName~TestPhoneNumbe dotnet test csharp/PhoneNumbers.Test --filter "FullyQualifiedName~TestPhoneNumberUtil" # whole class ``` -Pack the NuGet packages (mirrors AppVeyor): +Pack the NuGet packages (mirrors `publish_nuget.yml`; the workflow adds `-p:VersionPrefix=`): ```bash dotnet pack -c Release csharp/PhoneNumbers @@ -65,7 +65,7 @@ dotnet run -c Release --framework net10.0 -- --filter "*PhoneNumberWorkflowBench ## Architecture notes that span files - **Singleton + metadata loading.** `PhoneNumberUtil.GetInstance()` is the entry point. Region/country metadata is lazily loaded via `MetadataSource` + `IMetadataLoader` (default impl: `EmbeddedResourceMetadataLoader`, which reads per-region binary files generated at build time by `PhoneNumbers.MetadataBuilder` and embedded under `PhoneNumbers.metadata._`). The XML parser (`BuildMetadataFromXml.cs`) is still used at build time and by the legacy `PhoneNumberUtil(Stream)` constructor for consumers loading custom XML, but is no longer on the default load path. -- **Generated files.** `LocaleData.cs` (~48k lines) and `CountryCodeToRegionCodeMap.cs` are generated. `LocaleData.cs` is regenerated by `javac DumpLocale.java && java DumpLocale > csharp/PhoneNumbers/LocaleData.cs` (see `lib/update.sh`). Don't hand-edit either. +- **Generated files.** `LocaleData.cs` (~48k lines) and `CountryCodeToRegionCodeMap.cs` are generated. `LocaleData.cs` is regenerated by `javac DumpLocale.java && java DumpLocale > csharp/PhoneNumbers/LocaleData.cs` (see `lib/github-actions-metadata-update.sh`). Don't hand-edit either. - **Partial-class TFM split.** `PhoneNumberUtil.cs` is a `partial class` with framework-specific halves: `PhoneNumberUtil.net.cs` (modern .NET) and `PhoneNumberUtil.netstandard.cs` (netstandard2.0 fallbacks). When adding APIs that use newer BCL features, put the polyfill on the netstandard side. - **Subsystems and their entry types** (each ports a Java counterpart of the same name): - `PhoneNumberUtil` — parse / format / validate. @@ -85,5 +85,7 @@ dotnet run -c Release --framework net10.0 -- --filter "*PhoneNumberWorkflowBench ## CI and release -- PRs trigger `build_and_run_unit_tests_linux.yml` (Ubuntu, .NET 9, net9.0 target only). AppVeyor (`appveyor.yml`) runs the full multi-TFM matrix on Windows and is the gate for NuGet publishes. -- Releases are tag-driven on AppVeyor; metadata-bump releases are created automatically by `create_new_release_on_new_metadata_update.yml`. +- CI is GitHub Actions only, on `ubuntu-24.04-arm`. There are no Windows runners. +- PRs trigger `build_and_run_unit_tests_linux.yml` (net10.0 only) and `run_all_tests_and_upload_code_coverage.yml` (whole solution, every TFM, uploads to Codecov). +- The test projects' `netframework4.8` target is conditioned on `'$(OS)' == 'Windows_NT'` — it only builds for developers on Windows, and no CI job covers it. +- Releases are tag-driven: a `vX.Y.Z` tag fires `publish_nuget.yml`, which packs both projects at the tag's version and pushes to nuget.org via trusted publishing (GitHub OIDC, `NuGet/login`) — there is no API key secret. Metadata-bump tags are created by `create_new_release_on_new_metadata_update.yml`. diff --git a/README.md b/README.md index a222494a4..651a5f7cc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build status](https://ci.appveyor.com/api/projects/status/76abbk0qveot0mbo/branch/main?svg=true)](https://ci.appveyor.com/project/twcclegg/libphonenumber-csharp/branch/main) +[![Build status](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/run_all_tests_and_upload_code_coverage.yml/badge.svg?branch=main)](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/run_all_tests_and_upload_code_coverage.yml) [![codecov](https://codecov.io/gh/twcclegg/libphonenumber-csharp/branch/main/graph/badge.svg)](https://codecov.io/gh/twcclegg/libphonenumber-csharp) [![NuGet](https://img.shields.io/nuget/dt/libphonenumber-csharp.svg)](https://www.nuget.org/packages/libphonenumber-csharp/) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/twcclegg/libphonenumber-csharp/badge)](https://scorecard.dev/viewer/?uri=github.com/twcclegg/libphonenumber-csharp) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index a7b9504e1..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,42 +0,0 @@ -init: - - ps: >- - if ($env:APPVEYOR_SCHEDULED_BUILD -eq "true") - { - Exit - } - if ($env:APPVEYOR_REPO_TAG -eq "true") - { - Update-AppveyorBuild -Version "$($env:APPVEYOR_REPO_TAG_NAME.substring(1))" - } - else - { - Update-AppveyorBuild -Version "$(((nuget list libphonenumber-csharp -Source https://api.nuget.org/v3/index.json | findstr "libphonenumber-csharp[^.-]") -split ' ')[1])-$($env:APPVEYOR_REPO_COMMIT.substring(0,7))" - } -image: Visual Studio 2026 -configuration: Release -environment: - COVERALLS_REPO_TOKEN: - secure: vncyomjYijKQKcbLpvs7DFLyNWPjW7tk9KNF/bTV5J/RxOnFydZfsJSsp7SMlvUg -pull_requests: - do_not_increment_build_number: true -branches: - only: - - main -before_build: - - dotnet restore csharp -s https://api.nuget.org/v3/index.json -build_script: - - dotnet pack -c Release csharp\PhoneNumbers - - dotnet pack -c Release csharp\PhoneNumbers.Extensions -test_script: - - dotnet test csharp/PhoneNumbers.sln --configuration Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage -artifacts: - - path: csharp\PhoneNumbers\bin\Release\libphonenumber-csharp.* - - path: csharp\PhoneNumbers.Extensions\bin\Release\libphonenumber-csharp.extensions.* -nuget: - account_feed: true -deploy: - - provider: NuGet - on: - appveyor_repo_tag: true - api_key: - secure: /wzf+xtEThx1FALIEURrb7KXa0425dxlSgTtFUb+iJuMmme4RCtp9AN6DOFgcBvp diff --git a/csharp/PhoneNumbers.Extensions.Test/PhoneNumbers.Extensions.Test.csproj b/csharp/PhoneNumbers.Extensions.Test/PhoneNumbers.Extensions.Test.csproj index 002de7d91..45bfbda21 100644 --- a/csharp/PhoneNumbers.Extensions.Test/PhoneNumbers.Extensions.Test.csproj +++ b/csharp/PhoneNumbers.Extensions.Test/PhoneNumbers.Extensions.Test.csproj @@ -1,7 +1,9 @@ - netframework4.8;net8.0;net9.0;net10.0 + + netframework4.8;net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0 $(NoWarn);1591;CA1014;CA1062;CA1707;CA1812;CA1852 true true diff --git a/csharp/PhoneNumbers.Extensions/PhoneNumbers.Extensions.csproj b/csharp/PhoneNumbers.Extensions/PhoneNumbers.Extensions.csproj index 611349b32..bd5eefd73 100644 --- a/csharp/PhoneNumbers.Extensions/PhoneNumbers.Extensions.csproj +++ b/csharp/PhoneNumbers.Extensions/PhoneNumbers.Extensions.csproj @@ -5,7 +5,8 @@ libphonenumber-csharp.extensions PhoneNumbers.Extensions libphonenumber-csharp.extensions - $(APPVEYOR_BUILD_VERSION) + + 0.0.0 Thomas Clegg netstandard2.0;net8.0;net9.0;net10.0 preview @@ -17,7 +18,6 @@ https://github.com/twcclegg/libphonenumber-csharp git main - $APPVEYOR_REPO_COMMIT embedded true $(NoWarn);1591;CA1014;CA1031;CA1062;CA1707 diff --git a/csharp/PhoneNumbers.Test/PhoneNumbers.Test.csproj b/csharp/PhoneNumbers.Test/PhoneNumbers.Test.csproj index 532b91186..c03497092 100644 --- a/csharp/PhoneNumbers.Test/PhoneNumbers.Test.csproj +++ b/csharp/PhoneNumbers.Test/PhoneNumbers.Test.csproj @@ -1,7 +1,9 @@ - netframework4.8;net8.0;net9.0;net10.0 + + netframework4.8;net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0 + 0.0.0 Patrick Mézard;Thomas Clegg;Jarrod Alexander;Google;libphonenumber contributors netstandard2.0;net8.0;net9.0;net10.0 preview @@ -17,7 +18,6 @@ https://github.com/twcclegg/libphonenumber-csharp git main - $APPVEYOR_REPO_COMMIT README.md embedded true @@ -26,7 +26,7 @@ + all From 5330e20fd02b718329f3cc688c2b17021f47fab7 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Sun, 2 Aug 2026 14:06:58 -0500 Subject: [PATCH 3/6] fix: dispatch publish_nuget after creating the release tag --- .../create_new_release_on_new_metadata_update.yml | 4 +++- lib/github-actions-metadata-update.sh | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create_new_release_on_new_metadata_update.yml b/.github/workflows/create_new_release_on_new_metadata_update.yml index d8ff6caf7..e762d6696 100644 --- a/.github/workflows/create_new_release_on_new_metadata_update.yml +++ b/.github/workflows/create_new_release_on_new_metadata_update.yml @@ -25,9 +25,11 @@ concurrency: cancel-in-progress: false # Elevated above the read-only default because the update script pushes a commit -# and creates a GitHub release via GITHUB_TOKEN. +# and creates a GitHub release via GITHUB_TOKEN. actions: write lets it dispatch +# publish_nuget.yml, which the release's own push event cannot trigger. permissions: contents: write + actions: write jobs: create_new_release_on_new_metadata_update: diff --git a/lib/github-actions-metadata-update.sh b/lib/github-actions-metadata-update.sh index 9ea8579cc..3434027ca 100644 --- a/lib/github-actions-metadata-update.sh +++ b/lib/github-actions-metadata-update.sh @@ -54,6 +54,8 @@ Environment variables: UPSTREAM_TAG it replays any historical release pair. EXPECTED_MAJOR_VERSION Upstream major version this port tracks (default 9). TEST_TARGET_FRAMEWORK Framework used for the pre-commit test run (default net10.0). + PUBLISH_WORKFLOW Workflow dispatched to publish the release to nuget.org + (default publish_nuget.yml). Examples: # what would the nightly run do right now? @@ -105,6 +107,7 @@ UPSTREAM_TAG="${UPSTREAM_TAG:-}" DEPLOYED_VERSION="${DEPLOYED_VERSION:-}" EXPECTED_MAJOR_VERSION="${EXPECTED_MAJOR_VERSION:-9}" TEST_TARGET_FRAMEWORK="${TEST_TARGET_FRAMEWORK:-net10.0}" +PUBLISH_WORKFLOW="${PUBLISH_WORKFLOW:-publish_nuget.yml}" while [ $# -gt 0 ] do @@ -224,6 +227,13 @@ createRelease() { | ghApi -X POST --data @- "https://api.github.com/repos/$1/releases" > /dev/null } +# github suppresses push events from GITHUB_TOKEN, so ask for the publish run directly. +dispatchPublish() { + jq -n --arg ref "$2" '{ref: $ref}' \ + | ghApi -X POST --data @- \ + "https://api.github.com/repos/$1/actions/workflows/${PUBLISH_WORKFLOW}/dispatches" > /dev/null +} + if [ -n "${UPSTREAM_TAG}" ] then UPSTREAM_GITHUB_RELEASE_TAG="${UPSTREAM_TAG}" @@ -384,6 +394,7 @@ then log " - run dotnet restore, build and test (${TEST_TARGET_FRAMEWORK})" log " - commit \"feat: automatic upgrade to ${UPSTREAM_GITHUB_RELEASE_TAG}\" and push to main" log " - create release ${UPSTREAM_GITHUB_RELEASE_TAG} in ${GITHUB_REPOSITORY}" + log " - dispatch ${PUBLISH_WORKFLOW} against ${UPSTREAM_GITHUB_RELEASE_TAG} to publish to nuget" exit 0 fi @@ -426,3 +437,6 @@ git push createRelease "${GITHUB_REPOSITORY}" "${UPSTREAM_GITHUB_RELEASE_TAG}" log "created release ${UPSTREAM_GITHUB_RELEASE_TAG}" + +dispatchPublish "${GITHUB_REPOSITORY}" "${UPSTREAM_GITHUB_RELEASE_TAG}" +log "dispatched ${PUBLISH_WORKFLOW} for ${UPSTREAM_GITHUB_RELEASE_TAG}" From a23a591f2c0060bcf038e885081fa1bfbb74e15c Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Sun, 2 Aug 2026 15:06:08 -0500 Subject: [PATCH 4/6] feat: link nuget packages and upstream release from the github release --- lib/github-actions-metadata-update.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/github-actions-metadata-update.sh b/lib/github-actions-metadata-update.sh index 3434027ca..4dfcda674 100644 --- a/lib/github-actions-metadata-update.sh +++ b/lib/github-actions-metadata-update.sh @@ -44,6 +44,10 @@ Environment variables: (default google/libphonenumber). NUGET_PACKAGE_ID Package whose published version is compared against the upstream release (default libphonenumber-csharp). + NUGET_EXTENSIONS_PACKAGE_ID + Helper package of C#-idiomatic additions beyond the java + port, linked from the release notes + (default .extensions). SKIP_JAVA_CHECK Same as --skip-java-check (true/1/yes). SKIP_PROTO_CHECK Same as --skip-proto-check (true/1/yes). DRY_RUN Same as --dry-run (true/1/yes). @@ -160,6 +164,7 @@ done UPSTREAM_REPOSITORY="${UPSTREAM_REPOSITORY:-google/libphonenumber}" NUGET_PACKAGE_ID="${NUGET_PACKAGE_ID:-libphonenumber-csharp}" +NUGET_EXTENSIONS_PACKAGE_ID="${NUGET_EXTENSIONS_PACKAGE_ID:-${NUGET_PACKAGE_ID}.extensions}" GITHUB_ACTION_WORKING_DIRECTORY=$(pwd) # Which repository this run targets. Actions sets GITHUB_REPOSITORY for us; when @@ -222,8 +227,21 @@ getReleaseDelta() { ghApi "https://api.github.com/repos/$1/compare/$2...$3" } +# generate_release_notes appends the commit/PR changelog below the links. createRelease() { - jq -n --arg tag "$2" '{tag_name: $tag, name: $tag}' \ + jq -n --arg tag "$2" --arg version "${2#v}" \ + --arg pkg "${NUGET_PACKAGE_ID}" --arg ext "${NUGET_EXTENSIONS_PACKAGE_ID}" \ + --arg upstream "${UPSTREAM_REPOSITORY}" ' + { + tag_name: $tag, + name: $tag, + generate_release_notes: true, + body: ( + "[\($pkg) \($version)](https://www.nuget.org/packages/\($pkg)/\($version))" + + " · [\($ext) \($version)](https://www.nuget.org/packages/\($ext)/\($version))" + + " · [upstream \($tag)](https://github.com/\($upstream)/releases/tag/\($tag))" + ) + }' \ | ghApi -X POST --data @- "https://api.github.com/repos/$1/releases" > /dev/null } From 3c7c76df319b336175ad40afc0db0e2e5a880c68 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Mon, 3 Aug 2026 09:38:57 -0500 Subject: [PATCH 5/6] docs: update renamed performance test workflow link --- csharp/PhoneNumbers.PerformanceTest/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/PhoneNumbers.PerformanceTest/README.md b/csharp/PhoneNumbers.PerformanceTest/README.md index 60bafde2d..2f12610ab 100644 --- a/csharp/PhoneNumbers.PerformanceTest/README.md +++ b/csharp/PhoneNumbers.PerformanceTest/README.md @@ -1,6 +1,6 @@ ## Performance testing history -See [Github Actions](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/run_performance_tests_windows.yml) for a history of previous runs, in the logs, you can see the performance results for each method being tested +See [Github Actions](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/run_performance_tests.yml) for a history of previous runs, in the logs, you can see the performance results for each method being tested ## Running locally From 55f0744a063b647983672cf69cd07c10310276f1 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Mon, 3 Aug 2026 09:48:34 -0500 Subject: [PATCH 6/6] test: verify release packaging in ci --- .../build_and_run_unit_tests_linux.yml | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/build_and_run_unit_tests_linux.yml b/.github/workflows/build_and_run_unit_tests_linux.yml index c52ddf24d..e94043dff 100644 --- a/.github/workflows/build_and_run_unit_tests_linux.yml +++ b/.github/workflows/build_and_run_unit_tests_linux.yml @@ -33,3 +33,34 @@ jobs: - name: Test solution targeting dotnet10.0 only run: dotnet test --no-build --verbosity normal -p:TargetFrameworks=net10.0 working-directory: ./csharp + + # Mirrors publish_nuget.yml so a broken pack fails here instead of during a release. + # The dependency assertion matters most: the extensions package must pin the exact + # same libphonenumber-csharp version, which only happens if VersionPrefix reaches + # the ProjectReference. + - name: Verify release packaging + run: | + version=9.9.9 + dotnet pack csharp/PhoneNumbers -c Release --no-restore \ + -p:VersionPrefix="${version}" -o packtest + dotnet pack csharp/PhoneNumbers.Extensions -c Release --no-restore \ + -p:VersionPrefix="${version}" -o packtest + ls -l packtest + + for id in libphonenumber-csharp libphonenumber-csharp.extensions + do + if [ ! -f "packtest/${id}.${version}.nupkg" ] + then + echo "error: expected packtest/${id}.${version}.nupkg" >&2 + exit 1 + fi + done + + nuspec=$(unzip -p "packtest/libphonenumber-csharp.extensions.${version}.nupkg" '*.nuspec') + if ! grep -qE "id=\"libphonenumber-csharp\"[^>]*version=\"${version}\"" <<< "${nuspec}" + then + echo "error: extensions package does not depend on libphonenumber-csharp ${version}" >&2 + printf '%s\n' "${nuspec}" >&2 + exit 1 + fi + echo "packaging ok: both packages at ${version}, extensions dependency pinned"