diff --git a/biz.aQute.bndlib.tests/test/test/MacroTest.java b/biz.aQute.bndlib.tests/test/test/MacroTest.java index d51e767d8f..e8ce5da574 100644 --- a/biz.aQute.bndlib.tests/test/test/MacroTest.java +++ b/biz.aQute.bndlib.tests/test/test/MacroTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.condition.OS; import aQute.bnd.osgi.About; import aQute.bnd.osgi.Analyzer; @@ -1376,6 +1377,85 @@ public void testSystemAllowFail() throws Exception { } } + @Test + @DisabledOnOs(WINDOWS) + public void testSystemInPath() throws Exception { + try (Processor p = new Processor()) { + Macro macro = new Macro(p); + assertEquals("/tmp", macro.process("${system-in-path;/tmp;pwd}")); + } catch (IOException e) { + fail(e); + } + } + + @Test + @DisabledOnOs({ + OS.MAC, OS.LINUX + }) + public void testSystemInPathWindows() throws Exception { + try (Processor p = new Processor()) { + Macro macro = new Macro(p); + assertEquals("c:\\", macro.process("${system-in-path;c:/;echo %cd%}")); + } catch (IOException e) { + fail(e); + } + } + + @Test + @DisabledOnOs(WINDOWS) + /** + * Verify system-allow-fail command + */ + public void testSystemInPathAllowFailWorks() throws Exception { + try (Processor p = new Processor()) { + Macro macro = new Macro(p); + assertEquals("/tmp", macro.process("${system-in-path-allow-fail;/tmp;pwd}")); + } catch (IOException e) { + fail(e); + } + } + + @Test + @DisabledOnOs({ + OS.MAC, OS.LINUX + }) + public void testSystemInPathWindowsAllowFailWorks() throws Exception { + try (Processor p = new Processor()) { + Macro macro = new Macro(p); + assertEquals("c:\\", macro.process("${system-in-path-allow-fail;c:/;echo %cd%}")); + } catch (IOException e) { + fail(e); + } + } + + @Test + @DisabledOnOs(WINDOWS) + /** + * Verify system-allow-fail command + */ + public void testSystemInPathAllowFail() throws Exception { + try (Processor p = new Processor()) { + Macro macro = new Macro(p); + assertEquals("", macro.process("${system-in-path-allow-fail;/tmp;mostidioticcommandthatwillsurelyfail}")); + } catch (IOException e) { + fail(e); + } + } + + @Test + @DisabledOnOs({ + OS.MAC, OS.LINUX + }) + public void testSystemInPathWindowsAllowFail() throws Exception { + try (Processor p = new Processor()) { + Macro macro = new Macro(p); + assertEquals("", + macro.process("${system-in-path-allow-fail;c:/;mostidioticcommandthatwillsurelyfail}")); + } catch (IOException e) { + fail(e); + } + } + /** * Check that variables override macros. */ diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java index d506b1d170..63681d44a4 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Macro.java @@ -1195,21 +1195,37 @@ boolean isLocalTarget(String string) { * System command. Execute a command and insert the result. */ public String system_internal(boolean allowFail, String[] args) throws Exception { + return system_internal(allowFail, false, args); + } + + /** + * System command. Execute a command and insert the result. + */ + public String system_internal(boolean allowFail, boolean extractPath, String[] args) throws Exception { if (nosystem) throw new RuntimeException("Macros in this mode cannot excute system commands"); - - verifyCommand(args, allowFail ? _system_allow_failHelp : _systemHelp, null, 2, 3); - String command = args[1]; + String command = null; + String path = null; String input = null; - - if (args.length > 2) { - input = args[2]; + if (extractPath) { + verifyCommand(args, allowFail ? _system_in_path_allow_failHelp : _system_in_pathHelp, null, 3, 4); + path = args[1]; + command = args[2]; + if (args.length > 3) { + input = args[3]; + } + return domain.system(allowFail, path, command, input); + } else { + verifyCommand(args, allowFail ? _system_allow_failHelp : _systemHelp, null, 2, 3); + command = args[1]; + if (args.length > 2) { + input = args[2]; + } + return domain.system(allowFail, command, input); } - - return domain.system(allowFail, command, input); } - static final String _systemHelp = "${system;[;]}, execute a system command"; + static final String _systemHelp = "${system;[;]}, execute a system command in the projects directory"; public String _system(String[] args) throws Exception { return system_internal(false, args); @@ -1228,6 +1244,25 @@ public String _system_allow_fail(String[] args) throws Exception { } } + static final String _system_in_pathHelp = "${system-in-path;;[;]}, execute a system command in the given path"; + + public String _system_in_path(String[] args) throws Exception { + return system_internal(false, true, args); + } + + static final String _system_in_path_allow_failHelp = "${system-in-path-allow-fail;;;[;]}, execute a system command allowing command failure in the given path"; + + public String _system_in_path_allow_fail(String[] args) throws Exception { + String result = ""; + try { + result = system_internal(true, true, args); + return result == null ? "" : result; + } catch (Throwable t) { + /* ignore */ + return ""; + } + } + static final String _envHelp = "${env;[;alternative]}, get the environment variable"; public String _env(String[] args) { diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java index 6add8c69e7..d8db4bf1c8 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java @@ -2436,6 +2436,16 @@ public Parameters getParameters(String key, boolean allowDuplicates) { } public String system(boolean allowFail, String command, String input) throws IOException, InterruptedException { + return system(allowFail, getBase(), command, input); + } + + public String system(boolean allowFail, String runDirectory, String command, String input) + throws IOException, InterruptedException, InterruptedException { + return system(allowFail, new File(runDirectory), command, input); + } + + public String system(boolean allowFail, File runDirectory, String command, String input) + throws IOException, InterruptedException { List args; if (IO.isWindows()) { args = Lists.of("cmd", "/c", Command.windowsQuote(command)); @@ -2445,7 +2455,7 @@ public String system(boolean allowFail, String command, String input) throws IOE .collect(toList()); } - Process process = new ProcessBuilder(args).directory(getBase()) + Process process = new ProcessBuilder(args).directory(runDirectory) .start(); try (OutputStream stdin = process.getOutputStream()) { diff --git a/docs/_macros/system.md b/docs/_macros/system.md index 41afc46e69..47544ac4f8 100644 --- a/docs/_macros/system.md +++ b/docs/_macros/system.md @@ -1,72 +1,20 @@ --- layout: default class: Macro -title: system ';' STRING ( ';' STRING )? +title: system ';' CMD ( ';' INPUT )? summary: Execute a system command --- +Executes a System command in the current project directory. +This can be used to execute command line tools. If an INPUT is given, it will be given to the process via the Standard Input. - public String _system(String args[]) throws Exception { - return system_internal(false, args); - } +If the Process exits with anything other than `0`, the result will be become an error. - public String _system_allow_fail(String args[]) throws Exception { - String result = ""; - try { - result = system_internal(true, args); - } - catch (Throwable t) { - /* ignore */ - } - return result; - } +Usage Example: +``` +# Extracts the current git SHA for the Project +Git-SHA: ${system;git rev-list -1 --no-abbrev-commit HEAD} - /** - * System command. Execute a command and insert the result. - * - * @param args - * @param help - * @param patterns - * @param low - * @param high - */ - public String system_internal(boolean allowFail, String args[]) throws Exception { - if (nosystem) - throw new RuntimeException("Macros in this mode cannot excute system commands"); - - verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system") - + ";[;]}, execute a system command", null, 2, 3); - String command = args[1]; - String input = null; - - if (args.length > 2) { - input = args[2]; - } - - if ( File.separatorChar == '\\') - command = "cmd /c \"" + command + "\""; - - - Process process = Runtime.getRuntime().exec(command, null, domain.getBase()); - if (input != null) { - process.getOutputStream().write(input.getBytes("UTF-8")); - } - process.getOutputStream().close(); - - String s = IO.collect(process.getInputStream(), "UTF-8"); - int exitValue = process.waitFor(); - if (exitValue != 0) - return exitValue + ""; - - if (exitValue != 0) { - if (!allowFail) { - domain.error("System command " + command + " failed with exit code " + exitValue); - } else { - domain.warning("System command " + command + " failed with exit code " + exitValue + " (allowed)"); - - } - } - - return s.trim(); - } - +# Extracts the current git SHA for the Project and enters the password as input +Git-SHA: ${system;git rev-list -1 --no-abbrev-commit HEAD;mypassword} +``` \ No newline at end of file diff --git a/docs/_macros/system_allow_fail.md b/docs/_macros/system_allow_fail.md index c61dde4ead..10727b90c4 100644 --- a/docs/_macros/system_allow_fail.md +++ b/docs/_macros/system_allow_fail.md @@ -1,73 +1,20 @@ --- layout: default class: Macro -title: system_allow_fail ';' STRING ( ';' STRING )? +title: system-allow-fail ';' CMD ( ';' INPUT )? summary: Execute a system command but ignore any failures --- +Executes a System command in the current project directory. +This can be used to execute command line tools. If an INPUT is given, it will be given to the process via the Standard Input. +If the Process exits with anything other than `0`, the result will be be marked as a warning. - public String _system(String args[]) throws Exception { - return system_internal(false, args); - } +Usage Example: +``` +# Extracts the current git SHA for the Project +Git-SHA: ${system-allow-fail;git rev-list -1 --no-abbrev-commit HEAD} - public String _system_allow_fail(String args[]) throws Exception { - String result = ""; - try { - result = system_internal(true, args); - } - catch (Throwable t) { - /* ignore */ - } - return result; - } - - /** - * System command. Execute a command and insert the result. - * - * @param args - * @param help - * @param patterns - * @param low - * @param high - */ - public String system_internal(boolean allowFail, String args[]) throws Exception { - if (nosystem) - throw new RuntimeException("Macros in this mode cannot excute system commands"); - - verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system") - + ";[;]}, execute a system command", null, 2, 3); - String command = args[1]; - String input = null; - - if (args.length > 2) { - input = args[2]; - } - - if ( File.separatorChar == '\\') - command = "cmd /c \"" + command + "\""; - - - Process process = Runtime.getRuntime().exec(command, null, domain.getBase()); - if (input != null) { - process.getOutputStream().write(input.getBytes("UTF-8")); - } - process.getOutputStream().close(); - - String s = IO.collect(process.getInputStream(), "UTF-8"); - int exitValue = process.waitFor(); - if (exitValue != 0) - return exitValue + ""; - - if (exitValue != 0) { - if (!allowFail) { - domain.error("System command " + command + " failed with exit code " + exitValue); - } else { - domain.warning("System command " + command + " failed with exit code " + exitValue + " (allowed)"); - - } - } - - return s.trim(); - } - \ No newline at end of file +# Extracts the current git SHA for the Project and enters the password as input +Git-SHA: ${system-allow-fail;git rev-list -1 --no-abbrev-commit HEAD;mypassword} +``` \ No newline at end of file diff --git a/docs/_macros/system_in_path.md b/docs/_macros/system_in_path.md new file mode 100644 index 0000000000..64b935343a --- /dev/null +++ b/docs/_macros/system_in_path.md @@ -0,0 +1,20 @@ +--- +layout: default +class: Macro +title: system-in-path ';' PATH ';' CMD ( ';' INPUT )? +summary: Execute a system command +--- + +Executes a System command in the given path. The path can be anywhere, even outside the current Project. +This can be used to execute command line tools. If an INPUT is given, it will be given to the process via the Standard Input. + +If the Process exits with anything other than `0`, the result will be become an error. + +Usage Example: +``` +# Extracts the current git SHA in the given path +Git-SHA: ${system-in-path; ~/git/someproject; git rev-list -1 --no-abbrev-commit HEAD} + +# Extracts the current git SHA in the given path and enters the password as input +Git-SHA: ${system-in-path; ~/git/someproject; git rev-list -1 --no-abbrev-commit HEAD;mypassword} +``` \ No newline at end of file diff --git a/docs/_macros/system_in_path_allow_fail.md b/docs/_macros/system_in_path_allow_fail.md new file mode 100644 index 0000000000..afaf0ed638 --- /dev/null +++ b/docs/_macros/system_in_path_allow_fail.md @@ -0,0 +1,20 @@ +--- +layout: default +class: Macro +title: system-in-path-allow-fail ';' PATH ';' CMD ( ';' INPUT )? +summary: Execute a system command in the given path but ignore any failures +--- + +Executes a System command in the given path. The path can be anywhere, even outside the current Project. +This can be used to execute command line tools. If an INPUT is given, it will be given to the process via the Standard Input. + +If the Process exits with anything other than `0`, the result will be be marked as a warning. + +Usage Example: +``` +# Extracts the current git SHA in the given path +Git-SHA: ${system-in-path-allow-fail; ~/git/someproject; git rev-list -1 --no-abbrev-commit HEAD} + +# Extracts the current git SHA in the given path and enters the password as input +Git-SHA: ${system-in-path-allow-fail; ~/git/someproject; git rev-list -1 --no-abbrev-commit HEAD;mypassword} +``` \ No newline at end of file