diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ide/Ide.java b/core/deployment/src/main/java/io/quarkus/deployment/ide/Ide.java index e64c591d39da8..eaaa002bc6fa3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ide/Ide.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ide/Ide.java @@ -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 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 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; + } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeProcessor.java index d27b98c7ddaf8..6ad746a2dd86b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeProcessor.java @@ -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; @@ -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( @@ -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"); @@ -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); } @@ -150,12 +162,11 @@ public IdeRunningProcessBuildItem detectRunningIdeProcesses(LaunchModeBuildItem for (Map.Entry, Ide> entry : IDE_PROCESSES.entrySet()) { if (entry.getKey().test(processInfo)) { Ide ide = entry.getValue(); - if (IDE_ARGUMENTS_EXEC_INDICATOR.containsKey(ide)) { Function 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); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeUtil.java new file mode 100644 index 0000000000000..f15143c1ded9f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/ide/IdeUtil.java @@ -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"); + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/OpenIdeHandler.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/OpenIdeHandler.java index 20be633d2eac4..16a7190db07f2 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/OpenIdeHandler.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/OpenIdeHandler.java @@ -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; @@ -45,7 +44,7 @@ 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); @@ -53,12 +52,12 @@ protected void dispatch(RoutingContext routingContext, MultiMap form) { } 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) { @@ -74,11 +73,12 @@ private String toFileName(String className, String lang, String srcMainPath) { } - protected void launchInIDE(List 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);