diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8423fe..be3140e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,17 +203,15 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 # The other jobs never touch the Java layer: the plugin jars were only ever - # compiled by bootstrap on a live host, and the three test mains under - # plugins/*/test were run by hand. JDK 21 plus the Gradle major the plugin - # Dockerfiles pin (8.14) is that same toolchain, in CI. + # compiled by bootstrap on a live host, and the test mains under plugins/*/test + # were run by hand. JDK 25 is what the plugin build image runs (paper-api 26.x + # needs it); each module's wrapper brings the Gradle the image pins. - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1 with: distribution: temurin - java-version: '21' + java-version: '25' - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - with: - gradle-version: '8.14' - run: bash plugins/test.sh diff --git a/bootstrap_asset.go b/bootstrap_asset.go index 2372844..723f706 100644 --- a/bootstrap_asset.go +++ b/bootstrap_asset.go @@ -22,15 +22,17 @@ var bootstrapAssets embed.FS // developer's working tree carries gradle output (plugins/*/build, plugins/*/bin, // and for the modded loaders a decompiled Minecraft under build/) which would // otherwise be baked into every felis binary. Keep them explicit — add a source -// directory here, never a parent. +// directory here, never a parent. Each module's gradle/verification-metadata.xml rides +// along, since Gradle builds unverified without it; the wrapper stays out, because the +// image builds run the pinned build image's own gradle. // //go:embed deploy/game-stack.lock //go:embed deploy/limbo/Dockerfile deploy/limbo/entrypoint.sh //go:embed deploy/lobby/Dockerfile deploy/lobby/entrypoint.sh //go:embed deploy/paper/Dockerfile deploy/paper/entrypoint.sh -//go:embed plugins/limbo/build.gradle plugins/limbo/settings.gradle plugins/limbo/src -//go:embed plugins/paper/build.gradle plugins/paper/settings.gradle plugins/paper/src -//go:embed plugins/velocity/build.gradle plugins/velocity/settings.gradle plugins/velocity/src +//go:embed plugins/limbo/build.gradle plugins/limbo/settings.gradle plugins/limbo/src plugins/limbo/gradle/verification-metadata.xml +//go:embed plugins/paper/build.gradle plugins/paper/settings.gradle plugins/paper/src plugins/paper/gradle/verification-metadata.xml +//go:embed plugins/velocity/build.gradle plugins/velocity/settings.gradle plugins/velocity/src plugins/velocity/gradle/verification-metadata.xml //go:embed plugins/shared/src var gameStackAssets embed.FS diff --git a/bootstrap_asset_test.go b/bootstrap_asset_test.go index 267694c..a4c8161 100644 --- a/bootstrap_asset_test.go +++ b/bootstrap_asset_test.go @@ -1,6 +1,7 @@ package felis import ( + "encoding/xml" "io/fs" "os" "regexp" @@ -210,17 +211,7 @@ func requireEmbedded(t *testing.T, path string) { // with a strict KEY=value parser that dies on anything unexpected, so a malformed lock is a // failed install on every host. Check the shipped copy the same way here. func TestGameStackLockIsComplete(t *testing.T) { - lock := map[string]string{} - for line := range strings.SplitSeq(readGameStackFile(t, "deploy/game-stack.lock"), "\n") { - if line == "" || strings.HasPrefix(line, "#") { - continue - } - k, v, ok := strings.Cut(line, "=") - if !ok { - t.Fatalf("not a KEY=value line: %q", line) - } - lock[k] = v - } + lock := gameStackLock(t) m := regexp.MustCompile(`GAME_STACK_LOCK_KEYS="([^"]*)"`).FindStringSubmatch(BootstrapScript()) if m == nil { t.Fatal("bootstrap.sh no longer declares GAME_STACK_LOCK_KEYS") @@ -313,6 +304,189 @@ func TestDockerfileBaseImagesArePinnedByDigest(t *testing.T) { } } +// The plugin jars are built in three places the installer controls — the lobby and limbo +// image builds and bootstrap's Velocity build — and through each module's wrapper by a +// developer or CI. A tag alone is whatever it points at on build day, and two Gradle +// versions are two chances for a build to pass in one place and break in the other, so +// all of them run one image, pinned by digest, whose Gradle is the wrappers' Gradle. +func TestPluginBuildsRunOnePinnedGradle(t *testing.T) { + sources := map[string]string{ + "deploy/lobby/Dockerfile": readGameStackFile(t, "deploy/lobby/Dockerfile"), + "deploy/limbo/Dockerfile": readGameStackFile(t, "deploy/limbo/Dockerfile"), + "deploy/bootstrap.sh": BootstrapScript(), + } + anyRef := regexp.MustCompile(`gradle:[\w.-]+(@sha256:\w+)?`) + pinned := regexp.MustCompile(`^gradle:(\d+\.\d+(?:\.\d+)?)-jdk\d+@sha256:[0-9a-f]{64}$`) + images := map[string]bool{} + gradle := "" + for name, body := range sources { + refs := anyRef.FindAllString(body, -1) + if len(refs) == 0 { + t.Errorf("%s names no gradle image", name) + } + for _, ref := range refs { + m := pinned.FindStringSubmatch(ref) + if m == nil { + t.Errorf("%s: %s is not a gradle image pinned by digest", name, ref) + continue + } + images[ref] = true + gradle = m[1] + } + } + if len(images) != 1 { + t.Fatalf("the plugin builds use %d different gradle images, want one: %v", len(images), images) + } + // bootstrap names the image once and has to spend it where it builds the jar. + if !strings.Contains(BootstrapScript(), `"$PLUGIN_BUILD_IMAGE" gradle --no-daemon clean build`) { + t.Error("build_velocity_plugin does not build in $PLUGIN_BUILD_IMAGE") + } + + for _, module := range []string{"velocity", "paper", "limbo"} { + props := wrapperProperties(t, module) + if want := "gradle-" + gradle + "-bin.zip"; !strings.HasSuffix(props["distributionUrl"], "/"+want) { + t.Errorf("plugins/%s wrapper runs %s; the image builds run Gradle %s", module, props["distributionUrl"], gradle) + } + } + // The mods are no part of the install, but a wrapper without a checksum runs whatever + // the download handed it. + for _, module := range []string{"velocity", "paper", "limbo", "fabric", "forge", "neoforge"} { + if sum := wrapperProperties(t, module)["distributionSha256Sum"]; !regexp.MustCompile(`^[0-9a-f]{64}$`).MatchString(sum) { + t.Errorf("plugins/%s wrapper pins no distribution sha256 (got %q)", module, sum) + } + } +} + +// Each plugin compiles against the API of the exact build the install runs, and Gradle +// checks those bytes against the module's verification file. Nothing but this test ties +// the three to deploy/game-stack.lock: a lock refresh that leaves them behind builds the +// lobby against yesterday's API, or fails every image build on a checksum the file does +// not have. +func TestPluginApisAreTheLockedBuilds(t *testing.T) { + lock := gameStackLock(t) + + // Paper: paper--.jar runs; paper-api .build.- compiles. + jar := regexp.MustCompile(`/paper-([^/]+)-(\d+)\.jar$`).FindStringSubmatch(lock["PAPER_JAR_URL"]) + if jar == nil { + t.Fatalf("PAPER_JAR_URL %s does not name paper--.jar", lock["PAPER_JAR_URL"]) + } + dep := regexp.MustCompile(`compileOnly 'io\.papermc\.paper:paper-api:([^']+)'`). + FindStringSubmatch(readGameStackFile(t, "plugins/paper/build.gradle")) + if dep == nil { + t.Fatal("plugins/paper/build.gradle declares no paper-api dependency") + } + if !regexp.MustCompile(`^` + regexp.QuoteMeta(jar[1]+".build."+jar[2]) + `(-[a-z]+)?$`).MatchString(dep[1]) { + t.Errorf("paper-api %s is not the API of the locked server paper-%s-%s.jar", dep[1], jar[1], jar[2]) + } + requireVerified(t, "paper", "io.papermc.paper", "paper-api", dep[1]) + + // Limbo: the lock's release, passed to the image build, which refuses to guess one. + limbo := lock["LIMBO_VERSION"] + if !strings.Contains(BootstrapScript(), `--build-arg LIMBO_VERSION="$LIMBO_VERSION"`) { + t.Error("bootstrap.sh does not pass the locked LIMBO_VERSION to the limbo image build") + } + dockerfile := readGameStackFile(t, "deploy/limbo/Dockerfile") + if !regexp.MustCompile(`(?m)^ARG LIMBO_VERSION$`).MatchString(dockerfile) || + !strings.Contains(dockerfile, `if [ -z "${LIMBO_VERSION:-}" ]`) { + t.Error("deploy/limbo/Dockerfile does not require LIMBO_VERSION; a build without it would " + + "compile against a version nobody chose") + } + // LOOHP publishes the jar the login gate runs as the Limbo API artifact itself, so the + // checksum Gradle holds for it is the lock's: compiled-against and running are one file. + if got := requireVerified(t, "limbo", "com.loohp", "Limbo", limbo)["Limbo-"+limbo+".jar"]; got != lock["LIMBO_JAR_SHA256"] { + t.Errorf("verification-metadata.xml holds %q for Limbo-%s.jar; the login gate runs %s", got, limbo, lock["LIMBO_JAR_SHA256"]) + } + + // Velocity: the API default is the proxy the install runs. + api := regexp.MustCompile(`findProperty\('velocityApi'\) \?: '([^']+)'`). + FindStringSubmatch(readGameStackFile(t, "plugins/velocity/build.gradle")) + if api == nil { + t.Fatal("plugins/velocity/build.gradle has no velocityApi default") + } + if api[1] != lock["VELOCITY_VERSION"] { + t.Errorf("velocity-api defaults to %s; the install runs Velocity %s", api[1], lock["VELOCITY_VERSION"]) + } + requireVerified(t, "velocity", "com.velocitypowered", "velocity-api", api[1]) +} + +// requireVerified asserts the module's shipped verification file checks metadata and +// pins group:name:version, and returns that component's artifact sha256s by file name. +func requireVerified(t *testing.T, module, group, name, version string) map[string]string { + t.Helper() + path := "plugins/" + module + "/gradle/verification-metadata.xml" + var doc struct { + VerifyMetadata bool `xml:"configuration>verify-metadata"` + Components []struct { + Group string `xml:"group,attr"` + Name string `xml:"name,attr"` + Version string `xml:"version,attr"` + Artifacts []struct { + Name string `xml:"name,attr"` + SHA256 []struct { + Value string `xml:"value,attr"` + } `xml:"sha256"` + } `xml:"artifact"` + } `xml:"components>component"` + } + if err := xml.Unmarshal([]byte(readGameStackFile(t, path)), &doc); err != nil { + t.Fatalf("%s: %v", path, err) + } + if !doc.VerifyMetadata { + t.Errorf("%s does not verify metadata; a swapped pom could redirect the graph", path) + } + for _, c := range doc.Components { + if c.Group != group || c.Name != name || c.Version != version { + continue + } + sums := map[string]string{} + for _, a := range c.Artifacts { + if len(a.SHA256) > 0 { + sums[a.Name] = a.SHA256[0].Value + } + } + if sums[name+"-"+version+".jar"] == "" { + t.Errorf("%s pins %s:%s:%s but no sha256 for its jar", path, group, name, version) + } + return sums + } + t.Errorf("%s has no checksum for %s:%s:%s; the build would refuse it", path, group, name, version) + return nil +} + +// wrapperProperties reads a module's gradle-wrapper.properties off disk: the wrappers are +// for developers and CI, and nothing embeds them. +func wrapperProperties(t *testing.T, module string) map[string]string { + t.Helper() + b, err := os.ReadFile("plugins/" + module + "/gradle/wrapper/gradle-wrapper.properties") + if err != nil { + t.Fatal(err) + } + props := map[string]string{} + for line := range strings.SplitSeq(string(b), "\n") { + if k, v, ok := strings.Cut(strings.TrimSpace(line), "="); ok && !strings.HasPrefix(k, "#") { + props[k] = strings.ReplaceAll(v, `\:`, ":") + } + } + return props +} + +// gameStackLock parses the shipped deploy/game-stack.lock the way bootstrap.sh does. +func gameStackLock(t *testing.T) map[string]string { + t.Helper() + lock := map[string]string{} + for line := range strings.SplitSeq(readGameStackFile(t, "deploy/game-stack.lock"), "\n") { + if line == "" || strings.HasPrefix(line, "#") { + continue + } + k, v, ok := strings.Cut(line, "=") + if !ok { + t.Fatalf("not a KEY=value line: %q", line) + } + lock[k] = v + } + return lock +} + func readGameStackFile(t *testing.T, name string) string { t.Helper() b, err := gameStackAssets.ReadFile(name) diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh index 6694538..fc36b05 100644 --- a/deploy/bootstrap.sh +++ b/deploy/bootstrap.sh @@ -372,6 +372,9 @@ SYSTEM_SERVER_IMAGES="${STATE_DIR}/system-server-images" PG_FIREWALL_RULES="${STATE_DIR}/postgres-firewall.nft" PG_FIREWALL_SERVICE="/etc/systemd/system/felis-postgres-firewall.service" JRE_DIR="/opt/felis/jre" +# The gradle image the lobby and limbo Dockerfiles build their plugins in, digest +# included; build_velocity_plugin runs the same one. +PLUGIN_BUILD_IMAGE="gradle:9.8.0-jdk25@sha256:2b2fc1b1dfc3604a2acc916839f36eb5ee48fd7f232427fc5faca224c73bcb01" K3S_BIN_DIR="${K3S_BIN_DIR:-/usr/local/bin}" K3S_BIN="${K3S_BIN_DIR}/k3s" # k3s's containerd mirror config, written by configure_registry_mirror. A variable @@ -2126,7 +2129,8 @@ atomic_install_file() { # build_velocity_plugin compiles plugins/velocity in the same gradle image the two # Dockerfiles use, and drops the jar where Velocity will look for it. Docker is the -# toolchain here on purpose: the host needs no JDK and no gradle, only a JRE. +# toolchain here on purpose: the host needs no JDK and no gradle, only a JRE. Gradle +# checks every dependency against plugins/velocity/gradle/verification-metadata.xml. build_velocity_plugin() { log "building felis-velocity.jar (gradle in a container; the host gets no JDK)" prepare_velocity_layout @@ -2134,7 +2138,7 @@ build_velocity_plugin() { docker run --rm \ -v "${GAME_STACK_DIR}:/src:z" \ -w /src/plugins/velocity \ - gradle:8.14-jdk21 gradle --no-daemon clean build \ + "$PLUGIN_BUILD_IMAGE" gradle --no-daemon clean build \ || die "felis-velocity plugin build failed" local -a jars=( "${GAME_STACK_DIR}"/plugins/velocity/build/libs/felis-velocity-*.jar ) [ "${#jars[@]}" -eq 1 ] && [ -f "${jars[0]}" ] \ diff --git a/deploy/limbo/Dockerfile b/deploy/limbo/Dockerfile index aecbf0d..b169bca 100644 --- a/deploy/limbo/Dockerfile +++ b/deploy/limbo/Dockerfile @@ -29,22 +29,26 @@ # overridable via FELIS_HEALTH_PORT) and returns 200 only after the first tick. # ---- build the felis-limbo plugin jar ---- -# gradle:*-jdk21 — an official Gradle image on JDK 21. JDK 21 is required because -# current LOOHP/Limbo releases ship Java 21 API classes (class-file major 65); a -# JDK 17 fails to read them with "wrong version 65.0, should be 61.0". The image -# also provides the `gradle` binary (this tree vendors no Gradle wrapper). -# build.gradle still targets release 17 bytecode so the plugin loads on Java 17+. -FROM gradle:8.14-jdk21@sha256:5c4c0c4284de4a19951e82ac78f86dbcda2e136644bbfe159beba7ea3420cc80 AS plugin +# The same pinned Gradle image as the lobby build (see deploy/lobby/Dockerfile). Its JDK +# must be >= 21 because current LOOHP/Limbo releases ship Java 21 API classes +# (class-file major 65); a JDK 17 fails to read them with "wrong version 65.0, should be +# 61.0". build.gradle still targets release 17 bytecode so the plugin loads on Java 17+. +FROM gradle:9.8.0-jdk25@sha256:2b2fc1b1dfc3604a2acc916839f36eb5ee48fd7f232427fc5faca224c73bcb01 AS plugin WORKDIR /src # Copy what the limbo module needs: its own tree plus the shared link core it # srcDir-includes (../shared/src/main/java → /src/plugins/shared/src/main/java), so # the account-link client + config loader compile straight into the jar. COPY plugins/limbo/ ./plugins/limbo/ COPY plugins/shared/ ./plugins/shared/ -ARG LIMBO_VERSION=+ -RUN cd plugins/limbo \ - && (test -x ./gradlew && ./gradlew --no-daemon -PlimboVersion="$LIMBO_VERSION" build \ - || gradle --no-daemon -PlimboVersion="$LIMBO_VERSION" build) \ +# The Limbo API release to compile against: deploy/game-stack.lock's LIMBO_VERSION, which +# bootstrap passes. Required — the API is checked against the checksum +# plugins/limbo/gradle/verification-metadata.xml holds for that release. +ARG LIMBO_VERSION +RUN if [ -z "${LIMBO_VERSION:-}" ]; then \ + echo "LIMBO_VERSION is required (deploy/game-stack.lock)" >&2; exit 1; \ + fi \ + && cd plugins/limbo \ + && gradle --no-daemon -PlimboVersion="$LIMBO_VERSION" build \ && cp build/libs/*.jar /felis-limbo.jar # ---- assemble the runtime ---- diff --git a/deploy/lobby/Dockerfile b/deploy/lobby/Dockerfile index 3ddc53b..ba5e525 100644 --- a/deploy/lobby/Dockerfile +++ b/deploy/lobby/Dockerfile @@ -25,16 +25,19 @@ # Secret) and refuses to start without it: a lobby that cannot verify the proxy's signed # handshake would trust an offline, forgeable UUID. -# ---- build the felis-paper plugin jar (Paper API is Java 21) ---- -# gradle:8.14-jdk21 — an official Gradle image on JDK 21 (this tree vendors no Gradle -# wrapper, and a bare JDK image ships no `gradle`). JDK 21 matches the Paper API. -FROM gradle:8.14-jdk21@sha256:5c4c0c4284de4a19951e82ac78f86dbcda2e136644bbfe159beba7ea3420cc80 AS plugin +# ---- build the felis-paper plugin jar (Paper API is Java 25) ---- +# The official Gradle image on JDK 25, the Gradle version plugins/*/gradle/wrapper pins. +# The limbo Dockerfile and bootstrap's velocity build use this same image, digest and all +# (bootstrap_asset_test.go holds the three together). The image's own `gradle` runs the +# build rather than the module's wrapper, which would download the same distribution +# again on every image build. The build checks every dependency against +# plugins/paper/gradle/verification-metadata.xml and fails on a mismatch. +FROM gradle:9.8.0-jdk25@sha256:2b2fc1b1dfc3604a2acc916839f36eb5ee48fd7f232427fc5faca224c73bcb01 AS plugin WORKDIR /src COPY plugins/paper/ ./plugins/paper/ COPY plugins/shared/ ./plugins/shared/ RUN cd plugins/paper \ - && (test -x ./gradlew && ./gradlew --no-daemon build \ - || gradle --no-daemon build) \ + && gradle --no-daemon build \ && cp build/libs/*.jar /felis-paper.jar # ---- assemble the runtime ---- diff --git a/deploy/update-game-stack-lock.sh b/deploy/update-game-stack-lock.sh index ceedb17..355628c 100755 --- a/deploy/update-game-stack-lock.sh +++ b/deploy/update-game-stack-lock.sh @@ -10,7 +10,10 @@ # and hashed here; Paper and Velocity come from Fill's content-addressed URLs. # # Review the diff before committing: MC_VERSION moves the login gate's protocol, and the -# lobby, plain-Paper image and every client follow it. +# lobby, plain-Paper image and every client follow it. The plugins compile against these +# same builds (paper-api, the Limbo API, velocity-api) with their checksums pinned in +# plugins/*/gradle/verification-metadata.xml, so a moved build also moves those; go test . +# names what is left to bring along. set -Eeuo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -70,4 +73,4 @@ if [ "$check" = 1 ]; then die "upstream has newer builds than game-stack.lock" fi cp "$tmp" "$LOCK" -ok "game-stack.lock updated; run go test . and the bootstrap tests, then commit" +ok "game-stack.lock updated; move plugins/paper's paper-api pin to the new Paper build and regenerate the plugins' gradle/verification-metadata.xml (plugins/README.md \"Dependency verification\"), run go test . and the bootstrap tests, then commit" diff --git a/plugins/fabric/gradle/wrapper/gradle-wrapper.properties b/plugins/fabric/gradle/wrapper/gradle-wrapper.properties index b8fb7d2..4aebbe2 100644 --- a/plugins/fabric/gradle/wrapper/gradle-wrapper.properties +++ b/plugins/fabric/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 retries=0 diff --git a/plugins/forge/build.gradle b/plugins/forge/build.gradle index 0d4a8d7..a11a602 100644 --- a/plugins/forge/build.gradle +++ b/plugins/forge/build.gradle @@ -1,5 +1,7 @@ plugins { - id 'net.minecraftforge.gradle' version '[6.0,6.2)' + // An exact release, never a range: a range resolves to whatever ForgeGradle published + // last, so the same sources could build differently from one day to the next. + id 'net.minecraftforge.gradle' version '6.0.54' id 'java' } diff --git a/plugins/forge/gradle/wrapper/gradle-wrapper.properties b/plugins/forge/gradle/wrapper/gradle-wrapper.properties index b8fb7d2..4aebbe2 100644 --- a/plugins/forge/gradle/wrapper/gradle-wrapper.properties +++ b/plugins/forge/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 retries=0 diff --git a/plugins/limbo/build.gradle b/plugins/limbo/build.gradle index c01b374..a756a6e 100644 --- a/plugins/limbo/build.gradle +++ b/plugins/limbo/build.gradle @@ -5,16 +5,11 @@ plugins { group = 'best.lolicon.felis' version = '0.1.0' -// Build with a JDK 21 toolchain but emit portable Java 17 bytecode (options.release -// below). Current LOOHP/Limbo releases ship Java 21 API classes (class-file major -// 65), so the compiler JDK must be >= 21 to *read* them — a JDK 17 fails with -// "wrong version 65.0, should be 61.0". Targeting release 17 keeps the plugin -// loadable on any Limbo running Java 17+. See deploy/limbo/Dockerfile (21-jdk). -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} +// Emit portable Java 17 bytecode (options.release below) from whatever JDK runs Gradle. +// Current LOOHP/Limbo releases ship Java 21 API classes (class-file major 65), so that +// JDK must be >= 21 to *read* them — a JDK 17 fails with "wrong version 65.0, should be +// 61.0". The plugin build image and CI both run JDK 25. Targeting release 17 keeps the +// plugin loadable on any Limbo running Java 17+. repositories { mavenCentral() @@ -38,11 +33,18 @@ sourceSets { } } -// The Limbo API version this plugin compiles against. It is CODE-ONLY in this Go -// repo (not built by CI); a deployer building the image passes the release that -// matches the Limbo.jar they bundle, e.g. -PlimboVersion=0.7.11-ALPHA. Kept a -// property so no specific build number is hardcoded into the tree. -def limboVersion = project.findProperty('limboVersion') ?: '+' +// The Limbo API version this plugin compiles against: the release the login-gate image +// bundles, deploy/game-stack.lock's LIMBO_VERSION. deploy/bootstrap.sh passes it to the +// image build and plugins/test.sh reads it from the lock, e.g. +// -PlimboVersion=2026.0.3-ALPHA. It has no default: LOOHP's repository serves no +// maven-metadata.xml, so a dynamic version cannot resolve, and a guessed one would +// compile against an API the gate does not run. gradle/verification-metadata.xml holds +// the checksum of the locked release, so a lock refresh regenerates it (see +// plugins/README.md "Dependency verification"). +def limboVersion = project.findProperty('limboVersion') +if (!limboVersion) { + throw new GradleException('pass -PlimboVersion=') +} dependencies { // Limbo provides its own API at runtime (the plugin is loaded into the Limbo @@ -52,7 +54,7 @@ dependencies { tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' - // Portable output: read the Java 21 Limbo API with the JDK 21 toolchain above, - // but emit Java 17 bytecode so the plugin loads on any Limbo running Java 17+. + // Portable output: read the Java 21 Limbo API with a JDK >= 21, but emit Java 17 + // bytecode so the plugin loads on any Limbo running Java 17+. options.release = 17 } diff --git a/plugins/limbo/gradle/verification-metadata.xml b/plugins/limbo/gradle/verification-metadata.xml new file mode 100644 index 0000000..0712b29 --- /dev/null +++ b/plugins/limbo/gradle/verification-metadata.xml @@ -0,0 +1,354 @@ + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/limbo/gradle/wrapper/gradle-wrapper.jar b/plugins/limbo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5097068 Binary files /dev/null and b/plugins/limbo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/plugins/limbo/gradle/wrapper/gradle-wrapper.properties b/plugins/limbo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1eca32e --- /dev/null +++ b/plugins/limbo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,10 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=bafd5ce9cfaea0fbccfdc8439a1ac42fbd4cd9c89dc9a988228d8a2639a58e6c +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugins/limbo/gradlew b/plugins/limbo/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/plugins/limbo/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/plugins/limbo/gradlew.bat b/plugins/limbo/gradlew.bat new file mode 100644 index 0000000..3185a43 --- /dev/null +++ b/plugins/limbo/gradlew.bat @@ -0,0 +1,112 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. + +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. + +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel + +@rem This label must not be changed. We rely on old scripts being able to jump to this point. +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java b/plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java index ccb9af4..7c531f4 100644 --- a/plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java +++ b/plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java @@ -19,6 +19,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; import net.kyori.adventure.inventory.Book; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; @@ -99,6 +100,9 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener { private static final Logger LOG = Logger.getLogger("FelisLimbo"); private static final String HEALTH_PATH = "/healthz"; + // Limbo deprecated the String channel overload; its Key overload sends key.toString(), + // which for "felis:control" is the same channel string. + private static final Key CONTROL_CHANNEL = Key.key(Control.CHANNEL); private static final int DEFAULT_PORT = 8080; // Poll cadence and window. 20 ticks ≈ 1s at Limbo's tick rate; polling once a @@ -392,7 +396,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener { private void sendRelease(Player player) { try { - player.sendPluginMessage(Control.CHANNEL, Control.encode(ControlFrame.loginRelease(player.getName()))); + player.sendPluginMessage(CONTROL_CHANNEL, Control.encode(ControlFrame.loginRelease(player.getName()))); } catch (IOException | RuntimeException e) { // The next attempt sends again; the window bounds how long we keep trying. LOG.warning("FelisLimbo: could not send the lobby release for " + player.getUniqueId() diff --git a/plugins/neoforge/gradle/wrapper/gradle-wrapper.properties b/plugins/neoforge/gradle/wrapper/gradle-wrapper.properties index 680d395..a0209cd 100644 --- a/plugins/neoforge/gradle/wrapper/gradle-wrapper.properties +++ b/plugins/neoforge/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 retries=0 diff --git a/plugins/paper/build.gradle b/plugins/paper/build.gradle index 9fc6cd3..2fa8cd6 100644 --- a/plugins/paper/build.gradle +++ b/plugins/paper/build.gradle @@ -5,15 +5,15 @@ plugins { group = 'best.lolicon.felis' version = '0.1.0' -// Paper 1.21 runs on Java 21, and its API is published as a Java-21 artifact, so -// this one module needs a Java-21 toolchain (the rest of the suite is 17). The -// toolchain block makes that requirement explicit and self-enforcing: Gradle uses a -// detected JDK 21 to compile, regardless of which JDK runs Gradle, and fails loudly -// if none is present. The shared codec is plain Java-17 source, which 21 compiles +// The lobby runs Paper 26.x, which needs Java 25, and paper-api 26.x is published as a +// Java-25 artifact (its Gradle module metadata declares org.gradle.jvm.version = 25), so +// this module compiles with a Java-25 toolchain. The toolchain block makes that explicit +// and self-enforcing: Gradle uses a detected JDK 25 whichever JDK runs Gradle, and fails +// loudly if none is present. The shared codec is plain Java-17 source, which 25 compiles // forward-compatibly. java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } } @@ -28,7 +28,13 @@ repositories { dependencies { // paper-api is compile-only (the server provides it at runtime). It pulls in // Bukkit + Adventure, which is all the lobby face needs. - compileOnly 'io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT' + // + // Pinned to the API of the exact server the lobby image bundles: deploy/game-stack.lock's + // PAPER_JAR_URL is paper--.jar, and PaperMC publishes that build's API as + // .build.-. A release build, never a -SNAPSHOT, so the same sources + // compile against the same bytes every time (gradle/verification-metadata.xml checks + // them). deploy/bootstrap_test.sh fails when this and the lock drift apart. + compileOnly 'io.papermc.paper:paper-api:26.3.build.40-alpha' } // The lobby is a PURE UI face (spec §12): it speaks only the felis:control diff --git a/plugins/paper/gradle/verification-metadata.xml b/plugins/paper/gradle/verification-metadata.xml new file mode 100644 index 0000000..6845ac1 --- /dev/null +++ b/plugins/paper/gradle/verification-metadata.xml @@ -0,0 +1,583 @@ + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/paper/gradle/wrapper/gradle-wrapper.jar b/plugins/paper/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5097068 Binary files /dev/null and b/plugins/paper/gradle/wrapper/gradle-wrapper.jar differ diff --git a/plugins/paper/gradle/wrapper/gradle-wrapper.properties b/plugins/paper/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1eca32e --- /dev/null +++ b/plugins/paper/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,10 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=bafd5ce9cfaea0fbccfdc8439a1ac42fbd4cd9c89dc9a988228d8a2639a58e6c +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugins/paper/gradlew b/plugins/paper/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/plugins/paper/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/plugins/paper/gradlew.bat b/plugins/paper/gradlew.bat new file mode 100644 index 0000000..3185a43 --- /dev/null +++ b/plugins/paper/gradlew.bat @@ -0,0 +1,112 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. + +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. + +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel + +@rem This label must not be changed. We rely on old scripts being able to jump to this point. +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/plugins/test-mods.sh b/plugins/test-mods.sh index 1859d4c..d20eb80 100644 --- a/plugins/test-mods.sh +++ b/plugins/test-mods.sh @@ -12,7 +12,7 @@ # Each module pins its own Gradle via its vendored wrapper (fabric/forge: 8.8, # neoforge: 8.14) and targets a Java-17 Minecraft line (1.20.1 / 1.20.4), so run # this on JDK 17. The plugin jars bootstrap installs are different modules with a -# different gate: plugins/test.sh (JDK 21). The first run here downloads and +# different gate: plugins/test.sh (JDK 25). The first run here downloads and # decompiles Minecraft (minutes); the Gradle caches make later runs much faster. set -euo pipefail diff --git a/plugins/test.sh b/plugins/test.sh index 20f3d2f..bba9f4b 100644 --- a/plugins/test.sh +++ b/plugins/test.sh @@ -3,7 +3,8 @@ # # bash plugins/test.sh # -# Two gates, both runnable on any machine with a JDK 21 and Gradle: +# Two gates, both runnable on any machine with a JDK 25 (Gradle comes from each +# module's wrapper, sha256-pinned): # # 1. The hand-written, framework-free test mains under shared/test and # velocity/test. They check what "compiles" cannot: the felis:control codec @@ -22,14 +23,15 @@ # test of what we ship). # # 2. Production compile gates: the velocity/paper/limbo plugin jars — the three -# bootstrap bakes into the proxy and the game images — are built with the same -# Gradle major the plugin Dockerfiles pin, so a compile break is a red check -# here instead of an install-time surprise. limbo compiles against the API -# release bootstrap would bundle (resolved below, same source the installer -# reads), because the module's `+` default cannot resolve on its own. +# bootstrap bakes into the proxy and the game images — are built through each +# module's wrapper, the same Gradle the plugin build image runs, with every +# dependency checked against the module's gradle/verification-metadata.xml. A +# compile break or a swapped artifact is a red check here instead of an +# install-time surprise. limbo compiles against the API release the login gate +# bundles: deploy/game-stack.lock's LIMBO_VERSION, which bootstrap passes too. # -# No test framework and no wrapper: the mains are the same javac one-liners their -# javadocs document, so a local run and CI run the same bytes. +# No test framework: the mains are the same javac one-liners their javadocs document, +# so a local run and CI run the same bytes. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." @@ -121,24 +123,11 @@ java -cp "$work/opcard-classes:$adventure_api:$adventure_key:$examination_api" \ # --- 2. production compile gates ------------------------------------------------ for module in velocity paper; do - echo "==> gradle --no-daemon -p plugins/$module build" - gradle --no-daemon -p "plugins/$module" build + echo "==> plugins/$module: ./gradlew --no-daemon build" + ( cd "plugins/$module" && ./gradlew --no-daemon build ) done -# limbo is special: it compiles against the LOOHP/Limbo API release that the -# installer bundles, and that release is published nowhere except the CI artifact -# name (Limbo--.jar) — the same place deploy/bootstrap.sh reads it. -# The module's `+` version default cannot resolve (LOOHP's repository serves no -# maven-metadata.xml), so a bare `gradle -p plugins/limbo build` is never a valid -# command; the version must come from here or from bootstrap. -echo "==> resolving the newest LOOHP/Limbo CI build (for -PlimboVersion)" -limbo_meta="$(curl -fsSL --retry 5 --retry-delay 2 \ - https://ci.loohpjames.com/job/Limbo/lastSuccessfulBuild/api/json)" \ - || { echo "cannot read the LOOHP/Limbo CI build metadata; the limbo gate cannot pick a version" >&2; exit 1; } -limbo_file="$(printf '%s' "$limbo_meta" | grep -o 'Limbo-[0-9A-Za-z._-]*\.jar' || true)" -limbo_file="${limbo_file%%$'\n'*}" -[ -n "$limbo_file" ] || { echo "no Limbo jar in the LOOHP/Limbo CI artifact list" >&2; exit 1; } -limbo_version="${limbo_file%.jar}"; limbo_version="${limbo_version%-*}"; limbo_version="${limbo_version#Limbo-}" -[ -n "$limbo_version" ] || { echo "cannot parse the Limbo version out of ${limbo_file}" >&2; exit 1; } -echo "==> gradle --no-daemon -p plugins/limbo build (Limbo ${limbo_version})" -gradle --no-daemon -p plugins/limbo -PlimboVersion="$limbo_version" build +limbo_version="$(sed -n 's/^LIMBO_VERSION=//p' deploy/game-stack.lock)" +[ -n "$limbo_version" ] || { echo "deploy/game-stack.lock sets no LIMBO_VERSION" >&2; exit 1; } +echo "==> plugins/limbo: ./gradlew --no-daemon -PlimboVersion=${limbo_version} build" +( cd plugins/limbo && ./gradlew --no-daemon -PlimboVersion="$limbo_version" build ) diff --git a/plugins/velocity/build.gradle b/plugins/velocity/build.gradle index 81fa67d..c27f7cc 100644 --- a/plugins/velocity/build.gradle +++ b/plugins/velocity/build.gradle @@ -25,9 +25,12 @@ def velocityApi = findProperty('velocityApi') ?: '3.5.1' // unreleased — zero published builds; only the snapshot exists), build with a JDK 25 // toolchain and both knobs turned up: // -// gradle build -PvelocityApi=4.0.0-SNAPSHOT -PjavaTarget=25 +// ./gradlew build -PvelocityApi=4.0.0-SNAPSHOT -PjavaTarget=25 --dependency-verification lenient // -// That build is a CHECK, not an artifact — the jar we deploy is the default 21 one. +// That build is a CHECK, not an artifact — the jar we deploy is the default 21 one. The +// lenient flag is part of it: gradle/verification-metadata.xml pins the checksums of the +// 3.5.1 graph, and a snapshot's bytes change with every publish, so strict verification +// (the default) refuses it. def javaTarget = JavaVersion.toVersion(findProperty('javaTarget') ?: '21') java { diff --git a/plugins/velocity/gradle/verification-metadata.xml b/plugins/velocity/gradle/verification-metadata.xml new file mode 100644 index 0000000..06e94af --- /dev/null +++ b/plugins/velocity/gradle/verification-metadata.xml @@ -0,0 +1,388 @@ + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/velocity/gradle/wrapper/gradle-wrapper.jar b/plugins/velocity/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5097068 Binary files /dev/null and b/plugins/velocity/gradle/wrapper/gradle-wrapper.jar differ diff --git a/plugins/velocity/gradle/wrapper/gradle-wrapper.properties b/plugins/velocity/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1eca32e --- /dev/null +++ b/plugins/velocity/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,10 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=bafd5ce9cfaea0fbccfdc8439a1ac42fbd4cd9c89dc9a988228d8a2639a58e6c +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugins/velocity/gradlew b/plugins/velocity/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/plugins/velocity/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/plugins/velocity/gradlew.bat b/plugins/velocity/gradlew.bat new file mode 100644 index 0000000..3185a43 --- /dev/null +++ b/plugins/velocity/gradlew.bat @@ -0,0 +1,112 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. + +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. + +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel + +@rem This label must not be changed. We rely on old scripts being able to jump to this point. +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%