Skip to content

Commit ccfb36c

Browse files
committed
Merge pull request #49721 from froggy-hyun
* froggy-hyun/main: Polish "Add support for additional Homebrew home on macOS" Add support for additional Homebrew home on macOS Closes gh-49721
2 parents cdbf74f + 1519bcf commit ccfb36c

4 files changed

Lines changed: 68 additions & 26 deletions

File tree

buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/CredentialHelper.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ class CredentialHelper {
4141

4242
private static final String USR_LOCAL_BIN = "/usr/local/bin/";
4343

44+
private static final String OPT_HOMEBREW_BIN = "/opt/homebrew/bin/";
45+
46+
private static final String[] MAC_OS_BIN_DIRECTORIES = { OPT_HOMEBREW_BIN, USR_LOCAL_BIN };
47+
4448
private static final Set<String> CREDENTIAL_NOT_FOUND_MESSAGES = Set.of("credentials not found in native keychain",
4549
"no credentials server URL", "no credentials username");
4650

@@ -92,16 +96,19 @@ private Process start(ProcessBuilder processBuilder) throws IOException {
9296
if (!Platform.isMac()) {
9397
throw ex;
9498
}
95-
try {
96-
List<String> command = new ArrayList<>(processBuilder.command());
97-
command.set(0, USR_LOCAL_BIN + command.get(0));
98-
return processBuilder.command(command).start();
99-
}
100-
catch (Exception suppressed) {
101-
// Suppresses the exception and rethrows the original exception
102-
ex.addSuppressed(suppressed);
103-
throw ex;
99+
List<String> originalCommand = List.copyOf(processBuilder.command());
100+
String executable = originalCommand.get(0);
101+
for (String binDirectory : MAC_OS_BIN_DIRECTORIES) {
102+
List<String> command = new ArrayList<>(originalCommand);
103+
command.set(0, binDirectory + executable);
104+
try {
105+
return processBuilder.command(command).start();
106+
}
107+
catch (Exception suppressed) {
108+
ex.addSuppressed(suppressed);
109+
}
104110
}
111+
throw ex;
105112
}
106113
}
107114

buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/CredentialHelperTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,11 @@ void getWhenExecutableDoesNotExistErrorThrowsException() {
106106
.satisfies((ex) -> {
107107
if (Platform.isMac()) {
108108
assertThat(ex.getMessage()).doesNotContain("/usr/local/bin/");
109-
assertThat(ex.getSuppressed()).allSatisfy((suppressed) -> assertThat(suppressed)
110-
.hasMessageContaining("/usr/local/bin/" + executable));
109+
assertThat(ex.getSuppressed()).satisfiesExactlyInAnyOrder(
110+
(suppressed) -> assertThat(suppressed)
111+
.hasMessageContaining("/opt/homebrew/bin/" + executable),
112+
(suppressed) -> assertThat(suppressed)
113+
.hasMessageContaining("/usr/local/bin/" + executable));
111114
}
112115
});
113116
}

core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessRunner.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class ProcessRunner {
4444

4545
private static final String USR_LOCAL_BIN = "/usr/local/bin";
4646

47+
private static final String OPT_HOMEBREW_BIN = "/opt/homebrew/bin";
48+
49+
private static final String[] MAC_OS_BIN_DIRECTORIES = { OPT_HOMEBREW_BIN, USR_LOCAL_BIN };
50+
4751
private static final boolean MAC_OS = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac");
4852

4953
private static final Log logger = LogFactory.getLog(ProcessRunner.class);
@@ -108,11 +112,22 @@ private Process startProcess(String[] command) {
108112
}
109113
catch (IOException ex) {
110114
String path = processBuilder.environment().get("PATH");
111-
if (MAC_OS && path != null && !path.contains(USR_LOCAL_BIN)
112-
&& !command[0].startsWith(USR_LOCAL_BIN + "/")) {
113-
String[] localCommand = command.clone();
114-
localCommand[0] = USR_LOCAL_BIN + "/" + localCommand[0];
115-
return startProcess(localCommand);
115+
if (MAC_OS && path != null) {
116+
for (String binDirectory : MAC_OS_BIN_DIRECTORIES) {
117+
if (path.contains(binDirectory) || command[0].startsWith("/")) {
118+
continue;
119+
}
120+
String[] localCommand = command.clone();
121+
localCommand[0] = binDirectory + "/" + command[0];
122+
ProcessBuilder localProcessBuilder = new ProcessBuilder(localCommand);
123+
localProcessBuilder.directory(this.workingDirectory);
124+
try {
125+
return localProcessBuilder.start();
126+
}
127+
catch (IOException suppressed) {
128+
ex.addSuppressed(suppressed);
129+
}
130+
}
116131
}
117132
throw new ProcessStartException(command, ex);
118133
}

test-support/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/process/DisabledIfProcessUnavailableCondition.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ class DisabledIfProcessUnavailableCondition implements ExecutionCondition {
4343

4444
private static final String USR_LOCAL_BIN = "/usr/local/bin";
4545

46+
private static final String OPT_HOMEBREW_BIN = "/opt/homebrew/bin";
47+
48+
private static final String[] MAC_OS_BIN_DIRECTORIES = { OPT_HOMEBREW_BIN, USR_LOCAL_BIN };
49+
4650
private static final boolean MAC_OS = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac");
4751

4852
@Override
@@ -68,23 +72,36 @@ private Stream<String[]> getAnnotationValue(AnnotatedElement testElement) {
6872
private void check(String[] command) {
6973
ProcessBuilder processBuilder = new ProcessBuilder(command);
7074
try {
71-
Process process = processBuilder.start();
72-
Assert.state(process.waitFor(30, TimeUnit.SECONDS), "Process did not exit within 30 seconds");
73-
Assert.state(process.exitValue() == 0, () -> "Process exited with %d".formatted(process.exitValue()));
74-
process.destroy();
75+
check(processBuilder);
7576
}
7677
catch (Exception ex) {
7778
String path = processBuilder.environment().get("PATH");
78-
if (MAC_OS && path != null && !path.contains(USR_LOCAL_BIN)
79-
&& !command[0].startsWith(USR_LOCAL_BIN + "/")) {
80-
String[] localCommand = command.clone();
81-
localCommand[0] = USR_LOCAL_BIN + "/" + localCommand[0];
82-
check(localCommand);
83-
return;
79+
if (MAC_OS && path != null) {
80+
for (String binDirectory : MAC_OS_BIN_DIRECTORIES) {
81+
if (path.contains(binDirectory) || command[0].startsWith("/")) {
82+
continue;
83+
}
84+
String[] localCommand = command.clone();
85+
localCommand[0] = binDirectory + "/" + command[0];
86+
try {
87+
check(new ProcessBuilder(localCommand));
88+
return;
89+
}
90+
catch (Exception suppressed) {
91+
ex.addSuppressed(suppressed);
92+
}
93+
}
8494
}
8595
throw new RuntimeException(
8696
"Unable to start process '%s'".formatted(StringUtils.arrayToDelimitedString(command, " ")));
8797
}
8898
}
8999

100+
private void check(ProcessBuilder processBuilder) throws Exception {
101+
Process process = processBuilder.start();
102+
Assert.state(process.waitFor(30, TimeUnit.SECONDS), "Process did not exit within 30 seconds");
103+
Assert.state(process.exitValue() == 0, () -> "Process exited with %d".formatted(process.exitValue()));
104+
process.destroy();
105+
}
106+
90107
}

0 commit comments

Comments
 (0)