1+ import org.gradle.internal.os.OperatingSystem
12
3+ // ----------------------- Publishing (kept from your original) -----------------------
24publishing {
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