Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ This repository has multiple entry points that may trigger compilation/build beh
* Model-dependent requests, including formatting, must use `LanguageWorker.handle` so progress negotiation, initialization, and the initial build complete before model access. Formatting keeps duplicate requests because separate documents must each receive an answer.
* Run `InitialBuildProgressTest` and `LanguageWorkerTest` for startup changes; the extension's opt-in `scripts/test-lsp-readiness.js <compiler.jar>` verifies the actual stdio protocol.

### Language server startup archive

wurst4vscode starts the server with `-XX:+AutoCreateSharedArchive`: the JVM writes an AppCDS archive next to the
compiler jar when the first session ends, and the sessions after it start from it. That archive sits on the
runtime's own base archive, which `deploy.gradle` makes (`jlink --generate-cds-archive`; macOS copies a full JDK, which
has one) and `assembleSlimCompilerDist` checks for. Without it the JVM silently runs without any archive, which is
how the earlier attempt went unnoticed. jlink writes the archive with a JVM of its own, which takes on the options it is started with: the
CI exports `JAVA_TOOL_OPTIONS=-XX:+UseCompactObjectHeaders`, so `deploy.gradle` removes it (and the other JVM option variables) for that
step, or only `classes_coh.jsa` is made, which a runtime started without that option cannot use. The extension passes `-Xlog:disable` because the JVM reports archive trouble
on stdout, which is the protocol stream: never print anything of your own there.

### Build-map pipeline (centralized)

Map build behavior is centralized in:
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## 1.9 (in progress)

- The runtime of the distribution carries the base class data sharing archive of the JVM, which it never did: the
slim runtime is built with jlink, and the AppCDS archive the build tried to make for the language server needs a
base archive to sit on, so it could not be made and was never shipped. The build now makes the base archive
(`jlink --generate-cds-archive`) and fails when the runtime has none, and the dead archive task and its
`-languageServerAppCdsTrain` option are gone. With wurst4vscode starting the server with an archive of its own
(written next to the compiler jar when the first session ends), the language server of a small project is ready
after 3.7 s instead of 4.4 s, and castle fight after 17.3 s instead of 19.0 s (medians of four starts).

- The language server checks every file it was meant to check. A check which cannot start, because an import
does not resolve, used to forget the files it was planned for. A file edited in the meantime then got no
diagnostics once the import was fixed, since nothing it imports had changed. Those files are kept and checked
Expand Down
70 changes: 27 additions & 43 deletions de.peeeq.wurstscript/deploy.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def jreImageDir = layout.buildDirectory.dir("jre-wurst-25")

def distRoot = layout.buildDirectory.dir("dist/slim-${plat}")
def releasesDir = layout.buildDirectory.dir("releases")
def appCdsArchive = distRoot.map { it.file("wurst-runtime/wurst-lsp.jsa") }

// ----------------------- Toolchain / tool paths (providers) -------------------------
def toolchainSvc = extensions.getByType(JavaToolchainService)
Expand Down Expand Up @@ -173,15 +172,31 @@ tasks.register("jlinkRuntime25", Exec) {
logger.lifecycle("[jlink] Modules: ${mods}")

executable = jlinkPathProvider.get()
// The base archive is written by a JVM which jlink starts, and takes on the object header mode of the options
// that JVM is given. CI exports JAVA_TOOL_OPTIONS=-XX:+UseCompactObjectHeaders for the tests, which would
// leave only classes_coh.jsa, which a runtime started without that option cannot use.
["JAVA_TOOL_OPTIONS", "_JAVA_OPTIONS", "JDK_JAVA_OPTIONS"].each { environment.remove(it) }
args "--verbose",
"--module-path", jmodsDirProvider.get(),
"--add-modules", mods,
"--no-header-files",
"--no-man-pages",
"--strip-debug",
"--compress=zip-9",
// The language server starts from an AppCDS archive which the JVM writes next to the compiler
// jar (wurst4vscode, -XX:+AutoCreateSharedArchive). That archive sits on top of the base archive
// of the runtime; without one the JVM silently runs without any.
"--generate-cds-archive",
Comment thread
Frotty marked this conversation as resolved.
"--output", outDir.absolutePath
}

doLast {
// checked on the image itself: the dist folder is only added to, and may still hold an archive of an earlier build
def image = jreImageDir.get().asFile
if (!["bin/server/classes.jsa", "lib/server/classes.jsa"].any { new File(image, it).exists() }) {
throw new GradleException("jlink made no base CDS archive (classes.jsa under bin/server or lib/server) in " + image)
}
}
}

// 3) Assemble folder layout: jre + compiler.jar (no manifest)
Expand All @@ -200,14 +215,23 @@ tasks.register("assembleSlimCompilerDist", Copy) {
}
} else {
dependsOn("jlinkRuntime25", "shadowJar")
from(jreImageDir) { into("wurst-runtime") }
from(jreImageDir) {
into("wurst-runtime")
// for heaps above 32 GB, which this does not run with; it would add 14 MB to the download
exclude("**/classes_nocoops*.jsa")
}
}

from(fatJar) { into("wurst-compiler") }
into(distRoot)

