Add support for additional Homebrew home on macOS

Add /opt/homebrew/bin as an additional macOS fallback location when
starting external processes.

The previous fallback assumed /usr/local/bin only, which can fail on
Apple Silicon Homebrew setups in restricted PATH environments (for
example, IDE or UI-launched processes).

Update CredentialHelperTests to verify both macOS fallback paths are
attempted.

See gh-49721

Signed-off-by: 1233day <1233day@naver.com>
This commit is contained in:
1233day
2026-04-03 13:32:45 +02:00
committed by Stéphane Nicoll
parent cdbf74fc3a
commit 2d4c235522
4 changed files with 68 additions and 25 deletions
@@ -43,6 +43,10 @@ class DisabledIfProcessUnavailableCondition implements ExecutionCondition {
private static final String USR_LOCAL_BIN = "/usr/local/bin";
private static final String OPT_HOMEBREW_BIN = "/opt/homebrew/bin";
private static final String[] MAC_OS_BIN_DIRECTORIES = { OPT_HOMEBREW_BIN, USR_LOCAL_BIN };
private static final boolean MAC_OS = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac");
@Override
@@ -68,23 +72,35 @@ class DisabledIfProcessUnavailableCondition implements ExecutionCondition {
private void check(String[] command) {
ProcessBuilder processBuilder = new ProcessBuilder(command);
try {
Process process = processBuilder.start();
Assert.state(process.waitFor(30, TimeUnit.SECONDS), "Process did not exit within 30 seconds");
Assert.state(process.exitValue() == 0, () -> "Process exited with %d".formatted(process.exitValue()));
process.destroy();
check(processBuilder);
}
catch (Exception ex) {
String path = processBuilder.environment().get("PATH");
if (MAC_OS && path != null && !path.contains(USR_LOCAL_BIN)
&& !command[0].startsWith(USR_LOCAL_BIN + "/")) {
String[] localCommand = command.clone();
localCommand[0] = USR_LOCAL_BIN + "/" + localCommand[0];
check(localCommand);
return;
if (MAC_OS && path != null) {
for (String binDirectory : MAC_OS_BIN_DIRECTORIES) {
if (path.contains(binDirectory) || command[0].startsWith(binDirectory + "/")) {
continue;
}
String[] localCommand = command.clone();
localCommand[0] = binDirectory + "/" + command[0];
try {
check(new ProcessBuilder(localCommand));
return;
}
catch (Exception ignored) {
}
}
}
throw new RuntimeException(
"Unable to start process '%s'".formatted(StringUtils.arrayToDelimitedString(command, " ")));
}
}
private void check(ProcessBuilder processBuilder) throws Exception {
Process process = processBuilder.start();
Assert.state(process.waitFor(30, TimeUnit.SECONDS), "Process did not exit within 30 seconds");
Assert.state(process.exitValue() == 0, () -> "Process exited with %d".formatted(process.exitValue()));
process.destroy();
}
}