[Chore]: 准备 Qz-Miner 5.1.1 发布 #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Branch build | |
| on: | |
| push: | |
| branches: [ '**' ] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| prepare-baselines: | |
| name: Prepare GTNH baseline matrix | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| matrix: ${{ steps.baselines.outputs.matrix }} | |
| steps: | |
| - name: Checkout mod repo | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 32 | |
| submodules: recursive | |
| - name: Validate baseline manifest and build matrix | |
| id: baselines | |
| shell: bash | |
| run: | | |
| python3 <<'PY' | |
| import json | |
| import os | |
| import re | |
| from pathlib import Path | |
| SAFE_VALUE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}") | |
| def fail(message): | |
| raise SystemExit(message) | |
| def reject_duplicates(pairs): | |
| result = {} | |
| for key, value in pairs: | |
| if key in result: | |
| fail(f"duplicate JSON key: {key}") | |
| result[key] = value | |
| return result | |
| def reject_constant(value): | |
| fail(f"invalid JSON constant: {value}") | |
| manifest_path = Path("gradle/gtnh-baselines.json") | |
| raw = manifest_path.read_bytes() | |
| if len(raw) > 16384: | |
| fail("baseline manifest exceeds 16 KiB") | |
| try: | |
| text = raw.decode("ascii") | |
| except UnicodeDecodeError: | |
| fail("baseline manifest must contain ASCII only") | |
| data = json.loads( | |
| text, | |
| object_pairs_hook=reject_duplicates, | |
| parse_constant=reject_constant, | |
| ) | |
| if type(data) is not dict or set(data) != {"schemaVersion", "baselines"}: | |
| fail("baseline manifest must contain only schemaVersion and baselines") | |
| if type(data["schemaVersion"]) is not int or data["schemaVersion"] != 1: | |
| fail("unsupported baseline manifest schemaVersion") | |
| baselines = data["baselines"] | |
| if type(baselines) is not list or len(baselines) != 2: | |
| fail("baseline manifest must contain exactly two baselines") | |
| for index, baseline in enumerate(baselines): | |
| if type(baseline) is not dict or set(baseline) != {"manifest", "gregTechVersion"}: | |
| fail(f"baseline {index} must contain only manifest and gregTechVersion") | |
| for key in ("manifest", "gregTechVersion"): | |
| value = baseline[key] | |
| if type(value) is not str or SAFE_VALUE.fullmatch(value) is None: | |
| fail(f"baseline {index} has unsafe {key}") | |
| manifests = [baseline["manifest"] for baseline in baselines] | |
| if len(set(manifests)) != len(manifests): | |
| fail("baseline manifests must be unique") | |
| properties = Path("gradle.properties").read_text(encoding="utf-8") | |
| default_matches = re.findall( | |
| r"(?m)^\s*elytra\.manifest\.version\s*=\s*([^\s#]+)\s*$", | |
| properties, | |
| ) | |
| if len(default_matches) != 1 or SAFE_VALUE.fullmatch(default_matches[0]) is None: | |
| fail("gradle.properties must define one safe default manifest") | |
| if manifests.count(default_matches[0]) != 1: | |
| fail("default manifest must occur exactly once in the baseline manifest") | |
| matrix = json.dumps({"include": baselines}, separators=(",", ":")) | |
| with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output: | |
| print(f"matrix={matrix}", file=output) | |
| PY | |
| baseline: | |
| name: GTNH ${{ matrix.manifest }} | |
| needs: prepare-baselines | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| env: | |
| VERSION: 5.1.1-ci+${{ github.sha }} | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 2 | |
| matrix: ${{ fromJSON(needs.prepare-baselines.outputs.matrix) }} | |
| steps: | |
| - name: Checkout mod repo | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 32 | |
| submodules: recursive | |
| - name: Determine JDK versions | |
| id: list-jdk-versions | |
| shell: bash | |
| run: | | |
| ( | |
| echo 'java-versions<<EOF' | |
| echo 8 | |
| echo 17 | |
| echo 21 | |
| if [[ -f gradle/gradle-daemon-jvm.properties ]]; then | |
| yq -pprops -oprops '.toolchainVersion' gradle/gradle-daemon-jvm.properties | |
| fi | |
| echo EOF | |
| ) | tee -a "${GITHUB_OUTPUT}" | |
| - name: Set up JDK versions | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: ${{ steps.list-jdk-versions.outputs.java-versions }} | |
| distribution: 'zulu' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v5 | |
| with: | |
| build-scan-publish: true | |
| build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" | |
| build-scan-terms-of-use-agree: "yes" | |
| cache-disabled: true | |
| validate-wrappers: true | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Setup the workspace | |
| run: ./gradlew --info --stacktrace "-Pelytra.manifest.version=${{ matrix.manifest }}" setupCIWorkspace | |
| - name: Verify resolved GregTech baseline | |
| run: ./gradlew --info --stacktrace "-Pelytra.manifest.version=${{ matrix.manifest }}" "-Pqz.gtnh.expectedGregTechVersion=${{ matrix.gregTechVersion }}" verifyGtnhBaseline | |
| - name: Run tests | |
| run: ./gradlew --info --stacktrace "-Pelytra.manifest.version=${{ matrix.manifest }}" test | |
| - name: Run checks | |
| run: ./gradlew --info --stacktrace "-Pelytra.manifest.version=${{ matrix.manifest }}" check | |
| - name: Build the mod | |
| run: ./gradlew --info --stacktrace "-Pelytra.manifest.version=${{ matrix.manifest }}" build | |
| - name: Verify injected version in generated source and main JAR | |
| shell: bash | |
| run: | | |
| python3 <<'PY' | |
| import os | |
| import re | |
| import struct | |
| import zipfile | |
| from pathlib import Path | |
| expected = f"5.1.1-ci+{os.environ['GITHUB_SHA']}" | |
| declaration = re.compile( | |
| r'^\s*public\s+static\s+final\s+String\s+VERSION\s*=\s*"([^"]+)"\s*;\s*$', | |
| re.MULTILINE, | |
| ) | |
| generated = [] | |
| for path in sorted(Path("build").rglob("Tags.java")): | |
| values = declaration.findall(path.read_text(encoding="utf-8")) | |
| generated.extend((path, value) for value in values) | |
| if not generated: | |
| raise SystemExit("generated Tags.VERSION declaration not found") | |
| wrong = [(str(path), value) for path, value in generated if value != expected] | |
| if wrong: | |
| raise SystemExit(f"generated Tags.VERSION mismatch: {wrong!r}; expected {expected!r}") | |
| jars = sorted(Path("build/libs").glob("*.jar")) | |
| main_jars = [ | |
| path for path in jars | |
| if not path.name.endswith(("-dev.jar", "-sources.jar", "-javadoc.jar")) | |
| ] | |
| if len(main_jars) != 1: | |
| raise SystemExit(f"expected one main JAR, found {[str(path) for path in main_jars]!r}") | |
| with zipfile.ZipFile(main_jars[0]) as archive: | |
| class_bytes = archive.read("club/heiqi/qz_miner/Tags.class") | |
| if class_bytes[:4] != b"\xca\xfe\xba\xbe": | |
| raise SystemExit("generated Tags.class has an invalid classfile header") | |
| constants = [] | |
| offset = 8 | |
| constant_count = struct.unpack_from(">H", class_bytes, offset)[0] | |
| offset += 2 | |
| index = 1 | |
| fixed_width = {3: 4, 4: 4, 7: 2, 8: 2, 9: 4, 10: 4, 11: 4, | |
| 12: 4, 15: 3, 16: 2, 17: 4, 18: 4, 19: 2, 20: 2} | |
| while index < constant_count: | |
| tag = class_bytes[offset] | |
| offset += 1 | |
| if tag == 1: | |
| length = struct.unpack_from(">H", class_bytes, offset)[0] | |
| offset += 2 | |
| constants.append(class_bytes[offset:offset + length].decode("utf-8")) | |
| offset += length | |
| elif tag in (5, 6): | |
| offset += 8 | |
| index += 1 | |
| elif tag in fixed_width: | |
| offset += fixed_width[tag] | |
| else: | |
| raise SystemExit(f"unsupported constant-pool tag {tag}") | |
| index += 1 | |
| if expected not in constants: | |
| raise SystemExit(f"main JAR Tags.class does not contain exact version {expected!r}") | |
| PY | |
| build: | |
| name: build | |
| if: ${{ always() }} | |
| needs: [ prepare-baselines, baseline ] | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Require every baseline job to succeed | |
| shell: bash | |
| run: | | |
| test '${{ needs.prepare-baselines.result }}' = 'success' | |
| test '${{ needs.baseline.result }}' = 'success' |