Skip to content

Commit 4849c5a

Browse files
ctruedenclaude
andcommitted
Encapsulate Windows exec logic in Platforms.command(exe)
Replace the old baseCommand() method (which always returned ["cmd.exe", "/c"] on Windows, regardless of whether that was appropriate) with a new command(exe) method that takes the executable into account and returns the full invocation list. This makes the "should I wrap in a shell?" decision live in Platforms where it belongs, ensures it can't be called without providing the executable, and eliminates the footgun that caused the parentheses bug. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bc86914 commit 4849c5a

2 files changed

Lines changed: 18 additions & 14 deletions

File tree

src/main/java/org/apposed/appose/tool/Tool.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -322,15 +322,7 @@ private void doExec(File cwd, boolean silent, boolean includeFlags, String... ar
322322
capturedOutput.setLength(0);
323323
capturedError.setLength(0);
324324

325-
// On Windows, cmd.exe /c is needed for shell scripts and PATH resolution,
326-
// but absolute paths to native executables must be invoked directly:
327-
// cmd.exe treats parentheses and other characters as shell metacharacters,
328-
// so a path like C:\Users\foo\Fiji(1)\bin\pixi.exe would be misinterpreted.
329-
final List<String> cmd = new ArrayList<>();
330-
if (Platforms.isWindows() && !new File(command).isAbsolute()) {
331-
cmd.addAll(Arrays.asList("cmd.exe", "/c"));
332-
}
333-
cmd.add(command);
325+
final List<String> cmd = Platforms.command(command);
334326
if (includeFlags) cmd.addAll(flags);
335327
cmd.addAll(Arrays.asList(args));
336328

src/main/java/org/apposed/appose/util/Platforms.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,26 @@ public static boolean isExecutable(File file) {
9292
}
9393

9494
/**
95-
* Gets the arguments to prefix to execute a command in a separate process.
95+
* Gets the command list needed to invoke the given executable.
96+
* <p>
97+
* On Windows, absolute paths to native executables are invoked directly
98+
* via {@link ProcessBuilder}, bypassing {@code cmd.exe} entirely.
99+
* This is necessary because {@code cmd.exe} treats parentheses and other
100+
* characters as shell metacharacters, which would break paths like
101+
* {@code C:\Users\foo\Fiji(1)\bin\pixi.exe}.
102+
* Relative names (which need shell PATH resolution) still get
103+
* {@code ["cmd.exe", "/c"]} prepended.
104+
* </p>
96105
*
97-
* @return <code>{"cmd.exe", "/c"}</code> for Windows and an empty list otherwise.
106+
* @param exe The executable path or name to invoke.
107+
* @return A mutable list starting with the executable, preceded by
108+
* {@code cmd.exe /c} when needed.
98109
*/
99-
public static List< String > baseCommand() {
100-
final List< String > cmd = new ArrayList<>();
101-
if (Platforms.isWindows())
110+
public static List<String> command(String exe) {
111+
final List<String> cmd = new ArrayList<>();
112+
if (isWindows() && !new File(exe).isAbsolute())
102113
cmd.addAll(Arrays.asList("cmd.exe", "/c"));
114+
cmd.add(exe);
103115
return cmd;
104116
}
105117
}

0 commit comments

Comments
 (0)