Files
werkator/build.gradle.kts
16543f038b the sandbox config section is werkdock, not bwrap (#19)
`bwrap` named the mechanism one layer below the tool that actually runs it: since
v1.0.0 Werkator does not invoke bwrap at all, it shells out to the werkdock CLI —
which made `bwrap.werkdock` a key naming its own executor.

The section is `werkdock` now and that key is `werkdock.binary`; BwrapConfig,
BwrapOverrides and BwrapBuildRunner follow the name. A file still writing `bwrap`
is read as before and warned about once per file, in `renameLegacySandbox` on the
raw map of every layer before merging — so nothing downstream knows two names, and
the old name is not a way around the pinning either. Renaming rather than refusing,
because the section lives in the machine configuration of every webspace instance,
which no repository tracks; the hard refusal belongs to the release that sets
ConfigVersions.FORMAT_BROKE_IN, where a file declaring no version can be caught
by name at all.

WERKATOR_SANDBOX in tools/remote follows, and still accepts `bwrap`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #19
2026-09-03 20:41:18 +02:00

176 lines
6.5 KiB
Kotlin

import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("org.springframework.boot") version "4.0.6"
id("io.spring.dependency-management") version "1.1.7"
id("org.jlleitschuh.gradle.ktlint") version "14.2.0"
kotlin("jvm") version "2.2.0"
kotlin("plugin.spring") version "2.2.0"
}
group = "de.hoennig"
// bump at least the patch version for every deployment — and only then, not per commit —
// so the UI footer (BuildProperties), --version and the release notes identify what is
// actually running; a deployment bundles whatever was committed since the last one.
// ReleaseVersionConsistencyTest fails the build if this and the top releases.html entry disagree.
version = "1.2.0"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework:spring-web")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("info.picocli:picocli-spring-boot-starter:4.7.6")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
testImplementation("org.springframework.boot:spring-boot-starter-test")
// @WebMvcTest lives in its own module since Spring Boot 4
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
// Kotest
testImplementation("io.kotest:kotest-runner-junit5:6.1.5")
testImplementation("io.kotest:kotest-assertions-core:6.1.5")
testImplementation("io.kotest:kotest-extensions-spring:6.1.5")
// MockK
testImplementation("io.mockk:mockk:1.13.11")
testImplementation("com.ninja-squad:springmockk:5.0.1")
// WireMock
testImplementation("org.wiremock.integrations:wiremock-spring-boot:4.2.1")
// Testcontainers (versions managed by Spring Boot BOM)
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:testcontainers")
}
springBoot {
// exposes the project version to the UI footer via the BuildProperties bean;
// the volatile build time is excluded to keep builds repeatable
buildInfo {
properties {
excludes.set(setOf("time"))
}
}
}
tasks.bootJar {
// version-free jar name, so docs and scripts never contain the version;
// the version itself stays available via BuildProperties (UI footer, --version)
archiveFileName = "werkator.jar"
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
jvmTarget = JvmTarget.JVM_21
}
}
// Self-contained runtime bundle for hosts without a Java runtime (plan step 15, ADR 0006):
// a jlink-trimmed JRE plus werkator.jar plus the packaging/werkator launcher, packed as a tarball.
// The JDK module list below was computed from the exploded boot jar via
// jdeps -q --ignore-missing-deps --multi-release 21 --print-module-deps \
// --class-path 'BOOT-INF/lib/*' BOOT-INF/classes BOOT-INF/lib/*.jar
// plus runtime-only modules jdeps cannot detect: java.logging (Tomcat JULI),
// jdk.crypto.ec (TLS ECDHE), jdk.management (extended OS MXBeans), jdk.zipfs (nested jar access).
// Re-check the jdeps output when dependencies change.
val runtimeBundleModules =
listOf(
"java.base",
"java.compiler",
"java.desktop",
"java.instrument",
"java.logging",
"java.management",
"java.naming",
"java.net.http",
"java.prefs",
"java.scripting",
"java.security.jgss",
"java.sql",
"jdk.crypto.ec",
"jdk.jfr",
"jdk.management",
"jdk.unsupported",
"jdk.zipfs",
).joinToString(",")
val runtimeBundle by tasks.registering {
group = "distribution"
description = "Builds the self-contained runtime bundle (jlink JRE + jar + launcher) as a tar.gz"
dependsOn(tasks.bootJar)
val jdkHome = javaToolchains.launcherFor(java.toolchain).map { it.metadata.installationPath.asFile }
val jarFile = tasks.bootJar.flatMap { it.archiveFile }
val launcherFile = layout.projectDirectory.file("packaging/werkator").asFile
val stagingDir =
layout.buildDirectory
.dir("runtime-bundle")
.get()
.asFile
val tarballFile =
layout.buildDirectory
.file("distributions/werkator-runtime-linux-x64.tar.gz")
.get()
.asFile
inputs.files(tasks.bootJar.map { it.outputs.files })
inputs.file(launcherFile)
inputs.property("modules", runtimeBundleModules)
outputs.file(tarballFile)
doLast {
val bundleRoot = stagingDir.resolve("werkator")
bundleRoot.deleteRecursively()
bundleRoot.parentFile.mkdirs()
val jlink = jdkHome.get().resolve("bin/jlink")
val jlinkProcess =
ProcessBuilder(
jlink.absolutePath,
"--add-modules",
runtimeBundleModules,
"--strip-debug",
"--no-header-files",
"--no-man-pages",
"--compress",
"zip-6",
"--output",
bundleRoot.resolve("jre").absolutePath,
).redirectErrorStream(true).start()
val jlinkOutput = jlinkProcess.inputStream.bufferedReader().readText()
check(jlinkProcess.waitFor() == 0) { "jlink failed:\n$jlinkOutput" }
jarFile.get().asFile.copyTo(bundleRoot.resolve("lib/werkator.jar").also { it.parentFile.mkdirs() })
val launcher = launcherFile.copyTo(bundleRoot.resolve("bin/werkator").also { it.parentFile.mkdirs() })
check(launcher.setExecutable(true, false)) { "cannot make $launcher executable" }
tarballFile.parentFile.mkdirs()
// system tar preserves the execute bits of jre/bin/* and jre/lib/jspawnhelper
val tarProcess =
ProcessBuilder("tar", "-czf", tarballFile.absolutePath, "-C", stagingDir.absolutePath, "werkator")
.redirectErrorStream(true)
.start()
val tarOutput = tarProcess.inputStream.bufferedReader().readText()
check(tarProcess.waitFor() == 0) { "tar failed:\n$tarOutput" }
}
}
tasks.withType<Test> {
useJUnitPlatform()
}