2222permissions :
2323 contents : read
2424
25+ # Same prerelease version must not race its release/assets. Stable releases also
26+ # share the mutable Docker `latest` tag, so serialize all stable versions.
27+ concurrency :
28+ group : release-${{ contains(inputs.version, '-') && format('prerelease-{0}', inputs.version) || 'stable' }}
29+ cancel-in-progress : false
30+
2531jobs :
2632 # ====================================================
2733 # Job 0: 解析发布元信息
@@ -149,6 +155,26 @@ jobs:
149155 ref : ${{ needs.resolve_release_meta.outputs.source_ref }}
150156 token : ${{ secrets.DEPENDENCY_REPO_TOKEN }}
151157 fetch-depth : 0
158+ # Pull only the Maven reactor, release scripts/config and Element SPA.
159+ sparse-checkout : |
160+ chains-config
161+ java-chains-all
162+ java-chains-cli
163+ java-chains-cli-remote
164+ java-chains-common
165+ java-chains-core
166+ java-chains-exploit-api
167+ java-chains-exploits
168+ java-chains-log
169+ java-chains-nodes
170+ java-chains-plugin-api
171+ java-chains-rmi-rmg
172+ java-chains-server
173+ java-chains-tests
174+ java-chains-thirdparty
175+ java-chains-tomcat-tests
176+ java-chains-web-element
177+ scripts
152178 persist-credentials : false
153179
154180 - name : Checkout Release Packaging Script
@@ -177,6 +203,7 @@ jobs:
177203 id : frontend
178204 shell : bash
179205 run : |
206+ set -euo pipefail
180207 test -f java-chains-web-element/package.json
181208 test -f java-chains-web-element/pnpm-lock.yaml
182209 FRONTEND_SHA="$(git log -1 --format=%H -- java-chains-web-element)"
@@ -223,9 +250,12 @@ jobs:
223250 local ref="$3"
224251 local url="https://github.com/${repository}.git"
225252
226- echo "Cloning ${repository} @ ${ref}"
227- "$AUTHED" clone "$url" "$directory"
228- git -C "$directory" checkout "$ref"
253+ echo "Fetching ${repository} @ ${ref} (shallow)"
254+ mkdir "$directory"
255+ git -C "$directory" init -q
256+ git -C "$directory" remote add origin "$url"
257+ "$AUTHED" -C "$directory" fetch --depth=1 --no-tags origin "$ref"
258+ git -C "$directory" checkout --detach FETCH_HEAD
229259 echo "Resolved $(git -C "$directory" rev-parse HEAD) for ${repository}"
230260 }
231261
@@ -243,24 +273,18 @@ jobs:
243273 with :
244274 maven-version : 3.9.6
245275
246- - name : Set up Node.js
247- uses : actions/setup-node@v4
248- with :
249- # Match chains gate.yml / java-chains-server frontend.node.version
250- node-version : ' 22.12.0'
251-
276+ # pnpm must be available before setup-node resolves the pnpm store path.
252277 - uses : pnpm/action-setup@v4
253278 with :
254279 version : 9.15.0
255280
256- - name : Cache Maven dependencies
257- uses : actions/cache @v4
281+ - name : Set up Node.js
282+ uses : actions/setup-node @v4
258283 with :
259- path : ~/.m2/repository
260- key : ${{ runner.os }}-maven-v2-${{ hashFiles('**/pom.xml') }}
261- restore-keys : |
262- ${{ runner.os }}-maven-v2-
263- ${{ runner.os }}-maven-
284+ # Match chains gate.yml / java-chains-server frontend.node.version
285+ node-version : ' 22.12.0'
286+ cache : ' pnpm'
287+ cache-dependency-path : java-chains-web-element/pnpm-lock.yaml
264288
265289 - name : Install Source-Tag Build Dependencies
266290 run : scripts/ci/install-local-deps.sh
@@ -279,6 +303,7 @@ jobs:
279303 # server target/ but a new maven.build.timestamp → Vector A session mismatch.
280304 # Product surface: server + java-chains-cli (no java-chains-sdk).
281305 MVN_ARGS=(
306+ --threads 1C
282307 -pl java-chains-server,java-chains-cli
283308 -am
284309 clean
@@ -415,7 +440,6 @@ jobs:
415440 if : always()
416441 run : |
417442 rm -rf release-deps
418- rm -rf ~/.m2/repository
419443
420444 if find release_base -type f \( \
421445 -name "*.java" -o -name "*.go" -o -name "*.ts" -o -name "*.tsx" \
@@ -445,6 +469,9 @@ jobs:
445469 with :
446470 name : base-artifacts
447471 path : release_base/
472+ # JARs are already compressed. Store them without another zlib pass;
473+ # this upload gates every packaging and Docker job.
474+ compression-level : 0
448475
449476 # ====================================================
450477 # Job 2: Create Release
@@ -633,9 +660,11 @@ jobs:
633660 RELEASE_TAG="${{ needs.resolve_release_meta.outputs.release_tag }}"
634661 ASSET_NAME="${{ env.DIST_DIR }}.${{ matrix.ext }}"
635662 if [ "${{ matrix.ext }}" = "zip" ]; then
636- zip -r -q "$ASSET_NAME" "${{ env.DIST_DIR }}"
663+ # JDKs/JARs are already compressed; level 1 avoids wasting CPU for
664+ # negligible size savings on the six parallel distribution jobs.
665+ zip -r -q -1 "$ASSET_NAME" "${{ env.DIST_DIR }}"
637666 else
638- tar -czf "$ASSET_NAME" "${{ env.DIST_DIR }}"
667+ tar -I 'gzip -1' -cf "$ASSET_NAME" "${{ env.DIST_DIR }}"
639668 fi
640669
641670 gh release upload "$RELEASE_TAG" "$ASSET_NAME" --clobber --repo "$GITHUB_REPOSITORY"
@@ -658,28 +687,49 @@ jobs:
658687 env :
659688 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
660689 run : |
690+ set -euo pipefail
661691 RELEASE_TAG="${{ needs.resolve_release_meta.outputs.release_tag }}"
662692 RELEASE_VERSION="${{ needs.resolve_release_meta.outputs.release_version }}"
663693 cd base
664694
665- tar -czvf "java-chains-${RELEASE_VERSION}.tar.gz" java-chains.jar chains-config
666- gh release upload "$RELEASE_TAG" "java-chains-${RELEASE_VERSION}.tar.gz" --clobber --repo "$GITHUB_REPOSITORY"
667-
668695 mkdir cli_dist
669696 cp java-chains-cli.jar cli_dist/
670697 cp -r chains-config cli_dist/
671698 if [ -f java-chains-cli-remote.jar ]; then
672699 cp java-chains-cli-remote.jar cli_dist/
673700 fi
674- tar -czvf "java-chains-cli-${RELEASE_VERSION}.tar.gz" -C cli_dist .
675- gh release upload "$RELEASE_TAG" "java-chains-cli-${RELEASE_VERSION}.tar.gz" --clobber --repo "$GITHUB_REPOSITORY"
701+
702+ # These archives are independent. Build both on separate runner cores,
703+ # then upload them with one GitHub API call.
704+ tar -I 'gzip -1' -cf "java-chains-${RELEASE_VERSION}.tar.gz" \
705+ java-chains.jar chains-config &
706+ SERVER_ARCHIVE_PID=$!
707+ tar -I 'gzip -1' -cf "java-chains-cli-${RELEASE_VERSION}.tar.gz" \
708+ -C cli_dist . &
709+ CLI_ARCHIVE_PID=$!
710+
711+ wait "$SERVER_ARCHIVE_PID"
712+ wait "$CLI_ARCHIVE_PID"
713+
714+ gh release upload "$RELEASE_TAG" \
715+ "java-chains-${RELEASE_VERSION}.tar.gz" \
716+ "java-chains-cli-${RELEASE_VERSION}.tar.gz" \
717+ --clobber --repo "$GITHUB_REPOSITORY"
676718
677719 # ====================================================
678- # Job 5: Docker Build
720+ # Job 5: Docker Build(每个架构使用独立 runner)
679721 # ====================================================
680722 docker_build :
681723 needs : [ resolve_release_meta, build ]
682724 runs-on : ubuntu-latest
725+ strategy :
726+ fail-fast : false
727+ matrix :
728+ include :
729+ - platform : linux/amd64
730+ suffix : amd64
731+ - platform : linux/arm64
732+ suffix : arm64
683733 steps :
684734 - uses : actions/checkout@v4
685735 with :
@@ -699,16 +749,107 @@ jobs:
699749 ls -la
700750
701751 - uses : docker/setup-qemu-action@v3
752+ if : matrix.platform == 'linux/arm64'
702753 - uses : docker/setup-buildx-action@v3
703754 - uses : docker/login-action@v3
704755 with :
705756 username : ${{ secrets.DOCKER_HUB_USERNAME }}
706757 password : ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
707758
708- - uses : docker/build-push-action@v6
759+ - name : Build and push ${{ matrix.platform }} by digest
760+ id : docker_build
761+ uses : docker/build-push-action@v6
709762 with :
710763 file : Dockerfile
711764 context : .
712- platforms : linux/amd64,linux/arm64
713- push : true
714- tags : ${{ needs.resolve_release_meta.outputs.docker_tags }}
765+ platforms : ${{ matrix.platform }}
766+ outputs : type=image,name=javachains/javachains,push-by-digest=true,name-canonical=true,push=true
767+ cache-from : type=gha,scope=java-chains-${{ matrix.suffix }}
768+ cache-to : type=gha,mode=max,scope=java-chains-${{ matrix.suffix }}
769+
770+ - name : Export image digest
771+ shell : bash
772+ env :
773+ IMAGE_DIGEST : ${{ steps.docker_build.outputs.digest }}
774+ run : |
775+ set -euo pipefail
776+ [[ "$IMAGE_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]
777+ mkdir -p /tmp/docker-digests
778+ touch "/tmp/docker-digests/${IMAGE_DIGEST#sha256:}"
779+
780+ - name : Upload image digest
781+ uses : actions/upload-artifact@v4
782+ with :
783+ name : docker-digest-${{ matrix.suffix }}
784+ path : /tmp/docker-digests/*
785+ if-no-files-found : error
786+ retention-days : 1
787+
788+ # ====================================================
789+ # Job 6: 合并 Docker 多架构 manifest
790+ # ====================================================
791+ docker_merge :
792+ needs : [ resolve_release_meta, docker_build ]
793+ runs-on : ubuntu-latest
794+ steps :
795+ - name : Download image digests
796+ uses : actions/download-artifact@v4
797+ with :
798+ pattern : docker-digest-*
799+ path : /tmp/docker-digests
800+ merge-multiple : true
801+
802+ - uses : docker/setup-buildx-action@v3
803+
804+ - uses : docker/login-action@v3
805+ with :
806+ username : ${{ secrets.DOCKER_HUB_USERNAME }}
807+ password : ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
808+
809+ - name : Create and verify multi-platform image
810+ shell : bash
811+ env :
812+ DOCKER_TAGS : ${{ needs.resolve_release_meta.outputs.docker_tags }}
813+ run : |
814+ set -euo pipefail
815+
816+ shopt -s nullglob
817+ DIGEST_FILES=(/tmp/docker-digests/*)
818+ [ "${#DIGEST_FILES[@]}" -eq 2 ] || {
819+ echo "Expected 2 Docker digests, found ${#DIGEST_FILES[@]}"
820+ exit 1
821+ }
822+
823+ TAG_ARGS=()
824+ while IFS= read -r tag; do
825+ [ -n "$tag" ] && TAG_ARGS+=(--tag "$tag")
826+ done <<< "$DOCKER_TAGS"
827+ [ "${#TAG_ARGS[@]}" -gt 0 ] || {
828+ echo "No Docker tags resolved"
829+ exit 1
830+ }
831+
832+ DIGEST_REFS=()
833+ for digest_file in "${DIGEST_FILES[@]}"; do
834+ digest="${digest_file##*/}"
835+ [[ "$digest" =~ ^[0-9a-f]{64}$ ]] || {
836+ echo "Invalid Docker digest file: $digest"
837+ exit 1
838+ }
839+ DIGEST_REFS+=("javachains/javachains@sha256:${digest}")
840+ done
841+
842+ docker buildx imagetools create "${TAG_ARGS[@]}" "${DIGEST_REFS[@]}"
843+
844+ while IFS= read -r tag; do
845+ [ -n "$tag" ] || continue
846+ INSPECT_OUTPUT="$(docker buildx imagetools inspect "$tag")"
847+ printf '%s\n' "$INSPECT_OUTPUT"
848+ for required_platform in linux/amd64 linux/arm64; do
849+ grep -Eq "Platform:[[:space:]]+${required_platform}([[:space:]]|$)" \
850+ <<< "$INSPECT_OUTPUT" || {
851+ echo "Docker tag $tag is missing $required_platform"
852+ exit 1
853+ }
854+ done
855+ done <<< "$DOCKER_TAGS"
0 commit comments