diff --git a/.github/scripts/test_release_workflow.py b/.github/scripts/test_release_workflow.py index 794f71b..a2f642d 100644 --- a/.github/scripts/test_release_workflow.py +++ b/.github/scripts/test_release_workflow.py @@ -70,13 +70,14 @@ def validate(self, minecraft, names=None, mutation=None, release_exists=False, w '1.16.5': ('116051', 'master-1.16.5', '36.2.42'), '1.17.1': ('117011', 'master-1.17.1', '37.1.1'), '1.18.2': ('118021', 'master-1.18.2', '40.3.12'), - '1.19.4': ('119041', 'master-1.19.4', '45.4.5')}[minecraft] + '1.19.4': ('119041', 'master-1.19.4', '45.4.5'), + '1.20.1': ('120011', 'master-1.20.1', '47.4.28')}[minecraft] version = '2.4.2.' + suffix files={name:'C'*64 for name in ['main.jar','sources.jar','javadoc.jar','release.pom','CHANGELOG.txt','SHA256SUMS']} receipt={'schema':1,'source':SHA,'version':version,'files':files,'evidence_sha256':'F'*64,'qualified_locally':True,'manual_accepted':True} if local_receipt is not None:receipt=local_receipt values = dict(minecraft_version=minecraft, forge_version=forge, loader_name='forge', loader_code='1', - java_version='17' if minecraft in ('1.18.2','1.19.4') else '16' if minecraft=='1.17.1' else '8', java_toolchain_version='17.0.20.1+1' if minecraft in ('1.18.2','1.19.4') else '16.0.2+7' if minecraft=='1.17.1' else '8.0.502+7', gradle_java_version='17', mod_id='lootablebodies', + java_version='17' if minecraft in ('1.18.2','1.19.4','1.20.1') else '16' if minecraft=='1.17.1' else '8', java_toolchain_version='17.0.20.1+1' if minecraft in ('1.18.2','1.19.4','1.20.1') else '16.0.2+7' if minecraft=='1.17.1' else '8.0.502+7', gradle_java_version='17', mod_id='lootablebodies', mod_artifact='LootableBodies', mod_group='zone.moddev.mc.lootablebodies', mod_license='LGPL-2.1-only', curseforge_project_id='240968', mod_version=version) if mutation: @@ -86,13 +87,13 @@ def validate(self, minecraft, names=None, mutation=None, release_exists=False, w '1.14.4': MATRIX114 + ['Validate release workflow'], '1.15.2': MATRIX115, '1.16.5': MATRIX116}.get(minecraft, []) if names is None else names if minecraft=='1.17.1' and names is None:checks=CORE117+MATRIX117 - if minecraft in ('1.18.2','1.19.4') and names is None:checks=CORE118 + if minecraft in ('1.18.2','1.19.4','1.20.1') and names is None:checks=CORE118 def command(args, **options): if args[0] == 'git': return SHA + '\n' - if any('/actions/workflows/native'+code+'.yml/' in args[-1] for code in ['117','118','119']): - code={'1.17.1':'117','1.18.2':'118','1.19.4':'119'}[minecraft] + if any('/actions/workflows/native'+code+'.yml/' in args[-1] for code in ['117','118','119','120']): + code={'1.17.1':'117','1.18.2':'118','1.19.4':'119','1.20.1':'120'}[minecraft] row=dict(id=int(code),head_sha=SHA,head_branch=branch,event='push',path='.github/workflows/native'+code+'.yml',conclusion='success') if qualification:row.update(qualification) return json.dumps({'workflow_runs':[row]}) @@ -116,8 +117,8 @@ def existing(args, **options): patch('subprocess.check_output', side_effect=command), patch('subprocess.run', side_effect=existing): exec(compile(code, 'release-preflight', 'exec'), {}) - def test_future_120_candidate_cannot_dispatch_publication(self): - code, values = self.select('2.4.11.120011') + def test_future_1206_candidate_cannot_dispatch_publication(self): + code, values = self.select('2.4.12.120061') self.assertNotEqual(0, code) self.assertEqual({}, values) @@ -336,6 +337,24 @@ def test_119_requires_manual_acceptance_and_exact_artifacts(self): self.assertNotIn('gradlew', step) self.assertIn("minecraft_version == '1.19.4' && 'Java 17'", WORKFLOW.read_text()) + def test_120_requires_manual_acceptance_and_exact_artifacts(self): + code, values = self.select('2.4.11.120011') + self.assertEqual(0, code) + self.assertEqual('master-1.20.1', values['source_branch']) + self.validate('1.20.1') + for missing in CORE118: + with self.subTest(missing=missing), self.assertRaises(SystemExit): + self.validate('1.20.1', names=[name for name in CORE118 if name != missing]) + for kwargs in [{'audit_success':False}, {'local_receipt':{}}, + {'qualification':{'path':'.github/workflows/native118.yml'}}, + {'mutation':{'java_version':'8'}}]: + with self.subTest(kwargs=kwargs), self.assertRaises(SystemExit): + self.validate('1.20.1', **kwargs) + step = WORKFLOW.read_text().split('- name: Verify all six native120 bytes before publication', 1)[1].split(' - name:', 1)[0] + self.assertIn('python3 -B tools/native120_build.py --bundle build/release/bundle', step) + self.assertNotIn('gradlew', step) + self.assertIn("minecraft_version == '1.20.1' && 'Java 17'", WORKFLOW.read_text()) + def test_identity_changes_are_rejected(self): for mutation in [{'mod_id': 'other'}, {'minecraft_version': '1.10.2'}, {'mod_license': 'CC0-1.0'}]: with self.subTest(mutation=mutation), self.assertRaises(SystemExit): diff --git a/.github/workflows/artifact-audit-1.20.yml b/.github/workflows/artifact-audit-1.20.yml new file mode 100644 index 0000000..1f6bb58 --- /dev/null +++ b/.github/workflows/artifact-audit-1.20.yml @@ -0,0 +1,48 @@ +name: Audit LootableBodies 1.20 artifacts +run-name: Audit 1.20.1 artifacts from ${{ github.event.workflow_run.head_sha }} +on: + workflow_run: + workflows: [LootableBodies 1.20 CI] + types: [completed] +permissions: + contents: read + actions: read +jobs: + audit: + if: >- + github.repository == 'MinecraftModDevelopmentMods/LootableBodies' && + github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'master-1.20.1' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ github.event.workflow_run.head_sha }} + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: LootableBodies-1.20.1-${{ github.event.workflow_run.head_sha }} + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + path: audited + - name: Independently verify every locally qualified byte + env: + SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + python3 -B tools/native120_build.py --bundle audited + python3 - <<'PY' + import json,os + from pathlib import Path + accepted=json.loads(Path('.github/release120.json').read_text()) + Path('independent-audit.json').write_text(json.dumps({'source':os.environ['SOURCE_SHA'],'files':accepted['files'],'all_six_exact':True,'identical_to_locally_audited_bundle':True},indent=2)+'\n') + PY + # Exact hashes reuse the local class61/licence/identity/mapping audit. + # This independent receipt is a byte match, not a fresh gameplay run or + # another Gradle/Forge preparation. The build job audits new archives. + - name: Retain independent audit evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: native120-independent-audit-${{ github.event.workflow_run.head_sha }} + if-no-files-found: error + path: independent-audit.json diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index e8f824f..964caa2 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: inputs: release_version: - description: Complete version from gradle.properties, ending in 110021, 111021, 112021, 113021, 114041, 115021, 116051, 117011, 118021 or 119041. + description: Complete version from gradle.properties, ending in 110021, 111021, 112021, 113021, 114041, 115021, 116051, 117011, 118021, 119041 or 120011. required: true type: string curseforge_release_level: @@ -20,7 +20,7 @@ on: default: false type: boolean local_qualification: - description: Hash-bound local qualification receipt, required for the 1.17.1, 1.18.2 and 1.19.4 releases. + description: Hash-bound local qualification receipt, required for the 1.17.1, 1.18.2, 1.19.4 and 1.20.1 releases. required: false default: '' type: string @@ -54,7 +54,7 @@ jobs: set -euo pipefail [[ "$GITHUB_REPOSITORY" == 'MinecraftModDevelopmentMods/LootableBodies' ]] [[ "$GITHUB_REF" == 'refs/heads/master-1.12' ]] - [[ "$RELEASE_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(110021|111021|112021|113021|114041|115021|116051|117011|118021|119041)$ ]] + [[ "$RELEASE_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(110021|111021|112021|113021|114041|115021|116051|117011|118021|119041|120011)$ ]] case "$RELEASE_VERSION" in *.110021) minecraft_version=1.10.2; source_branch=1.10.2 ;; *.111021) minecraft_version=1.11.2; source_branch=1.11.2-dev ;; @@ -66,6 +66,7 @@ jobs: *.117011) minecraft_version=1.17.1; source_branch=master-1.17.1 ;; *.118021) minecraft_version=1.18.2; source_branch=master-1.18.2 ;; *.119041) minecraft_version=1.19.4; source_branch=master-1.19.4 ;; + *.120011) minecraft_version=1.20.1; source_branch=master-1.20.1 ;; *) exit 1 ;; esac echo "minecraft_version=$minecraft_version" >> "$GITHUB_OUTPUT" @@ -100,12 +101,13 @@ jobs: '1.16.5': ('master-1.16.5', '36.2.42', '116051'), '1.17.1': ('master-1.17.1', '37.1.1', '117011'), '1.18.2': ('master-1.18.2', '40.3.12', '118021'), - '1.19.4': ('master-1.19.4', '45.4.5', '119041')} + '1.19.4': ('master-1.19.4', '45.4.5', '119041'), + '1.20.1': ('master-1.20.1', '47.4.28', '120011')} branch, forge, suffix = targets[minecraft] if os.environ['SOURCE_BRANCH'] != branch: raise SystemExit('The selected branch does not match its Minecraft version') native17 = minecraft == '1.17.1' - archive_only = minecraft in ('1.18.2', '1.19.4') + archive_only = minecraft in ('1.18.2', '1.19.4', '1.20.1') locally_qualified = native17 or archive_only expected = {'minecraft_version': minecraft, 'forge_version': forge, 'loader_name': 'forge', 'loader_code': '1', 'java_version': '17' if archive_only else '16' if native17 else '8', @@ -149,13 +151,13 @@ jobs: except (ValueError,TypeError): raise SystemExit('A local qualification receipt is required') keys={'schema','source','version','files','evidence_sha256','qualified_locally','manual_accepted'} - target_code={'1.17.1':'117','1.18.2':'118','1.19.4':'119'}[minecraft] + target_code={'1.17.1':'117','1.18.2':'118','1.19.4':'119','1.20.1':'120'}[minecraft] metadata=json.loads(Path(f'.github/release{target_code}.json').read_text()) accepted=metadata['files'] if archive_only and (metadata.get('local_qualification_complete') is not True or metadata.get('qualification_integrity_status')!='verified'): raise SystemExit(f'The local {minecraft} qualification declaration is incomplete') - if minecraft == '1.19.4' and metadata.get('manual_accepted') is not True: - raise SystemExit('The local 1.19.4 candidate has not been accepted') + if minecraft in ('1.19.4', '1.20.1') and metadata.get('manual_accepted') is not True: + raise SystemExit(f'The local {minecraft} candidate has not been accepted') if (not isinstance(local,dict) or set(local)!=keys or type(local['schema']) is not int or local['schema']!=1 or local['source']!=sha or local['version']!=version or local['files']!=accepted or local['qualified_locally'] is not True or local['manual_accepted'] is not True @@ -173,7 +175,7 @@ jobs: matching = [check for check in checks if check['name'] == name] if not matching or max(matching, key=lambda c: c['id'])['conclusion'] != 'success': raise SystemExit(f'The selected commit has no successful {name} check') - if minecraft in ('1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19.4'): + if minecraft in ('1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19.4', '1.20.1'): target = minecraft.rsplit('.', 1)[0] audits = json.loads(subprocess.check_output(['gh', 'api', f'repos/{repo}/actions/workflows/artifact-audit-{target}.yml/runs?event=workflow_run&per_page=100'], text=True))['workflow_runs'] @@ -207,27 +209,27 @@ jobs: with: ref: ${{ needs.preflight.outputs.release_sha }} - name: Install Mavenizer Java - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: '25.0.3+9.0.LTS' - name: Install production Java - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin - java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }} + java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }} - name: Install Gradle Java - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin - java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || '17.0.1+12' }} + java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || '17.0.1+12' }} - name: Pin the production toolchain - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' run: | - if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' ]]; then + if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.20.1' ]]; then echo "LOOTABLEBODIES_JAVA17_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" elif [ '${{ needs.preflight.outputs.minecraft_version }}' = '1.17.1' ]; then echo "LOOTABLEBODIES_JAVA16_HOME=$JAVA_HOME_16_X64" >> "$GITHUB_ENV" @@ -236,7 +238,7 @@ jobs: fi echo "LOOTABLEBODIES_MAVENIZER_JAVA_HOME=$JAVA_HOME_25_X64" >> "$GITHUB_ENV" - name: Set up Gradle - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - name: Download the qualified native117 six-file bundle if: needs.preflight.outputs.minecraft_version == '1.17.1' @@ -280,8 +282,19 @@ jobs: - name: Verify all six native119 bytes before publication if: needs.preflight.outputs.minecraft_version == '1.19.4' run: python3 -B tools/native119_build.py --bundle build/release/bundle + - name: Download the qualified native120 six-file bundle + if: needs.preflight.outputs.minecraft_version == '1.20.1' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: LootableBodies-1.20.1-${{ needs.preflight.outputs.release_sha }} + run-id: ${{ needs.preflight.outputs.qualification_run }} + github-token: ${{ github.token }} + path: build/release/bundle + - name: Verify all six native120 bytes before publication + if: needs.preflight.outputs.minecraft_version == '1.20.1' + run: python3 -B tools/native120_build.py --bundle build/release/bundle - name: Build, test and audit the release - if: needs.preflight.outputs.minecraft_version != '1.17.1' && needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' + if: needs.preflight.outputs.minecraft_version != '1.17.1' && needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' run: | chmod +x ./gradlew ./gradlew clean build javadoc verifyEclipseProductionClasspath prepareReleaseBundle \ @@ -386,15 +399,15 @@ jobs: uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin - java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }} + java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }} - name: Install Gradle Java uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin - java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || '17.0.1+12' }} + java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || '17.0.1+12' }} - name: Pin the production toolchain run: | - if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' ]]; then + if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.20.1' ]]; then echo "LOOTABLEBODIES_JAVA17_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" elif [ '${{ needs.preflight.outputs.minecraft_version }}' = '1.17.1' ]; then echo "LOOTABLEBODIES_JAVA16_HOME=$JAVA_HOME_16_X64" >> "$GITHUB_ENV" @@ -452,7 +465,7 @@ jobs: game-versions: ${{ needs.preflight.outputs.minecraft_version }} game-version-filter: none environment: client | server - java: ${{ needs.preflight.outputs.minecraft_version == '1.19.4' && 'Java 17' || needs.preflight.outputs.minecraft_version == '1.17.1' && 'Java 16' || 'Java 8' }} + java: ${{ needs.preflight.outputs.minecraft_version == '1.19.4' && 'Java 17' || needs.preflight.outputs.minecraft_version == '1.20.1' && 'Java 17' || needs.preflight.outputs.minecraft_version == '1.17.1' && 'Java 16' || 'Java 8' }} fail-mode: fail files: | ${{ runner.temp }}/release-input/LootableBodies-${{ needs.preflight.outputs.release_version }}.jar diff --git a/.github/workflows/release-workflow-checks.yml b/.github/workflows/release-workflow-checks.yml index 2f3febd..19fbc6d 100644 --- a/.github/workflows/release-workflow-checks.yml +++ b/.github/workflows/release-workflow-checks.yml @@ -24,7 +24,7 @@ jobs: - name: Check release workflow syntax run: >- "$RUNNER_TEMP/actionlint/actionlint" -shellcheck= -pyflakes= - .github/workflows/build.yml .github/workflows/migration-matrix.yml .github/workflows/deploy-release.yml .github/workflows/release-workflow-checks.yml .github/workflows/artifact-audit-1.11.yml .github/workflows/artifact-audit-1.12.yml .github/workflows/artifact-audit-1.13.yml .github/workflows/artifact-audit-1.14.yml .github/workflows/artifact-audit-1.15.yml .github/workflows/artifact-audit-1.16.yml .github/workflows/artifact-audit-1.17.yml .github/workflows/artifact-audit-1.18.yml .github/workflows/artifact-audit-1.19.yml + .github/workflows/build.yml .github/workflows/migration-matrix.yml .github/workflows/deploy-release.yml .github/workflows/release-workflow-checks.yml .github/workflows/artifact-audit-1.11.yml .github/workflows/artifact-audit-1.12.yml .github/workflows/artifact-audit-1.13.yml .github/workflows/artifact-audit-1.14.yml .github/workflows/artifact-audit-1.15.yml .github/workflows/artifact-audit-1.16.yml .github/workflows/artifact-audit-1.17.yml .github/workflows/artifact-audit-1.18.yml .github/workflows/artifact-audit-1.19.yml .github/workflows/artifact-audit-1.20.yml - name: Test version selection and publication checks run: python3 .github/scripts/test_release_workflow.py -v