-
Notifications
You must be signed in to change notification settings - Fork 16
242 lines (213 loc) · 8.87 KB
/
Copy pathbranch-ci.yml
File metadata and controls
242 lines (213 loc) · 8.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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'