Skip to content

Commit 14ff199

Browse files
committed
ci: optimize element-only release workflow
1 parent 0a6f27e commit 14ff199

1 file changed

Lines changed: 170 additions & 29 deletions

File tree

.github/workflows/build-and-release.yml

Lines changed: 170 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ on:
2222
permissions:
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+
2531
jobs:
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

Comments
 (0)