diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0d7aca..7058f26 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,6 +57,11 @@ jobs: grep -Fq "## [${RELEASE_VERSION}] -" CHANGELOG.md test -s ".github/release-notes/${RELEASE_VERSION}.md" scripts/verify-revapi-allowlist.sh + ./mvnw --quiet --batch-mode -N -Prelease \ + -Dcentral.autoPublish=true -Dcentral.waitUntil=published \ + help:effective-pom -Doutput="${RUNNER_TEMP}/effective-pom.xml" + grep -Fq "true" "${RUNNER_TEMP}/effective-pom.xml" + grep -Fq "published" "${RUNNER_TEMP}/effective-pom.xml" test -n "${CENTRAL_USERNAME}" test -n "${CENTRAL_TOKEN}" test -n "${GPG_PASSPHRASE}" @@ -131,65 +136,46 @@ jobs: shell: bash run: | set -euo pipefail - base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j" - missing=0 - while read -r artifact_id _packaging; do - [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue - pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom" - if ! curl \ - --connect-timeout 5 \ - --max-time 10 \ - --fail \ - --silent \ - --show-error \ - --head \ - "${pom_url}" >/dev/null; then - missing=1 - break - fi - done < config/release/public-artifacts.txt - if [[ "${missing}" -eq 0 ]]; then + status=0 + scripts/check-central-release.sh "${RELEASE_VERSION}" || status=$? + if [[ "${status}" -eq 0 ]]; then echo "Release ${RELEASE_VERSION} is already available from Maven Central; skipping deploy." exit 0 fi + if [[ "${status}" -ne 1 ]]; then + echo "Could not determine whether ${RELEASE_VERSION} is published; refusing to deploy." >&2 + exit "${status}" + fi ./mvnw --batch-mode --no-transfer-progress \ - -Prelease -DskipTests -DautoPublish=true -DwaitUntil=published deploy + -Prelease -DskipTests \ + -Dcentral.autoPublish=true -Dcentral.waitUntil=published deploy - name: Wait for Maven Central resolution shell: bash run: | set -euo pipefail - base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j" - for attempt in $(seq 1 30); do - missing=0 - while read -r artifact_id _packaging; do - [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue - pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom" - if ! curl \ - --connect-timeout 5 \ - --max-time 10 \ - --fail \ - --silent \ - --show-error \ - --head \ - "${pom_url}" >/dev/null; then - missing=1 - break - fi - done < config/release/public-artifacts.txt - [[ "${missing}" -eq 0 ]] && break - if [[ "${attempt}" -eq 30 ]]; then + for attempt in $(seq 1 60); do + # Unlike the deploy guard, a transport failure is retried here: this + # loop only waits, so an unreachable Central is just another retry. + status=0 + scripts/check-central-release.sh "${RELEASE_VERSION}" || status=$? + if [[ "${status}" -eq 0 ]]; then + break + fi + if [[ "${attempt}" -eq 60 ]]; then echo "Release did not become resolvable from Maven Central in time." >&2 + echo "Check the deployment state at https://central.sonatype.com/publishing/deployments" >&2 + echo "Once it reports published, re-run this failed job to keep the released commit." >&2 exit 1 fi - sleep 20 + sleep 30 done consumer_repository=$(mktemp -d) ./mvnw --batch-mode --no-transfer-progress \ -f config/release-smoke/pom.xml \ -Dmaven.repo.local="${consumer_repository}" \ -Ddatex4j.version="${RELEASE_VERSION}" \ - -Ddatex4j.repository="${base_url%/dev/juherr/datex4j}" \ + -Ddatex4j.repository="https://repo1.maven.org/maven2" \ verify finalize: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddf836c..c270b92 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -169,3 +169,16 @@ Release steps: The workflow safely resumes when its signed tag or GitHub Release already exists at the same commit. It refuses conflicting tags or releases. + +Resume a failed release with `gh run rerun --failed` rather than a new dispatch. A re-run +keeps the original commit, so the signed tag still points at the code that produced the published +artifacts. The publishing job skips the deploy only when `scripts/check-central-release.sh` resolves +every published file — each POM plus the main, sources, and javadoc jars — so a partially propagated +release still redeploys instead of being frozen. The re-run then replays the remaining verification, +tag, and release steps. + +The publishing job drives `central-publishing-maven-plugin` through the `central.autoPublish` and +`central.waitUntil` properties. The root POM binds them into an explicit plugin ``, +which takes precedence over the plugin's own `autoPublish` and `waitUntil` user properties — those +`-D` flags are silently ignored. `Validate release inputs` asserts the effective POM resolves to +`true` and `published` before anything is uploaded. diff --git a/scripts/check-central-release.sh b/scripts/check-central-release.sh new file mode 100755 index 0000000..62c887b --- /dev/null +++ b/scripts/check-central-release.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +version=$1 +project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +inventory="${project_root}/config/release/public-artifacts.txt" +base_url=${CENTRAL_BASE_URL:-https://repo1.maven.org/maven2/dev/juherr/datex4j} + +# Exit 0: every payload resolves. Exit 1: Central answered, something is absent. +# Exit 2: Central could not be questioned. Callers must not read 2 as "absent" — +# deploying on a transport failure would republish an already published version. +resolves() { + local url=$1 + local http_code curl_status=0 + + http_code=$(curl \ + --connect-timeout 5 \ + --max-time 10 \ + --silent \ + --head \ + --output /dev/null \ + --write-out '%{http_code}' \ + "${url}") || curl_status=$? + + if [[ "${curl_status}" -ne 0 ]]; then + echo "Maven Central is unreachable (curl exit ${curl_status}): ${url}" >&2 + exit 2 + fi + + case "${http_code}" in + 200) return 0 ;; + 404) return 1 ;; + *) + echo "Unexpected HTTP ${http_code} from Maven Central: ${url}" >&2 + exit 2 + ;; + esac +} + +# Mirrors the payload set asserted locally by verify-central-bundle.sh, so a +# release is only considered complete once every published file resolves. +while read -r artifact_id packaging; do + [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue + + base="${base_url}/${artifact_id}/${version}/${artifact_id}-${version}" + payloads=("${base}.pom") + case "${packaging}" in + jar) + payloads+=("${base}.jar" "${base}-sources.jar" "${base}-javadoc.jar") + ;; + pom) ;; + *) + echo "Unsupported packaging '${packaging}' for ${artifact_id}" >&2 + exit 2 + ;; + esac + + for payload in "${payloads[@]}"; do + if ! resolves "${payload}"; then + echo "Not published to Maven Central: ${payload}" >&2 + exit 1 + fi + done +done < "${inventory}"