diff --git a/SpecsUtils/src/pt/up/fe/specs/util/SpecsGraphviz.java b/SpecsUtils/src/pt/up/fe/specs/util/SpecsGraphviz.java index d0d6b3da..120242a6 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/SpecsGraphviz.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/SpecsGraphviz.java @@ -29,8 +29,13 @@ public class SpecsGraphviz { private static final Lazy IS_DOT_AVAILABLE = Lazy.newInstance(SpecsGraphviz::checkDot); private static boolean checkDot() { - var result = SpecsSystem.runProcess(Arrays.asList("dot", "-?"), false, false); - return result.getReturnValue() == 0; + try { + var result = SpecsSystem.runProcess(Arrays.asList("dot", "-?"), false, false); + return result.getReturnValue() == 0; + } catch (RuntimeException e) { + // The launch itself failed, e.g. Graphviz is not installed + return false; + } } public static boolean isDotAvailable() { @@ -47,9 +52,19 @@ public static void renderDot(File dotFile, DotRenderFormat format, File outputFi var command = Arrays.asList("dot", format.getFlag(), dotFile.getAbsolutePath(), "-o", outputFile.getAbsolutePath()); - var result = SpecsSystem.runProcess(command, false, false); - if (result.getReturnValue() == 0) { - SpecsLogs.debug(() -> "Rendered dot file '" + dotFile.getAbsolutePath() + "' as " + format); + try { + var result = SpecsSystem.runProcess(command, false, false); + if (result.getReturnValue() == 0) { + SpecsLogs.debug(() -> "Rendered dot file '" + dotFile.getAbsolutePath() + "' as " + format); + } + } catch (RuntimeException e) { + // Only a missing Graphviz installation is skipped; any other + // failure is rethrown + if (!SpecsSystem.isLaunchFailure(e)) { + throw e; + } + + SpecsLogs.msgInfo("Graphviz not available, could not render dot file '" + dotFile.getAbsolutePath() + "'"); } } diff --git a/SpecsUtils/src/pt/up/fe/specs/util/SpecsSystem.java b/SpecsUtils/src/pt/up/fe/specs/util/SpecsSystem.java index 13c0c9a7..cd4400b3 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/SpecsSystem.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/SpecsSystem.java @@ -190,15 +190,7 @@ public static ProcessOutput runProcess(ProcessBuilder builder, Process process; try { - // Experiment: Calling Garbage Collector before starting process in order to - // reduce memory required to fork VM - // http://www.bryanmarty.com/2012/01/14/forking-jvm/ - long totalMemBefore = Runtime.getRuntime().totalMemory(); - System.gc(); - long totalMemAfter = Runtime.getRuntime().totalMemory(); - SpecsLogs.msgLib("Preparing to run process, memory before -> after GC: " - + SpecsStrings.parseSize(totalMemBefore) + " -> " + SpecsStrings.parseSize(totalMemAfter)); - process = builder.start(); + process = builder.start(); } catch (IOException e) { throw new RuntimeException("Could not start process", e); @@ -254,19 +246,6 @@ private static void processCommand(ProcessBuilder builder) { newCommand.add("/c"); newCommand.addAll(builder.command()); - builder.command(newCommand); - } else if (isLinux()) { - // Update command - List newCommand = new ArrayList<>(4); - newCommand.add("bash"); - // Same user - newCommand.add("-l"); - // Command - newCommand.add("-c"); - newCommand.add(builder.command().stream() - .map(arg -> arg.replace(" ", "\\ ")) - .collect(Collectors.joining(" "))); - builder.command(newCommand); } @@ -599,6 +578,26 @@ public static boolean isCommandAvailable(List command, File workingdir) } + /** + * Checks if the throwable was caused by a failure to launch the process + * itself (e.g., command not found or not executable), as opposed to a + * failure after a successful launch. + * + * @param throwable + * @return + */ + public static boolean isLaunchFailure(Throwable throwable) { + for (Throwable currentThrowable = throwable; currentThrowable != null; currentThrowable = currentThrowable + .getCause()) { + if (currentThrowable instanceof IOException) { + return true; + } + } + + return false; + } + + /** * Adds a path to the java.library.path property, and flushes the path cache so * that subsequent System.load calls diff --git a/SpecsUtils/src/pt/up/fe/specs/util/jobs/execution/ProcessExecution.java b/SpecsUtils/src/pt/up/fe/specs/util/jobs/execution/ProcessExecution.java index 8c926594..5ae0ddfa 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/jobs/execution/ProcessExecution.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/jobs/execution/ProcessExecution.java @@ -16,6 +16,7 @@ import java.io.File; import java.util.List; +import pt.up.fe.specs.util.SpecsLogs; import pt.up.fe.specs.util.SpecsSystem; /** @@ -45,7 +46,20 @@ public ProcessExecution(List commandArgs, String workingFoldername) { */ @Override public int run() { - return SpecsSystem.run(this.commandArgs, new File(this.workingFoldername)); + try { + return SpecsSystem.run(this.commandArgs, new File(this.workingFoldername)); + } catch (RuntimeException e) { + // Only a launch failure due to a missing command maps to a return + // code (the shell convention of 127, 'command not found'). Any + // other failure is not representable as an exit code and is + // rethrown, as the process may even have been running. + if (!SpecsSystem.isLaunchFailure(e)) { + throw e; + } + + SpecsLogs.msgInfo("Could not run command '" + getCommandString() + "': " + e.getMessage()); + return 127; + } } @Override