294 lines
13 KiB
Groovy
294 lines
13 KiB
Groovy
plugins {
|
|
id 'application'
|
|
id 'com.diffplug.spotless' version '7.0.2'
|
|
}
|
|
|
|
group = 'com.eliasmueller.llamamod'
|
|
// Release builds pass -PreleaseVersion=<tag without v> (see .gitea/workflows/release.yml).
|
|
version = findProperty('releaseVersion') ?: '0.2.1'
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
}
|
|
|
|
def lwjglVersion = '3.4.2'
|
|
def lwjglNatives = ['natives-linux', 'natives-windows']
|
|
|
|
dependencies {
|
|
implementation 'com.formdev:flatlaf:3.5.4'
|
|
implementation 'com.formdev:flatlaf-fonts-inter:4.1'
|
|
// level editor viewport: LWJGL (OpenGL) inside Swing via lwjgl3-awt; natives for both packages
|
|
implementation platform("org.lwjgl:lwjgl-bom:${lwjglVersion}")
|
|
implementation('org.lwjglx:lwjgl3-awt:0.2.5') { transitive = false }
|
|
implementation 'org.joml:joml:1.10.8'
|
|
['lwjgl', 'lwjgl-opengl', 'lwjgl-jawt', 'lwjgl-egl'].each { m ->
|
|
implementation "org.lwjgl:${m}"
|
|
if (m in ['lwjgl', 'lwjgl-opengl']) {
|
|
lwjglNatives.each { n -> runtimeOnly "org.lwjgl:${m}::${n}" }
|
|
}
|
|
}
|
|
testImplementation platform('org.junit:junit-bom:5.11.4')
|
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
}
|
|
|
|
def targetJavaVersion = 25
|
|
java {
|
|
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
|
}
|
|
|
|
tasks.withType(JavaCompile).configureEach {
|
|
options.encoding = 'UTF-8'
|
|
options.release.set(targetJavaVersion)
|
|
}
|
|
|
|
application {
|
|
mainClass = 'com.eliasmueller.llamamod.Main'
|
|
}
|
|
|
|
// build-info.properties: version and a build id (hash of the compiled classes and resources, without
|
|
// the edition switch, so an unchanged rebuild keeps the id and the mod cache stays valid)
|
|
def buildInfoDir = layout.buildDirectory.dir('generated/buildinfo')
|
|
tasks.register('buildInfo') {
|
|
def classes = sourceSets.main.output.classesDirs
|
|
def resources = fileTree('src/main/resources') { exclude 'com/eliasmueller/llamamod/edition.properties' }
|
|
def ver = project.version.toString()
|
|
def outDir = buildInfoDir
|
|
inputs.files(classes).withPathSensitivity(PathSensitivity.RELATIVE)
|
|
inputs.files(resources).withPathSensitivity(PathSensitivity.RELATIVE)
|
|
inputs.property('version', ver)
|
|
outputs.dir(outDir)
|
|
doLast {
|
|
def md = java.security.MessageDigest.getInstance('SHA-256')
|
|
def add = { File root ->
|
|
if (!root.exists()) {
|
|
return
|
|
}
|
|
def files = []
|
|
root.eachFileRecurse(groovy.io.FileType.FILES) { files << it }
|
|
files.sort { root.toPath().relativize(it.toPath()).toString().replace('\\', '/') }.each { f ->
|
|
if (f.name == 'edition.properties') {
|
|
return
|
|
}
|
|
md.update(root.toPath().relativize(f.toPath()).toString().replace('\\', '/').getBytes('UTF-8'))
|
|
md.update(f.bytes)
|
|
}
|
|
}
|
|
classes.files.each(add)
|
|
add(file('src/main/resources'))
|
|
def id = md.digest().encodeHex().toString().substring(0, 10)
|
|
def f = outDir.get().file('com/eliasmueller/llamamod/build-info.properties').asFile
|
|
f.parentFile.mkdirs()
|
|
f.text = "version=${ver}\nbuild=${id}\n"
|
|
}
|
|
}
|
|
|
|
// user manual (docs/manual, Markdown + images) bundled for the editor's Help → Manual window
|
|
processResources {
|
|
from('docs/manual') {
|
|
into 'com/eliasmueller/llamamod/editor/tools/help/manual'
|
|
}
|
|
from(tasks.named('buildInfo'))
|
|
}
|
|
|
|
test {
|
|
useJUnitPlatform()
|
|
// golden tests: -Dllamamod.cd=<cd dir> (or env LLAMAMOD_CD) -Dllamamod.ref=<python reference dir>;
|
|
// tests that need the CD are skipped when the folder does not exist
|
|
systemProperty 'llamamod.cd', System.getProperty('llamamod.cd', System.getenv('LLAMAMOD_CD') ?: "${System.getProperty('user.home')}/.wine-lama/iso-dk/Setup/Eng/Cd")
|
|
systemProperty 'llamamod.ref', System.getProperty('llamamod.ref', '')
|
|
// level builder references: python3 tools/levelbuild.py levels/testwelt.py <lbref>/testwelt.wad etc.
|
|
systemProperty 'llamamod.lbref', System.getProperty('llamamod.lbref', '')
|
|
// English groove.exe (2000-10-17) for the version tests; never committed, skipped when absent
|
|
systemProperty 'llamamod.exeEn', System.getProperty('llamamod.exeEn', "${projectDir}/re/groove_en.exe")
|
|
maxHeapSize = '2g'
|
|
}
|
|
|
|
// fat jars with all dependencies: full edition (fatJar) and launcher edition without the editor UI
|
|
// (fatJarLauncher, edition.properties editor=false; the editor classes stay, mods need them)
|
|
def editions = ['full', 'launcher']
|
|
def launcherEditionDir = layout.buildDirectory.dir('generated/edition-launcher')
|
|
tasks.register('launcherEdition') {
|
|
def outDir = launcherEditionDir
|
|
outputs.dir(outDir)
|
|
doLast {
|
|
def f = outDir.get().file('com/eliasmueller/llamamod/edition.properties').asFile
|
|
f.parentFile.mkdirs()
|
|
f.text = 'editor=false\n'
|
|
}
|
|
}
|
|
editions.each { ed ->
|
|
tasks.register(ed == 'full' ? 'fatJar' : 'fatJarLauncher', Jar) {
|
|
archiveClassifier = ed == 'full' ? 'all' : 'launcher'
|
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
manifest {
|
|
attributes 'Main-Class': application.mainClass, 'Implementation-Version': project.version, 'Multi-Release': 'true'
|
|
}
|
|
if (ed == 'full') {
|
|
from sourceSets.main.output
|
|
} else {
|
|
from(tasks.named('launcherEdition'))
|
|
from(sourceSets.main.output) { exclude 'com/eliasmueller/llamamod/edition.properties' }
|
|
}
|
|
dependsOn configurations.runtimeClasspath
|
|
from { configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) } }
|
|
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class', 'META-INF/versions/*/module-info.class'
|
|
}
|
|
}
|
|
|
|
spotless {
|
|
java {
|
|
target 'src/**/*.java'
|
|
palantirJavaFormat()
|
|
removeUnusedImports()
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------- packaging
|
|
// Self-contained Linux folder (+ .tar.gz) and Windows zip (LlamaMod.exe + runtime), both built on
|
|
// Linux. jlink cross-links using the Temurin jmods of the target platform; the jlink binary must
|
|
// be the exact same JDK version (JDK 25 ships no jmods, so they are downloaded separately).
|
|
// Downloads are cached in ~/opt (override: -Pllamamod.opt=<dir>).
|
|
def runtimeVersion = '25.0.5+8'
|
|
def optDir = file(findProperty('llamamod.opt') ?: "${System.getProperty('user.home')}/opt")
|
|
def distDir = layout.buildDirectory.dir('dist').get().asFile
|
|
// jdk.unsupported: sun.misc.Unsafe for LWJGL
|
|
def runtimeModules = 'java.base,java.desktop,java.prefs,java.logging,jdk.charsets,jdk.zipfs,jdk.unsupported'
|
|
def extraModules = [linux: '', windows: ',jdk.crypto.mscapi']
|
|
|
|
def adoptium = { String os, String imageType ->
|
|
"https://api.adoptium.net/v3/binary/version/jdk-${runtimeVersion.replace('+', '%2B')}/${os}/x64/${imageType}/hotspot/normal/eclipse"
|
|
}
|
|
def sh = { List cmd ->
|
|
def p = new ProcessBuilder(cmd.collect { it.toString() }).inheritIO().start()
|
|
if (p.waitFor() != 0) {
|
|
throw new GradleException("Command failed: ${cmd.join(' ')}")
|
|
}
|
|
}
|
|
def download = { String url, File target ->
|
|
if (!target.exists()) {
|
|
target.parentFile.mkdirs()
|
|
logger.lifecycle("Downloading ${url}")
|
|
def tmp = new File(target.path + '.part')
|
|
new URI(url).toURL().withInputStream { i -> tmp.withOutputStream { it << i } }
|
|
tmp.renameTo(target)
|
|
}
|
|
target
|
|
}
|
|
|
|
tasks.register('fetchJdk') {
|
|
description = 'Downloads the Linux JDK (for jlink) and the jmods of both target platforms.'
|
|
doLast {
|
|
def jdk = new File(optDir, "jdk-${runtimeVersion}")
|
|
if (!new File(jdk, 'bin/jlink').exists()) {
|
|
def tgz = download(adoptium('linux', 'jdk'), new File(optDir, "dl/jdk-${runtimeVersion}-linux.tar.gz"))
|
|
sh(['tar', 'xzf', tgz, '-C', optDir])
|
|
}
|
|
['linux': 'tar.gz', 'windows': 'zip'].each { os, ext ->
|
|
def dir = new File(optDir, "jmods-${runtimeVersion}-${os}")
|
|
if (!dir.exists()) {
|
|
def arc = download(adoptium(os, 'jmods'), new File(optDir, "dl/jmods-${runtimeVersion}-${os}.${ext}"))
|
|
def tmp = new File(optDir, "jmods-${runtimeVersion}-${os}.tmp")
|
|
project.delete(tmp)
|
|
tmp.mkdirs()
|
|
if (ext == 'zip') {
|
|
project.copy { from project.zipTree(arc); into tmp }
|
|
} else {
|
|
sh(['tar', 'xzf', arc, '-C', tmp])
|
|
}
|
|
// archives contain one top-level folder holding the *.jmod files
|
|
def jmodDir = fileTree(tmp).matching { include '**/java.base.jmod' }.singleFile.parentFile
|
|
jmodDir.renameTo(dir)
|
|
project.delete(tmp)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def jlinkTask = { String name, String os ->
|
|
tasks.register(name) {
|
|
dependsOn 'fetchJdk'
|
|
def out = new File(layout.buildDirectory.get().asFile, "runtime-${os}")
|
|
outputs.dir(out)
|
|
doLast {
|
|
project.delete(out)
|
|
sh([new File(optDir, "jdk-${runtimeVersion}/bin/jlink").path,
|
|
'--module-path', new File(optDir, "jmods-${runtimeVersion}-${os}").path,
|
|
'--add-modules', runtimeModules + extraModules[os],
|
|
'--strip-debug', '--no-header-files', '--no-man-pages',
|
|
'--compress', 'zip-9',
|
|
'--output', out.path])
|
|
}
|
|
}
|
|
}
|
|
jlinkTask('jlinkLinux', 'linux')
|
|
jlinkTask('jlinkWindows', 'windows')
|
|
|
|
def jarTask = { String ed -> ed == 'full' ? 'fatJar' : 'fatJarLauncher' }
|
|
def suffix = { String ed -> ed == 'full' ? '' : 'Launcher' }
|
|
def packageName = { String ed, String os -> "LlamaMod-${project.version}-${ed}-${os}-x64" }
|
|
|
|
editions.each { ed ->
|
|
def stage = new File(distDir, "${ed}/LlamaMod-linux-x64")
|
|
tasks.register("linuxImage${suffix(ed)}", Sync) {
|
|
dependsOn jarTask(ed), 'jlinkLinux'
|
|
from(layout.buildDirectory.dir('runtime-linux')) { into 'runtime' }
|
|
from(tasks.named(jarTask(ed))) { into 'lib'; rename { 'llamamod.jar' } }
|
|
from('packaging/linux') { filePermissions { unix('rwxr-xr-x') }; include 'LlamaMod', '*.sh' }
|
|
from('packaging/linux') { exclude 'LlamaMod', '*.sh' }
|
|
from('src/main/resources/com/eliasmueller/llamamod/ui/icon-256.png') { rename { 'llamamod.png' } }
|
|
into stage
|
|
// jlink marks legal/ files read-only, so Sync cannot overwrite them
|
|
doFirst { project.delete(stage) }
|
|
}
|
|
|
|
tasks.register("packageLinux${suffix(ed)}") {
|
|
description = "Self-contained Linux build (${ed} edition): build/dist/${packageName(ed, 'linux')}.tar.gz."
|
|
dependsOn "linuxImage${suffix(ed)}"
|
|
doLast {
|
|
// system tar keeps the executable bits of the runtime (Gradle's Tar normalizes them)
|
|
sh(['tar', 'czf', new File(distDir, "${packageName(ed, 'linux')}.tar.gz"), '-C', stage.parentFile, stage.name])
|
|
}
|
|
}
|
|
|
|
def exe = new File(layout.buildDirectory.get().asFile, "exe/${ed}/LlamaMod.exe")
|
|
tasks.register("exeWindows${suffix(ed)}") {
|
|
description = 'Wraps the fat jar into LlamaMod.exe with launch4j (Linux binary, downloaded to ~/opt).'
|
|
dependsOn jarTask(ed)
|
|
inputs.files(tasks.named(jarTask(ed)))
|
|
inputs.dir('packaging/windows')
|
|
outputs.file(exe)
|
|
doLast {
|
|
def l4j = new File(optDir, 'launch4j')
|
|
if (!new File(l4j, 'launch4j.jar').exists()) {
|
|
def tgz = download('https://sourceforge.net/projects/launch4j/files/launch4j-3/3.50/launch4j-3.50-linux-x64.tgz/download',
|
|
new File(optDir, 'dl/launch4j-3.50-linux-x64.tgz'))
|
|
sh(['tar', 'xzf', tgz, '-C', optDir])
|
|
}
|
|
exe.parentFile.mkdirs()
|
|
def cfg = new File(exe.parentFile, 'launch4j.xml')
|
|
cfg.text = file('packaging/windows/launch4j.xml.in').text
|
|
.replace('@JAR@', tasks.named(jarTask(ed)).get().archiveFile.get().asFile.path)
|
|
.replace('@OUT@', exe.path)
|
|
.replace('@ICON@', file('packaging/icon.ico').path)
|
|
.replace('@VERSION@', project.version.toString())
|
|
sh([new File(optDir, "jdk-${runtimeVersion}/bin/java").path, '-jar', new File(l4j, 'launch4j.jar').path, cfg.path])
|
|
}
|
|
}
|
|
|
|
tasks.register("packageWindows${suffix(ed)}", Zip) {
|
|
description = "Windows build (made on Linux, ${ed} edition): build/dist/${packageName(ed, 'windows')}.zip."
|
|
dependsOn "exeWindows${suffix(ed)}", 'jlinkWindows'
|
|
from(layout.buildDirectory.dir('runtime-windows')) { into 'LlamaMod/runtime' }
|
|
from(exe) { into 'LlamaMod' }
|
|
from('packaging/windows') { exclude '*.in'; into 'LlamaMod' }
|
|
destinationDirectory = distDir
|
|
archiveFileName = "${packageName(ed, 'windows')}.zip"
|
|
}
|
|
}
|
|
|
|
tasks.register('packageAll') {
|
|
description = 'Linux and Windows packages of both editions (full, launcher) in build/dist.'
|
|
dependsOn editions.collectMany { ["packageLinux${suffix(it)}", "packageWindows${suffix(it)}"] }
|
|
}
|