From f15355f29f7651c65e19145e4b81e28511ab9612 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 30 Mar 2021 18:11:14 +0300 Subject: [PATCH] Add support for opening the IDE from DevUI even if the default ide script 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: #15721 --- .../java/io/quarkus/deployment/ide/Ide.java | 68 ++++++++++++++++--- .../quarkus/deployment/ide/IdeProcessor.java | 25 +++++-- .../io/quarkus/deployment/ide/IdeUtil.java | 19 ++++++ .../devmode/console/OpenIdeHandler.java | 12 ++-- 4 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/ide/IdeUtil.java 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);