doLast {
logger.lifecycle("[dist] Folder ready at: ${distRoot.get().asFile}")
def runtime = new File(distRoot.get().asFile, "wurst-runtime")
if (!["bin/server/classes.jsa", "lib/server/classes.jsa"].any { new File(runtime, it).exists() }) {
throw new GradleException("The runtime has no base CDS archive (classes.jsa under bin/server or lib/server), " +
"so the language server cannot start from an AppCDS archive.")
}
}
}

Expand All @@ -224,51 +248,11 @@ tasks.named("assembleSlimCompilerDist", Copy) { t ->
}
}

tasks.register("generateAppCdsArchive", Exec) {
description = "Best-effort AppCDS archive generation for language-server startup."
group = "distribution"
dependsOn("assembleSlimCompilerDist")

inputs.file(fatJar)
inputs.dir(distRoot)
outputs.file(appCdsArchive)
ignoreExitValue = true

doFirst {
def distDir = distRoot.get().asFile
def archiveFile = appCdsArchive.get().asFile
def runtimeJava = new File(distDir, "wurst-runtime/bin/java${os.isWindows() ? '.exe' : ''}")
def compilerJar = new File(distDir, "wurst-compiler/${fatJar.get().asFile.name}")

archiveFile.parentFile.mkdirs()
if (archiveFile.exists()) {
archiveFile.delete()
}

executable = runtimeJava.absolutePath
args "-Xshare:auto",
"-XX:ArchiveClassesAtExit=${archiveFile.absolutePath}",
"-jar", compilerJar.absolutePath,
"-languageServerAppCdsTrain"

logger.lifecycle("[appcds] Training LS startup with ${runtimeJava.absolutePath}")
}

doLast {
def archiveFile = appCdsArchive.get().asFile
if (executionResult.get().exitValue != 0 || !archiveFile.exists()) {
logger.warn("[appcds] Archive generation did not succeed on ${plat}; continuing without shipped AppCDS archive.")
} else {
logger.lifecycle("[appcds] Wrote ${archiveFile.absolutePath}")
}
}
}

// 4) Package ZIP on all platforms
tasks.register("packageSlimCompilerDistZip", Zip) {
description = "Packages slim dist as a ZIP archive (all platforms)."
group = "distribution"
dependsOn("generateAppCdsArchive")
dependsOn("assembleSlimCompilerDist")

from(distRoot)
destinationDirectory.set(releasesDir)
Expand Down
5 changes: 0 additions & 5 deletions de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ public static void main(String[] args) {
return;
}

if (runArgs.isLanguageServerAppCdsTrain()) {
LanguageServerStarter.trainForAppCds();
return;
}

if (runArgs.isLanguageServer()) {
LanguageServerStarter.start();
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,5 @@ public static void start() {
launcher.startListening();
}

public static void trainForAppCds() {
WurstLanguageServer server = new WurstLanguageServer();
try {
server.getTextDocumentService();
server.getWorkspaceService();
} finally {
server.shutdown().join();
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public class RunArgs {
private final RunOption optionExportObjects;
private final RunOption optionStartServer;
private final RunOption optionLanguageServer;
private final RunOption optionLanguageServerAppCdsTrain;
private final RunOption optionNoExtractMapScript;
private final RunOption optionFixInstall;
private final RunOption optionCopyMap;
Expand Down Expand Up @@ -142,7 +141,6 @@ public RunArgs(String... args) {

optionLanguageServer = addOption("languageServer", "Starts a language server which can be used by editors to get services "
+ "like code completion, validations, and find declaration. The communication to the language server is via standard input output.");
optionLanguageServerAppCdsTrain = addOption("languageServerAppCdsTrain", "Starts and immediately stops a lightweight language-server startup path for AppCDS training.");

optionHelp = addOption("help", "Prints this help message.");
optionDisablePjass = addOption("noPJass", "Disables PJass checks for the generated code.");
Expand Down Expand Up @@ -371,10 +369,6 @@ public boolean isLanguageServer() {
return optionLanguageServer.isSet;
}

public boolean isLanguageServerAppCdsTrain() {
return optionLanguageServerAppCdsTrain.isSet;
}

public boolean isNoExtractMapScript() {
return optionNoExtractMapScript.isSet;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void exposesModeAndSafetyFlags() {
RunArgs args = new RunArgs("-build", "-dev", "-stacktraces", "-nodebug", "-uncheckedDispatch",
"-injectobjects", "-hotreload", "-hotstart", "-noPJass", "-legacyJassChecks",
"-compactOutput", "-measure", "-compiletimeCache", "-noExtractMapScript",
"-prettyPrint", "-languageServer", "-languageServerAppCdsTrain");
"-prettyPrint", "-languageServer");

Assert.assertTrue(args.isBuild());
Assert.assertTrue(args.isDevBuild());
Expand All @@ -56,7 +56,6 @@ public void exposesModeAndSafetyFlags() {
Assert.assertTrue(args.isNoExtractMapScript());
Assert.assertTrue(args.isPrettyPrint());
Assert.assertTrue(args.isLanguageServer());
Assert.assertTrue(args.isLanguageServerAppCdsTrain());
}

@Test
Expand Down
Loading