From 138e0f667d67a4f2e8c9b16e9fc8a621a39d0d4d Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 14 Aug 2026 14:52:32 +0200 Subject: [PATCH 1/4] Respect Lua mode in CLI compilation --- .../de/peeeq/wurstio/CompilationProcess.java | 63 +++++++++++++------ .../src/main/java/de/peeeq/wurstio/Main.java | 15 +++-- .../wurstio/CompilationProcessLuaTests.java | 34 ++++++++++ 3 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java index dc60e61fa..4b4b701a1 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java @@ -12,13 +12,16 @@ import de.peeeq.wurstscript.intermediatelang.interpreter.ILStackFrame; import de.peeeq.wurstscript.jassAst.JassProg; import de.peeeq.wurstscript.jassprinter.JassPrinter; +import de.peeeq.wurstscript.luaAst.LuaCompilationUnit; import de.peeeq.wurstscript.translation.imtranslation.ImTranslator; +import de.peeeq.wurstscript.translation.lua.translation.LuaTranslator; import de.peeeq.wurstscript.utils.Utils; import org.eclipse.jdt.annotation.Nullable; import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.util.Locale; import java.util.Optional; import java.util.function.Supplier; @@ -87,30 +90,48 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) { timeTaker.measure("Run compiletime functions", () ->compiler.runCompiletime(WurstProjectConfigData.empty(), isProd, false)); - JassProg jassProg = timeTaker.measure("Transform program to Jass", - compiler::transformProgToJass); + CharSequence mapScript; + File outputMapscript; + if (runArgs.isLua()) { + LuaCompilationUnit luaCode = timeTaker.measure("Transform program to Lua", + compiler::transformProgToLua); + if (luaCode == null || gui.getErrorCount() > 0) { + return null; + } - if (jassProg == null || gui.getErrorCount() > 0) { - return null; - } + gui.sendProgress("Printing Lua"); + StringBuilder luaOutput = new StringBuilder(); + timeTaker.measure("Print Lua", () -> luaCode.print(luaOutput, 0)); + mapScript = luaOutput; + LuaTranslator.assertNoLeakedHashtableNativeCalls(mapScript.toString()); + LuaTranslator.assertNoLeakedGetHandleIdCalls(mapScript.toString()); + CharSequence compiledLua = mapScript; + outputMapscript = timeTaker.measure("Write Lua", + () -> writeMapscript(compiledLua)); + } else { + JassProg jassProg = timeTaker.measure("Transform program to Jass", + compiler::transformProgToJass); - boolean withSpace; - withSpace = !runArgs.isOptimize(); + if (jassProg == null || gui.getErrorCount() > 0) { + return null; + } - gui.sendProgress("Printing Jass"); + boolean withSpace = !runArgs.isOptimize(); + gui.sendProgress("Printing Jass"); - JassPrinter printer = new JassPrinter(withSpace, jassProg); - CharSequence mapScript = timeTaker.measure("Print Jass", - (Supplier) printer::printProg); + JassPrinter printer = new JassPrinter(withSpace, jassProg); + mapScript = timeTaker.measure("Print Jass", + (Supplier) printer::printProg); - // output to file - File outputMapscript = timeTaker.measure("Print Jass", - () -> writeMapscript(mapScript)); + CharSequence compiledJass = mapScript; + outputMapscript = timeTaker.measure("Write Jass", + () -> writeMapscript(compiledJass)); - if (!runArgs.isDisablePjass() && !runArgs.isLegacyJassTypeChecks()) { - boolean pjassError = timeTaker.measure("Run PJass", + if (!runArgs.isDisablePjass() && !runArgs.isLegacyJassTypeChecks()) { + boolean pjassError = timeTaker.measure("Run PJass", () -> runPjass(outputMapscript)); - if (pjassError) return null; + if (pjassError) return null; + } } timeTaker.printReport(); return mapScript; @@ -140,9 +161,13 @@ private File writeMapscript(CharSequence mapScript) { gui.sendProgress("Writing output file"); File outputMapscript; if (runArgs.getOutFile() != null) { - outputMapscript = new File(runArgs.getOutFile()); + String outputPath = runArgs.getOutFile(); + if (runArgs.isLua() && outputPath.toLowerCase(Locale.ROOT).endsWith(".j")) { + outputPath = outputPath.substring(0, outputPath.length() - 2) + ".lua"; + } + outputMapscript = new File(outputPath); } else { - outputMapscript = new File("./temp/output.j"); + outputMapscript = new File("./temp/output." + (runArgs.isLua() ? "lua" : "j")); } outputMapscript.getParentFile().mkdirs(); try { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java index 74181b452..4fe76cd54 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java @@ -181,9 +181,16 @@ public static void main(String[] args) { compiledScript = compilationProcess.doCompilation(mpqEditor, projectFolder, true); if (compiledScript != null) { gui.sendProgress("Writing to map"); - mpqEditor.deleteFile("war3map.j"); - byte[] war3map = compiledScript.toString().getBytes(Charsets.UTF_8); - mpqEditor.insertFile("war3map.j", war3map); + String mapScriptName = compileArgs.isLua() ? "war3map.lua" : "war3map.j"; + if (compileArgs.isLua()) { + mpqEditor.deleteFile("war3map.j"); + mpqEditor.deleteFile("scripts\\war3map.j"); + } else { + mpqEditor.deleteFile("war3map.lua"); + mpqEditor.deleteFile("scripts\\war3map.lua"); + } + byte[] mapScript = compiledScript.toString().getBytes(Charsets.UTF_8); + mpqEditor.insertFile(mapScriptName, mapScript); } ImportFile.importFilesFromImports(projectFolder, mpqEditor); } @@ -192,7 +199,7 @@ public static void main(String[] args) { } if (compiledScript != null) { - File scriptFile = new File("compiled.j.txt"); + File scriptFile = new File(compileArgs.isLua() ? "compiled.lua.txt" : "compiled.j.txt"); Files.write(compiledScript.toString().getBytes(Charsets.UTF_8), scriptFile); } diff --git a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java new file mode 100644 index 000000000..bfcbe00c4 --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java @@ -0,0 +1,34 @@ +package de.peeeq.wurstio; + +import de.peeeq.wurstscript.RunArgs; +import de.peeeq.wurstscript.gui.WurstGuiCliImpl; +import org.testng.annotations.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class CompilationProcessLuaTests { + + @Test + public void luaModeDoesNotEmitJassScript() throws Exception { + Path project = Files.createTempDirectory("wurst-cli-lua"); + Path source = project.resolve("Main.wurst"); + Path requestedJassOutput = project.resolve("output.j"); + Path output = project.resolve("output.lua"); + Files.writeString(source, "package Main\nfunction foo()\nendpackage\n"); + + RunArgs runArgs = new RunArgs("-lua", "-out", requestedJassOutput.toString(), source.toString()); + CompilationProcess process = new CompilationProcess(new WurstGuiCliImpl(true), runArgs); + + CharSequence result = process.doCompilation(null, project.toFile(), false); + + assertTrue(result != null, "Lua compilation should succeed"); + assertTrue(Files.exists(output), "Lua output should be written"); + assertFalse(Files.exists(requestedJassOutput), "Lua compilation must not emit a .j file"); + assertFalse(result.toString().contains("takes nothing returns nothing"), + "CLI Lua compilation must not use the Jass backend"); + } +} From a0b3b834a951bf51d54251c491e894336c4902ae Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 14 Aug 2026 16:00:22 +0200 Subject: [PATCH 2/4] Route workspace map builds through shared pipeline --- .../src/main/java/de/peeeq/wurstio/Main.java | 45 ++++++------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java index 4fe76cd54..57752900d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java @@ -1,7 +1,5 @@ package de.peeeq.wurstio; -import com.google.common.base.Charsets; -import com.google.common.io.Files; import org.wurstscript.projectconfig.WurstProjectConfigData; import org.wurstscript.projectconfig.WurstProjectConfigReader; import de.peeeq.wurstio.compilationserver.WurstServer; @@ -11,8 +9,6 @@ import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstio.languageserver.requests.CliBuildMap; import de.peeeq.wurstio.map.importer.ImportFile; -import de.peeeq.wurstio.mpq.MpqEditor; -import de.peeeq.wurstio.mpq.MpqEditorFactory; import de.peeeq.wurstio.objectreader.ObjectExportService; import de.peeeq.wurstscript.CompileTimeInfo; import de.peeeq.wurstscript.ErrorReporting; @@ -147,11 +143,19 @@ public static void main(String[] args) { compileArgs = new RunArgs(mergedArgs); } - if (runArgs.isBuild() && runArgs.getInputmap() != null && workspaceroot != null) { + if (workspaceroot != null) { Path root = Paths.get(workspaceroot); - Path inputMap = root.resolve(runArgs.getInputmap()); + Path inputMap = runArgs.isBuild() && runArgs.getInputmap() != null + ? root.resolve(runArgs.getInputmap()) + : runArgs.getMapFile() == null ? null : Paths.get(runArgs.getMapFile()); WurstProjectConfigData projectConfig = WurstProjectConfigReader.load(root.resolve(FILE_NAME)); - if (java.nio.file.Files.exists(inputMap) && projectConfig != null) { + if (inputMap != null) { + if (!java.nio.file.Files.exists(inputMap)) { + throw new RuntimeException("Input map does not exist: " + inputMap); + } + if (projectConfig == null) { + throw new RuntimeException(FILE_NAME + " file doesn't exist or is invalid."); + } CliBuildMap cliBuildMap = new CliBuildMap( WFile.create(root.toFile()), Optional.of(inputMap.toFile()), @@ -170,37 +174,14 @@ public static void main(String[] args) { } } - String mapFilePath = runArgs.getMapFile(); - CompilationProcess compilationProcess = new CompilationProcess(gui, compileArgs); @Nullable CharSequence compiledScript; - if (mapFilePath != null && workspaceroot != null) { - try (MpqEditor mpqEditor = MpqEditorFactory.getEditor(Optional.of(new File(mapFilePath)))) { - File projectFolder = Paths.get(workspaceroot).toFile(); - compiledScript = compilationProcess.doCompilation(mpqEditor, projectFolder, true); - if (compiledScript != null) { - gui.sendProgress("Writing to map"); - String mapScriptName = compileArgs.isLua() ? "war3map.lua" : "war3map.j"; - if (compileArgs.isLua()) { - mpqEditor.deleteFile("war3map.j"); - mpqEditor.deleteFile("scripts\\war3map.j"); - } else { - mpqEditor.deleteFile("war3map.lua"); - mpqEditor.deleteFile("scripts\\war3map.lua"); - } - byte[] mapScript = compiledScript.toString().getBytes(Charsets.UTF_8); - mpqEditor.insertFile(mapScriptName, mapScript); - } - ImportFile.importFilesFromImports(projectFolder, mpqEditor); - } - } else { - compiledScript = compilationProcess.doCompilation(null, true); - } + compiledScript = compilationProcess.doCompilation(null, true); if (compiledScript != null) { File scriptFile = new File(compileArgs.isLua() ? "compiled.lua.txt" : "compiled.j.txt"); - Files.write(compiledScript.toString().getBytes(Charsets.UTF_8), scriptFile); + java.nio.file.Files.writeString(scriptFile.toPath(), compiledScript); } gui.sendProgress("Finished!"); From 1ddbc3dcf7940618582601d65389f6cddc51d312 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 14 Aug 2026 16:44:43 +0200 Subject: [PATCH 3/4] Remove stale Jass output in Lua mode --- .../src/main/java/de/peeeq/wurstio/CompilationProcess.java | 5 +++++ .../java/de/peeeq/wurstio/CompilationProcessLuaTests.java | 1 + 2 files changed, 6 insertions(+) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java index 4b4b701a1..821d9ac4f 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java @@ -160,9 +160,11 @@ private boolean runPjass(File outputMapscript) { private File writeMapscript(CharSequence mapScript) { gui.sendProgress("Writing output file"); File outputMapscript; + File staleJassOutput = null; if (runArgs.getOutFile() != null) { String outputPath = runArgs.getOutFile(); if (runArgs.isLua() && outputPath.toLowerCase(Locale.ROOT).endsWith(".j")) { + staleJassOutput = new File(outputPath); outputPath = outputPath.substring(0, outputPath.length() - 2) + ".lua"; } outputMapscript = new File(outputPath); @@ -171,6 +173,9 @@ private File writeMapscript(CharSequence mapScript) { } outputMapscript.getParentFile().mkdirs(); try { + if (staleJassOutput != null) { + java.nio.file.Files.deleteIfExists(staleJassOutput.toPath()); + } FileUtils.write(mapScript, outputMapscript); return outputMapscript; } catch (IOException e) { diff --git a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java index bfcbe00c4..43f4baf6c 100644 --- a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java +++ b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java @@ -19,6 +19,7 @@ public void luaModeDoesNotEmitJassScript() throws Exception { Path requestedJassOutput = project.resolve("output.j"); Path output = project.resolve("output.lua"); Files.writeString(source, "package Main\nfunction foo()\nendpackage\n"); + Files.writeString(requestedJassOutput, "stale jass output"); RunArgs runArgs = new RunArgs("-lua", "-out", requestedJassOutput.toString(), source.toString()); CompilationProcess process = new CompilationProcess(new WurstGuiCliImpl(true), runArgs); From f49651a2872b75a0d219084d949f9e62d58aefc7 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 14 Aug 2026 17:15:17 +0200 Subject: [PATCH 4/4] Preserve map output and tests in CLI builds --- .../de/peeeq/wurstio/CompilationProcess.java | 7 ++-- .../languageserver/requests/MapRequest.java | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java index 821d9ac4f..00582ff58 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java @@ -85,7 +85,7 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) { if (runArgs.isRunTests()) { timeTaker.measure("Run tests", - () -> runTests(compiler.getImTranslator(), compiler, runArgs.getTestTimeout(), runArgs.getTestFilter())); + () -> runTests(gui, compiler, runArgs)); } timeTaker.measure("Run compiletime functions", () ->compiler.runCompiletime(WurstProjectConfigData.empty(), isProd, false)); @@ -183,14 +183,15 @@ private File writeMapscript(CharSequence mapScript) { } } - private void runTests(ImTranslator translator, WurstCompilerJassImpl compiler, int testTimeout, Optional testFilter) { + public static void runTests(WurstGui gui, WurstCompilerJassImpl compiler, RunArgs runArgs) { + ImTranslator translator = compiler.getImTranslator(); PrintStream out = System.out; // tests gui.sendProgress("Running tests"); if (!runArgs.isCompactOutput()) { System.out.println("Running tests"); } - RunTests runTests = new RunTests(Optional.empty(), 0, 0, Optional.empty(), testTimeout, testFilter, runArgs.isCompactOutput()) { + RunTests runTests = new RunTests(Optional.empty(), 0, 0, Optional.empty(), runArgs.getTestTimeout(), runArgs.getTestFilter(), runArgs.isCompactOutput()) { @Override protected void print(String message) { out.print(message); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java index 1153ea15d..ca30ce142 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java @@ -3,6 +3,7 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; import org.wurstscript.projectconfig.WurstProjectConfigData; +import de.peeeq.wurstio.CompilationProcess; import de.peeeq.wurstio.Pjass; import de.peeeq.wurstio.TimeTaker; import de.peeeq.wurstio.UtilsIO; @@ -172,6 +173,13 @@ protected File compileMap(File projectFolder, WurstGui gui, Optional mapCo throw new RequestFailedException(MessageType.Error, "Could not compile project (error in translation): " + gui.getErrorList().get(0)); } + if (runArgs.isRunTests()) { + CompilationProcess.runTests(gui, compiler, runArgs); + if (gui.getErrorCount() > 0) { + throw new RequestFailedException(MessageType.Error, "Could not compile project: tests failed."); + } + } + timeTaker.measure("Runinng Compiletime Functions", () -> compiler.runCompiletime(projectConfigData, isProd, runArgs.isCompiletimeCache())); if (runArgs.isLua()) { @@ -768,6 +776,7 @@ protected File executeBuildMapPipeline(ModelManager modelManager, WurstGui gui, CompilationResult result = compileScript(modelManager, gui, Optional.of(targetMapFile), projectConfig, buildDir, isProductionBuild()); injectMapData(gui, Optional.of(targetMapFile), result); + writeRequestedScript(result); targetMapFile = ensureWritableBuildOutput(targetMapFile, true); java.nio.file.Files.copy(getCachedMapFile().toPath(), targetMapFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); @@ -787,6 +796,30 @@ protected File executeBuildMapPipeline(ModelManager modelManager, WurstGui gui, return targetMapFile; } + private void writeRequestedScript(CompilationResult result) throws IOException { + if (runArgs.getOutFile() == null || result.script == null) { + return; + } + + String outputPath = runArgs.getOutFile(); + File outputFile = new File(outputPath); + if (runArgs.isLua() && outputPath.toLowerCase(Locale.ROOT).endsWith(".j")) { + java.nio.file.Files.deleteIfExists(outputFile.toPath()); + outputPath = outputPath.substring(0, outputPath.length() - 2) + ".lua"; + outputFile = new File(outputPath); + } + + File parent = outputFile.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + java.nio.file.Files.copy( + result.script.toPath(), + outputFile.toPath(), + java.nio.file.StandardCopyOption.REPLACE_EXISTING + ); + } + protected boolean isProductionBuild() { return !runArgs.isDevBuild(); }