From 99b4d892780f4d8f7c84cb3f4d8f2051d6dbea7a Mon Sep 17 00:00:00 2001 From: SandraAhlgrimm Date: Thu, 10 Sep 2026 11:18:10 +0200 Subject: [PATCH 1/5] Java: replace maven-release-plugin with CI-friendly versions Make the Java release pipeline a read-only consumer of the commit being released, consistent with the other SDKs. A Java release now creates no commits on `main`, requires no elevated repository token or ruleset bypass, and publishes artifacts whose version and source SHA match the other SDK artifacts in the same release. - POMs: adopt ${revision} CI-friendly versions (single property in java/pom.xml); remove maven-release-plugin. - java-publish-maven.yml: drop preflight/prepare-release/rollback-release; add resolve-source; build all classifiers + deploy from one immutable SHA with -Drevision; contents: read only; no JAVA_RELEASE_TOKEN. - publish.yml: publish-java contents: read + sourceSha; create the java/vX.Y.Z traceability tag and trigger the site deploy in github-release after publication succeeds. - java/README.md: document the new versioning/release model. Fixes #2579 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/java-publish-maven.yml | 423 +++++------------------ .github/workflows/publish.yml | 45 ++- java/README.md | 23 ++ java/copilot-native/pom.xml | 2 +- java/pom.xml | 18 +- java/sdk/pom.xml | 2 +- 6 files changed, 170 insertions(+), 343 deletions(-) diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml index d1ef78f66b..50580fa601 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -3,6 +3,14 @@ name: "Java Publish to Maven Central" env: HUSKY: 0 +# This workflow is a read-only consumer of the commit being released. It builds +# every native classifier and the primary Java SDK artifact from a single +# immutable source SHA, injects the release version with -Drevision=, and +# publishes to Maven Central. It never commits to, tags, or otherwise mutates +# the repository. The cross-language `vX.Y.Z` GitHub Release and the +# `java/vX.Y.Z` traceability tag are created by .github/workflows/publish.yml +# only after publication succeeds. + on: workflow_dispatch: inputs: @@ -10,8 +18,8 @@ on: description: "Release version (e.g., 1.0.0). If empty, derives from pom.xml by removing -SNAPSHOT" required: false type: string - developmentVersion: - description: "Next development version (e.g., 1.0.1-SNAPSHOT). If empty, increments patch version" + sourceSha: + description: "Immutable source commit to build and publish from. Defaults to the commit this workflow runs on." required: false type: string prerelease: @@ -25,8 +33,8 @@ on: description: "Release version (e.g., 1.0.0). If empty, derives from pom.xml by removing -SNAPSHOT" required: false type: string - developmentVersion: - description: "Next development version (e.g., 1.0.1-SNAPSHOT). If empty, increments patch version" + sourceSha: + description: "Immutable source commit to build and publish from. Defaults to the commit this workflow runs on." required: false type: string prerelease: @@ -38,11 +46,13 @@ on: mavenPublished: description: "Whether the Java package was published to Maven Central" value: ${{ jobs.deploy-maven.outputs.published }} + version: + description: "The published release version" + value: ${{ jobs.resolve-source.outputs.release_version }} + sourceSha: + description: "The immutable source commit that was published" + value: ${{ jobs.resolve-source.outputs.source_sha }} secrets: - JAVA_RELEASE_TOKEN: - required: true - JAVA_RELEASE_GITHUB_TOKEN: - required: true JAVA_MAVEN_CENTRAL_USERNAME: required: true JAVA_MAVEN_CENTRAL_PASSWORD: @@ -60,52 +70,29 @@ concurrency: cancel-in-progress: false jobs: - preflight: - name: Preflight checks - runs-on: ubuntu-latest - steps: - - name: Verify JAVA_RELEASE_TOKEN can push to repository - run: | - PUSH=$(gh api repos/${{ github.repository }} --jq '.permissions.push // false') - if [ "$PUSH" != "true" ]; then - echo "::error::JAVA_RELEASE_TOKEN lacks push permission on ${{ github.repository }}. It is required for pushing release commits and tags to main." - exit 1 - fi - echo "JAVA_RELEASE_TOKEN push access OK" - env: - GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_TOKEN }} - - prepare-release: - name: Prepare Java release - needs: preflight + resolve-source: + name: Resolve immutable release source runs-on: ubuntu-latest permissions: - contents: write + contents: read defaults: run: shell: bash working-directory: ./java outputs: + source_sha: ${{ steps.source.outputs.sha }} release_version: ${{ steps.versions.outputs.release_version }} - development_version: ${{ steps.versions.outputs.development_version }} - release_tag: ${{ steps.release-identity.outputs.release_tag }} - tag_commit: ${{ steps.release-identity.outputs.tag_commit }} - pre_prepare_commit: ${{ steps.update-docs.outputs.pre_prepare_commit }} - post_prepare_commit: ${{ steps.release-identity.outputs.post_prepare_commit }} - docs_commit: ${{ steps.update-docs.outputs.docs_commit_sha }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: main - fetch-depth: 0 - token: ${{ secrets.JAVA_RELEASE_TOKEN }} + ref: ${{ inputs.sourceSha || github.sha }} + fetch-depth: 1 + persist-credentials: false - - name: Configure Git for Maven Release + - id: source run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - uses: ./.github/actions/setup-copilot + SHA=$(git rev-parse HEAD) + echo "sha=$SHA" >> "$GITHUB_OUTPUT" - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: @@ -113,10 +100,10 @@ jobs: distribution: "microsoft" cache: "maven" - - name: Determine versions + - name: Determine release version id: versions run: | - CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + CURRENT_VERSION=$(mvn -N help:evaluate -Dexpression=project.version -q -DforceStdout) echo "Current pom.xml version: $CURRENT_VERSION" if [ -n "${{ inputs.releaseVersion }}" ]; then @@ -126,86 +113,20 @@ jobs: fi echo "Release version: $RELEASE_VERSION" - if [ -n "${{ inputs.developmentVersion }}" ]; then - DEV_VERSION="${{ inputs.developmentVersion }}" - if [[ "$DEV_VERSION" != *-SNAPSHOT ]]; then - echo "::error::developmentVersion '${DEV_VERSION}' must end with '-SNAPSHOT'." - exit 1 - fi - else - if ! echo "$RELEASE_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-(preview|(beta-)?java(-preview)?)\.[0-9]+)?$'; then - echo "Error: RELEASE_VERSION '$RELEASE_VERSION' is invalid." >&2 - exit 1 - fi - BASE_VERSION=$(echo "$RELEASE_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+') - QUALIFIER=$(echo "$RELEASE_VERSION" | sed "s|^${BASE_VERSION}||") - IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION" - DEV_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))${QUALIFIER}-SNAPSHOT" + if ! echo "$RELEASE_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-(preview|(beta-)?java(-preview)?)\.[0-9]+)?$'; then + echo "::error::RELEASE_VERSION '$RELEASE_VERSION' is invalid." + exit 1 + fi + if [[ "$RELEASE_VERSION" == *-SNAPSHOT ]]; then + echo "::error::RELEASE_VERSION '$RELEASE_VERSION' must not be a SNAPSHOT." + exit 1 fi echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" - echo "development_version=$DEV_VERSION" >> "$GITHUB_OUTPUT" - - - name: Update documentation with release version - id: update-docs - run: | - VERSION="${{ steps.versions.outputs.release_version }}" - DEV_VERSION="${{ steps.versions.outputs.development_version }}" - - for attempt in 1 2 3; do - git fetch origin main - git reset --hard origin/main - if [ -z "${{ inputs.releaseVersion }}" ]; then - LIVE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - if [ "${LIVE_VERSION%-SNAPSHOT}" != "$VERSION" ]; then - echo "::error::main advanced from release version $VERSION to ${LIVE_VERSION%-SNAPSHOT}; restart the release with the current version." - exit 1 - fi - fi - ./scripts/test-update-documentation-versions.sh - ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md sdk/jbang-example.java - git add README.md sdk/jbang-example.java - git commit -m "docs: update version references to ${VERSION}" - - if git push origin HEAD:main; then - echo "pre_prepare_commit=$(git rev-parse HEAD^)" >> "$GITHUB_OUTPUT" - echo "docs_commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - break - fi - - if [ "$attempt" -eq 3 ]; then - echo "::error::main continued to advance while preparing the Java release." - exit 1 - fi - echo "main advanced during release preparation; retrying from the latest commit." - done - - - name: Prepare Release - run: | - mvn -B release:prepare \ - -DreleaseVersion=${{ steps.versions.outputs.release_version }} \ - -DdevelopmentVersion=${{ steps.versions.outputs.development_version }} \ - -DtagNameFormat=java/v@{project.version} \ - -DpushChanges=true \ - -Darguments="-DskipTests" - env: - MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }} - JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }} - - - name: Record immutable release identity - id: release-identity - run: | - TAG="java/v${{ steps.versions.outputs.release_version }}" - TAG_COMMIT=$(git rev-parse "${TAG}^{commit}") - POST_PREPARE_COMMIT=$(git rev-parse HEAD) - echo "release_tag=$TAG" >> "$GITHUB_OUTPUT" - echo "tag_commit=$TAG_COMMIT" >> "$GITHUB_OUTPUT" - echo "post_prepare_commit=$POST_PREPARE_COMMIT" >> "$GITHUB_OUTPUT" build-linux-arm64-classifier: name: Build Linux ARM64 native classifier - needs: prepare-release + needs: resolve-source runs-on: ubuntu-24.04-arm permissions: contents: read @@ -216,7 +137,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ needs.prepare-release.outputs.release_tag }} + ref: ${{ needs.resolve-source.outputs.source_sha }} fetch-depth: 1 persist-credentials: false @@ -234,13 +155,13 @@ jobs: run: | set -euo pipefail SOURCE_COMMIT=$(git rev-parse HEAD) - if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then - echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved release source." exit 1 fi + VERSION="${{ needs.resolve-source.outputs.release_version }}" node copilot-native/scripts/validate-native-host.mjs linux-arm64 - mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc - VERSION="${{ needs.prepare-release.outputs.release_version }}" + mvn -B -pl copilot-native package -DskipTests -Drevision="$VERSION" -Dcopilot.native.libc=glibc JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" test -f "$JAR" @@ -257,14 +178,14 @@ jobs: with: name: java-native-linux-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} path: | - java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-linux-arm64.jar - java/copilot-native/target/linux-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.resolve-source.outputs.release_version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ needs.resolve-source.outputs.release_version }}.sha256 if-no-files-found: error retention-days: 1 build-windows-classifier: name: Build Windows native classifier - needs: prepare-release + needs: resolve-source runs-on: windows-latest permissions: contents: read @@ -275,7 +196,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ needs.prepare-release.outputs.release_tag }} + ref: ${{ needs.resolve-source.outputs.source_sha }} fetch-depth: 1 persist-credentials: false @@ -292,12 +213,12 @@ jobs: - name: Build and validate win32-x64 classifier run: | $sourceCommit = git rev-parse HEAD - if ($sourceCommit -ne '${{ needs.prepare-release.outputs.tag_commit }}') { - throw "Checked out $sourceCommit instead of the prepared tag commit." + if ($sourceCommit -ne '${{ needs.resolve-source.outputs.source_sha }}') { + throw "Checked out $sourceCommit instead of the resolved release source." } + $version = '${{ needs.resolve-source.outputs.release_version }}' node copilot-native/scripts/validate-native-host.mjs win32-x64 - mvn -B -pl copilot-native package -DskipTests - $version = '${{ needs.prepare-release.outputs.release_version }}' + mvn -B -pl copilot-native package -DskipTests "-Drevision=$version" $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-x64.jar" $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { @@ -314,14 +235,14 @@ jobs: with: name: java-native-win32-x64-release-${{ github.run_id }}-${{ github.run_attempt }} path: | - java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-win32-x64.jar - java/copilot-native/target/win32-x64-${{ needs.prepare-release.outputs.release_version }}.sha256 + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.resolve-source.outputs.release_version }}-win32-x64.jar + java/copilot-native/target/win32-x64-${{ needs.resolve-source.outputs.release_version }}.sha256 if-no-files-found: error retention-days: 1 build-windows-arm64-classifier: name: Build Windows ARM64 native classifier - needs: prepare-release + needs: resolve-source runs-on: windows-11-arm permissions: contents: read @@ -332,7 +253,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ needs.prepare-release.outputs.release_tag }} + ref: ${{ needs.resolve-source.outputs.source_sha }} fetch-depth: 1 persist-credentials: false @@ -349,12 +270,12 @@ jobs: - name: Build and validate win32-arm64 classifier run: | $sourceCommit = git rev-parse HEAD - if ($sourceCommit -ne '${{ needs.prepare-release.outputs.tag_commit }}') { - throw "Checked out $sourceCommit instead of the prepared tag commit." + if ($sourceCommit -ne '${{ needs.resolve-source.outputs.source_sha }}') { + throw "Checked out $sourceCommit instead of the resolved release source." } + $version = '${{ needs.resolve-source.outputs.release_version }}' node copilot-native/scripts/validate-native-host.mjs win32-arm64 - mvn -B -pl copilot-native package -DskipTests - $version = '${{ needs.prepare-release.outputs.release_version }}' + mvn -B -pl copilot-native package -DskipTests "-Drevision=$version" $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-arm64.jar" $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { @@ -371,14 +292,14 @@ jobs: with: name: java-native-win32-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} path: | - java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-win32-arm64.jar - java/copilot-native/target/win32-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.resolve-source.outputs.release_version }}-win32-arm64.jar + java/copilot-native/target/win32-arm64-${{ needs.resolve-source.outputs.release_version }}.sha256 if-no-files-found: error retention-days: 1 build-darwin-classifier: name: Build Darwin native classifier - needs: prepare-release + needs: resolve-source runs-on: macos-26 permissions: contents: read @@ -389,7 +310,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ needs.prepare-release.outputs.release_tag }} + ref: ${{ needs.resolve-source.outputs.source_sha }} fetch-depth: 1 persist-credentials: false @@ -407,13 +328,13 @@ jobs: run: | set -euo pipefail SOURCE_COMMIT=$(git rev-parse HEAD) - if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then - echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved release source." exit 1 fi + VERSION="${{ needs.resolve-source.outputs.release_version }}" node copilot-native/scripts/validate-native-host.mjs darwin-arm64 - mvn -B -pl copilot-native package -DskipTests - VERSION="${{ needs.prepare-release.outputs.release_version }}" + mvn -B -pl copilot-native package -DskipTests -Drevision="$VERSION" JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" test -f "$JAR" @@ -430,8 +351,8 @@ jobs: with: name: java-native-darwin-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} path: | - java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-darwin-arm64.jar - java/copilot-native/target/darwin-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.resolve-source.outputs.release_version }}-darwin-arm64.jar + java/copilot-native/target/darwin-arm64-${{ needs.resolve-source.outputs.release_version }}.sha256 if-no-files-found: error retention-days: 1 @@ -439,7 +360,7 @@ jobs: name: Deploy Java release to Maven Central needs: [ - prepare-release, + resolve-source, build-linux-arm64-classifier, build-windows-classifier, build-windows-arm64-classifier, @@ -453,12 +374,12 @@ jobs: shell: bash working-directory: ./java outputs: - version: ${{ needs.prepare-release.outputs.release_version }} + version: ${{ needs.resolve-source.outputs.release_version }} published: ${{ steps.publish-maven.outcome == 'success' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ needs.prepare-release.outputs.release_tag }} + ref: ${{ needs.resolve-source.outputs.source_sha }} fetch-depth: 1 persist-credentials: false @@ -503,11 +424,11 @@ jobs: id: linux-arm64-artifact run: | SOURCE_COMMIT=$(git rev-parse HEAD) - if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then - echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved release source." exit 1 fi - VERSION="${{ needs.prepare-release.outputs.release_version }}" + VERSION="${{ needs.resolve-source.outputs.release_version }}" ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-linux-arm64" JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" MANIFEST="$ARTIFACT_DIRECTORY/linux-arm64-$VERSION.sha256" @@ -524,11 +445,11 @@ jobs: id: windows-artifact run: | SOURCE_COMMIT=$(git rev-parse HEAD) - if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then - echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved release source." exit 1 fi - VERSION="${{ needs.prepare-release.outputs.release_version }}" + VERSION="${{ needs.resolve-source.outputs.release_version }}" ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-win32-x64" JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-x64.jar" MANIFEST="$ARTIFACT_DIRECTORY/win32-x64-$VERSION.sha256" @@ -545,11 +466,11 @@ jobs: id: darwin-artifact run: | SOURCE_COMMIT=$(git rev-parse HEAD) - if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then - echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved release source." exit 1 fi - VERSION="${{ needs.prepare-release.outputs.release_version }}" + VERSION="${{ needs.resolve-source.outputs.release_version }}" ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-darwin-arm64" JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" MANIFEST="$ARTIFACT_DIRECTORY/darwin-arm64-$VERSION.sha256" @@ -566,11 +487,11 @@ jobs: id: windows-arm64-artifact run: | SOURCE_COMMIT=$(git rev-parse HEAD) - if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then - echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved release source." exit 1 fi - VERSION="${{ needs.prepare-release.outputs.release_version }}" + VERSION="${{ needs.resolve-source.outputs.release_version }}" ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-win32-arm64" JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-arm64.jar" MANIFEST="$ARTIFACT_DIRECTORY/win32-arm64-$VERSION.sha256" @@ -586,8 +507,8 @@ jobs: - name: Build Linux classifier and deploy complete release id: publish-maven run: | - VERSION="${{ needs.prepare-release.outputs.release_version }}" - mvn -B deploy -DskipTests -Prelease -Dcopilot.native.libc=glibc \ + VERSION="${{ needs.resolve-source.outputs.release_version }}" + mvn -B deploy -DskipTests -Prelease -Drevision="$VERSION" -Dcopilot.native.libc=glibc \ "-Dcopilot.native.external.linux.arm64.classifier.path=${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}" \ "-Dcopilot.native.external.win32.classifier.path=${{ steps.windows-artifact.outputs.windows_jar }}" \ "-Dcopilot.native.external.win32.arm64.classifier.path=${{ steps.windows-arm64-artifact.outputs.windows_arm64_jar }}" \ @@ -597,9 +518,9 @@ jobs: node copilot-native/scripts/validate-native-artifact.mjs \ classifier linux-x64 "$LINUX_JAR" "$(basename "$LINUX_JAR")" .. LINUX_SHA=$(sha256sum "$LINUX_JAR" | cut -d ' ' -f 1) - GROUP_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.groupId -DforceStdout) - ARTIFACT_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.artifactId -DforceStdout) - POM_VERSION=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.version -DforceStdout) + GROUP_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.groupId -Drevision="$VERSION" -DforceStdout) + ARTIFACT_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.artifactId -Drevision="$VERSION" -DforceStdout) + POM_VERSION=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.version -Drevision="$VERSION" -DforceStdout) if [ -z "$GROUP_ID" ] || [ -z "$ARTIFACT_ID" ] || [ "$POM_VERSION" != "$VERSION" ]; then echo "::error::Unexpected copilot-native Maven coordinates: $GROUP_ID:$ARTIFACT_ID:$POM_VERSION (expected version $VERSION)" exit 1 @@ -607,9 +528,7 @@ jobs: { echo "### Maven Central Release" echo "- **Version:** $VERSION" - echo "- **Source tag:** \`${{ needs.prepare-release.outputs.release_tag }}\`" - echo "- **Source commit:** \`${{ needs.prepare-release.outputs.tag_commit }}\`" - echo "- **Next development version:** ${{ needs.prepare-release.outputs.development_version }}" + echo "- **Source commit:** \`${{ needs.resolve-source.outputs.source_sha }}\`" echo "- **Repository:** Maven Central" echo "" echo "#### Maven Coordinates" @@ -636,165 +555,3 @@ jobs: MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }} MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }} JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }} - - rollback-release: - name: Roll back failed Java release preparation - needs: - [ - prepare-release, - build-linux-arm64-classifier, - build-windows-classifier, - build-windows-arm64-classifier, - build-darwin-classifier, - deploy-maven, - ] - if: ${{ failure() && needs.prepare-release.outputs.docs_commit != '' }} - runs-on: ubuntu-latest - permissions: - contents: write - defaults: - run: - shell: bash - working-directory: ./java - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - token: ${{ secrets.JAVA_RELEASE_TOKEN }} - - - name: Safely revert release commits and tag - env: - DOCS_COMMIT: ${{ needs.prepare-release.outputs.docs_commit }} - PREPARE_RESULT: ${{ needs.prepare-release.result }} - PRE_PREPARE_COMMIT: ${{ needs.prepare-release.outputs.pre_prepare_commit }} - POST_PREPARE_COMMIT: ${{ needs.prepare-release.outputs.post_prepare_commit }} - RELEASE_TAG: java/v${{ needs.prepare-release.outputs.release_version }} - TAG_COMMIT: ${{ needs.prepare-release.outputs.tag_commit }} - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git fetch origin main --tags - MAIN_COMMIT=$(git rev-parse origin/main) - echo "Rollback inspection: main=$MAIN_COMMIT docs=$DOCS_COMMIT pre=$PRE_PREPARE_COMMIT post=$POST_PREPARE_COMMIT tag=$RELEASE_TAG" - - unsafe_rollback() { - echo "::error::Unsafe rollback state: $*" >&2 - exit 1 - } - - if ! git cat-file -e "${PRE_PREPARE_COMMIT}^{commit}"; then - unsafe_rollback "Recorded pre-prepare commit does not exist: $PRE_PREPARE_COMMIT" - fi - if ! git cat-file -e "${DOCS_COMMIT}^{commit}"; then - unsafe_rollback "Recorded documentation commit does not exist: $DOCS_COMMIT" - fi - if ! git merge-base --is-ancestor "$PRE_PREPARE_COMMIT" "$MAIN_COMMIT"; then - unsafe_rollback "Recorded pre-prepare commit is not an ancestor of main." - fi - if [ "$(git rev-parse "${DOCS_COMMIT}^")" != "$PRE_PREPARE_COMMIT" ]; then - unsafe_rollback "Documentation commit is not immediately based on the recorded pre-prepare commit." - fi - - mapfile -t ROLLBACK_COMMITS < <(git rev-list --reverse "$PRE_PREPARE_COMMIT..$MAIN_COMMIT") - FIRST_PARENT_COUNT=$(git rev-list --count --first-parent "$PRE_PREPARE_COMMIT..$MAIN_COMMIT") - if [ "${#ROLLBACK_COMMITS[@]}" -ne "$FIRST_PARENT_COUNT" ]; then - unsafe_rollback "Release range contains merged history." - fi - if [ "${#ROLLBACK_COMMITS[@]}" -lt 1 ] || [ "${#ROLLBACK_COMMITS[@]}" -gt 3 ]; then - unsafe_rollback "Expected one to three release-preparation commits after the recorded base; found ${#ROLLBACK_COMMITS[@]}." - fi - if [ "${ROLLBACK_COMMITS[0]}" != "$DOCS_COMMIT" ]; then - unsafe_rollback "The first commit after the recorded base is not the recorded documentation commit." - fi - - EXPECTED_PARENT="$PRE_PREPARE_COMMIT" - for COMMIT in "${ROLLBACK_COMMITS[@]}"; do - if git rev-parse -q --verify "${COMMIT}^2" >/dev/null; then - unsafe_rollback "Release range contains merge commit $COMMIT." - fi - if [ "$(git rev-parse "${COMMIT}^")" != "$EXPECTED_PARENT" ]; then - unsafe_rollback "Release range is not a linear continuation of the recorded base." - fi - EXPECTED_PARENT="$COMMIT" - done - - RELEASE_VERSION="${RELEASE_TAG#java/v}" - if [ "$(git log -1 --format=%s "$DOCS_COMMIT")" != "docs: update version references to $RELEASE_VERSION" ]; then - unsafe_rollback "Recorded documentation commit has an unexpected subject." - fi - - RELEASE_PREPARE_COMMIT="" - if [ "${#ROLLBACK_COMMITS[@]}" -ge 2 ]; then - RELEASE_PREPARE_COMMIT="${ROLLBACK_COMMITS[1]}" - if [ "$(git log -1 --format=%s "$RELEASE_PREPARE_COMMIT")" != "[maven-release-plugin] prepare release $RELEASE_TAG" ]; then - unsafe_rollback "Release-version commit has an unexpected subject." - fi - fi - if [ "${#ROLLBACK_COMMITS[@]}" -eq 3 ] && [ "$(git log -1 --format=%s "${ROLLBACK_COMMITS[2]}")" != "[maven-release-plugin] prepare for next development iteration" ]; then - unsafe_rollback "Development-version commit has an unexpected subject." - fi - - TAG_OBJECT=$(git ls-remote --refs origin "refs/tags/$RELEASE_TAG" | awk '{print $1}') - if [ -n "$TAG_OBJECT" ]; then - git fetch --no-tags origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG" - REMOTE_TAG_COMMIT=$(git rev-parse "${RELEASE_TAG}^{commit}") - if [ -z "$RELEASE_PREPARE_COMMIT" ] || [ "$REMOTE_TAG_COMMIT" != "$RELEASE_PREPARE_COMMIT" ]; then - unsafe_rollback "Release tag does not point to the guarded release-version commit." - fi - elif [ -n "$TAG_COMMIT" ]; then - unsafe_rollback "Recorded release tag is absent from the remote." - fi - - if [ "$PREPARE_RESULT" = "success" ]; then - if [ -z "$POST_PREPARE_COMMIT" ] || [ -z "$TAG_COMMIT" ]; then - unsafe_rollback "Successful preparation did not record its immutable release identity." - fi - if [ "$MAIN_COMMIT" != "$POST_PREPARE_COMMIT" ]; then - unsafe_rollback "main has advanced beyond the recorded post-prepare commit." - fi - if [ -z "$TAG_OBJECT" ] || [ "$REMOTE_TAG_COMMIT" != "$TAG_COMMIT" ]; then - unsafe_rollback "Remote release tag does not match the recorded tag commit." - fi - fi - - git checkout -B release-rollback "$MAIN_COMMIT" - git revert --no-edit "$PRE_PREPARE_COMMIT..$MAIN_COMMIT" - if [ -n "$TAG_OBJECT" ]; then - git push --atomic \ - "--force-with-lease=refs/heads/main:$MAIN_COMMIT" \ - "--force-with-lease=refs/tags/$RELEASE_TAG:$TAG_OBJECT" \ - origin HEAD:refs/heads/main ":refs/tags/$RELEASE_TAG" - else - git push \ - "--force-with-lease=refs/heads/main:$MAIN_COMMIT" \ - origin HEAD:refs/heads/main - fi - echo "Release preparation rollback completed after guarded history inspection." - - deploy-site: - name: Deploy Documentation Site - needs: [preflight, deploy-maven] - if: github.ref == 'refs/heads/main' && needs.deploy-maven.outputs.published == 'true' - runs-on: ubuntu-latest - steps: - - name: Trigger site deployment on standalone repo - run: | - VERSION="${{ needs.deploy-maven.outputs.version }}" - TAG="java/v${VERSION}" - PUBLISH_AS_LATEST=true - if [ "${{ inputs.prerelease }}" = "true" ]; then - PUBLISH_AS_LATEST=false - fi - echo "Triggering site deployment for version ${VERSION} (tag: ${TAG})" - gh workflow run deploy-site.yml \ - --repo github/copilot-sdk-java \ - -f version="${VERSION}" \ - -f publish_as_latest="${PUBLISH_AS_LATEST}" \ - -f monorepo_tag="${TAG}" - { - echo "### Site Deployment" - echo "Triggered deploy-site.yml on github/copilot-sdk-java for version ${VERSION}" - } >> "$GITHUB_STEP_SUMMARY" - env: - GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5e1d277259..1f6bb6df5a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -383,11 +383,11 @@ jobs: if: github.event.inputs.dist-tag != 'unstable' && github.ref == 'refs/heads/main' needs: version permissions: - contents: write - id-token: write + contents: read uses: ./.github/workflows/java-publish-maven.yml with: releaseVersion: ${{ needs.version.outputs.version }} + sourceSha: ${{ github.sha }} prerelease: ${{ github.event.inputs.dist-tag == 'prerelease' }} secrets: inherit @@ -485,3 +485,44 @@ jobs: fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Tag Java SDK + # Language-scoped traceability tag for the Maven Central release, + # created only after publication succeeded and pointing at the original + # release commit. Java is included in the cross-language `vX.Y.Z` + # GitHub Release. + if: github.event.inputs.dist-tag == 'latest' || github.event.inputs.dist-tag == 'prerelease' + run: | + set -e + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch --tags + VERSION="${{ needs.version.outputs.version }}" + TAG_NAME="java/v${VERSION}" + if git tag "$TAG_NAME" ${{ github.sha }} 2>/dev/null; then + git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git "$TAG_NAME" + echo "Created and pushed tag $TAG_NAME" + else + echo "Tag $TAG_NAME already exists, skipping tag push" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Trigger Java documentation site deploy + # Runs after the java/vX.Y.Z tag exists so the standalone site repo can + # check out the monorepo at that tag. + if: github.event.inputs.dist-tag == 'latest' || github.event.inputs.dist-tag == 'prerelease' + run: | + set -e + VERSION="${{ needs.version.outputs.version }}" + TAG="java/v${VERSION}" + PUBLISH_AS_LATEST=true + if [ "${{ github.event.inputs.dist-tag }}" = "prerelease" ]; then + PUBLISH_AS_LATEST=false + fi + echo "Triggering site deployment for version ${VERSION} (tag: ${TAG})" + gh workflow run deploy-site.yml \ + --repo github/copilot-sdk-java \ + -f version="${VERSION}" \ + -f publish_as_latest="${PUBLISH_AS_LATEST}" \ + -f monorepo_tag="${TAG}" + env: + GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_GITHUB_TOKEN }} diff --git a/java/README.md b/java/README.md index 9b8b243247..c857734f05 100644 --- a/java/README.md +++ b/java/README.md @@ -661,6 +661,29 @@ mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dc Each classifier JAR includes `runtime.node`, `platform.properties`, and `copilot-runtime` (or `copilot-runtime.exe`) under its `native/` directory. It does not contain the legacy `copilot` SEA. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. +### Versioning and releases + +The Java SDK uses [Maven CI-friendly versions](https://maven.apache.org/maven-ci-friendly.html). Every module declares `${revision}`, and the single source of truth is the `` property in `java/pom.xml`. The committed value stays a `-SNAPSHOT` (for example `1.0.14-SNAPSHOT`) and is only used for local development and the daily snapshot publish. + +Releasing is intentionally a **read-only** operation that never mutates the repository: + +- The release version is computed by the shared release pipeline (`.github/workflows/publish.yml`) — the same version used by every other language SDK — and injected at build time with `-Drevision=X.Y.Z`. The POM is **not** edited or committed. +- `.github/workflows/java-publish-maven.yml` builds every native classifier and the primary artifact from a single immutable source commit and publishes to Maven Central. It creates no commits, no branch-protection bypass, and requires no elevated repository token. +- The `java/vX.Y.Z` traceability tag and the cross-language `vX.Y.Z` GitHub Release are created by `publish.yml` **after** publication succeeds, pointing at the original release commit. + +Because there is no `maven-release-plugin` and no `release:prepare` ceremony, the POM deliberately does not track the "next" release version. To cut a release locally for validation, resolve `${revision}` explicitly: + +```bash +# Build/deploy with an explicit release version, without touching the POM +mvn -Prelease deploy -Drevision=1.2.3 -DskipTests + +# Confirm the flattened, published POM carries the literal version (no ${revision}) +mvn -pl sdk process-resources -Drevision=1.2.3 -DskipTests +cat sdk/.flattened-pom.xml +``` + +`flatten-maven-plugin` (ossrh mode) resolves `${revision}` into the installed and published POMs, so downstream consumers never see the unresolved property. Documentation version references are updated through a normal reviewed pull request (see `scripts/update-documentation-versions.sh`), not as a side effect of publishing. + ## License MIT — see [LICENSE](sdk/LICENSE) for details. diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index 7624deb4fe..b7d8c188cc 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -8,7 +8,7 @@ com.github copilot-sdk-java-parent - 1.0.14-SNAPSHOT + ${revision} ../pom.xml diff --git a/java/pom.xml b/java/pom.xml index b17cdbde7d..2fd5b562f2 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java-parent - 1.0.14-SNAPSHOT + ${revision} pom GitHub Copilot SDK :: Java :: Parent @@ -47,6 +47,17 @@ + + 1.0.14-SNAPSHOT 17 UTF-8 /g, ""); + if (pom.includes("${revision}")) { + throw new Error( + `Published POM contains unresolved \${revision}: ${pomPath}`, + ); + } + if (/)/.test(pom)) { + throw new Error(`Published POM must not depend on a parent: ${pomPath}`); + } + + // Maven writes the flattened project's coordinates before nested elements. + // Match that header so dependency coordinates cannot satisfy this check. + const coordinates = pom.match( + /]*>\s*[^<]+<\/modelVersion>\s*([^<]+)<\/groupId>\s*([^<]+)<\/artifactId>\s*([^<]+)<\/version>/, + ); + if (!coordinates) { + throw new Error( + `Published POM is missing flattened project coordinates: ${pomPath}`, + ); + } + const [, groupId, publishedArtifactId, publishedVersion] = coordinates.map( + (value) => value.trim(), + ); + if ( + groupId !== "com.github" || + publishedArtifactId !== artifactId || + publishedVersion !== version + ) { + throw new Error( + `Unexpected Maven coordinates in ${pomPath}: ${groupId}:${publishedArtifactId}:${publishedVersion} (expected com.github:${artifactId}:${version})`, + ); + } +} + function main() { const [repositoryPath, artifactId, version, repoRoot, signatures] = process.argv.slice(2); diff --git a/java/copilot-native/scripts/validate-native-artifact.test.mjs b/java/copilot-native/scripts/validate-native-artifact.test.mjs index c07695a484..2986d709d8 100644 --- a/java/copilot-native/scripts/validate-native-artifact.test.mjs +++ b/java/copilot-native/scripts/validate-native-artifact.test.mjs @@ -341,122 +341,101 @@ test("accepts a matching SHA-256 manifest", (t) => { ); }); -test("validates one complete signed local publication", (t) => { - const fixture = createFixture(t); - const artifactId = "copilot-sdk-java-runtime"; - const version = "1.2.3"; - const publicationDirectory = path.join( - fixture.root, - "repository", - "com", - "github", - artifactId, - version, - ); - fs.mkdirSync(publicationDirectory, { recursive: true }); - const primaryJar = `${artifactId}-${version}.jar`; - const linuxJar = `${artifactId}-${version}-linux-x64.jar`; - const linuxArm64Jar = `${artifactId}-${version}-linux-arm64.jar`; - const windowsJar = `${artifactId}-${version}-win32-x64.jar`; - const windowsArm64Jar = `${artifactId}-${version}-win32-arm64.jar`; - const darwinJar = `${artifactId}-${version}-darwin-arm64.jar`; - writeStoredZip(path.join(publicationDirectory, primaryJar), [ - ["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"], - ]); - writeStoredZip( - path.join(publicationDirectory, `${artifactId}-${version}-sources.jar`), - [], - ); - writeStoredZip( - path.join(publicationDirectory, `${artifactId}-${version}-javadoc.jar`), - [], - ); - createNativeClassifierTestFixture({ - classifier: "linux-x64", - outputPath: path.join(publicationDirectory, linuxJar), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier: "linux-arm64", - outputPath: path.join(publicationDirectory, linuxArm64Jar), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier, - outputPath: path.join(publicationDirectory, windowsJar), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier: "win32-arm64", - outputPath: path.join(publicationDirectory, windowsArm64Jar), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier: "darwin-arm64", - outputPath: path.join(publicationDirectory, darwinJar), - repoRoot: fixture.repoRoot, - }); - fs.writeFileSync( - path.join(publicationDirectory, `${artifactId}-${version}.pom`), - "", - ); - for (const artifact of [ - primaryJar, - `${artifactId}-${version}.pom`, - `${artifactId}-${version}-sources.jar`, - `${artifactId}-${version}-javadoc.jar`, - linuxJar, - linuxArm64Jar, - windowsJar, - windowsArm64Jar, - darwinJar, +for (const artifactId of ["copilot-sdk-java-runtime", "copilot-sdk-java"]) { + for (const version of ["0.0.0-ci", "1.2.3-SNAPSHOT"]) { + test(`validates signed ${artifactId} publication at ${version}`, (t) => { + const fixture = createPublicationFixture(t, { artifactId, version }); + + assert.equal( + validateLocalPublication({ ...fixture, requireSignatures: true }), + fixture.publicationDirectory, + ); + }); + } + + for (const { name, from, to, error } of [ + { + name: "unresolved revision", + from: "1.2.3", + to: "${revision}", + error: /unresolved.*revision/, + }, + { + name: "the committed snapshot instead of the release version", + from: "1.2.3", + to: "1.2.3-SNAPSHOT", + error: /Unexpected Maven coordinates/, + }, + { + name: "an incorrect group", + from: "com.github", + to: "org.example", + error: /Unexpected Maven coordinates/, + }, + { + name: "an incorrect artifact", + from: `${artifactId}`, + to: "different-artifact", + error: /Unexpected Maven coordinates/, + }, + { + name: "a dependency version in place of the project version", + from: "1.2.3", + to: "1.2.3", + error: /missing flattened project coordinates/, + }, + { + name: "a commented-out project version", + from: "1.2.3", + to: "", + error: /missing flattened project coordinates/, + }, + { + name: "an unpublished parent reference", + from: "4.0.0", + to: `4.0.0 + + com.github + copilot-sdk-java-parent + 1.2.3 + `, + error: /must not depend on a parent/, + }, + { + name: "unresolved revision in a dependency", + from: "", + to: "${revision}", + error: /unresolved.*revision/, + }, ]) { - fs.writeFileSync( - path.join(publicationDirectory, `${artifact}.asc`), - "signature", - ); + test(`${artifactId} publication rejects ${name}`, (t) => { + const fixture = createPublicationFixture(t, { artifactId }); + const pom = fs.readFileSync(fixture.pomPath, "utf8"); + assert.ok(pom.includes(from)); + fs.writeFileSync(fixture.pomPath, pom.replace(from, to)); + + assert.throws(() => validateLocalPublication(fixture), error); + }); } +} - assert.equal( - validateLocalPublication({ - artifactId, - repositoryPath: path.join(fixture.root, "repository"), - repoRoot: fixture.repoRoot, - requireSignatures: true, - version, - }), - publicationDirectory, +test("local release publication requires a POM signature", (t) => { + const fixture = createPublicationFixture(t); + fs.rmSync(`${fixture.pomPath}.asc`); + + assert.throws( + () => validateLocalPublication({ ...fixture, requireSignatures: true }), + /missing signature.*\.pom\.asc/, ); }); test("local publication validation rejects cross-classifier contamination", (t) => { - const fixture = createFixture(t); - const artifactId = "copilot-sdk-java-runtime"; - const version = "1.2.3"; - const publicationDirectory = path.join( - fixture.root, - "repository", - "com", - "github", - artifactId, - version, - ); - fs.mkdirSync(publicationDirectory, { recursive: true }); - - writeStoredZip( - path.join(publicationDirectory, `${artifactId}-${version}.jar`), - [["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"]], - ); + const fixture = createPublicationFixture(t); writeStoredZip( - path.join(publicationDirectory, `${artifactId}-${version}-sources.jar`), - [], - ); - writeStoredZip( - path.join(publicationDirectory, `${artifactId}-${version}-javadoc.jar`), - [], - ); - writeStoredZip( - path.join(publicationDirectory, `${artifactId}-${version}-linux-x64.jar`), + path.join( + fixture.publicationDirectory, + `${fixture.artifactId}-${fixture.version}-linux-x64.jar`, + ), [ ["native/linux-x64/runtime.node", "runtime"], ["native/linux-x64/copilot-runtime", "runtime wrapper"], @@ -467,55 +446,78 @@ test("local publication validation rejects cross-classifier contamination", (t) ["native/win32-x64/runtime.node", "wrong platform"], ], ); - createNativeClassifierTestFixture({ - classifier: "linux-arm64", - outputPath: path.join( - publicationDirectory, - `${artifactId}-${version}-linux-arm64.jar`, - ), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier: "win32-x64", - outputPath: path.join( - publicationDirectory, - `${artifactId}-${version}-win32-x64.jar`, - ), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier: "win32-arm64", - outputPath: path.join( - publicationDirectory, - `${artifactId}-${version}-win32-arm64.jar`, - ), - repoRoot: fixture.repoRoot, - }); - createNativeClassifierTestFixture({ - classifier: "darwin-arm64", - outputPath: path.join( - publicationDirectory, - `${artifactId}-${version}-darwin-arm64.jar`, - ), - repoRoot: fixture.repoRoot, - }); + + assert.throws(() => validateLocalPublication(fixture), /must not contain/); +}); + +function createPublicationFixture( + t, + { artifactId = "copilot-sdk-java-runtime", version = "1.2.3" } = {}, +) { + const fixture = createFixture(t); + const repositoryPath = path.join(fixture.root, "repository"); + const publicationDirectory = path.join( + repositoryPath, + "com", + "github", + artifactId, + version, + ); + fs.mkdirSync(publicationDirectory, { recursive: true }); + const pomPath = path.join( + publicationDirectory, + `${artifactId}-${version}.pom`, + ); fs.writeFileSync( - path.join(publicationDirectory, `${artifactId}-${version}.pom`), - "", + pomPath, + ` + + 4.0.0 + com.github + ${artifactId} + ${version} +`, ); - - assert.throws( - () => - validateLocalPublication({ - artifactId, - repositoryPath: path.join(fixture.root, "repository"), + for (const suffix of ["", "-sources", "-javadoc"]) { + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}${suffix}.jar`), + [["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"]], + ); + } + if (artifactId === "copilot-sdk-java-runtime") { + for (const nativeClassifier of [ + "linux-x64", + "linux-arm64", + "win32-x64", + "win32-arm64", + "darwin-arm64", + ]) { + createNativeClassifierTestFixture({ + classifier: nativeClassifier, + outputPath: path.join( + publicationDirectory, + `${artifactId}-${version}-${nativeClassifier}.jar`, + ), repoRoot: fixture.repoRoot, - requireSignatures: false, - version, - }), - /must not contain/, - ); -}); + }); + } + } + for (const artifact of fs.readdirSync(publicationDirectory)) { + fs.writeFileSync( + path.join(publicationDirectory, `${artifact}.asc`), + "signature", + ); + } + + return { + ...fixture, + artifactId, + version, + repositoryPath, + publicationDirectory, + pomPath, + }; +} function createFixture(t) { const fixtureParent = path.join( From 3cf9da418ad96f78c76a1f7b3e385dc9b2c354f4 Mon Sep 17 00:00:00 2001 From: SandraAhlgrimm Date: Thu, 10 Sep 2026 14:08:25 +0200 Subject: [PATCH 3/5] [Java] Fix local publication and release cache isolation Use Maven install for CI's local release assembly instead of invoking Central's deploy goal, and remove the ineffective skipPublishing profile. Disable dependency caching in the SHA-selectable release workflow, including automatic npm caching and the unnecessary cached CLI setup. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/java-publish-maven.yml | 15 +++++++-------- .github/workflows/java-sdk-tests.yml | 7 ++----- java/copilot-native/pom.xml | 24 ------------------------ 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml index 50580fa601..a2ae02d712 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -10,6 +10,8 @@ env: # the repository. The cross-language `vX.Y.Z` GitHub Release and the # `java/vX.Y.Z` traceability tag are created by .github/workflows/publish.yml # only after publication succeeds. +# Dispatches can select a different source SHA than the workflow's branch. +# Keep dependency caches disabled so that source cannot poison branch caches. on: workflow_dispatch: @@ -98,7 +100,6 @@ jobs: with: java-version: "25" distribution: "microsoft" - cache: "maven" - name: Determine release version id: versions @@ -145,11 +146,11 @@ jobs: with: java-version: "25" distribution: "microsoft" - cache: "maven" - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 + package-manager-cache: false - name: Build and validate linux-arm64 classifier run: | @@ -204,11 +205,11 @@ jobs: with: java-version: "25" distribution: "microsoft" - cache: "maven" - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 + package-manager-cache: false - name: Build and validate win32-x64 classifier run: | @@ -261,11 +262,11 @@ jobs: with: java-version: "25" distribution: "microsoft" - cache: "maven" - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 + package-manager-cache: false - name: Build and validate win32-arm64 classifier run: | @@ -318,11 +319,11 @@ jobs: with: java-version: "25" distribution: "microsoft" - cache: "maven" - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 + package-manager-cache: false - name: Build and validate darwin-arm64 classifier run: | @@ -383,13 +384,10 @@ jobs: fetch-depth: 1 persist-credentials: false - - uses: ./.github/actions/setup-copilot - - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: "25" distribution: "microsoft" - cache: "maven" server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD @@ -399,6 +397,7 @@ jobs: - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 + package-manager-cache: false - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index 1abb91cfe4..10baf64478 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -361,7 +361,7 @@ jobs: name: java-native-publication-darwin-arm64-${{ github.run_id }} path: ${{ github.workspace }}/java/native-publication-input/darwin - - name: Verify native inputs and deploy the complete local release + - name: Verify native inputs and install the complete local release run: | set -euo pipefail test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-linux-arm64.outputs.source_sha }}" @@ -409,11 +409,9 @@ jobs: --quick-generate-key 'Copilot SDK local validation ' rsa2048 sign 1d LOCAL_REPOSITORY="$GITHUB_WORKSPACE/java/copilot-native/target/local-publication-repository" rm -rf "$LOCAL_REPOSITORY" - mvn -B -pl copilot-native deploy -Prelease -DskipTests \ + mvn -B -pl copilot-native install -Prelease -DskipTests \ "-Drevision=$VERSION" \ -Dcopilot.native.libc=glibc \ - -Dcopilot.native.test.local.publication=true \ - -DskipPublishing=true \ "-Dcopilot.native.external.linux.arm64.classifier.path=$LINUX_ARM64_JAR" \ "-Dcopilot.native.external.win32.classifier.path=$WINDOWS_JAR" \ "-Dcopilot.native.external.win32.arm64.classifier.path=$WINDOWS_ARM64_JAR" \ @@ -423,7 +421,6 @@ jobs: "$LOCAL_REPOSITORY" copilot-sdk-java-runtime "$VERSION" .. --signatures mvn -B -pl sdk install -Prelease -DskipTests \ "-Drevision=$VERSION" \ - -DskipPublishing=true \ "-Dmaven.repo.local=$LOCAL_REPOSITORY" node copilot-native/scripts/validate-local-publication.mjs \ "$LOCAL_REPOSITORY" copilot-sdk-java "$VERSION" .. --signatures diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index b7d8c188cc..c4d244365b 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -884,30 +884,6 @@ - - - local-publication-validation - - - copilot.native.test.local.publication - true - - - - - - org.sonatype.central - central-publishing-maven-plugin - - true - - - - -