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 30, 2021
1 parent aa4b5c5 commit a1418f3
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 24 deletions.
72 changes: 61 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,73 @@
package io.quarkus.deployment.ide;

import java.io.File;
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(null);

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

private String effectiveCommand;

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

private Ide(String executable) {
this.executable = 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 String getExecutable() {
return 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(NULL_FILE).redirectOutput(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 setExecutable(String executable) {
this.executable = executable;
public void setMachineSpecificCommand(String machineSpecificCommand) {
this.machineSpecificCommand = machineSpecificCommand;
}

// copied from Java 9
// TODO remove when we move to Java 11

private static final File NULL_FILE = new File(IdeUtil.isWindows() ? "NUL" : "/dev/null");
}
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,14 @@
package io.quarkus.deployment.ide;

import java.util.Locale;

final class IdeUtil {

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 a1418f3

Please sign in to comment.