From 0818cfd7758d623452c68a86b256210c96dff541 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:15:08 +0000 Subject: [PATCH] ci: unify plugin artifact and ignore conventions --- .github/scripts/plugin-artifact.py | 70 +++++++++++++++++++++++++++++ .github/workflows/build.yml | 15 +++++-- .github/workflows/maven-release.yml | 42 +++++------------ .github/workflows/release.yml | 2 - .gitignore | 9 +++- 5 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 .github/scripts/plugin-artifact.py diff --git a/.github/scripts/plugin-artifact.py b/.github/scripts/plugin-artifact.py new file mode 100644 index 0000000..587ca45 --- /dev/null +++ b/.github/scripts/plugin-artifact.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Validate the Maven runtime JAR and optionally stage a release bundle.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import shutil +import zipfile + + +def validate(source, version): + source = Path(source).resolve() + target = Path.cwd().resolve() / "target" + if not source.is_relative_to(target) or not source.is_file(): + raise ValueError("Expected the Maven runtime JAR inside target/") + if not source.name.endswith(f"-{version}.jar"): + raise ValueError("Runtime JAR filename must end with the Maven version") + with zipfile.ZipFile(source) as jar: + descriptors = {"plugin.yml", "paper-plugin.yml"}.intersection(jar.namelist()) + if not descriptors: + raise ValueError("Runtime JAR has no plugin descriptor") + if jar.testzip() is not None: + raise ValueError("Runtime JAR is corrupt") + for name in descriptors: + text = jar.read(name).decode("utf-8-sig") + versions = re.findall( + r'''^version:[ \t]*(?:"([^"\r\n]*)"|'([^'\r\n]*)'|([^\s#]+))(?:(?:[ \t]+\#[^\r\n]*)|[ \t]*)\r?$''', + text, re.MULTILINE, + ) + if len(versions) != 1 or next((v for v in versions[0] if v), "") != version: + raise ValueError(f"{name} version must match Maven version {version}") + return source + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--jar", required=True, type=Path) + parser.add_argument("--version", required=True) + parser.add_argument("--stage", type=Path) + parser.add_argument("--dependencies", type=Path) + args = parser.parse_args() + try: + source = validate(args.jar, args.version) + if args.stage: + if os.environ["TAG"] != f"v{args.version}": + raise ValueError("Release tag must match the Maven version") + dependencies = json.loads(args.dependencies.read_text()) if args.dependencies else [] + digest = hashlib.sha256(source.read_bytes()).hexdigest() + metadata = { + "repository": os.environ["GITHUB_REPOSITORY"], + "commit": os.environ["GITHUB_SHA"], + "tag": os.environ["TAG"], + "run": f"{os.environ['GITHUB_SERVER_URL']}/{os.environ['GITHUB_REPOSITORY']}/actions/runs/{os.environ['GITHUB_RUN_ID']}", + "plugin_dependencies": dependencies, + "artifact": source.name, + "sha256": digest, + } + args.stage.mkdir() + shutil.copyfile(source, args.stage / source.name) + (args.stage / "SHA256SUMS").write_text(f"{digest} {source.name}\n") + (args.stage / "build.json").write_text(json.dumps(metadata, indent=2) + "\n") + print(f"Verified {source.name}: filename and embedded version match {args.version}") + except (ValueError, OSError, KeyError, zipfile.BadZipFile) as error: + parser.exit(1, f"Plugin artifact validation failed: {error}\n") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a7a55c..451834f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,16 +30,25 @@ jobs: shell: bash run: | dev_version="DEV-$(date -u +%Y%m%d-%H%M)" - mvn -B --no-transfer-progress org.codehaus.mojo:versions-maven-plugin:2.22.0:set \ + mvn -B --no-transfer-progress -P'!deploy-live' org.codehaus.mojo:versions-maven-plugin:2.22.0:set \ -DnewVersion="$dev_version" -DgenerateBackupPoms=false - mvn -B --no-transfer-progress help:evaluate \ + mvn -B --no-transfer-progress -P'!deploy-live' help:evaluate \ -Dexpression=project.build.finalName -Doutput="$RUNNER_TEMP/final-name" final_name=$(cat "$RUNNER_TEMP/final-name") + [[ "$final_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1 + echo "version=$dev_version" >> "$GITHUB_OUTPUT" echo "name=$final_name" >> "$GITHUB_OUTPUT" echo "jar=target/$final_name.jar" >> "$GITHUB_OUTPUT" - name: Run unit tests and build - run: mvn -B --no-transfer-progress clean verify -DskipTests=false -Dmaven.test.skip=false + run: mvn -B --no-transfer-progress -P'!deploy-live' clean verify -DskipTests=false -Dmaven.test.skip=false + + - name: Verify runtime JAR + env: + ARTIFACT_PATH: ${{ steps.dev.outputs.jar }} + BUILD_VERSION: ${{ steps.dev.outputs.version }} + run: python3 .github/scripts/plugin-artifact.py --jar "$ARTIFACT_PATH" --version "$BUILD_VERSION" + - uses: actions/upload-artifact@v7 with: name: ${{ steps.dev.outputs.name }}-${{ github.run_id }}-${{ github.run_attempt }} diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml index 7cf726a..1659a9b 100644 --- a/.github/workflows/maven-release.yml +++ b/.github/workflows/maven-release.yml @@ -6,10 +6,6 @@ on: java-version: required: true type: string - artifact-path: - description: Exact deployable JAR path, with optional {version} placeholder - required: true - type: string secrets: DEPS_TOKEN: required: false @@ -59,6 +55,7 @@ jobs: fi - name: Check Maven version and build + id: maven env: TAG: ${{ github.ref_name }} run: | @@ -69,38 +66,21 @@ jobs: echo '::error::The tag must match project.version exactly.' exit 1 } - mvn -B --no-transfer-progress -P'!deploy-live' clean verify + mvn -B --no-transfer-progress -P'!deploy-live' help:evaluate \ + -Dexpression=project.build.finalName -Doutput="$RUNNER_TEMP/final-name" + final_name=$(cat "$RUNNER_TEMP/final-name") + [[ "$final_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1 + echo "jar=target/$final_name.jar" >> "$GITHUB_OUTPUT" + mvn -B --no-transfer-progress -P'!deploy-live' clean verify -DskipTests=false -Dmaven.test.skip=false - name: Stage only the release JAR env: - ARTIFACT_PATH: ${{ inputs.artifact-path }} + ARTIFACT_PATH: ${{ steps.maven.outputs.jar }} TAG: ${{ github.ref_name }} run: | - python3 - <<'PY' - import hashlib, json, os, pathlib, shutil, zipfile - root = pathlib.Path.cwd().resolve() - source = (root / os.environ['ARTIFACT_PATH'].replace('{version}', os.environ['TAG'][1:])).resolve() - if not source.is_relative_to(root / 'target') or not source.is_file() or source.suffix != '.jar': - raise SystemExit('Expected one existing JAR inside target/') - with zipfile.ZipFile(source) as jar: - if not {'plugin.yml', 'paper-plugin.yml'}.intersection(jar.namelist()): - raise SystemExit('Release JAR has no plugin descriptor') - if jar.testzip() is not None: - raise SystemExit('Release JAR is corrupt') - dest = pathlib.Path(os.environ['RUNNER_TEMP']) / 'plugin-release' - dest.mkdir() - shutil.copyfile(source, dest / source.name) - digest = hashlib.sha256((dest / source.name).read_bytes()).hexdigest() - (dest / 'SHA256SUMS').write_text(f'{digest} {source.name}\n') - (dest / 'build.json').write_text(json.dumps({ - 'repository': os.environ['GITHUB_REPOSITORY'], - 'commit': os.environ['GITHUB_SHA'], - 'tag': os.environ['TAG'], - 'run': f"{os.environ['GITHUB_SERVER_URL']}/{os.environ['GITHUB_REPOSITORY']}/actions/runs/{os.environ['GITHUB_RUN_ID']}", - 'artifact': source.name, - 'sha256': digest, - }, indent=2) + '\n') - PY + python3 .github/scripts/plugin-artifact.py \ + --jar "$ARTIFACT_PATH" --version "${TAG#v}" \ + --stage "$RUNNER_TEMP/plugin-release" - uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0e1b7d..241e773 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,5 @@ jobs: uses: ./.github/workflows/maven-release.yml with: java-version: '21' - # Exact path, not a glob. {version} is replaced with the tag without v. - artifact-path: target/goldsmithing-{version}.jar secrets: DEPS_TOKEN: ${{ secrets.DEPS_TOKEN }} diff --git a/.gitignore b/.gitignore index e37bc5b..fee0cd8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,16 @@ nbbuild/ nbdist/ *.class dependency-reduced-pom.xml +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties # Build dependencies /.build/ -/*.jar -/libs/*.jar +*.jar +!.mvn/wrapper/maven-wrapper.jar # Python caches and environments __pycache__/