Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/scripts/plugin-artifact.py
Original file line number Diff line number Diff line change
@@ -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()
15 changes: 12 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,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
Expand Down
44 changes: 12 additions & 32 deletions .github/workflows/maven-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -64,6 +60,7 @@ jobs:
fi

- name: Check Maven version and build
id: maven
env:
TAG: ${{ github.ref_name }}
run: |
Expand All @@ -74,39 +71,22 @@ 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']}",
'plugin_dependencies': json.loads(pathlib.Path('.build/plugin-dependencies.json').read_text()),
'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" \
--dependencies .build/plugin-dependencies.json

- uses: actions/upload-artifact@v7
with:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/surgery-{version}.jar
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>net.tfminecraft</groupId>
<artifactId>surgery</artifactId>
<version>1.2.0</version>
<version>1.2.1</version>

<name>surgery</name>
<url>tfminecraft.net</url>
Expand Down