gradle: implement dev server task

This commit is contained in:
Alexey Andreev 2024-03-13 16:20:45 +01:00
parent 7341fb38a6
commit bbd02b0067
35 changed files with 1583 additions and 207 deletions

View File

@ -3,7 +3,6 @@
<component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>8.41.1</checkstyleVersion>
<scanScope>JavaOnlyWithTests</scanScope>
<option name="thirdPartyClasspath" />
<option name="activeLocationIds">
<option value="c65b2ab0-cb40-4423-befb-a37d514deee5" />
</option>

View File

@ -16,5 +16,4 @@
package org.teavm.common.json;
public abstract class JsonNumericValue extends JsonValue {
public abstract double asNumber();
}

View File

@ -19,6 +19,7 @@ import java.io.IOException;
import java.io.Reader;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
public class JsonParser {
private JsonConsumer consumer;
@ -32,6 +33,8 @@ public class JsonParser {
}
public void parse(Reader reader) throws IOException {
lineNumber = 0;
columnNumber = 0;
lastChar = reader.read();
skipWhitespaces(reader);
if (lastChar == -1) {
@ -412,4 +415,8 @@ public class JsonParser {
private static boolean isDigit(int c) {
return c >= '0' && c <= '9';
}
public static JsonParser ofValue(Consumer<JsonValue> consumer) {
return new JsonParser(new JsonVisitingConsumer(JsonValueParserVisitor.create(consumer)));
}
}

View File

@ -27,4 +27,8 @@ public abstract class JsonValue {
public long asIntNumber() {
throw new IllegalStateException();
}
public double asNumber() {
throw new IllegalStateException();
}
}

View File

@ -18,6 +18,8 @@ package org.teavm.common.json;
import java.util.function.Consumer;
public abstract class JsonValueParserVisitor extends JsonAllErrorVisitor {
private JsonValue deferred;
public abstract void consume(JsonValue value);
public static JsonValueParserVisitor create(Consumer<JsonValue> consumer) {
@ -32,7 +34,7 @@ public abstract class JsonValueParserVisitor extends JsonAllErrorVisitor {
@Override
public JsonVisitor object(JsonErrorReporter reporter) {
var jsonObject = new JsonObjectValue();
consume(jsonObject);
deferred = jsonObject;
return new JsonAllErrorVisitor() {
@Override
public JsonVisitor property(JsonErrorReporter reporter, String name) {
@ -46,19 +48,22 @@ public abstract class JsonValueParserVisitor extends JsonAllErrorVisitor {
};
}
@Override
public void end(JsonErrorReporter reporter) {
super.end(reporter);
var value = deferred;
deferred = null;
consume(value);
}
@Override
public JsonVisitor array(JsonErrorReporter reporter) {
var jsonArray = new JsonArrayValue();
consume(jsonArray);
return new JsonAllErrorVisitor() {
deferred = jsonArray;
return new JsonValueParserVisitor() {
@Override
public JsonVisitor array(JsonErrorReporter reporter) {
return new JsonValueParserVisitor() {
@Override
public void consume(JsonValue value) {
jsonArray.add(value);
}
};
public void consume(JsonValue value) {
jsonArray.add(value);
}
};
}

View File

@ -22,9 +22,17 @@ plugins {
id("org.teavm")
}
configurations {
create("teavmCli")
create("teavmClasslib")
}
dependencies {
teavm(teavm.libs.jsoApis)
compileOnly("jakarta.servlet:jakarta.servlet-api:6.0.0")
"teavmCli"("org.teavm:teavm-cli:0.10.0-SNAPSHOT")
"teavmClasslib"("org.teavm:teavm-classlib:0.10.0-SNAPSHOT")
}
teavm.js {
@ -33,3 +41,14 @@ teavm.js {
sourceMap = true
sourceFilePolicy = SourceFilePolicy.LINK_LOCAL_FILES
}
tasks.register<JavaExec>("runCli") {
classpath(configurations["teavmCli"])
mainClass = "org.teavm.cli.devserver.TeaVMDevServerRunner"
args = listOf("--json-interface", "--no-watch", "-p",
layout.buildDirectory.dir("classes/java/teavm").get().asFile.absolutePath,
) + configurations["teavmClasslib"].flatMap { listOf("-p", it.absolutePath) } + listOf(
"--", "org.teavm.samples.hello.Client"
)
println(args)
}

View File

@ -24,6 +24,7 @@ public final class PiCalculator {
}
public static void main(String[] args) {
System.out.println("hello1");
var start = System.currentTimeMillis();
int n = Integer.parseInt(args[0]);
int j = 0;

View File

@ -26,7 +26,6 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -530,7 +529,7 @@ public class IncrementalCBuilder {
}
private void fireBuildComplete(TeaVM vm) {
SimpleBuildResult result = new SimpleBuildResult(vm, Collections.emptyList());
SimpleBuildResult result = new SimpleBuildResult(vm);
for (BuilderListener listener : listeners) {
listener.compilationComplete(result);
}

View File

@ -27,6 +27,7 @@ dependencies {
implementation(project(":tools:devserver"))
implementation(project(":tools:c-incremental"))
implementation(libs.commons.cli)
implementation(libs.jetty.server)
runtimeOnly(project(":classlib"))
runtimeOnly(project(":metaprogramming:impl"))

View File

@ -0,0 +1,45 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.cli.devserver;
import java.util.function.Consumer;
import org.teavm.common.json.JsonValue;
import org.teavm.devserver.DevServer;
public class JsonCommandReader implements Consumer<JsonValue> {
private DevServer devServer;
public JsonCommandReader(DevServer devServer) {
this.devServer = devServer;
}
@Override
public void accept(JsonValue jsonValue) {
var obj = jsonValue.asObject();
var type = obj.get("type").asString();
switch (type) {
case "build":
devServer.buildProject();
break;
case "cancel":
devServer.cancelBuild();
break;
case "stop":
System.exit(0);
break;
}
}
}

View File

@ -0,0 +1,234 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.cli.devserver;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.log.Logger;
import org.teavm.common.JsonUtil;
import org.teavm.devserver.DevServerListener;
import org.teavm.diagnostics.DefaultProblemTextConsumer;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.tooling.TeaVMToolLog;
import org.teavm.tooling.builder.BuildResult;
public class JsonCommandWriter implements TeaVMToolLog, DevServerListener, Logger {
private PrintWriter writer = new PrintWriter(System.out, false, StandardCharsets.UTF_8);
@Override
public void info(String text) {
writeMessage("info", text, null);
}
@Override
public void debug(String text) {
writeMessage("debug", text, null);
}
@Override
public void warning(String text) {
writeMessage("warning", text, null);
}
@Override
public void error(String text) {
writeMessage("error", text, null);
}
@Override
public void info(String text, Throwable e) {
writeMessage("info", text, e);
}
@Override
public void debug(String text, Throwable e) {
writeMessage("debug", text, e);
}
@Override
public void warning(String text, Throwable e) {
writeMessage("warning", text, e);
}
@Override
public void error(String text, Throwable e) {
writeMessage("error", text, e);
}
@Override
public String getName() {
return "dev-server";
}
@Override
public void warn(String s, Object... objects) {
writeMessage("warning", format(s, objects), null);
}
@Override
public void warn(Throwable throwable) {
writeMessage("warning", "", throwable);
}
@Override
public void warn(String s, Throwable throwable) {
writeMessage("warning", s, throwable);
}
@Override
public void info(String s, Object... objects) {
writeMessage("info", format(s, objects), null);
}
@Override
public void info(Throwable throwable) {
writeMessage("info", "", throwable);
}
@Override
public boolean isDebugEnabled() {
return true;
}
@Override
public void setDebugEnabled(boolean b) {
}
@Override
public void debug(String s, Object... objects) {
writeMessage("debug", format(s, objects), null);
}
@Override
public void debug(String s, long l) {
writeMessage("debug", format(s, new Object[] { l }), null);
}
@Override
public void debug(Throwable throwable) {
writeMessage("debug", "", null);
}
@Override
public Logger getLogger(String s) {
return this;
}
@Override
public void ignore(Throwable throwable) {
}
private String format(String message, Object[] args) {
var index = 0;
var sb = new StringBuilder();
for (var i = 0; i < args.length; ++i) {
var next = message.indexOf("{}", index);
if (next < 0) {
break;
}
sb.append(message, index, next);
sb.append(args[i]);
index = next + 2;
}
sb.append(message, index, message.length());
return sb.toString();
}
private synchronized void writeMessage(String level, String message, Throwable throwable) {
try {
writer.append("{\"type\":\"log\",\"level\":\"").append(level).append("\",\"message\":\"");
JsonUtil.writeEscapedString(writer, message);
writer.append("\"");
if (throwable != null) {
writer.append(",\"throwable\":\"");
var throwableBuffer = new StringWriter();
var throwableWriter = new PrintWriter(throwableBuffer);
throwable.printStackTrace(throwableWriter);
JsonUtil.writeEscapedString(writer, throwableBuffer.toString());
writer.append("\"");
}
writer.append("}");
writer.println();
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public synchronized void compilationStarted() {
writer.append("{\"type\":\"compilation-started\"}");
writer.println();
writer.flush();
}
@Override
public synchronized void compilationProgress(double progress) {
writer.append("{\"type\":\"compilation-progress\",\"progress\":").append(String.valueOf(progress))
.append("}");
writer.println();
writer.flush();
}
@Override
public synchronized void compilationComplete(BuildResult result) {
var consumer = new DefaultProblemTextConsumer();
try {
writer.append("{\"type\":\"compilation-complete\"");
if (result != null && !result.getProblems().getProblems().isEmpty()) {
writer.append(",\"problems\":[");
for (var i = 0; i < result.getProblems().getProblems().size(); ++i) {
if (i > 0) {
writer.append(",");
}
var problem = result.getProblems().getProblems().get(i);
writer.append("{\"severity\":");
switch (problem.getSeverity()) {
case ERROR:
writer.append("\"error\"");
break;
case WARNING:
writer.append("\"warning\"");
break;
}
writer.append(",\"location\":\"");
var sb = new StringBuilder();
TeaVMProblemRenderer.renderCallStack(result.getCallGraph(), problem.getLocation(), sb);
JsonUtil.writeEscapedString(writer, sb.toString());
writer.append("\",\"message\":\"");
problem.render(consumer);
JsonUtil.writeEscapedString(writer, consumer.getText());
writer.append("\"}");
}
writer.append("]");
}
writer.append("}");
writer.println();
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public synchronized void compilationCancelled() {
writer.append("{\"type\":\"compilation-cancelled\"}");
writer.println();
writer.flush();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018 Alexey Andreev.
* Copyright 2024 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,9 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.cli;
package org.teavm.cli.devserver;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
@ -23,6 +29,8 @@ import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.eclipse.jetty.util.log.Log;
import org.teavm.common.json.JsonParser;
import org.teavm.devserver.DevServer;
import org.teavm.tooling.ConsoleTeaVMToolLog;
@ -30,6 +38,7 @@ public final class TeaVMDevServerRunner {
private static Options options = new Options();
private DevServer devServer;
private CommandLine commandLine;
private JsonCommandWriter jsonWriter;
static {
setupOptions();
@ -56,10 +65,21 @@ public final class TeaVMDevServerRunner {
.build());
options.addOption(Option.builder("s")
.argName("sourcepath")
.hasArg()
.hasArgs()
.desc("source path (either directory or jar file which contains source code)")
.longOpt("sourcepath")
.build());
options.addOption(Option.builder()
.argName("classnames")
.hasArgs()
.desc("list of classes that should be preserved during the build (e.g. to use with reflection)")
.longOpt("preserved-classes")
.build());
options.addOption(Option.builder()
.valueSeparator()
.hasArgs()
.longOpt("property")
.build());
options.addOption(Option.builder()
.argName("number")
.hasArg()
@ -94,6 +114,14 @@ public final class TeaVMDevServerRunner {
.desc("delegate requests from path")
.longOpt("proxy-path")
.build());
options.addOption(Option.builder()
.desc("don't watch file system changes")
.longOpt("no-watch")
.build());
options.addOption(Option.builder()
.desc("JSON interface over stdout")
.longOpt("json-interface")
.build());
}
private TeaVMDevServerRunner(CommandLine commandLine) {
@ -117,7 +145,16 @@ public final class TeaVMDevServerRunner {
TeaVMDevServerRunner runner = new TeaVMDevServerRunner(commandLine);
runner.parseArguments();
runner.runAll();
runner.devServer.start();
if (runner.jsonWriter != null) {
runner.readStdinCommands();
} else {
try {
runner.devServer.awaitServer();
} catch (InterruptedException e) {
// do nothing
}
}
}
private void parseArguments() {
@ -128,7 +165,6 @@ public final class TeaVMDevServerRunner {
devServer.setIndicator(commandLine.hasOption("indicator"));
devServer.setDeobfuscateStack(commandLine.hasOption("deobfuscate-stack"));
devServer.setReloadedAutomatically(commandLine.hasOption("auto-reload"));
devServer.setLog(new ConsoleTeaVMToolLog(commandLine.hasOption('v')));
if (commandLine.hasOption("port")) {
try {
devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port")));
@ -138,12 +174,29 @@ public final class TeaVMDevServerRunner {
}
}
var properties = commandLine.getOptionProperties("property");
for (var property : properties.stringPropertyNames()) {
devServer.getProperties().put(property, properties.getProperty(property));
}
if (commandLine.hasOption("preserved-classes")) {
devServer.getPreservedClasses().addAll(List.of(commandLine.getOptionValues("preserved-classes")));
}
if (commandLine.hasOption("proxy-url")) {
devServer.setProxyUrl(commandLine.getOptionValue("proxy-url"));
}
if (commandLine.hasOption("proxy-path")) {
devServer.setProxyPath(commandLine.getOptionValue("proxy-path"));
}
if (commandLine.hasOption("no-watch")) {
devServer.setFileSystemWatched(false);
}
if (commandLine.hasOption("json-interface")) {
setupJsonInterface(devServer);
} else {
devServer.setLog(new ConsoleTeaVMToolLog(commandLine.hasOption('v')));
}
String[] args = commandLine.getArgs();
if (args.length != 1) {
@ -175,8 +228,29 @@ public final class TeaVMDevServerRunner {
}
}
private void runAll() {
devServer.start();
private void setupJsonInterface(DevServer devServer) {
jsonWriter = new JsonCommandWriter();
devServer.setLog(jsonWriter);
devServer.addListener(jsonWriter);
devServer.setCompileOnStartup(false);
devServer.setLogBuildErrors(false);
Log.setLog(jsonWriter);
}
private void readStdinCommands() {
var commandReader = new JsonCommandReader(devServer);
var parser = JsonParser.ofValue(commandReader);
try (var reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
while (true) {
var command = reader.readLine();
if (command == null) {
break;
}
parser.parse(new StringReader(command));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void printUsage() {

View File

@ -15,7 +15,6 @@
*/
package org.teavm.tooling.builder;
import java.util.Collection;
import org.teavm.callgraph.CallGraph;
import org.teavm.diagnostics.ProblemProvider;
@ -23,10 +22,4 @@ public interface BuildResult {
CallGraph getCallGraph();
ProblemProvider getProblems();
Collection<String> getUsedResources();
Collection<String> getClasses();
Collection<String> getGeneratedFiles();
}

View File

@ -22,10 +22,8 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.teavm.backend.javascript.JSModuleType;
import org.teavm.backend.wasm.render.WasmBinaryVersion;
import org.teavm.callgraph.CallGraph;
@ -287,12 +285,8 @@ public class InProcessBuildStrategy implements BuildStrategy {
throw new BuildException(e);
}
var generatedFiles = tool.getGeneratedFiles().stream()
.map(File::getAbsolutePath)
.collect(Collectors.toSet());
return new InProcessBuildResult(tool.getDependencyInfo().getCallGraph(),
tool.getProblemProvider(), tool.getClasses(), tool.getUsedResources(), generatedFiles);
tool.getProblemProvider());
}
private URLClassLoader buildClassLoader() {
@ -310,17 +304,10 @@ public class InProcessBuildStrategy implements BuildStrategy {
static class InProcessBuildResult implements BuildResult {
private CallGraph callGraph;
private ProblemProvider problemProvider;
private Collection<String> classes;
private Collection<String> usedResources;
private Collection<String> generatedFiles;
InProcessBuildResult(CallGraph callGraph, ProblemProvider problemProvider,
Collection<String> classes, Collection<String> usedResources, Collection<String> generatedFiles) {
InProcessBuildResult(CallGraph callGraph, ProblemProvider problemProvider) {
this.callGraph = callGraph;
this.problemProvider = problemProvider;
this.classes = classes;
this.usedResources = usedResources;
this.generatedFiles = generatedFiles;
}
@Override
@ -332,20 +319,5 @@ public class InProcessBuildStrategy implements BuildStrategy {
public ProblemProvider getProblems() {
return problemProvider;
}
@Override
public Collection<String> getClasses() {
return classes;
}
@Override
public Collection<String> getUsedResources() {
return usedResources;
}
@Override
public Collection<String> getGeneratedFiles() {
return generatedFiles;
}
}
}

View File

@ -17,7 +17,6 @@ package org.teavm.tooling.builder;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import org.teavm.backend.javascript.JSModuleType;
@ -246,21 +245,6 @@ public class RemoteBuildStrategy implements BuildStrategy {
public ProblemProvider getProblems() {
return problems;
}
@Override
public Collection<String> getUsedResources() {
return response.usedResources;
}
@Override
public Collection<String> getClasses() {
return response.classes;
}
@Override
public Collection<String> getGeneratedFiles() {
return response.generatedFiles;
}
};
}

View File

@ -15,21 +15,15 @@
*/
package org.teavm.tooling.builder;
import java.util.Collection;
import java.util.List;
import org.teavm.callgraph.CallGraph;
import org.teavm.diagnostics.ProblemProvider;
import org.teavm.tooling.InstructionLocationReader;
import org.teavm.vm.TeaVM;
public class SimpleBuildResult implements BuildResult {
private TeaVM vm;
private List<String> generatedFiles;
private Collection<String> usedResources;
public SimpleBuildResult(TeaVM vm, List<String> generatedFiles) {
public SimpleBuildResult(TeaVM vm) {
this.vm = vm;
this.generatedFiles = generatedFiles;
}
@Override
@ -41,22 +35,4 @@ public class SimpleBuildResult implements BuildResult {
public ProblemProvider getProblems() {
return vm.getProblemProvider();
}
@Override
public Collection<String> getUsedResources() {
if (usedResources == null) {
usedResources = InstructionLocationReader.extractUsedResources(vm);
}
return usedResources;
}
@Override
public Collection<String> getClasses() {
return vm.getClasses();
}
@Override
public Collection<String> getGeneratedFiles() {
return generatedFiles;
}
}

View File

@ -186,11 +186,6 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi
response.callGraph = tool.getDependencyInfo().getCallGraph();
response.problems.addAll(tool.getProblemProvider().getProblems());
response.severeProblems.addAll(tool.getProblemProvider().getSevereProblems());
response.classes.addAll(tool.getClasses());
response.usedResources.addAll(tool.getUsedResources());
response.generatedFiles.addAll(tool.getGeneratedFiles().stream()
.map(File::getAbsolutePath)
.collect(Collectors.toSet()));
}
return response;

View File

@ -17,9 +17,7 @@ package org.teavm.tooling.daemon;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.teavm.callgraph.CallGraph;
import org.teavm.diagnostics.Problem;
@ -27,8 +25,5 @@ public class RemoteBuildResponse implements Serializable {
public CallGraph callGraph;
public final List<Problem> problems = new ArrayList<>();
public final List<Problem> severeProblems = new ArrayList<>();
public final Set<String> usedResources = new HashSet<>();
public final Set<String> classes = new HashSet<>();
public final Set<String> generatedFiles = new HashSet<>();
public Throwable exception;
}

View File

@ -98,16 +98,22 @@ public class FileSystemWatcher {
return !changedFiles.isEmpty() || pollNow();
}
public void pollChanges() throws IOException {
while (pollNow()) {
// continue polling
}
}
public void waitForChange(int timeout) throws InterruptedException, IOException {
if (!hasChanges()) {
take();
}
while (poll(timeout)) {
// continue polling
}
while (pollNow()) {
// continue polling
if (timeout > 0) {
while (poll(timeout)) {
// continue polling
}
}
pollChanges();
}
public List<File> grabChangedFiles() {

View File

@ -86,6 +86,7 @@ import org.teavm.parsing.resource.ResourceClassHolderMapper;
import org.teavm.tooling.EmptyTeaVMToolLog;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.tooling.TeaVMToolLog;
import org.teavm.tooling.builder.BuildResult;
import org.teavm.tooling.builder.SimpleBuildResult;
import org.teavm.tooling.util.FileSystemWatcher;
import org.teavm.vm.MemoryBuildTarget;
@ -119,6 +120,8 @@ public class CodeServlet extends HttpServlet {
private String proxyProtocol;
private int proxyPort;
private String proxyBaseUrl;
private Map<String, String> properties = new LinkedHashMap<>();
private List<String> preservedClasses = new ArrayList<>();
private Map<String, Supplier<InputStream>> sourceFileCache = new HashMap<>();
@ -148,6 +151,9 @@ public class CodeServlet extends HttpServlet {
private InMemorySymbolTable fileSymbolTable = new InMemorySymbolTable();
private InMemorySymbolTable variableSymbolTable = new InMemorySymbolTable();
private ReferenceCache referenceCache = new ReferenceCache();
private boolean fileSystemWatched = true;
private boolean compileOnStartup = true;
private boolean logBuildErrors = true;
public CodeServlet(String mainClass, String[] classPath) {
this.mainClass = mainClass;
@ -201,6 +207,26 @@ public class CodeServlet extends HttpServlet {
this.proxyPath = normalizePath(proxyPath);
}
public void setFileSystemWatched(boolean fileSystemWatched) {
this.fileSystemWatched = fileSystemWatched;
}
public void setCompileOnStartup(boolean compileOnStartup) {
this.compileOnStartup = compileOnStartup;
}
public List<String> getPreservedClasses() {
return preservedClasses;
}
public Map<String, String> getProperties() {
return properties;
}
public void setLogBuildErrors(boolean logBuildErrors) {
this.logBuildErrors = logBuildErrors;
}
public void addProgressHandler(ProgressHandler handler) {
synchronized (progressHandlers) {
progressHandlers.add(handler);
@ -241,9 +267,13 @@ public class CodeServlet extends HttpServlet {
}
public void buildProject() {
synchronized (statusLock) {
if (waiting) {
buildThread.interrupt();
if (buildThread == null) {
runCompilerThread();
} else {
synchronized (statusLock) {
if (waiting) {
buildThread.interrupt();
}
}
}
}
@ -617,7 +647,7 @@ public class CodeServlet extends HttpServlet {
}
stopped = true;
synchronized (statusLock) {
if (waiting) {
if (buildThread != null && waiting) {
buildThread.interrupt();
}
}
@ -626,7 +656,13 @@ public class CodeServlet extends HttpServlet {
@Override
public void init() throws ServletException {
super.init();
Thread thread = new Thread(this::runTeaVM);
if (compileOnStartup) {
runCompilerThread();
}
}
private void runCompilerThread() {
var thread = new Thread(this::runTeaVM);
thread.setName("TeaVM compiler");
thread.start();
buildThread = thread;
@ -726,8 +762,13 @@ public class CodeServlet extends HttpServlet {
try {
initBuilder();
var hasJob = true;
while (!stopped) {
buildOnce();
if (hasJob) {
buildOnce();
} else {
emptyBuild();
}
if (stopped) {
break;
@ -737,11 +778,22 @@ public class CodeServlet extends HttpServlet {
synchronized (statusLock) {
waiting = true;
}
watcher.waitForChange(750);
if (fileSystemWatched) {
watcher.waitForChange(750);
log.info("Changes detected. Recompiling.");
} else {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
watcher.pollChanges();
}
synchronized (statusLock) {
waiting = false;
}
log.info("Changes detected. Recompiling.");
} catch (InterruptedException e) {
if (stopped) {
break;
@ -760,6 +812,7 @@ public class CodeServlet extends HttpServlet {
}
classSource.evict(staleClasses);
hasJob = !staleClasses.isEmpty();
}
log.info("Build process stopped");
} catch (Throwable e) {
@ -836,6 +889,10 @@ public class CodeServlet extends HttpServlet {
vm.setProgressListener(progressListener);
vm.setProgramCache(programCache);
vm.installPlugins();
for (var className : preservedClasses) {
vm.preserveType(className);
}
vm.getProperties().putAll(properties);
vm.setLastKnownClasses(lastReachedClasses);
vm.setEntryPoint(mainClass);
@ -850,6 +907,12 @@ public class CodeServlet extends HttpServlet {
postBuild(vm, startTime);
}
private void emptyBuild() {
fireBuildStarted();
log.info("No files changed, nothing to do");
fireBuildCompleteWithResult(null);
}
private ClassReaderSource packClasses(ClassReaderSource source, Collection<? extends String> classNames) {
MemoryCachedClassReaderSource packedSource = createCachedSource();
packedSource.setProvider(source::get);
@ -912,7 +975,6 @@ public class CodeServlet extends HttpServlet {
private void postBuild(TeaVM vm, long startTime) {
if (!vm.wasCancelled()) {
log.info("Recompiled stale methods: " + programCache.getPendingItemsCount());
fireBuildComplete(vm);
if (vm.getProblemProvider().getSevereProblems().isEmpty()) {
log.info("Build complete successfully");
saveNewResult();
@ -926,7 +988,10 @@ public class CodeServlet extends HttpServlet {
reportCompilationComplete(false);
}
printStats(vm, startTime);
TeaVMProblemRenderer.describeProblems(vm, log);
if (logBuildErrors) {
TeaVMProblemRenderer.describeProblems(vm, log);
}
fireBuildComplete(vm);
} else {
log.info("Build cancelled");
fireBuildCancelled();
@ -1056,9 +1121,12 @@ public class CodeServlet extends HttpServlet {
}
private void fireBuildComplete(TeaVM vm) {
SimpleBuildResult result = new SimpleBuildResult(vm, new ArrayList<>(buildTarget.getNames()));
for (DevServerListener listener : listeners) {
listener.compilationComplete(result);
fireBuildCompleteWithResult(new SimpleBuildResult(vm));
}
private void fireBuildCompleteWithResult(BuildResult buildResult) {
for (var listener : listeners) {
listener.compilationComplete(buildResult);
}
}
@ -1089,7 +1157,7 @@ public class CodeServlet extends HttpServlet {
@Override
public TeaVMProgressFeedback progressReached(int progress) {
if (indicator) {
if (indicator || !listeners.isEmpty()) {
int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit;
if (current != last) {
if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) {

View File

@ -16,7 +16,9 @@
package org.teavm.devserver;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
@ -32,9 +34,14 @@ public class DevServer {
private boolean indicator;
private boolean deobfuscateStack;
private boolean reloadedAutomatically;
private boolean fileSystemWatched = true;
private TeaVMToolLog log;
private CodeServlet servlet;
private List<DevServerListener> listeners = new ArrayList<>();
private Map<String, String> properties = new LinkedHashMap<>();
private List<String> preservedClasses = new ArrayList<>();
private boolean compileOnStartup;
private boolean logBuildErrors = true;
private Server server;
private int port = 9090;
@ -88,6 +95,10 @@ public class DevServer {
this.reloadedAutomatically = reloadedAutomatically;
}
public void setFileSystemWatched(boolean fileSystemWatched) {
this.fileSystemWatched = fileSystemWatched;
}
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
@ -100,6 +111,18 @@ public class DevServer {
return sourcePath;
}
public void setCompileOnStartup(boolean compileOnStartup) {
this.compileOnStartup = compileOnStartup;
}
public List<String> getPreservedClasses() {
return preservedClasses;
}
public Map<String, String> getProperties() {
return properties;
}
public void invalidateCache() {
servlet.invalidateCache();
}
@ -116,6 +139,10 @@ public class DevServer {
listeners.add(listener);
}
public void setLogBuildErrors(boolean logBuildErrors) {
this.logBuildErrors = logBuildErrors;
}
public void start() {
server = new Server();
ServerConnector connector = new ServerConnector(server);
@ -138,6 +165,11 @@ public class DevServer {
servlet.setDebugPort(debugPort);
servlet.setProxyUrl(proxyUrl);
servlet.setProxyPath(proxyPath);
servlet.setFileSystemWatched(fileSystemWatched);
servlet.setCompileOnStartup(compileOnStartup);
servlet.setLogBuildErrors(logBuildErrors);
servlet.getProperties().putAll(properties);
servlet.getPreservedClasses().addAll(preservedClasses);
for (DevServerListener listener : listeners) {
servlet.addListener(listener);
}
@ -147,12 +179,15 @@ public class DevServer {
try {
server.start();
server.join();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void awaitServer() throws InterruptedException {
server.join();
}
public void stop() {
try {
server.stop();

View File

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (window) {
var boot = BOOT_FLAG;
var reload = RELOAD_FLAG;
var indicatorVisible = INDICATOR_FLAG;
var debugPort = DEBUG_PORT;
var deobfuscate = DEOBFUSCATE_FLAG;
var fileName = FILE_NAME;
var pathToFile = PATH_TO_FILE;
(function () {
let boot = BOOT_FLAG;
let reload = RELOAD_FLAG;
let indicatorVisible = INDICATOR_FLAG;
let debugPort = DEBUG_PORT;
let deobfuscate = DEOBFUSCATE_FLAG;
let fileName = FILE_NAME;
let pathToFile = PATH_TO_FILE;
function createWebSocket() {
return new WebSocket("ws://WS_PATH");
@ -28,18 +28,18 @@
function createIndicator() {
function createMainElement() {
var element = document.createElement("div");
let element = document.createElement("div");
element.style.position = "fixed";
element.style.left = "0";
element.style.bottom = "0";
element.style.backgroundColor = "black";
element.style.color = "white";
element.style.opacity = 0.4;
element.style.opacity = "0.4";
element.style.padding = "5px";
element.style.fontSize = "18px";
element.style.fontWeight = "bold";
element.style.pointerEvents = "none";
element.style.zIndex = 1000;
element.style.zIndex = "1000";
element.style.display = "none";
return element;
}
@ -49,7 +49,7 @@
}
function createProgressElements() {
var element = document.createElement("span");
const element = document.createElement("span");
element.style.display = "none";
element.style.marginLeft = "10px";
element.style.width = "150px";
@ -61,7 +61,7 @@
element.style.backgroundColor = "white";
element.style.position = "relative";
var progress = document.createElement("span");
const progress = document.createElement("span");
progress.style.display = "block";
progress.style.position = "absolute";
progress.style.left = "0";
@ -80,9 +80,9 @@
};
}
var container = createMainElement();
var label = createLabelElement();
var progress = createProgressElements();
const container = createMainElement();
const label = createLabelElement();
const progress = createProgressElements();
container.appendChild(label);
container.appendChild(progress.container);
@ -92,7 +92,7 @@
progress: progress,
timer: void 0,
show: function(text, timeout) {
show(text, timeout) {
this.container.style.display = "block";
this.label.innerText = text;
if (this.timer) {
@ -118,7 +118,7 @@
};
}
var indicator = createIndicator();
let indicator = createIndicator();
function onLoad() {
document.body.appendChild(indicator.container);
}
@ -137,15 +137,15 @@
}
if (typeof main === 'function') {
var oldMain = main;
let oldMain = main;
main = function() {
var args = arguments;
window.$teavm_deobfuscator_callback = function() {
const args = arguments;
window.$teavm_deobfuscator_callback = () => {
oldMain.apply(window, args);
};
var elem = document.createElement("script");
const elem = document.createElement("script");
elem.src = pathToFile + fileName + ".deobfuscator.js";
elem.onload = function() {
elem.onload = () => {
$teavm_deobfuscator([pathToFile + fileName + ".teavmdbg", pathToFile + fileName]);
};
document.head.append(elem);
@ -165,9 +165,9 @@
main();
}
var ws = createWebSocket();
let ws = createWebSocket();
ws.onmessage = function(event) {
var message = JSON.parse(event.data);
const message = JSON.parse(event.data);
switch (message.command) {
case "compiling":
indicator.show("Compiling...");
@ -177,9 +177,9 @@
if (message.success) {
indicator.show("Compilation complete", 10);
if (reload) {
window.location.reload(true);
window.location.reload();
} else if (boot) {
var scriptElem = document.createElement("script");
const scriptElem = document.createElement("script");
scriptElem.src = pathToFile + fileName;
scriptElem.onload = startMain;
document.head.appendChild(scriptElem);
@ -197,12 +197,12 @@
}
if (debugPort > 0) {
var connected = false;
let connected = false;
function connectDebugAgent(event) {
if (event.source !== window) {
return;
}
var data = event.data;
const data = event.data;
if (typeof data.teavmDebuggerRequest !== "undefined" && !connected) {
connected = true;
window.postMessage({teavmDebugger: {port: debugPort}}, "*");
@ -211,4 +211,4 @@
window.addEventListener("message", connectDebugAgent);
window.postMessage({teavmDebugger: {port: debugPort}}, "*");
}
})(this);
})();

View File

@ -74,6 +74,7 @@ val createConfig by tasks.registering {
val jsoImpl = findArtifactCoordinates(":jso:impl")
val metaprogrammingImpl = findArtifactCoordinates(":metaprogramming:impl")
val tools = findArtifactCoordinates(":tools:core")
val cli = findArtifactCoordinates(":tools:cli")
val junit = findArtifactCoordinates(":tools:junit")
doLast {
val file = File(baseDir, "org/teavm/gradle/config/ArtifactCoordinates.java")
@ -93,6 +94,7 @@ val createConfig by tasks.registering {
public static final String JUNIT = "$junit";
public static final String TOOLS = "$tools";
public static final String CLI = "$cli";
private ArtifactCoordinates() {
}

View File

@ -16,6 +16,7 @@
package org.teavm.gradle;
import groovy.lang.Closure;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.model.ObjectFactory;
@ -24,6 +25,7 @@ import org.teavm.gradle.api.OptimizationLevel;
import org.teavm.gradle.api.SourceFilePolicy;
import org.teavm.gradle.api.TeaVMCConfiguration;
import org.teavm.gradle.api.TeaVMCommonConfiguration;
import org.teavm.gradle.api.TeaVMDevServerConfiguration;
import org.teavm.gradle.api.TeaVMExtension;
import org.teavm.gradle.api.TeaVMJSConfiguration;
import org.teavm.gradle.api.TeaVMWasiConfiguration;
@ -38,7 +40,7 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio
TeaVMExtensionImpl(Project project, ObjectFactory objectFactory) {
super(project, objectFactory);
js = objectFactory.newInstance(TeaVMJSConfiguration.class);
js = objectFactory.newInstance(JsConfigImpl.class);
wasm = objectFactory.newInstance(TeaVMWasmConfiguration.class);
wasi = objectFactory.newInstance(TeaVMWasiConfiguration.class);
c = objectFactory.newInstance(TeaVMCConfiguration.class);
@ -72,6 +74,14 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio
js.getSourceFilePolicy().convention(property("js.sourceFilePolicy")
.map(SourceFilePolicy::valueOf)
.orElse(SourceFilePolicy.DO_NOTHING));
js.getDevServer().getStackDeobfuscated().convention(property("js.devServer.stackDeobfuscated")
.map(Boolean::parseBoolean));
js.getDevServer().getIndicator().convention(property("js.devServer.indicator").map(Boolean::parseBoolean));
js.getDevServer().getAutoReload().convention(property("js.devServer.autoReload").map(Boolean::parseBoolean));
js.getDevServer().getPort().convention(property("js.devServer.port").map(Integer::parseInt));
js.getDevServer().getProxyUrl().convention(property("js.devServer.proxy.url"));
js.getDevServer().getProxyPath().convention(property("js.devServer.proxy.path"));
js.getDevServer().getProcessMemory().convention(property("js.devServer.memory").map(Integer::parseInt));
}
private void setupWasmDefaults() {
@ -199,4 +209,28 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio
target.getOutOfProcess().convention(source.getOutOfProcess());
target.getProcessMemory().convention(source.getProcessMemory());
}
static abstract class JsConfigImpl implements TeaVMJSConfiguration {
private TeaVMDevServerConfiguration devServer;
@Inject
public JsConfigImpl(Project project) {
devServer = project.getObjects().newInstance(TeaVMDevServerConfiguration.class);
}
@Override
public void devServer(Action<TeaVMDevServerConfiguration> action) {
action.execute(devServer);
}
@Override
public TeaVMDevServerConfiguration getDevServer() {
return devServer;
}
@Override
public void devServer(Closure<?> action) {
action.rehydrate(getDevServer(), action.getOwner(), action.getThisObject()).call();
}
}
}

View File

@ -26,6 +26,7 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.gradle.api.artifacts.result.ResolvedDependencyResult;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaPlugin;
@ -40,17 +41,22 @@ import org.teavm.gradle.tasks.GenerateCTask;
import org.teavm.gradle.tasks.GenerateJavaScriptTask;
import org.teavm.gradle.tasks.GenerateWasiTask;
import org.teavm.gradle.tasks.GenerateWasmTask;
import org.teavm.gradle.tasks.JavaScriptDevServerTask;
import org.teavm.gradle.tasks.StopJavaScriptDevServerTask;
import org.teavm.gradle.tasks.TeaVMTask;
public class TeaVMPlugin implements Plugin<Project> {
public static final String EXTENSION_NAME = "teavm";
public static final String SOURCE_SET_NAME = "teavm";
public static final String JS_TASK_NAME = "generateJavaScript";
public static final String JS_DEV_SERVER_TASK_NAME = "javaScriptDevServer";
public static final String STOP_JS_DEV_SERVER_TASK_NAME = "stopJavaScriptDevServer";
public static final String WASM_TASK_NAME = "generateWasm";
public static final String WASI_TASK_NAME = "generateWasi";
public static final String C_TASK_NAME = "generateC";
public static final String CONFIGURATION_NAME = "teavm";
public static final String CLASSPATH_CONFIGURATION_NAME = "teavmClasspath";
public static final String TASK_GROUP = "TeaVM";
private ObjectFactory objectFactory;
@Inject
@ -105,7 +111,11 @@ public class TeaVMPlugin implements Plugin<Project> {
private void registerTasks(Project project) {
var compilerConfig = project.getConfigurations().detachedConfiguration(
project.getDependencies().create(ArtifactCoordinates.TOOLS));
var cliConfig = project.getConfigurations().detachedConfiguration(
project.getDependencies().create(ArtifactCoordinates.CLI));
registerJsTask(project, compilerConfig);
registerJsDevServerTask(project, cliConfig);
registerStopJsDevServerTask(project);
registerWasmTask(project, compilerConfig);
registerWasiTask(project, compilerConfig);
registerCTask(project, compilerConfig);
@ -125,47 +135,44 @@ public class TeaVMPlugin implements Plugin<Project> {
task.getSourceFilePolicy().convention(js.getSourceFilePolicy());
task.getMaxTopLevelNames().convention(js.getMaxTopLevelNames());
task.getSourceFiles().from(project.provider(() -> {
var result = new ArrayList<File>();
addSourceDirs(project, result);
return result;
}));
task.getSourceFiles().from(project.provider(() -> {
var dependencies = project.getConfigurations()
.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)
.getIncoming()
.getResolutionResult()
.getAllDependencies();
setupSources(task.getSourceFiles(), project);
});
}
var result = new ArrayList<File>();
for (var dependencyResult : dependencies) {
if (!(dependencyResult instanceof ResolvedDependencyResult)) {
continue;
}
var id = ((ResolvedDependencyResult) dependencyResult).getSelected().getId();
if (id instanceof ProjectComponentIdentifier) {
var path = ((ProjectComponentIdentifier) id).getProjectPath();
var refProject = project.getRootProject().findProject(path);
if (refProject != null) {
addSourceDirs(refProject, result);
}
} else if (id instanceof ModuleComponentIdentifier) {
var moduleId = (ModuleComponentIdentifier) id;
var sourcesDep = project.getDependencies().create(Map.of(
"group", moduleId.getGroup(),
"name", moduleId.getModuleIdentifier().getName(),
"version", moduleId.getVersion(),
"classifier", "sources"
));
var tmpConfig = project.getConfigurations().detachedConfiguration(sourcesDep);
tmpConfig.setTransitive(false);
if (!tmpConfig.getResolvedConfiguration().hasError()) {
result.addAll(tmpConfig.getResolvedConfiguration().getLenientConfiguration().getFiles());
}
}
}
return result;
}));
private void registerJsDevServerTask(Project project, Configuration configuration) {
var extension = project.getExtensions().getByType(TeaVMExtension.class);
project.getTasks().create(JS_DEV_SERVER_TASK_NAME, JavaScriptDevServerTask.class, task -> {
var js = extension.getJs();
task.setGroup(TASK_GROUP);
task.getMainClass().convention(js.getMainClass());
task.getClasspath().from(task.getProject().getConfigurations().getByName(CLASSPATH_CONFIGURATION_NAME));
task.getPreservedClasses().addAll(js.getPreservedClasses());
task.getProcessMemory().convention(js.getDevServer().getProcessMemory());
task.getProperties().putAll(js.getProperties());
task.getServerClasspath().from(configuration);
task.getTargetFilePath().convention(js.getRelativePathInOutputDir());
task.getTargetFileName().convention(js.getTargetFileName());
task.getStackDeobfuscated().convention(js.getDevServer().getStackDeobfuscated());
task.getIndicator().convention(js.getDevServer().getIndicator());
task.getAutoReload().convention(js.getDevServer().getAutoReload());
task.getPort().convention(js.getDevServer().getPort());
task.getProxyUrl().convention(js.getDevServer().getProxyUrl());
task.getProxyPath().convention(js.getDevServer().getProxyPath());
task.getProcessMemory().convention(js.getDevServer().getProcessMemory());
var sourceSets = project.getExtensions().findByType(SourceSetContainer.class);
if (sourceSets != null) {
task.getClasspath().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput());
task.getClasspath().from(sourceSets.getByName(SOURCE_SET_NAME).getOutput());
}
setupSources(task.getSourceFiles(), project);
});
}
private void registerStopJsDevServerTask(Project project) {
project.getTasks().create(STOP_JS_DEV_SERVER_TASK_NAME, StopJavaScriptDevServerTask.class, task -> {
task.setGroup(TASK_GROUP);
});
}
@ -270,5 +277,51 @@ public class TeaVMPlugin implements Plugin<Project> {
task.getClasspath().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput());
task.getClasspath().from(sourceSets.getByName(SOURCE_SET_NAME).getOutput());
}
task.setGroup(TASK_GROUP);
}
private void setupSources(ConfigurableFileCollection sources, Project project) {
sources.from(project.provider(() -> {
var result = new ArrayList<File>();
addSourceDirs(project, result);
return result;
}));
sources.from(project.provider(() -> {
var dependencies = project.getConfigurations()
.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)
.getIncoming()
.getResolutionResult()
.getAllDependencies();
var result = new ArrayList<File>();
for (var dependencyResult : dependencies) {
if (!(dependencyResult instanceof ResolvedDependencyResult)) {
continue;
}
var id = ((ResolvedDependencyResult) dependencyResult).getSelected().getId();
if (id instanceof ProjectComponentIdentifier) {
var path = ((ProjectComponentIdentifier) id).getProjectPath();
var refProject = project.getRootProject().findProject(path);
if (refProject != null) {
addSourceDirs(refProject, result);
}
} else if (id instanceof ModuleComponentIdentifier) {
var moduleId = (ModuleComponentIdentifier) id;
var sourcesDep = project.getDependencies().create(Map.of(
"group", moduleId.getGroup(),
"name", moduleId.getModuleIdentifier().getName(),
"version", moduleId.getVersion(),
"classifier", "sources"
));
var tmpConfig = project.getConfigurations().detachedConfiguration(sourcesDep);
tmpConfig.setTransitive(false);
if (!tmpConfig.getResolvedConfiguration().hasError()) {
result.addAll(tmpConfig.getResolvedConfiguration().getLenientConfiguration().getFiles());
}
}
}
return result;
}));
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.gradle.api;
import org.gradle.api.provider.Property;
public interface TeaVMDevServerConfiguration {
Property<Boolean> getStackDeobfuscated();
Property<Boolean> getIndicator();
Property<Boolean> getAutoReload();
Property<Integer> getPort();
Property<String> getProxyUrl();
Property<String> getProxyPath();
Property<Integer> getProcessMemory();
}

View File

@ -15,6 +15,9 @@
*/
package org.teavm.gradle.api;
import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import org.gradle.api.Action;
import org.gradle.api.provider.Property;
public interface TeaVMJSConfiguration extends TeaVMWebConfiguration {
@ -33,4 +36,10 @@ public interface TeaVMJSConfiguration extends TeaVMWebConfiguration {
Property<SourceFilePolicy> getSourceFilePolicy();
Property<Integer> getMaxTopLevelNames();
TeaVMDevServerConfiguration getDevServer();
void devServer(Action<TeaVMDevServerConfiguration> action);
void devServer(@DelegatesTo(TeaVMDevServerConfiguration.class) Closure<?> action);
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.gradle.tasks;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.gradle.api.Project;
import org.gradle.api.invocation.Gradle;
public final class DevServerManager {
private static DevServerManager instance;
private final ConcurrentMap<String, ProjectDevServerManager> projectManagers = new ConcurrentHashMap<>();
private DevServerManager() {
}
public ProjectDevServerManager getProjectManager(String path) {
return projectManagers.computeIfAbsent(path, this::createProjectManager);
}
private ProjectDevServerManager createProjectManager(String path) {
return new ProjectDevServerManager();
}
public void cleanup(Gradle gradle) {
var allProjectPaths = new HashSet<String>();
collectProjects(gradle.getRootProject(), allProjectPaths);
var keysToRemove = new HashSet<>(projectManagers.keySet());
keysToRemove.removeAll(allProjectPaths);
for (var path : keysToRemove) {
var pm = projectManagers.remove(path);
if (pm != null) {
pm.stop(gradle.getRootProject().getLogger());
}
}
}
public static DevServerManager instance() {
if (instance == null) {
instance = new DevServerManager();
}
return instance;
}
private static void collectProjects(Project project, Set<String> collector) {
if (!collector.add(project.getPath())) {
return;
}
for (var child : project.getChildProjects().values()) {
collectProjects(child, collector);
}
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.gradle.tasks;
import java.io.IOException;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
public abstract class JavaScriptDevServerTask extends DefaultTask {
@Classpath
public abstract ConfigurableFileCollection getClasspath();
@Input
@Optional
public abstract Property<String> getTargetFileName();
@Input
@Optional
public abstract Property<String> getTargetFilePath();
@Input
@Optional
public abstract MapProperty<String, String> getProperties();
@Input
@Optional
public abstract ListProperty<String> getPreservedClasses();
@Input
public abstract Property<String> getMainClass();
@Input
@Optional
public abstract Property<Boolean> getStackDeobfuscated();
@Input
@Optional
public abstract Property<Boolean> getIndicator();
@Input
@Optional
public abstract Property<Integer> getPort();
@InputFiles
public abstract ConfigurableFileCollection getSourceFiles();
@Input
@Optional
public abstract Property<Boolean> getAutoReload();
@Input
@Optional
public abstract Property<String> getProxyUrl();
@Input
@Optional
public abstract Property<String> getProxyPath();
@Input
@Optional
public abstract Property<Integer> getProcessMemory();
@Classpath
public abstract ConfigurableFileCollection getServerClasspath();
@Internal
public abstract Property<Integer> getServerDebugPort();
@Inject
protected abstract ProgressLoggerFactory getProgressLoggerFactory();
@TaskAction
public void compileInCodeServer() throws IOException {
var codeServerManager = DevServerManager.instance();
codeServerManager.cleanup(getProject().getGradle());
var pm = codeServerManager.getProjectManager(getProject().getPath());
pm.setClasspath(getClasspath().getFiles());
pm.setSources(getSourceFiles().getFiles());
if (getTargetFileName().isPresent()) {
pm.setTargetFileName(getTargetFileName().get());
}
if (getTargetFilePath().isPresent()) {
pm.setTargetFilePath(getTargetFilePath().get());
}
pm.setProperties(getProperties().get());
pm.setPreservedClasses(getPreservedClasses().get());
pm.setServerClasspath(getServerClasspath().getFiles());
pm.setMainClass(getMainClass().get());
pm.setStackDeobfuscated(!getStackDeobfuscated().isPresent() || getStackDeobfuscated().get());
pm.setIndicator(getIndicator().isPresent() && getIndicator().get());
pm.setAutoReload(getAutoReload().isPresent() && getAutoReload().get());
if (getPort().isPresent()) {
pm.setPort(getPort().get());
}
if (getProxyUrl().isPresent()) {
pm.setProxyUrl(getProxyUrl().get());
}
if (getProxyPath().isPresent()) {
pm.setProxyPath(getProxyPath().get());
}
if (getProcessMemory().isPresent()) {
pm.setProcessMemory(getProcessMemory().get());
}
if (getServerDebugPort().isPresent()) {
pm.setDebugPort(getServerDebugPort().get());
}
var progress = getProgressLoggerFactory().newOperation(getClass());
progress.start("Compilation", getName());
pm.runBuild(getLogger(), progress);
progress.completed();
}
}

View File

@ -0,0 +1,589 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.gradle.tasks;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;
import org.gradle.api.GradleException;
import org.gradle.api.logging.Logger;
import org.gradle.internal.logging.progress.ProgressLogger;
import org.teavm.common.json.JsonArrayValue;
import org.teavm.common.json.JsonObjectValue;
import org.teavm.common.json.JsonParser;
import org.teavm.common.json.JsonValue;
public class ProjectDevServerManager {
private Set<File> serverClasspath = new LinkedHashSet<>();
private Set<File> classpath = new LinkedHashSet<>();
private String targetFileName;
private String targetFilePath;
private Map<String, String> properties = new LinkedHashMap<>();
private Set<String> preservedClasses = new LinkedHashSet<>();
private String mainClass;
private boolean stackDeobfuscated;
private boolean indicator;
private int port;
private Set<File> sources = new HashSet<>();
private boolean autoReload;
private String proxyUrl;
private String proxyPath;
private int processMemory;
private int debugPort;
private Process process;
private Thread processKillHook;
private Thread commandInputThread;
private Thread stderrThread;
private BufferedWriter commandOutput;
private JsonParser jsonParser;
private BlockingQueue<Runnable> eventQueue = new LinkedBlockingQueue<>();
private boolean eventQueueDone;
private Logger logger;
private ProgressLogger progressLogger;
private Set<File> runningServerClasspath = new HashSet<>();
private Set<File> runningClasspath = new HashSet<>();
private String runningTargetFileName;
private String runningTargetFilePath;
private Map<String, String> runningProperties = new HashMap<>();
private Set<String> runningPreservedClasses = new HashSet<>();
private String runningMainClass;
private boolean runningStackDeobfuscated;
private boolean runningIndicator;
private int runningPort;
private Set<File> runningSources = new HashSet<>();
private boolean runningAutoReload;
private String runningProxyUrl;
private String runningProxyPath;
private int runningProcessMemory;
private int runningDebugPort;
ProjectDevServerManager() {
jsonParser = JsonParser.ofValue(this::parseCommand);
}
public void setServerClasspath(Set<File> serverClasspath) {
this.serverClasspath.clear();
this.serverClasspath.addAll(serverClasspath);
}
public void setClasspath(Set<File> classpath) {
this.classpath.clear();
this.classpath.addAll(classpath);
}
public void setProperties(Map<String, String> properties) {
this.properties.clear();
this.properties.putAll(properties);
}
public void setPreservedClasses(Collection<String> preservedClasses) {
this.preservedClasses.clear();
this.preservedClasses.addAll(preservedClasses);
}
public void setTargetFileName(String targetFileName) {
this.targetFileName = targetFileName;
}
public void setTargetFilePath(String targetFilePath) {
this.targetFilePath = targetFilePath;
}
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
}
public void setStackDeobfuscated(boolean stackDeobfuscated) {
this.stackDeobfuscated = stackDeobfuscated;
}
public void setIndicator(boolean indicator) {
this.indicator = indicator;
}
public void setPort(int port) {
this.port = port;
}
public void setSources(Set<File> sources) {
this.sources.clear();
this.sources.addAll(sources);
}
public void setAutoReload(boolean autoReload) {
this.autoReload = autoReload;
}
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
public void setProxyPath(String proxyPath) {
this.proxyPath = proxyPath;
}
public void setProcessMemory(int processMemory) {
this.processMemory = processMemory;
}
public void setDebugPort(int debugPort) {
this.debugPort = debugPort;
}
public void runBuild(Logger logger, ProgressLogger progressLogger) throws IOException {
restartIfNecessary(logger);
try {
schedule(() -> {
try {
commandOutput.write("{\"type\":\"build\"}\n");
commandOutput.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (InterruptedException e) {
return;
}
processQueue(logger, progressLogger);
}
private void processQueue(Logger logger, ProgressLogger progressLogger) {
eventQueueDone = false;
this.logger = logger;
this.progressLogger = progressLogger;
var stoppedUnexpectedly = new boolean[1];
var processMonitorThread = new Thread(() -> {
try {
process.waitFor();
schedule(() -> {
stoppedUnexpectedly[0] = true;
});
stopEventQueue();
} catch (InterruptedException e) {
// do nothing
}
});
processMonitorThread.setDaemon(true);
processMonitorThread.setName("Dev server process crash monitor");
processMonitorThread.start();
try {
while (!eventQueueDone || !eventQueue.isEmpty()) {
Runnable command;
try {
command = eventQueue.take();
} catch (InterruptedException e) {
break;
}
command.run();
}
if (stoppedUnexpectedly[0]) {
logger.error("Dev server process stopped unexpectedly");
throw new GradleException();
}
} finally {
this.logger = null;
this.progressLogger = null;
processMonitorThread.interrupt();
}
}
private void restartIfNecessary(Logger logger) throws IOException {
if (process != null && !checkProcess()) {
logger.info("Changes detected in TeaVM development server config, restarting server");
stop(logger);
}
if (process == null || !process.isAlive()) {
start(logger);
}
}
public void stop(Logger logger) {
if (process != null) {
logger.info("Stopping TeaVM development server, PID = {}", process.pid());
if (process.isAlive()) {
try {
commandOutput.write("{\"type\":\"stop\"}\n");
commandOutput.flush();
} catch (IOException e) {
process.destroy();
}
try {
process.waitFor();
} catch (InterruptedException e) {
// do nothing
}
} else {
logger.info("Process was dead");
}
process = null;
Runtime.getRuntime().removeShutdownHook(processKillHook);
processKillHook = null;
commandInputThread.interrupt();
commandInputThread = null;
stderrThread.interrupt();
stderrThread = null;
commandOutput = null;
} else {
logger.info("No development server running, doing nothing");
}
}
private void start(Logger logger) throws IOException {
logger.info("Starting TeaVM development server");
var pb = new ProcessBuilder();
pb.command(getBuilderCommand().toArray(new String[0]));
process = pb.start();
processKillHook = new Thread(() -> process.destroy());
Runtime.getRuntime().addShutdownHook(processKillHook);
commandOutput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8));
commandInputThread = new Thread(this::readCommandsFromProcess);
commandInputThread.setName("TeaVM development server command reader");
commandInputThread.setDaemon(true);
commandInputThread.start();
stderrThread = new Thread(this::readStderrFromProcess);
stderrThread.setName("TeaVM development server stderr reader");
stderrThread.setDaemon(true);
stderrThread.start();
logger.info("Development server started");
}
private void readCommandsFromProcess() {
try (var input = new BufferedReader(new InputStreamReader(process.getInputStream(),
StandardCharsets.UTF_8))) {
while (!Thread.currentThread().isInterrupted()) {
var command = input.readLine();
if (command == null) {
break;
}
schedule(() -> readCommand(command));
}
} catch (IOException e) {
try {
stopEventQueue();
if (logger != null) {
logger.error("IO error occurred reading stdout of development server process", e);
}
} catch (InterruptedException e2) {
if (logger != null) {
logger.info("Development server process input thread interrupted");
}
}
} catch (InterruptedException e) {
if (logger != null) {
logger.info("Development server process input thread interrupted");
}
}
}
private void readStderrFromProcess() {
try (var input = new BufferedReader(new InputStreamReader(process.getErrorStream(),
StandardCharsets.UTF_8))) {
while (!Thread.currentThread().isInterrupted()) {
var line = input.readLine();
if (line == null) {
break;
}
schedule(() -> logger.warn("server stderr: {}", line));
}
} catch (IOException e) {
if (logger != null) {
logger.error("IO error occurred reading stderr of development server process", e);
}
} catch (InterruptedException e) {
if (logger != null) {
logger.info("Development server process input thread interrupted");
}
}
}
private void stopEventQueue() throws InterruptedException {
schedule(() -> eventQueueDone = true);
}
private void schedule(Runnable command) throws InterruptedException {
eventQueue.put(command);
}
private void readCommand(String command) {
try {
jsonParser.parse(new StringReader(command));
} catch (IOException e) {
// This should not happen
throw new RuntimeException(e);
} catch (RuntimeException e) {
throw new RuntimeException("Error reading command: " + command, e);
}
}
private void parseCommand(JsonValue command) {
var obj = (JsonObjectValue) command;
var type = obj.get("type").asString();
try {
switch (type) {
case "log":
logCommand(obj);
break;
case "compilation-started":
// do nothing
break;
case "compilation-progress":
progressCommand(obj);
break;
case "compilation-complete":
completeCommand(obj);
break;
case "compilation-cancelled":
stopEventQueue();
break;
}
} catch (InterruptedException e) {
// do nothing
}
}
private void logCommand(JsonObjectValue command) throws InterruptedException {
if (logger == null) {
return;
}
var level = command.get("level").asString();
var message = command.get("message").asString();
var throwable = command.get("throwable");
if (throwable != null) {
message += "\n" + throwable.asString();
}
var messageToReport = message;
switch (level) {
case "debug":
schedule(() -> logger.debug(messageToReport));
break;
case "info":
schedule(() -> logger.info(messageToReport));
break;
case "warning":
schedule(() -> logger.warn(messageToReport));
break;
case "error":
schedule(() -> logger.error(messageToReport));
break;
}
}
private void progressCommand(JsonObjectValue command) throws InterruptedException {
if (progressLogger == null) {
return;
}
var progress = command.get("progress").asNumber();
var roundedResult = (int) (progress * 1000 + 5) / 10;
var result = Math.min(100, roundedResult / 10.0);
schedule(() -> progressLogger.progress(result + " %"));
}
private void completeCommand(JsonObjectValue command) throws InterruptedException {
var problemsJson = command.get("problems");
if (problemsJson != null && logger != null) {
reportProblems((JsonArrayValue) problemsJson);
}
stopEventQueue();
}
private void reportProblems(JsonArrayValue json) throws InterruptedException {
var hasSevere = false;
for (var i = 0; i < json.size(); ++i) {
var problem = json.get(i).asObject();
var severity = problem.get("severity").asString();
var sb = new StringBuilder();
sb.append(problem.get("message").asString());
sb.append(problem.get("location").asString());
var message = sb.toString();
switch (severity) {
case "error":
hasSevere = true;
schedule(() -> logger.error(message));
break;
case "warning":
schedule(() -> logger.warn(message));
break;
}
}
if (hasSevere) {
schedule(() -> {
throw new GradleException("Errors occurred during TeaVM build");
});
}
}
private List<String> getBuilderCommand() {
var command = new ArrayList<String>();
var javaHome = System.getProperty("java.home");
var javaExec = javaHome + "/bin/java";
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
javaExec += ".exe";
}
command.add(javaExec);
if (!serverClasspath.isEmpty()) {
command.add("-cp");
command.add(serverClasspath.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator)));
}
runningServerClasspath.clear();
runningServerClasspath.addAll(serverClasspath);
if (processMemory != 0) {
command.add("-Xmx" + processMemory + "m");
}
runningProcessMemory = processMemory;
if (debugPort != 0) {
command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:" + debugPort);
}
runningDebugPort = debugPort;
command.add("org.teavm.cli.devserver.TeaVMDevServerRunner");
command.add("--json-interface");
command.add("--no-watch");
if (targetFileName != null) {
command.add("--targetfile");
command.add(targetFileName);
}
runningTargetFileName = targetFileName;
if (targetFilePath != null) {
command.add("--targetdir");
command.add(targetFilePath);
}
runningTargetFilePath = targetFilePath;
if (!classpath.isEmpty()) {
command.add("--classpath");
command.addAll(classpath.stream()
.map(File::getAbsolutePath)
.collect(Collectors.toList()));
}
runningClasspath.clear();
runningClasspath.addAll(classpath);
if (!sources.isEmpty()) {
command.add("--sourcepath");
command.addAll(sources.stream()
.map(File::getAbsolutePath)
.collect(Collectors.toList()));
}
runningSources.clear();
runningSources.addAll(sources);
if (port != 0) {
command.add("--port");
command.add(String.valueOf(port));
}
runningPort = port;
if (indicator) {
command.add("--indicator");
}
runningIndicator = indicator;
if (stackDeobfuscated) {
command.add("--deobfuscate-stack");
}
runningStackDeobfuscated = stackDeobfuscated;
if (autoReload) {
command.add("--auto-reload");
}
runningAutoReload = autoReload;
if (proxyUrl != null) {
command.add("--proxy-url");
command.add(proxyUrl);
}
runningProxyUrl = proxyUrl;
if (proxyPath != null) {
command.add("--proxy-path");
command.add(proxyPath);
}
runningProxyPath = proxyPath;
for (var entry : properties.entrySet()) {
command.add("--property");
command.add(entry.getKey() + "=" + entry.getValue());
}
runningProperties.clear();
runningProperties.putAll(properties);
if (!preservedClasses.isEmpty()) {
command.add("--preserved-classes");
command.addAll(preservedClasses);
}
runningPreservedClasses.clear();
runningPreservedClasses.addAll(preservedClasses);
command.add("--");
command.add(mainClass);
runningMainClass = mainClass;
return command;
}
private boolean checkProcess() {
return Objects.equals(serverClasspath, runningServerClasspath)
&& Objects.equals(classpath, runningClasspath)
&& Objects.equals(targetFileName, runningTargetFileName)
&& Objects.equals(targetFilePath, runningTargetFilePath)
&& Objects.equals(properties, runningProperties)
&& Objects.equals(preservedClasses, runningPreservedClasses)
&& Objects.equals(mainClass, runningMainClass)
&& stackDeobfuscated == runningStackDeobfuscated
&& indicator == runningIndicator
&& port == runningPort
&& Objects.equals(sources, runningSources)
&& autoReload == runningAutoReload
&& Objects.equals(proxyUrl, runningProxyUrl)
&& Objects.equals(proxyPath, runningProxyPath)
&& processMemory == runningProcessMemory
&& debugPort == runningDebugPort;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2024 Alexey Andreev.
*
* 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
*
* http://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.
*/
package org.teavm.gradle.tasks;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
public abstract class StopJavaScriptDevServerTask extends DefaultTask {
@TaskAction
public void stopServer() {
var codeServerManager = DevServerManager.instance();
codeServerManager.cleanup(getProject().getGradle());
var pm = codeServerManager.getProjectManager(getProject().getPath());
pm.stop(getLogger());
}
}

View File

@ -52,7 +52,6 @@ import org.teavm.vm.TeaVMProgressListener;
public abstract class TeaVMTask extends DefaultTask {
public TeaVMTask() {
setGroup("TeaVM");
getDebugInformation().convention(false);
getTargetFileName().convention("bundle");
getOptimization().convention(OptimizationLevel.BALANCED);

View File

@ -22,10 +22,10 @@ plugins {
description = "All-in one JAR file that used by IDE plugins"
dependencies {
implementation(project(path = ":tools:core"))
implementation(project(path = ":tools:devserver"))
implementation(project(path = ":classlib"))
implementation(project(path = ":tools:chrome-rdp"))
api(project(":tools:core"))
api(project(":tools:devserver"))
api(project(":classlib"))
api(project(":tools:chrome-rdp"))
}
tasks.shadowJar {

View File

@ -32,7 +32,8 @@ intellij {
}
dependencies {
implementation(project(path = ":tools:ide-deps", configuration = "shadow").setTransitive(false))
compileOnly(project(":tools:ide-deps"))
runtimeOnly(project(path = ":tools:ide-deps", configuration = "shadow").setTransitive(false))
}
tasks {

View File

@ -153,6 +153,7 @@ public class DevServerRunner extends UnicastRemoteObject implements DevServerMan
DevServerRunner daemon = new DevServerRunner(server);
System.out.println(PORT_MESSAGE_PREFIX + daemon.port);
server.start();
server.awaitServer();
try {
daemon.registry.unbind(ID);
@ -327,7 +328,9 @@ public class DevServerRunner extends UnicastRemoteObject implements DevServerMan
@Override
public void compilationComplete(BuildResult buildResult) {
DevServerBuildResult result = new DevServerBuildResult();
result.problems.addAll(buildResult.getProblems().getProblems());
if (buildResult != null) {
result.problems.addAll(buildResult.getProblems().getProblems());
}
for (DevServerManagerListener listener : getListeners()) {
try {
listener.compilationComplete(result);