From 75ce4e7de4857cd637f381c00a3663a6bb92a1d9 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:14:04 +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 | 158 +++++++++------------------- 5 files changed, 143 insertions(+), 144 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 8ea2fcf..034d4f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,16 +24,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" + - name: Upload plugin jar uses: actions/upload-artifact@v7 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 5da4e31..349be05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,5 +14,3 @@ 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/CoreProtect-{version}.jar diff --git a/.gitignore b/.gitignore index b0a27a2..b90d7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,11 @@ nbbuild/ nbdist/ *.class dependency-reduced-pom.xml +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties # Build dependencies /.build/ @@ -48,113 +53,50 @@ Desktop.ini # Local investigation notes and test artifacts /.scratch/ -# Repository-specific files -*.pydevproject -.metadata -bin/ -tmp/ -*~.nib -local.properties -.loadpath -.gradle/ -.externalToolBuilders/ -*.launch -.cproject -.buildpath -nb-configuration.xml -nbbuild.xml -nbproject -src/test -maven_output.txt -mvnw -mvnw.cmd -gradle -gradlew -gradlew.bat -*.suo -*.user -*.sln.docstates -[Dd]ebug/ -[Rr]elease/ -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.vspscc -.builds -*.dotCover -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.psess -*.vsp -_ReSharper* -[Ee]xpress -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html -publish -[Bb]in -[Oo]bj -sql -TestResults -*.Cache -ClientBin -stylecop.* -~$* -*.dbmdl -Generated_Code #added for RIA/Silverlight projects -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -.claude -AGENTS.md -CLAUDE.md -.cursorrules -.windsurfrules -.clinerules -context.json -context.yml -issues.md -run.sh -run_*.sh -no-log4j2.xml -database.db -*tests.txt -test*.db -nullnull -refactor.txt -*.ctxt -.mtj.tmp/ -*.war -*.ear -*.zip -*.tar.gz -*.rar -docs/dev/ -CoreProtect-TestAPI -CoreProtect-Fabric -CoreProtect-Hytale -publish/ -/src/main/java/net/tfminecraft/coreprotect/utility/extensions -!/src/main/java/net/tfminecraft/coreprotect/utility/extensions/Extensions.java +# Repository-specific generated or local files +/bin/ +/tmp/ +/.metadata/ +/.gradle/ +/.externalToolBuilders/ /.apt_generated/ /.apt_generated_tests/ -server/* +/.mtj.tmp/ +/local.properties +/.loadpath +/.cproject +/.buildpath +/nb-configuration.xml +/nbbuild.xml +/maven_output.txt +/.claude/ +/AGENTS.md +/CLAUDE.md +/.cursorrules +/.windsurfrules +/.clinerules +/context.json +/context.yml +/issues.md +/run.sh +/run_*.sh +/no-log4j2.xml +/database.db +/test*.db +/*tests.txt +/nullnull +/refactor.txt +/*.ctxt +/*.war +/*.ear +/*.zip +/*.tar.gz +/*.rar +/docs/dev/ +/CoreProtect-TestAPI/ +/CoreProtect-Fabric/ +/CoreProtect-Hytale/ +/publish/ +/server/ +/src/main/java/net/tfminecraft/coreprotect/utility/extensions/* +!/src/main/java/net/tfminecraft/coreprotect/utility/extensions/Extensions.java