Skip to content

Commit e00c3e4

Browse files
authored
Merge pull request #1592 from s4chinjha/fix-maven-home-detection
Fix Maven home detection using PATH when MAVEN_HOME is not set
2 parents 00aca06 + 5b847a0 commit e00c3e4

File tree

2 files changed

+162
-14
lines changed

2 files changed

+162
-14
lines changed

plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Config.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.jenkins.tools.pluginmodernizer.core.config;
22

3+
import edu.umd.cs.findbugs.annotations.Nullable;
34
import io.jenkins.tools.pluginmodernizer.core.model.Plugin;
45
import io.jenkins.tools.pluginmodernizer.core.model.Recipe;
56
import java.net.URL;
@@ -26,6 +27,7 @@ public static void setDebug(boolean debug) {
2627
private final URL githubApiUrl;
2728
private final Path cachePath;
2829
private final Path mavenHome;
30+
private volatile Path detectedMavenHome;
2931
private final Path mavenLocalRepo;
3032
private final boolean skipMetadata;
3133
private final boolean overrideOptOutPlugins;
@@ -167,10 +169,33 @@ public Path getCachePath() {
167169
}
168170

169171
public Path getMavenHome() {
170-
if (mavenHome == null) {
171-
return null;
172+
if (mavenHome != null) {
173+
return mavenHome.toAbsolutePath();
172174
}
173-
return mavenHome.toAbsolutePath();
175+
176+
if (detectedMavenHome != null) {
177+
return detectedMavenHome.toAbsolutePath();
178+
}
179+
180+
return null;
181+
}
182+
183+
/**
184+
* Maven home explicitly configured via CLI/env (does not include detected value).
185+
*/
186+
public @Nullable Path getConfiguredMavenHome() {
187+
return mavenHome == null ? null : mavenHome.toAbsolutePath();
188+
}
189+
190+
/**
191+
* Maven home detected from PATH and cached for subsequent use.
192+
*/
193+
public @Nullable Path getDetectedMavenHome() {
194+
return detectedMavenHome == null ? null : detectedMavenHome.toAbsolutePath();
195+
}
196+
197+
public void setMavenHome(Path mavenHome) {
198+
this.detectedMavenHome = mavenHome;
174199
}
175200

176201
public Path getMavenLocalRepo() {

plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/MavenInvoker.java

Lines changed: 134 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
import io.jenkins.tools.pluginmodernizer.core.model.Recipe;
1111
import io.jenkins.tools.pluginmodernizer.core.utils.JdkFetcher;
1212
import jakarta.inject.Inject;
13+
import java.io.BufferedReader;
1314
import java.io.IOException;
15+
import java.io.InputStreamReader;
16+
import java.nio.charset.StandardCharsets;
1417
import java.nio.file.Files;
1518
import java.nio.file.Path;
1619
import java.util.ArrayList;
@@ -55,7 +58,7 @@ public class MavenInvoker {
5558
AtomicReference<String> version = new AtomicReference<>();
5659
try {
5760
InvocationRequest request = new DefaultInvocationRequest();
58-
request.setMavenHome(config.getMavenHome().toFile());
61+
request.setMavenHome(getEffectiveMavenHome().toFile());
5962
request.setBatchMode(true);
6063
request.addArg("-q");
6164
request.addArg("--version");
@@ -181,13 +184,16 @@ private void validatePom(Plugin plugin) {
181184
* @throws IllegalArgumentException if the Maven home directory is not set or invalid.
182185
*/
183186
public void validateMaven() {
184-
Path mavenHome = config.getMavenHome();
185-
if (mavenHome == null) {
186-
throw new ModernizerException(
187-
"Neither MAVEN_HOME nor M2_HOME environment variables are set. Or use --maven-home if running from CLI");
188-
}
187+
Path mavenHome = getEffectiveMavenHome();
188+
189+
Path mvnUnix = mavenHome.resolve("bin/mvn");
190+
Path mvnCmd = mavenHome.resolve("bin/mvn.cmd");
191+
Path mvnBat = mavenHome.resolve("bin/mvn.bat");
189192

190-
if (!Files.isDirectory(mavenHome) || !Files.isExecutable(mavenHome.resolve("bin/mvn"))) {
193+
if (!Files.isDirectory(mavenHome)
194+
|| (!mvnUnix.toFile().canExecute()
195+
&& !mvnCmd.toFile().exists()
196+
&& !mvnBat.toFile().exists())) {
191197
throw new ModernizerException("Invalid Maven home directory at '%s'.".formatted(mavenHome));
192198
}
193199

@@ -200,6 +206,123 @@ public void validateMaven() {
200206
}
201207
}
202208

209+
@SuppressWarnings("OS_COMMAND_INJECTION")
210+
@Nullable
211+
private Path detectMavenHome() {
212+
String os = System.getProperty("os.name");
213+
if (os == null) {
214+
os = "";
215+
}
216+
217+
ProcessBuilder processBuilder;
218+
if (os.toLowerCase().contains("win")) {
219+
processBuilder = new ProcessBuilder("where", "mvn");
220+
} else {
221+
processBuilder = new ProcessBuilder("which", "mvn");
222+
}
223+
processBuilder.redirectErrorStream(true);
224+
225+
String mvnPath = null;
226+
StringBuilder output = new StringBuilder();
227+
Process process = null;
228+
229+
try {
230+
process = processBuilder.start();
231+
232+
try (BufferedReader reader =
233+
new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
234+
String line;
235+
while ((line = reader.readLine()) != null) {
236+
if (output.length() > 0) {
237+
output.append('\n');
238+
}
239+
output.append(line);
240+
241+
if (mvnPath == null && !line.isBlank()) {
242+
mvnPath = line;
243+
}
244+
}
245+
}
246+
247+
int exitCode = process.waitFor();
248+
if (exitCode != 0 || mvnPath == null || mvnPath.isBlank()) {
249+
LOG.debug("Maven not found in PATH (exitCode=" + exitCode + ", output=" + sanitize(output.toString())
250+
+ ")");
251+
return null;
252+
}
253+
254+
Path mvn = Path.of(mvnPath).toRealPath();
255+
Path binDir = mvn.getParent();
256+
if (binDir == null) {
257+
LOG.debug("Failed to detect Maven home from mvn path (no parent): " + sanitize(mvnPath));
258+
return null;
259+
}
260+
261+
Path mavenHome = binDir.getParent();
262+
if (mavenHome == null) {
263+
LOG.debug("Failed to detect Maven home from mvn path (no grandparent): " + sanitize(mvnPath));
264+
return null;
265+
}
266+
return mavenHome;
267+
} catch (InterruptedException e) {
268+
Thread.currentThread().interrupt();
269+
LOG.debug("Interrupted while detecting Maven from PATH", e);
270+
return null;
271+
} catch (Exception e) {
272+
LOG.debug("Failed to detect Maven from PATH", e);
273+
return null;
274+
} finally {
275+
if (process != null) {
276+
process.destroy();
277+
}
278+
}
279+
}
280+
281+
private boolean isValidMavenHome(Path mavenHome) {
282+
if (mavenHome == null || !Files.isDirectory(mavenHome)) {
283+
return false;
284+
}
285+
286+
Path mvnUnix = mavenHome.resolve("bin/mvn");
287+
Path mvnCmd = mavenHome.resolve("bin/mvn.cmd");
288+
Path mvnBat = mavenHome.resolve("bin/mvn.bat");
289+
return mvnUnix.toFile().canExecute()
290+
|| mvnCmd.toFile().exists()
291+
|| mvnBat.toFile().exists();
292+
}
293+
294+
private String sanitize(String input) {
295+
return input == null ? null : input.replaceAll("[\\r\\n]", "");
296+
}
297+
298+
private Path getEffectiveMavenHome() {
299+
Path configured = config.getConfiguredMavenHome();
300+
if (configured != null) {
301+
if (isValidMavenHome(configured)) {
302+
return configured;
303+
}
304+
LOG.warn("Configured Maven home is invalid: " + sanitize(configured.toString())
305+
+ ". Falling back to PATH detection.");
306+
}
307+
308+
Path cachedDetected = config.getDetectedMavenHome();
309+
if (cachedDetected != null) {
310+
if (isValidMavenHome(cachedDetected)) {
311+
return cachedDetected;
312+
}
313+
LOG.debug("Cached detected Maven home is invalid: " + sanitize(cachedDetected.toString()));
314+
}
315+
316+
Path detected = detectMavenHome();
317+
if (detected != null && isValidMavenHome(detected)) {
318+
LOG.info("Detected Maven home from PATH: " + sanitize(detected.toString()));
319+
config.setMavenHome(detected);
320+
return detected;
321+
}
322+
323+
throw new ModernizerException("Maven not found. Please set MAVEN_HOME or ensure 'mvn' is available in PATH.");
324+
}
325+
203326
/**
204327
* Validate the Maven version.
205328
* @throws IllegalArgumentException if the Maven version is too old or cannot be determined.
@@ -228,10 +351,10 @@ public void validateMavenVersion() {
228351
*/
229352
private InvocationRequest createInvocationRequest(Plugin plugin, String... args) {
230353
InvocationRequest request = new DefaultInvocationRequest();
231-
request.setMavenHome(config.getMavenHome().toFile());
354+
request.setMavenHome(getEffectiveMavenHome().toFile());
232355
request.setPomFile(plugin.getLocalRepository().resolve("pom.xml").toFile());
233356
request.addArgs(List.of(args));
234-
if (config.isDebug()) {
357+
if (Config.isDebug()) {
235358
request.addArg("-X");
236359
}
237360
return request;
@@ -244,12 +367,12 @@ private InvocationRequest createInvocationRequest(Plugin plugin, String... args)
244367
*/
245368
private void handleInvocationResult(Plugin plugin, InvocationResult result) {
246369
if (result.getExitCode() != 0) {
247-
LOG.error(plugin.getMarker(), "Build fail with code: {}", result.getExitCode());
370+
LOG.error(plugin.getMarker(), "Build failed with code: {}", result.getExitCode());
248371
if (result.getExecutionException() != null) {
249372
plugin.addError("Maven generic exception occurred", result.getExecutionException());
250373
} else {
251374
String errorMessage;
252-
if (config.isDebug()) {
375+
if (Config.isDebug()) {
253376
errorMessage = "Build failed with code: " + result.getExitCode();
254377
} else {
255378
errorMessage = "Build failed";

0 commit comments

Comments
 (0)