`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
176 lines
6.5 KiB
Kotlin
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()
|
|
}
|