Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2201.11.0] Integrate "fast-run" support to the debug server #43703

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2020, WSO2 LLC. (http://www.wso2.org).
*
* WSO2 Inc. licenses this file to you 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.ballerinalang.langserver.commons.workspace;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Represents the context required to run a Ballerina program using the LS workspace manager.
*
* @param balSourcePath Ballerina source file path to run
* @param programArgs program arguments to run the program
* @param env environment variables to be added to the program
* @param debugPort debug port to be used for debugging (if available)
* @since 2201.11.0
*/
public record RunContext(Path balSourcePath, List<String> programArgs, Map<String, String> env, Integer debugPort) {

public static class Builder {

private final Path sourcePath;
private List<String> programArgs = new ArrayList<>();
private Map<String, String> env = Map.of();
private int debugPort = -1;

public Builder(Path sourcePath) {
this.sourcePath = sourcePath;
}

public Builder withProgramArgs(List<String> programArgs) {
this.programArgs = programArgs;
return this;
}

public Builder withEnv(Map<String, String> env) {
this.env = env;
return this;
}

public Builder withDebugPort(int debugPort) {
this.debugPort = debugPort;
return this;
}

public RunContext build() {
return new RunContext(sourcePath, programArgs, env, debugPort);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,13 @@ public interface WorkspaceManager {

/**
* Compiles and runs the project of the given file path. Run happens in a separate process.
* @param filePath Path that belongs to the project to be run.
*
* @param runContext context related to the project to be run.
* @return Process created by running the project. Empty if failed due to non process related issues.
* @throws IOException If failed to start the process.
* @since 2201.6.0
*/
Optional<Process> run(Path filePath, List<String> mainFuncArgs) throws IOException;
Optional<Process> run(RunContext runContext) throws IOException;

/**
* Stop a running process started with {@link #run}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,32 @@
package org.ballerinalang.langserver.command.executors;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.ballerinalang.annotation.JavaSPIService;
import org.ballerinalang.langserver.commons.ExecuteCommandContext;
import org.ballerinalang.langserver.commons.client.ExtendedLanguageClient;
import org.ballerinalang.langserver.commons.command.CommandArgument;
import org.ballerinalang.langserver.commons.command.LSCommandExecutorException;
import org.ballerinalang.langserver.commons.command.spi.LSCommandExecutor;
import org.ballerinalang.langserver.commons.workspace.RunContext;
import org.eclipse.lsp4j.LogTraceParams;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* Command executor for running a Ballerina file. Each project at most has a single instance running at a time.
Expand All @@ -42,36 +52,108 @@
@JavaSPIService("org.ballerinalang.langserver.commons.command.spi.LSCommandExecutor")
public class RunExecutor implements LSCommandExecutor {

private static final String RUN_COMMAND = "RUN";

// commands arg names
private static final String ARG_PATH = "path";
private static final String ARG_PROGRAM_ARGS = "programArgs";
private static final String ARG_ENV = "env";
private static final String ARG_DEBUG_PORT = "debugPort";

// output channels
private static final String ERROR_CHANNEL = "err";
private static final String OUT_CHANNEL = "out";

@Override
public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorException {
try {
Optional<Process> processOpt = context.workspace().run(extractPath(context),
extractMainFunctionArgs(context));
RunContext workspaceRunContext = getWorkspaceRunContext(context);
Optional<Process> processOpt = context.workspace().run(workspaceRunContext);
if (processOpt.isEmpty()) {
return false;
}
Process process = processOpt.get();
listenOutputAsync(context.getLanguageClient(), process::getInputStream, "out");
listenOutputAsync(context.getLanguageClient(), process::getErrorStream, "err");
listenOutputAsync(context.getLanguageClient(), process::getInputStream, OUT_CHANNEL);
listenOutputAsync(context.getLanguageClient(), process::getErrorStream, ERROR_CHANNEL);
return true;
} catch (IOException e) {
LogTraceParams error = new LogTraceParams("Error while running the program in fast-run mode: " +
e.getMessage(), ERROR_CHANNEL);
context.getLanguageClient().logTrace(error);
throw new LSCommandExecutorException(e);
} catch (Exception e) {
LogTraceParams error = new LogTraceParams("Unexpected error while executing the fast-run: " +
e.getMessage(), ERROR_CHANNEL);
context.getLanguageClient().logTrace(error);

Check warning on line 87 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L80-L87

Added lines #L80 - L87 were not covered by tests
throw new LSCommandExecutorException(e);
}
}

private static Path extractPath(ExecuteCommandContext context) {
return Path.of(context.getArguments().get(0).<JsonPrimitive>value().getAsString());
private RunContext getWorkspaceRunContext(ExecuteCommandContext context) {
RunContext.Builder builder = new RunContext.Builder(extractPath(context));
builder.withProgramArgs(extractProgramArgs(context));
builder.withEnv(extractEnvVariables(context));
builder.withDebugPort(extractDebugArgs(context));

return builder.build();
}

private static List<String> extractMainFunctionArgs(ExecuteCommandContext context) {
List<String> args = new ArrayList<>();
if (context.getArguments().size() == 1) {
return args;
}
context.getArguments().get(1).<JsonArray>value().getAsJsonArray().iterator().forEachRemaining(arg -> {
args.add(arg.getAsString());
});
return args;
private Path extractPath(ExecuteCommandContext context) {
return getCommandArgWithName(context, ARG_PATH)
.map(CommandArgument::<JsonPrimitive>value)
.map(JsonPrimitive::getAsString)
.map(pathStr -> {
try {
Path path = Path.of(pathStr);
if (!Files.exists(path)) {
throw new IllegalArgumentException("Specified path does not exist: " + pathStr);

Check warning on line 109 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L109

Added line #L109 was not covered by tests
}
return path;
} catch (InvalidPathException e) {
throw new IllegalArgumentException("Invalid path: " + pathStr, e);

Check warning on line 113 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L112-L113

Added lines #L112 - L113 were not covered by tests
}
})
.orElseThrow(() -> new IllegalArgumentException("Path argument is required"));
}

private int extractDebugArgs(ExecuteCommandContext context) {
return getCommandArgWithName(context, ARG_DEBUG_PORT)
.map(CommandArgument::<JsonPrimitive>value)
.map(JsonPrimitive::getAsInt)
.orElse(-1);
}

private List<String> extractProgramArgs(ExecuteCommandContext context) {
return getCommandArgWithName(context, ARG_PROGRAM_ARGS)
.map(arg -> arg.<JsonArray>value().getAsJsonArray())
.map(jsonArray -> StreamSupport.stream(jsonArray.spliterator(), false)
.filter(JsonElement::isJsonPrimitive)
.map(JsonElement::getAsJsonPrimitive)
.filter(JsonPrimitive::isString)
.map(JsonPrimitive::getAsString)
.collect(Collectors.toList()))

Check warning on line 134 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L130-L134

Added lines #L130 - L134 were not covered by tests
.orElse(Collections.emptyList());
}

private Map<String, String> extractEnvVariables(ExecuteCommandContext context) {
return getCommandArgWithName(context, ARG_ENV)
.map(CommandArgument::<JsonObject>value)
.map(jsonObject -> {
Map<String, String> envMap = new HashMap<>();

Check warning on line 142 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L142

Added line #L142 was not covered by tests
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
if (entry.getValue().isJsonPrimitive() && entry.getValue().getAsJsonPrimitive().isString()) {
envMap.put(entry.getKey(), entry.getValue().getAsString());

Check warning on line 145 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L145

Added line #L145 was not covered by tests
}
}
return Collections.unmodifiableMap(envMap);

Check warning on line 148 in language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java

View check run for this annotation

Codecov / codecov/patch

language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java#L147-L148

Added lines #L147 - L148 were not covered by tests
})
.orElse(Map.of());
}

private static Optional<CommandArgument> getCommandArgWithName(ExecuteCommandContext context, String name) {
return context.getArguments().stream()
.filter(commandArg -> commandArg.key().equals(name))
.findAny();
}

public void listenOutputAsync(ExtendedLanguageClient client, Supplier<InputStream> getInputStream, String channel) {
Expand All @@ -94,6 +176,6 @@

@Override
public String getCommand() {
return "RUN";
return RUN_COMMAND;
}
}
Loading
Loading