Skip to content

Commit

Permalink
Add support for opening the IDE from DevUI even if the default ide sc…
Browse files Browse the repository at this point in the history
…ript doesn't exist

Currently support for this is only added for IntelliJ.
Code is opened client side (via the URL handler it installs)
so this support isn't needed, Netbeans already used something similar
and Eclipse seems to be sort of problematic because we don't have
a command which we can test that would not launch the IDE.

Fixes: quarkusio#15721
  • Loading branch information
geoand committed Mar 31, 2021
1 parent aa4b5c5 commit f15355f
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 24 deletions.
68 changes: 57 additions & 11 deletions core/deployment/src/main/java/io/quarkus/deployment/ide/Ide.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,69 @@
package io.quarkus.deployment.ide;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

public enum Ide {

IDEA("idea"),
ECLIPSE("eclipse"),
VSCODE("code"),
NETBEANS("netbeans");
IDEA("idea", "--help"),
ECLIPSE("eclipse", (String[]) null),
VSCODE("code", "--version"),
NETBEANS("netbeans", "--help");

private final String defaultCommand;
private final List<String> markerArgs;
private String machineSpecificCommand;

private String executable;
private String effectiveCommand;

private Ide(String executable) {
this.executable = executable;
Ide(String defaultCommand, String... markerArgs) {
this.defaultCommand = defaultCommand;
this.markerArgs = markerArgs != null ? Arrays.asList(markerArgs) : Collections.emptyList();
}

public String getExecutable() {
return executable;
/**
* Attempts to launch the default IDE script. If it succeeds, then that command is used (as the command is on the $PATH),
* otherwise the full path of the command (determined earlier in the process by looking at the running processes)
* is used.
*/
public String getEffectiveCommand() {
if (effectiveCommand != null) {
return effectiveCommand;
}
effectiveCommand = doGetEffectiveCommand();
return effectiveCommand;
}

public void setExecutable(String executable) {
this.executable = executable;
private String doGetEffectiveCommand() {
if (defaultCommand != null) {
if (markerArgs == null) {
// in this case there is nothing much we can do but hope that the default command will work
return defaultCommand;
} else {
try {
List<String> command = new ArrayList<>(1 + markerArgs.size());
command.add(defaultCommand);
command.addAll(markerArgs);
new ProcessBuilder(command).redirectError(IdeUtil.NULL_FILE).redirectOutput(IdeUtil.NULL_FILE).start()
.waitFor(10,
TimeUnit.SECONDS);
return defaultCommand;
} catch (Exception e) {
return machineSpecificCommand;
}
}
} else {
// in this case the IDE does not provide a default command so we need to rely on what was found
// from inspecting the running processes
return machineSpecificCommand;
}
}

public void setMachineSpecificCommand(String machineSpecificCommand) {
this.machineSpecificCommand = machineSpecificCommand;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.deployment.ide;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
Expand Down Expand Up @@ -41,7 +42,8 @@ public class IdeProcessor {

IDE_MARKER_FILES = Collections.unmodifiableMap(IDE_MARKER_FILES);

IDE_PROCESSES.put((processInfo -> processInfo.containInCommand("idea")), Ide.IDEA);
IDE_PROCESSES.put((processInfo -> processInfo.containInCommand("idea") && processInfo.command.endsWith("java")),
Ide.IDEA);
IDE_PROCESSES.put((processInfo -> processInfo.containInCommand("code")), Ide.VSCODE);
IDE_PROCESSES.put((processInfo -> processInfo.containInCommand("eclipse")), Ide.ECLIPSE);
IDE_PROCESSES.put(
Expand All @@ -51,8 +53,7 @@ public class IdeProcessor {
IDE_ARGUMENTS_EXEC_INDICATOR.put(Ide.NETBEANS, (ProcessInfo processInfo) -> {
String platform = processInfo.getArgumentValue("-Dnetbeans.home");
if (platform != null && !platform.isEmpty()) {
String os = System.getProperty("os.name");
if (os.startsWith("Windows") || os.startsWith("windows")) {
if (IdeUtil.isWindows()) {
platform = platform.replace("platform", "bin/netbeans.exe");
} else {
platform = platform.replace("platform", "bin/netbeans");
Expand All @@ -61,6 +62,17 @@ public class IdeProcessor {
}
return null;
});
IDE_ARGUMENTS_EXEC_INDICATOR.put(Ide.IDEA, (ProcessInfo processInfo) -> {
// converts something like '/home/test/software/idea/ideaIU-x.y.z/idea-IU-x.y.z/jbr/bin/java ....'
// into '/home/test/software/idea/ideaIU-203.5981.114/idea-IU-203.5981.114/bin/idea.sh'
String command = processInfo.getCommand();
int jbrIndex = command.indexOf("jbr");
if ((jbrIndex > -1) && command.endsWith("java")) {
String ideaHome = command.substring(0, jbrIndex);
return (ideaHome + "bin" + File.separator + "idea") + (IdeUtil.isWindows() ? ".exe" : ".sh");
}
return null;
});

IDE_PROCESSES = Collections.unmodifiableMap(IDE_PROCESSES);
}
Expand Down Expand Up @@ -150,12 +162,11 @@ public IdeRunningProcessBuildItem detectRunningIdeProcesses(LaunchModeBuildItem
for (Map.Entry<Predicate<ProcessInfo>, Ide> entry : IDE_PROCESSES.entrySet()) {
if (entry.getKey().test(processInfo)) {
Ide ide = entry.getValue();

if (IDE_ARGUMENTS_EXEC_INDICATOR.containsKey(ide)) {
Function<ProcessInfo, String> execIndicator = IDE_ARGUMENTS_EXEC_INDICATOR.get(ide);
String executeLine = execIndicator.apply(processInfo);
if (executeLine != null) {
ide.setExecutable(executeLine);
String machineSpecificCommand = execIndicator.apply(processInfo);
if (machineSpecificCommand != null) {
ide.setMachineSpecificCommand(machineSpecificCommand);
}
}
result.add(ide);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.deployment.ide;

import java.io.File;
import java.util.Locale;

final class IdeUtil {

// copied from Java 9
// TODO remove when we move to Java 11
static final File NULL_FILE = new File(isWindows() ? "NUL" : "/dev/null");

private IdeUtil() {
}

static boolean isWindows() {
String os = System.getProperty("os.name");
return os.toLowerCase(Locale.ENGLISH).startsWith("windows");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -45,20 +44,20 @@ protected void dispatch(RoutingContext routingContext, MultiMap form) {
}

if (ide != null) {
typicalProcessLaunch(routingContext, className, lang, srcMainPath, line, ide.getExecutable());
typicalProcessLaunch(routingContext, className, lang, srcMainPath, line, ide);
} else {
log.debug("Unhandled IDE : " + ide);
routingContext.fail(500);
}
}

private void typicalProcessLaunch(RoutingContext routingContext, String className, String lang, String srcMainPath,
String line, String binary) {
String line, Ide ide) {
String arg = toFileName(className, lang, srcMainPath);
if (!isNullOrEmpty(line)) {
arg = arg + ":" + line;
}
launchInIDE(Arrays.asList(binary, arg), routingContext);
launchInIDE(ide, arg, routingContext);
}

private String toFileName(String className, String lang, String srcMainPath) {
Expand All @@ -74,11 +73,12 @@ private String toFileName(String className, String lang, String srcMainPath) {

}

protected void launchInIDE(List<String> command, RoutingContext routingContext) {
protected void launchInIDE(Ide ide, String arg, RoutingContext routingContext) {
new Thread(new Runnable() {
public void run() {
try {
new ProcessBuilder(command).inheritIO().start().waitFor(10, TimeUnit.SECONDS);
new ProcessBuilder(Arrays.asList(ide.getEffectiveCommand(), arg)).inheritIO().start().waitFor(10,
TimeUnit.SECONDS);
routingContext.response().setStatusCode(200).end();
} catch (Exception e) {
routingContext.fail(e);
Expand Down

0 comments on commit f15355f

Please sign in to comment.