Skip to content

Commit 3d1d42c

Browse files
committed
wip jre bundling
1 parent 9639832 commit 3d1d42c

3 files changed

Lines changed: 275 additions & 52 deletions

File tree

.github/workflows/build.yml

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,58 @@
1-
name: Build Pipeline
1+
name: CI (build & test)
2+
23
on:
34
push:
4-
branches:
5-
- master
5+
branches: [ master ]
66
pull_request:
77
types: [opened, synchronize, reopened]
8+
89
jobs:
910
build:
10-
name: Build and analyze
11-
runs-on: ubuntu-latest
11+
name: Gradle build on ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
runs-on: ${{ matrix.os }}
15+
permissions:
16+
contents: read
17+
env:
18+
GRADLE_OPTS: -Dorg.gradle.daemon=false
19+
defaults:
20+
run:
21+
working-directory: de.peeeq.wurstscript
22+
23+
matrix:
24+
os: [ ubuntu-latest, windows-latest, macos-13, macos-14 ] # Linux, Win, macOS Intel, macOS ARM
25+
1226
steps:
13-
- uses: actions/checkout@v3
27+
- name: Checkout
28+
uses: actions/checkout@v4
1429
with:
15-
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
16-
- name: Set up JDK 17
17-
uses: actions/setup-java@v3
30+
fetch-depth: 0 # needed for your JGit version/describe
31+
32+
- name: Setup Temurin JDK 25
33+
uses: actions/setup-java@v4
1834
with:
19-
java-version: 25
20-
distribution: 'zulu' # Alternative distribution options are available
21-
- name: Cache Gradle packages
22-
uses: actions/cache@v3
35+
distribution: temurin
36+
java-version: '25'
37+
38+
- name: Validate Gradle wrapper
39+
uses: gradle/actions/wrapper-validation@v4
40+
41+
- name: Setup Gradle cache
42+
uses: gradle/actions/setup-gradle@v4
43+
44+
- name: Run tests
45+
run: ./gradlew test --no-daemon --stacktrace
46+
47+
# Build slim jlink image + package for the *current runner OS*
48+
- name: Package slim runtime
49+
run: ./gradlew checksumSlimCompilerDist --no-daemon --stacktrace
50+
51+
- name: Upload packaged artifact (per-OS)
52+
uses: actions/upload-artifact@v4
2353
with:
24-
path: ~/.gradle/caches
25-
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
26-
restore-keys: ${{ runner.os }}-gradle
27-
- name: Build & Run Tests
28-
working-directory: ./de.peeeq.wurstscript
29-
env:
30-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
31-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
32-
run: ./gradlew test --info
54+
name: wurst-compiler-${{ matrix.os }}
55+
path: |
56+
de.peeeq.wurstscript/build/releases/*.zip
57+
de.peeeq.wurstscript/build/releases/*.tar.gz
58+
de.peeeq.wurstscript/build/releases/*.sha256

.github/workflows/release.yml

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,65 @@
1-
name: Deploy Pipeline
1+
name: Release slim runtimes (jlink)
22

33
on:
44
push:
5-
branches:
6-
- master
5+
tags:
6+
- 'v*' # e.g. v1.8.1.0
77
workflow_dispatch:
88

99
jobs:
10-
deploy:
11-
name: Build WurstScript and Upload to GitHub Release
12-
runs-on: ubuntu-latest
10+
build:
11+
name: Build ${{ matrix.plat }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
include:
16+
- os: windows-latest
17+
plat: win-x64
18+
- os: ubuntu-latest
19+
plat: linux-x64
20+
- os: macos-13
21+
plat: macos-x64
22+
- os: macos-14
23+
plat: macos-arm64
24+
25+
runs-on: ${{ matrix.os }}
26+
permissions:
27+
contents: write
28+
env:
29+
GRADLE_OPTS: -Dorg.gradle.daemon=false
30+
defaults:
31+
run:
32+
working-directory: de.peeeq.wurstscript
1333

1434
steps:
15-
- name: Checkout code
35+
- name: Checkout
1636
uses: actions/checkout@v4
37+
with:
38+
fetch-depth: 0 # your version task uses git describe
1739

18-
- name: Set up JDK 17
40+
- name: Setup Temurin JDK 25
1941
uses: actions/setup-java@v4
2042
with:
21-
java-version: 17
2243
distribution: temurin
44+
java-version: '25'
2345

24-
- name: Grant Gradle permissions
25-
working-directory: ./de.peeeq.wurstscript
26-
run: chmod +x ./gradlew
46+
- name: Validate Gradle wrapper
47+
uses: gradle/actions/wrapper-validation@v4
2748

28-
- name: Build WurstScript zips
29-
working-directory: ./de.peeeq.wurstscript
30-
run: ./gradlew create_zips
49+
- name: Setup Gradle cache
50+
uses: gradle/actions/setup-gradle@v4
3151

32-
- name: Create or update nightly-master tag
33-
run: |
34-
git config user.name "github-actions[bot]"
35-
git config user.email "github-actions[bot]@users.noreply.github.com"
36-
git tag -f nightly-master
37-
git push -f origin nightly-master
52+
# Build fat jar + jdeps + jlink + package + checksum for this OS
53+
- name: Package slim runtime (per-OS)
54+
run: ./gradlew --no-daemon --stacktrace checksumSlimCompilerDist
3855

39-
- name: Create GitHub Release and upload zips
40-
uses: softprops/action-gh-release@v1
56+
- name: Upload to GitHub Release
57+
uses: softprops/action-gh-release@v2
4158
with:
42-
tag_name: nightly-master
43-
name: Nightly Build (master)
44-
body: "This release is automatically updated from the latest push to `master`."
45-
draft: false
46-
prerelease: true
59+
# Attaches the per-OS files produced on this runner
4760
files: |
48-
de.peeeq.wurstscript/build/distributions/wurstpack_complete.zip
49-
de.peeeq.wurstscript/build/distributions/wurstpack_compiler.zip
61+
de.peeeq.wurstscript/build/releases/wurst-compiler-*-*.zip
62+
de.peeeq.wurstscript/build/releases/wurst-compiler-*-*.tar.gz
63+
de.peeeq.wurstscript/build/releases/*.sha256
5064
env:
5165
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

de.peeeq.wurstscript/deploy.gradle

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import org.gradle.internal.os.OperatingSystem
12

3+
// ----------------------- Publishing (kept from your original) -----------------------
24
publishing {
35
publications {
46
mavenJava(MavenPublication) {
@@ -14,3 +16,184 @@ publishing {
1416
// or: maven { url = uri("${buildDir}/repo") }
1517
}
1618
}
19+
20+
// ----------------------- Tool discovery (jdeps/jlink via toolchain) -----------------
21+
def toolExecutable = { String toolName ->
22+
def svc = project.extensions.getByType(org.gradle.jvm.toolchain.JavaToolchainService)
23+
def launcher = svc.launcherFor(project.java.toolchain)
24+
def javaHome = launcher.get().metadata.installationPath.asFile
25+
def ext = OperatingSystem.current().isWindows() ? ".exe" : ""
26+
new File(javaHome, "bin/${toolName}${ext}").absolutePath
27+
}
28+
29+
// ----------------------- Common providers/locations ---------------------------------
30+
def fatJar = tasks.named('shadowJar').flatMap { it.archiveFile }
31+
32+
// Allow adding modules if reflection/ServiceLoader pulls them in:
33+
// ./gradlew jlinkRuntime25 -PextraJdkModules=java.desktop,jdk.crypto.ec
34+
def extraJdkModules = (project.findProperty("extraJdkModules") ?: "").toString().trim()
35+
36+
def jlinkWorkDir = layout.buildDirectory.dir("jlink")
37+
def modulesTxt = jlinkWorkDir.map { it.file("modules.txt") }
38+
def jreImageDir = layout.buildDirectory.dir("jre-wurst-25")
39+
40+
def os = OperatingSystem.current()
41+
def arch = System.getProperty("os.arch") // "amd64"/"x86_64"/"aarch64"
42+
def plat = os.isWindows() ? "win-x64"
43+
: os.isMacOsX() ? (arch == "aarch64" ? "macos-arm64" : "macos-x64")
44+
: "linux-x64"
45+
46+
def distRoot = layout.buildDirectory.dir("dist/slim-${plat}")
47+
def releasesDir = layout.buildDirectory.dir("releases")
48+
49+
// ----------------------- Tasks: jdeps → jlink → assemble → package ------------------
50+
51+
// 1) Detect JDK modules via jdeps (from the fat jar)
52+
tasks.register("jdepsModules") {
53+
description = "Detects required JDK modules for the fat compiler.jar via jdeps"
54+
group = "distribution"
55+
56+
inputs.file(fatJar)
57+
outputs.file(modulesTxt)
58+
59+
doLast {
60+
ExecOperations execOps = project.services.get(ExecOperations)
61+
def jdeps = toolExecutable("jdeps")
62+
def outBuf = new ByteArrayOutputStream()
63+
64+
modulesTxt.get().asFile.parentFile.mkdirs()
65+
66+
execOps.exec {
67+
commandLine jdeps,
68+
"--multi-release", "25",
69+
"--ignore-missing-deps",
70+
"--print-module-deps",
71+
fatJar.get().asFile.absolutePath
72+
standardOutput = outBuf
73+
}
74+
75+
def detected = outBuf.toString().trim()
76+
if (detected.isEmpty()) detected = "java.base"
77+
if (!extraJdkModules.isEmpty()) detected = detected + "," + extraJdkModules
78+
79+
modulesTxt.get().asFile.text = detected
80+
logger.lifecycle("[jdeps] Using modules: ${detected}")
81+
}
82+
}
83+
84+
// 2) Build slim runtime with jlink (overwrite if exists)
85+
tasks.register("jlinkRuntime25") {
86+
description = "Builds a slim Java 25 runtime image containing only the needed modules"
87+
group = "distribution"
88+
dependsOn("shadowJar", "jdepsModules")
89+
90+
inputs.file(modulesTxt)
91+
outputs.dir(jreImageDir)
92+
93+
doLast {
94+
ExecOperations execOps = project.services.get(ExecOperations)
95+
def jlink = toolExecutable("jlink")
96+
def mods = modulesTxt.get().asFile.text.trim()
97+
def outDir = jreImageDir.get().asFile
98+
99+
// jlink requires the output dir to NOT exist
100+
if (outDir.exists()) {
101+
project.delete(outDir)
102+
}
103+
outDir.parentFile.mkdirs()
104+
105+
execOps.exec {
106+
commandLine jlink,
107+
"--add-modules", mods,
108+
"--no-header-files",
109+
"--no-man-pages",
110+
"--strip-java-debug-attributes",
111+
"--compress=2",
112+
"--output", outDir.absolutePath
113+
}
114+
logger.lifecycle("[jlink] Runtime created at: ${outDir}")
115+
}
116+
}
117+
118+
// 3) Assemble folder layout: jre + compiler.jar (no manifest)
119+
tasks.register("assembleSlimCompilerDist", Copy) {
120+
description = "Assembles dist folder with slim JRE and compiler.jar (no manifest)."
121+
group = "distribution"
122+
dependsOn("jlinkRuntime25", "shadowJar")
123+
124+
from(jreImageDir) { into("wurst-runtime") }
125+
from(fatJar) { into("wurst-compiler") }
126+
into(distRoot)
127+
128+
doLast {
129+
logger.lifecycle("[dist] Folder ready at: ${distRoot.get().asFile}")
130+
}
131+
}
132+
133+
// 4a) Package ZIP on Windows
134+
tasks.register("packageSlimCompilerDistZip", Zip) {
135+
description = "Packages slim dist as a ZIP archive (Windows)."
136+
group = "distribution"
137+
enabled = os.isWindows()
138+
139+
dependsOn("assembleSlimCompilerDist")
140+
141+
from(distRoot)
142+
destinationDirectory.set(releasesDir)
143+
archiveFileName.set("wurst-compiler-${project.version}-${plat}.zip")
144+
}
145+
146+
// 4b) Package tar.gz on Linux/macOS
147+
tasks.register("packageSlimCompilerDistTar", Tar) {
148+
description = "Packages slim dist as a tar.gz archive (Linux/macOS)."
149+
group = "distribution"
150+
enabled = !os.isWindows()
151+
152+
dependsOn("assembleSlimCompilerDist")
153+
154+
from(distRoot)
155+
destinationDirectory.set(releasesDir)
156+
compression = Compression.GZIP
157+
archiveExtension.set("tar.gz")
158+
archiveFileName.set("wurst-compiler-${project.version}-${plat}.tar.gz")
159+
}
160+
161+
// 4c) OS-aware convenience wrapper
162+
tasks.register("packageSlimCompilerDist") {
163+
description = "Packages slim dist for the current platform (zip on Windows, tar.gz elsewhere)."
164+
group = "distribution"
165+
dependsOn(os.isWindows() ? "packageSlimCompilerDistZip" : "packageSlimCompilerDistTar")
166+
}
167+
168+
// 5) SHA-256 checksum for the packaged archive
169+
tasks.register("checksumSlimCompilerDist") {
170+
description = "Writes SHA-256 alongside the packaged archive."
171+
group = "distribution"
172+
dependsOn("packageSlimCompilerDist")
173+
174+
outputs.upToDateWhen { false }
175+
176+
doLast {
177+
def outDir = releasesDir.get().asFile
178+
def expectedZip = new File(outDir, "wurst-compiler-${project.version}-${plat}.zip")
179+
def expectedTarGz = new File(outDir, "wurst-compiler-${project.version}-${plat}.tar.gz")
180+
def archFile = expectedZip.exists() ? expectedZip : (expectedTarGz.exists() ? expectedTarGz : null)
181+
if (archFile == null) throw new GradleException("Archive not found in ${outDir}")
182+
183+
def md = java.security.MessageDigest.getInstance("SHA-256")
184+
archFile.withInputStream { is ->
185+
byte[] buf = new byte[8192]
186+
for (int r = is.read(buf); r != -1; r = is.read(buf)) {
187+
md.update(buf, 0, r)
188+
}
189+
}
190+
def hex = md.digest().collect { String.format("%02x", it) }.join()
191+
def sumFile = new File(outDir, archFile.name + ".sha256")
192+
sumFile.text = "${hex} ${archFile.name}\n"
193+
194+
logger.lifecycle("[sha256] ${sumFile.name} -> ${hex}")
195+
logger.lifecycle("[release] Upload: ${archFile.absolutePath}")
196+
}
197+
}
198+
199+

0 commit comments

Comments
 (0)