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 fabdcb6bd..4b0a7801c 100644 --- a/.github/workflows/create_new_release_on_new_metadata_update.yml +++ b/.github/workflows/create_new_release_on_new_metadata_update.yml @@ -6,6 +6,19 @@ on: schedule: - cron: '0 8 * * *' workflow_dispatch: + inputs: + skip_java_check: + description: 'Continue even when the upstream release changes .java files (only tick this once those changes have been reviewed as not needing a port)' + type: boolean + default: false + skip_proto_check: + description: 'Continue even when the upstream release changes .proto files' + type: boolean + default: false + dry_run: + description: 'Report what the run would do and stop before changing anything - nothing is committed, pushed or released' + type: boolean + default: false concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,14 +33,29 @@ jobs: create_new_release_on_new_metadata_update: runs-on: ubuntu-latest steps: + # Credentials are persisted on purpose: the update script pushes through this checkout. - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup .NET uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: - dotnet-version: 9.x + dotnet-version: 10.x + # DumpLocale.java regenerates csharp/PhoneNumbers/LocaleData.cs from java.util.Locale, + # so the jdk is an input to the generated file rather than an incidental tool. Pinned + # to temurin 17 to match the header of the committed LocaleData.cs - changing it would + # rewrite the locale data from a different CLDR version. + - name: Setup Java + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + with: + distribution: temurin + java-version: '17' - name: Get new metadata information and create new GitHub release timeout-minutes: 30 + # The token is read from GITHUB_TOKEN below rather than passed as an + # argument, so it never appears on the process command line. run: | - bash lib/github-actions-metadata-update.sh ${GITHUB_TOKEN} + bash lib/github-actions-metadata-update.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SKIP_JAVA_CHECK: ${{ inputs.skip_java_check || false }} + SKIP_PROTO_CHECK: ${{ inputs.skip_proto_check || false }} + DRY_RUN: ${{ inputs.dry_run || false }} diff --git a/README.md b/README.md index b5e9e68b5..a222494a4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The original Apache License 2.0 was preserved. See [this](csharp/README.md) for details about the port. -Phone number metadata is updated in the Google repo approximately every two weeks. This library is automatically updated by a [scheduled github action](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/create_new_release_on_new_metadata_update.yml) to include the latest metadata, usually within a day. +Phone number metadata is updated in the Google repo approximately every two weeks. This library is automatically updated by a [scheduled github action](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/create_new_release_on_new_metadata_update.yml) to include the latest metadata, usually within a day. See [Metadata updates](#metadata-updates) for how that works and how to run it manually. ## Installation @@ -157,6 +157,49 @@ For more information on metadata usage, please refer to the [main repository faq dotnet test csharp/PhoneNumbers.sln ``` +## Metadata updates + +The [`create_new_release_on_new_metadata_update`](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/create_new_release_on_new_metadata_update.yml) workflow runs daily and drives [`lib/github-actions-metadata-update.sh`](lib/github-actions-metadata-update.sh). When the latest `google/libphonenumber` release is newer than the published NuGet package, it copies the upstream `resources/`, regenerates `csharp/PhoneNumbers/LocaleData.cs`, builds and tests, then commits, pushes and creates a matching GitHub release. + +Before doing any of that it inspects the upstream diff and stops if it contains `.java` or `.proto` files, because changes to the Java sources may need porting by hand and an unattended metadata bump would silently skip them. + +### Skipping the java (or proto) check + +If you have reviewed the upstream diff and the Java changes don't need porting (for example test-only or build-file changes), you can run the update anyway: + +* **From the Actions UI** — open [create_new_release_on_new_metadata_update](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/create_new_release_on_new_metadata_update.yml), click **Run workflow**, and tick **skip_java_check** (and/or **skip_proto_check**). Scheduled runs always leave both unticked. +* **Locally** — pass the flag or set the environment variable: + + ```bash + bash lib/github-actions-metadata-update.sh --skip-java-check "$GITHUB_TOKEN" + # or + SKIP_JAVA_CHECK=true bash lib/github-actions-metadata-update.sh "$GITHUB_TOKEN" + ``` + + `--skip-proto-check` / `SKIP_PROTO_CHECK` work the same way, and `--help` lists every option. + +Skipping a check means the release ships upstream metadata from a version whose Java-side changes were not ported, so read the upstream diff first — the script prints the offending filenames before it stops. + +### Dry runs + +`--dry-run` (or the **dry_run** workflow input) runs every read-only step — version lookups, repository checks, the upstream diff gates, the upstream clone — reports what a real run would do, and stops before the first change to the working tree. Nothing is copied, generated, committed, pushed or released, no token is required, and the clean-`main` requirement is relaxed to a warning so it works from a feature branch: + +```bash +# what would tonight's scheduled run do? +bash lib/github-actions-metadata-update.sh --dry-run +``` + +`UPSTREAM_TAG` and `DEPLOYED_VERSION` override the two version lookups, which lets you replay any historical release pair — useful for seeing how a given release trips the gates: + +```bash +UPSTREAM_TAG=v9.0.33 DEPLOYED_VERSION=9.0.32 \ + bash lib/github-actions-metadata-update.sh --dry-run +``` + +### Running it against a fork + +Nothing about the target repository is hard-coded. The script commits and pushes through whatever checkout it runs in, and takes the repository to release from `GITHUB_REPOSITORY` — set automatically by GitHub Actions, and otherwise derived from the `origin` remote. So a fork releases to itself, and the dry-run summary names the repository it would publish to. `UPSTREAM_REPOSITORY` (default `google/libphonenumber`) and `NUGET_PACKAGE_ID` (default `libphonenumber-csharp`) are overridable the same way. + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/lib/github-actions-metadata-update.sh b/lib/github-actions-metadata-update.sh index 098d78b26..9ea8579cc 100644 --- a/lib/github-actions-metadata-update.sh +++ b/lib/github-actions-metadata-update.sh @@ -1,125 +1,428 @@ #! /bin/bash +# Syncs resources/ + LocaleData.cs from the latest google/libphonenumber release, +# builds and tests, then commits, pushes and creates a GitHub release. +# # Exit on any error, treat unset variables as errors, and fail a pipeline if any -# stage fails. The pipefail matters here: every network read below is `curl | jq` -# (or `curl | grep | sed`), and without it a failed curl would feed empty input to -# the parser and the script would happily proceed with an empty version string — -# potentially cutting a bogus release. Fail closed instead. +# stage fails. The pipefail matters here: every network read below is `curl | jq`, +# and without it a failed curl would feed empty input to the parser and the script +# would happily proceed with an empty version string — potentially cutting a bogus +# release. Fail closed instead. set -euo pipefail -if [ $# -ne 1 ] +# Exit codes +readonly EXIT_USAGE=2 +readonly EXIT_MISSING_PREREQUISITE=3 +readonly EXIT_NEEDS_ATTENTION=4 + +usage() { + cat <<'EOF' +Usage: github-actions-metadata-update.sh [options] [github-token] + +The GitHub token may be supplied as the positional argument or via the +GITHUB_TOKEN environment variable. It is required unless --dry-run is used. + +Options: + --skip-java-check Continue even when the upstream diff contains .java files. + Only use this when those changes have been reviewed and do + not need porting to the C# library. + --skip-proto-check Continue even when the upstream diff contains .proto files. + --dry-run Run every read-only step - version lookups, repository + checks, the upstream diff gates and the upstream clone - + report what would happen, then stop before the first + change to the working tree. Nothing is copied, generated, + committed, pushed or released. On a branch other than a + clean main the usual hard checks become warnings, so a dry + run works from a feature branch. + -h, --help Show this help and exit. + +Environment variables: + GITHUB_TOKEN GitHub token used for the api calls and the release. + GITHUB_REPOSITORY owner/name of the repository to commit to and release + from. Set automatically by GitHub Actions; falls back + to the origin remote, so a fork releases to itself. + UPSTREAM_REPOSITORY Repository the metadata comes from + (default google/libphonenumber). + NUGET_PACKAGE_ID Package whose published version is compared against + the upstream release (default libphonenumber-csharp). + 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). + UPSTREAM_TAG Use this upstream tag (e.g. v9.0.33) instead of asking + github for the latest release. Mainly for dry runs. + DEPLOYED_VERSION Use this published version (e.g. 9.0.32) instead of + asking nuget.org. Mainly for dry runs - together with + 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). + +Examples: + # what would the nightly run do right now? + github-actions-metadata-update.sh --dry-run + + # replay a release that changed java files, with the override in place + UPSTREAM_TAG=v9.0.33 DEPLOYED_VERSION=9.0.32 \ + github-actions-metadata-update.sh --dry-run --skip-java-check +EOF +} + +log() { + echo "$*" +} + +warn() { + echo "warning: $*" >&2 +} + +# fail +fail() { + local code=$1 + shift + echo "error: $*" >&2 + if [ -n "${GITHUB_STEP_SUMMARY:-}" ] + then + echo "$*" >> "${GITHUB_STEP_SUMMARY}" + fi + exit "${code}" +} + +# Lower casing without ${var,,}, which needs bash 4 - macOS still ships bash 3.2. +toLower() { + printf '%s' "$1" | tr '[:upper:]' '[:lower:]' +} + +isTrue() { + case "$(toLower "$1")" in + true|1|yes|y) return 0 ;; + *) return 1 ;; + esac +} + +GITHUB_TOKEN="${GITHUB_TOKEN:-}" +SKIP_JAVA_CHECK="${SKIP_JAVA_CHECK:-false}" +SKIP_PROTO_CHECK="${SKIP_PROTO_CHECK:-false}" +DRY_RUN="${DRY_RUN:-false}" +UPSTREAM_TAG="${UPSTREAM_TAG:-}" +DEPLOYED_VERSION="${DEPLOYED_VERSION:-}" +EXPECTED_MAJOR_VERSION="${EXPECTED_MAJOR_VERSION:-9}" +TEST_TARGET_FRAMEWORK="${TEST_TARGET_FRAMEWORK:-net10.0}" + +while [ $# -gt 0 ] +do + case "$1" in + --skip-java-check) SKIP_JAVA_CHECK=true ;; + --skip-proto-check) SKIP_PROTO_CHECK=true ;; + --dry-run) DRY_RUN=true ;; + -h|--help) usage; exit 0 ;; + -*) usage >&2; fail ${EXIT_USAGE} "unknown option: $1" ;; + *) GITHUB_TOKEN="$1" ;; + esac + shift +done + +if isTrue "${DRY_RUN}" then - echo "GitHub token required" - exit 123 + log "dry run: no files will be changed, nothing will be committed, pushed or released" + if [ -z "${GITHUB_TOKEN}" ] + then + warn "no github token, api calls will be unauthenticated and subject to a much lower rate limit" + fi +elif [ -z "${GITHUB_TOKEN}" ] +then + usage >&2 + fail ${EXIT_USAGE} "GitHub token required" fi -if ! command -v jq &> /dev/null +for tool in curl jq git +do + if ! command -v "${tool}" &> /dev/null + then + fail ${EXIT_MISSING_PREREQUISITE} "${tool} required" + fi +done + +# Only needed once the script starts generating and building, which a dry run +# never reaches - report them there rather than refusing to run. +for tool in javac java dotnet +do + if ! command -v "${tool}" &> /dev/null + then + if isTrue "${DRY_RUN}" + then + warn "${tool} not found, a real run would stop here" + else + fail ${EXIT_MISSING_PREREQUISITE} "${tool} required" + fi + fi +done + +UPSTREAM_REPOSITORY="${UPSTREAM_REPOSITORY:-google/libphonenumber}" +NUGET_PACKAGE_ID="${NUGET_PACKAGE_ID:-libphonenumber-csharp}" +GITHUB_ACTION_WORKING_DIRECTORY=$(pwd) + +# Which repository this run targets. Actions sets GITHUB_REPOSITORY for us; when +# it is not set fall back to the origin remote, so a fork or a scratch clone +# releases to itself instead of to the upstream project. +resolveRepository() { + local url + + if [ -n "${GITHUB_REPOSITORY:-}" ] + then + echo "${GITHUB_REPOSITORY}" + return 0 + fi + + url=$(git remote get-url origin 2> /dev/null || true) + url="${url%.git}" + + case "${url}" in + *github.com[:/]*) echo "${url##*github.com}" | sed 's|^[:/]*||' ;; + *) return 1 ;; + esac +} + +# Authenticated api calls, so the job is not subject to the unauthenticated rate +# limit shared by every action runner on the same address. The header is built as +# an array so the token stays a single argument, and is omitted entirely when +# there is no token (dry runs only). +GITHUB_AUTH_HEADER=() +if [ -n "${GITHUB_TOKEN}" ] then - echo "jq required" - exit 123 + GITHUB_AUTH_HEADER=(-H "Authorization: Bearer ${GITHUB_TOKEN}") fi +ghApi() { + curl --fail --silent --show-error --location --retry 3 --retry-delay 5 \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + ${GITHUB_AUTH_HEADER[@]+"${GITHUB_AUTH_HEADER[@]}"} \ + "$@" +} + getLatestGitHubRelease() { - curl --fail --silent --show-error --location "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name + ghApi "https://api.github.com/repos/$1/releases/latest" | jq -er '.tag_name' } +# The flat container index is specified as a list of versions, not a sorted one, +# so pick the highest stable version rather than trusting document order. getLatestNugetRelease() { - curl --fail --silent --show-error --location "https://www.nuget.org/packages/$1/" | grep 'og:title' | sed "s/.*$1 \([^\"]*\).*/\1/" + local packageId + packageId=$(toLower "$1") + + curl --fail --silent --show-error --location --retry 3 --retry-delay 5 \ + "https://api.nuget.org/v3-flatcontainer/${packageId}/index.json" \ + | jq -er '.versions[] | select(test("-") | not)' \ + | sort -V \ + | tail -n 1 } getReleaseDelta() { - curl --fail --silent --show-error --location "https://api.github.com/repos/$1/compare/$2...$3" | jq .files[].filename + ghApi "https://api.github.com/repos/$1/compare/$2...$3" } createRelease() { - curl --fail --silent --show-error -H "Authorization: Bearer $GITHUB_TOKEN" -d "{\"tag_name\":\"$2\",\"name\":\"$2\"}" --location "https://api.github.com/repos/$1/releases" + jq -n --arg tag "$2" '{tag_name: $tag, name: $tag}' \ + | ghApi -X POST --data @- "https://api.github.com/repos/$1/releases" > /dev/null } -GITHUB_TOKEN=$1 -UPSTREAM_GITHUB_RELEASE_TAG=$(getLatestGitHubRelease google/libphonenumber) -DEPLOYED_NUGET_TAG=$(getLatestNugetRelease libphonenumber-csharp) -GITHUB_REPOSITORY_OWNER=twcclegg -GITHUB_REPOSITORY_NAME=libphonenumber-csharp -GITHUB_ACTION_WORKING_DIRECTORY=$(pwd) +if [ -n "${UPSTREAM_TAG}" ] +then + UPSTREAM_GITHUB_RELEASE_TAG="${UPSTREAM_TAG}" + log "${UPSTREAM_REPOSITORY} release overridden to ${UPSTREAM_GITHUB_RELEASE_TAG}" +else + UPSTREAM_GITHUB_RELEASE_TAG=$(getLatestGitHubRelease "${UPSTREAM_REPOSITORY}") + log "${UPSTREAM_REPOSITORY} latest release is ${UPSTREAM_GITHUB_RELEASE_TAG}" +fi + +if [ -n "${DEPLOYED_VERSION}" ] +then + DEPLOYED_NUGET_TAG="${DEPLOYED_VERSION}" + log "${NUGET_PACKAGE_ID} version overridden to ${DEPLOYED_NUGET_TAG}" +else + DEPLOYED_NUGET_TAG=$(getLatestNugetRelease "${NUGET_PACKAGE_ID}") + log "${NUGET_PACKAGE_ID} latest release is ${DEPLOYED_NUGET_TAG}" +fi + +if [[ ! "${UPSTREAM_GITHUB_RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] +then + fail 1 "unexpected upstream release tag: ${UPSTREAM_GITHUB_RELEASE_TAG}" +fi + +if [[ ! "${DEPLOYED_NUGET_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] +then + fail 1 "unexpected deployed nuget version: ${DEPLOYED_NUGET_TAG}" +fi -echo "google/libphonenumber latest release is ${UPSTREAM_GITHUB_RELEASE_TAG}" -echo "libphonenumber-csharp latest release is ${DEPLOYED_NUGET_TAG}" +UPSTREAM_VERSION="${UPSTREAM_GITHUB_RELEASE_TAG#v}" +UPSTREAM_MAJOR_VERSION="${UPSTREAM_VERSION%%.*}" -if [ "${UPSTREAM_GITHUB_RELEASE_TAG:1:1}" != "9" ] +if [ "${UPSTREAM_MAJOR_VERSION}" != "${EXPECTED_MAJOR_VERSION}" ] then - echo "major version update" - exit 123 + fail ${EXIT_NEEDS_ATTENTION} \ + "major version update: upstream is ${UPSTREAM_GITHUB_RELEASE_TAG}, this port tracks ${EXPECTED_MAJOR_VERSION}.x" fi -if [ "$DEPLOYED_NUGET_TAG" = "${UPSTREAM_GITHUB_RELEASE_TAG:1}" ] +if [ "${DEPLOYED_NUGET_TAG}" = "${UPSTREAM_VERSION}" ] then - echo "versions match, new release not required" + log "versions match, new release not required" exit 0 fi -mkdir ~/GitHub +# Nothing to do when the published package is already ahead of upstream, which +# happens after a C# only patch release. +OLDEST_VERSION=$(printf '%s\n%s\n' "${UPSTREAM_VERSION}" "${DEPLOYED_NUGET_TAG}" | sort -V | head -n 1) +if [ "${OLDEST_VERSION}" = "${UPSTREAM_VERSION}" ] +then + log "deployed version ${DEPLOYED_NUGET_TAG} is ahead of upstream ${UPSTREAM_VERSION}, new release not required" + exit 0 +fi + +# The checkout this script runs in is the one that gets committed and pushed, so +# it is the one that has to be on a clean main. +cd "${GITHUB_ACTION_WORKING_DIRECTORY}" + +if [ ! -d resources ] || [ ! -f lib/DumpLocale.java ] +then + fail 1 "must be run from the root of the repository (no resources/ and lib/DumpLocale.java here)" +fi + +GITHUB_REPOSITORY=$(resolveRepository) \ + || fail 1 "could not determine the target repository, set GITHUB_REPOSITORY to owner/name" + +if [[ ! "${GITHUB_REPOSITORY}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] +then + fail 1 "unexpected target repository: ${GITHUB_REPOSITORY}" +fi + +log "target repository is ${GITHUB_REPOSITORY}" -cd ~/GitHub -git clone https://github.com/${GITHUB_REPOSITORY_OWNER}/${GITHUB_REPOSITORY_NAME}.git -cd ${GITHUB_REPOSITORY_NAME} -git checkout main +# A dry run changes nothing, so neither of these is dangerous there - downgrade +# them to warnings so the pipeline can be exercised from a feature branch. +requireRepositoryState() { + if isTrue "${DRY_RUN}" + then + warn "$1 (ignored for the dry run)" + else + fail 1 "$1" + fi +} -cd ~/GitHub/$GITHUB_REPOSITORY_NAME -if [ $(git branch --show-current) != "main" ] +if [ "$(git branch --show-current)" != "main" ] then - echo "must be on main branch" - exit 123 + requireRepositoryState "must be on main branch" fi if [ -n "$(git status --porcelain)" ] then - echo "working directory is not clean" - exit 123 + requireRepositoryState "working directory is not clean" +fi + +WORK_DIR=$(mktemp -d) +cleanup() { + rm -rf "${WORK_DIR}" +} +trap cleanup EXIT + +COMPARE_JSON=$(getReleaseDelta "${UPSTREAM_REPOSITORY}" "v${DEPLOYED_NUGET_TAG}" "${UPSTREAM_GITHUB_RELEASE_TAG}") +FILES=$(jq -er '.files // error("compare response contains no file list") | .[].filename' <<< "${COMPARE_JSON}") + +if [ -z "${FILES}" ] +then + fail 1 "no changed files reported between v${DEPLOYED_NUGET_TAG} and ${UPSTREAM_GITHUB_RELEASE_TAG}" +fi + +# The compare api returns at most 300 files, so the checks below can miss changes +# in a very large release. +if [ "$(jq -r '.files | length' <<< "${COMPARE_JSON}")" -ge 300 ] +then + warn "the compare api returned the maximum of 300 files, the change list may be truncated" +fi + +JAVA_FILES=$(grep -E '\.java$' <<< "${FILES}" || true) +if [ -n "${JAVA_FILES}" ] +then + printf 'upstream diff contains java files:\n%s\n' "${JAVA_FILES}" + if isTrue "${SKIP_JAVA_CHECK}" + then + warn "continuing anyway because --skip-java-check / SKIP_JAVA_CHECK is set" + else + fail ${EXIT_NEEDS_ATTENTION} \ + "has java files, automatic update not possible (re-run with --skip-java-check or SKIP_JAVA_CHECK=true to override)" + fi fi -cd ~/GitHub -git clone https://github.com/google/libphonenumber.git -cd libphonenumber -git checkout master +PROTO_FILES=$(grep -E '\.proto$' <<< "${FILES}" || true) +if [ -n "${PROTO_FILES}" ] +then + printf 'upstream diff contains proto files:\n%s\n' "${PROTO_FILES}" + if isTrue "${SKIP_PROTO_CHECK}" + then + warn "continuing anyway because --skip-proto-check / SKIP_PROTO_CHECK is set" + else + fail ${EXIT_NEEDS_ATTENTION} \ + "has proto files, automatic update not possible (re-run with --skip-proto-check or SKIP_PROTO_CHECK=true to override)" + fi +fi -cd ~/GitHub/libphonenumber -PREVIOUS=$(git describe --abbrev=0) +# Cloning upstream is safe either way: it only writes to the temporary directory, +# and it is the last thing that can fail before the working tree is touched. +git -c advice.detachedHead=false clone --quiet --depth 1 --branch "${UPSTREAM_GITHUB_RELEASE_TAG}" \ + "https://github.com/${UPSTREAM_REPOSITORY}.git" "${WORK_DIR}/libphonenumber" -FILES=$(getReleaseDelta google/libphonenumber "v${DEPLOYED_NUGET_TAG}" $UPSTREAM_GITHUB_RELEASE_TAG) +UPSTREAM_RESOURCES="${WORK_DIR}/libphonenumber/resources" +if [ -z "$(ls -A "${UPSTREAM_RESOURCES}" 2> /dev/null)" ] +then + fail 1 "upstream resources directory is missing or empty" +fi -if echo $FILES | grep '\.java' +# Everything past this point writes to the working tree or to github. +if isTrue "${DRY_RUN}" then - echo "has java files, automatic update not possible" - exit 123 + log "" + log "dry run complete, a real run would now:" + log " - replace ${GITHUB_ACTION_WORKING_DIRECTORY}/resources with $(find "${UPSTREAM_RESOURCES}" -type f | wc -l | tr -d ' ') files from ${UPSTREAM_GITHUB_RELEASE_TAG}" + log " - regenerate csharp/PhoneNumbers/LocaleData.cs with $(java -version 2>&1 | head -n 1 || echo 'the local jdk')" + 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}" + exit 0 fi -if echo $FILES | grep 'proto' +rm -rf "${GITHUB_ACTION_WORKING_DIRECTORY:?}/resources" +mkdir -p "${GITHUB_ACTION_WORKING_DIRECTORY}/resources" +cp -r "${UPSTREAM_RESOURCES}/." "${GITHUB_ACTION_WORKING_DIRECTORY}/resources/" + +# Generate into the temporary directory first, so a failure part way through can +# never leave a truncated LocaleData.cs or a stray DumpLocale.class behind for +# `git add -A` to pick up. +cd "${GITHUB_ACTION_WORKING_DIRECTORY}/lib" +javac -d "${WORK_DIR}/classes" DumpLocale.java +java -cp "${WORK_DIR}/classes" DumpLocale > "${WORK_DIR}/LocaleData.cs" + +if [ ! -s "${WORK_DIR}/LocaleData.cs" ] then - echo "has proto files, automatic update not possible" - exit 123 + fail 1 "DumpLocale produced no output" fi -git config --global user.email '<>' -git config --global user.name 'libphonenumber-csharp-bot' +mv "${WORK_DIR}/LocaleData.cs" "${GITHUB_ACTION_WORKING_DIRECTORY}/csharp/PhoneNumbers/LocaleData.cs" -git fetch origin -git reset --hard $UPSTREAM_GITHUB_RELEASE_TAG -rm -rf ${GITHUB_ACTION_WORKING_DIRECTORY}/resources/* -cp -r resources/* ${GITHUB_ACTION_WORKING_DIRECTORY}/resources -cd ${GITHUB_ACTION_WORKING_DIRECTORY} -cd lib -javac DumpLocale.java && java DumpLocale > ../csharp/PhoneNumbers/LocaleData.cs -rm DumpLocale.class +cd "${GITHUB_ACTION_WORKING_DIRECTORY}" +if [ -z "$(git status --porcelain)" ] +then + log "no changes after metadata sync, new release not required" + exit 0 +fi # Ensure project builds and passes tests before committing -cd ${GITHUB_ACTION_WORKING_DIRECTORY} -cd csharp +cd "${GITHUB_ACTION_WORKING_DIRECTORY}/csharp" dotnet restore dotnet build --no-restore -dotnet test --no-build --verbosity normal -p:TargetFrameworks=net9.0 +dotnet test --no-build --verbosity normal "-p:TargetFrameworks=${TEST_TARGET_FRAMEWORK}" -cd ${GITHUB_ACTION_WORKING_DIRECTORY} +cd "${GITHUB_ACTION_WORKING_DIRECTORY}" git add -A -git commit -m "feat: automatic upgrade to ${UPSTREAM_GITHUB_RELEASE_TAG}" +git -c user.email='<>' -c user.name='libphonenumber-csharp-bot' \ + commit -m "feat: automatic upgrade to ${UPSTREAM_GITHUB_RELEASE_TAG}" git push -createRelease ${GITHUB_REPOSITORY_OWNER}/${GITHUB_REPOSITORY_NAME} $UPSTREAM_GITHUB_RELEASE_TAG +createRelease "${GITHUB_REPOSITORY}" "${UPSTREAM_GITHUB_RELEASE_TAG}" +log "created release ${UPSTREAM_GITHUB_RELEASE_TAG}"