Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 27 additions & 21 deletions src/main/java/org/apache/maven/shared/verifier/ForkedLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,29 +67,37 @@ class ForkedLauncher implements MavenLauncher {
}

ForkedLauncher(String mavenHome, Map<String, String> envVars, boolean debugJvm, boolean wrapper) {
this(mavenHome, envVars, debugJvm, wrapper, null);
}

ForkedLauncher(
String mavenHome, Map<String, String> envVars, boolean debugJvm, boolean wrapper, String mvnExecutable) {
this.mavenHome = mavenHome;
this.envVars = envVars;
if (mvnExecutable != null && !mvnExecutable.isEmpty()) {
executable = mvnExecutable;
} else {
if (wrapper) {
final StringBuilder script = new StringBuilder();

if (wrapper) {
final StringBuilder script = new StringBuilder();

if (!isWindows()) {
script.append("./");
}

script.append("mvnw");
if (!isWindows()) {
script.append("./");
}

if (debugJvm) {
script.append("Debug");
}
executable = script.toString();
} else {
String script = "mvn" + (debugJvm ? "Debug" : "");
script.append("mvnw");

if (mavenHome != null) {
executable = new File(mavenHome, "bin/" + script).getPath();
if (debugJvm) {
script.append("Debug");
}
executable = script.toString();
} else {
executable = script;
String script = "mvn" + (debugJvm ? "Debug" : "");

if (mavenHome != null) {
executable = new File(mavenHome, "bin/" + script).getPath();
} else {
executable = script;
}
}
}
}
Expand Down Expand Up @@ -119,10 +127,8 @@ public int run(

cmd.setWorkingDirectory(workingDirectory);

for (Object o : systemProperties.keySet()) {
String key = (String) o;
String value = systemProperties.getProperty(key);
cmd.createArg().setValue("-D" + key + "=" + value);
for (Map.Entry entry : systemProperties.entrySet()) {
cmd.createArg().setValue("-D" + entry.getKey() + "=" + entry.getValue());
}

for (String cliArg : cliArgs) {
Expand Down
116 changes: 111 additions & 5 deletions src/main/java/org/apache/maven/shared/verifier/Verifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ public class Verifier {

private String mavenHome;

private String mvnExecutable;

// will launch mvn with -X
private boolean mavenDebug = false;

Expand Down Expand Up @@ -1220,7 +1222,7 @@ protected MavenLauncher getMavenLauncher(Map<String, String> envVars) throws Lau

return embeddedLauncher;
} else {
return new ForkedLauncher(mavenHome, envVars, debugJvm, useWrapper);
return new ForkedLauncher(mavenHome, envVars, debugJvm, useWrapper, mvnExecutable);
}
}

Expand Down Expand Up @@ -1272,17 +1274,24 @@ private static String getLogContents(File logFile) {

private void findLocalRepo(String settingsFile) throws VerificationException {
if (localRepo == null) {
localRepo = System.getProperty("maven.repo.local");
}

if (localRepo == null) {
// Settings file is the primary configuration source — consult it first.
try {
localRepo = retrieveLocalRepo(settingsFile);
} catch (SettingsBuildingException e) {
throw new VerificationException("Cannot read settings.xml to determine local repository location", e);
}
}

if (localRepo == null) {
// System property is the last resort before the hardcoded default.
// It is intentionally checked AFTER the settings file because
// Embedded3xLauncher.run() calls System.setProperties(null) during embedded
// Maven executions, creating a race window where this property may transiently
// hold a value injected by a concurrent Maven invocation running in the same JVM.
// Prefer Builder.localRepository() for an entirely race-free alternative.
localRepo = System.getProperty("maven.repo.local");
}

if (localRepo == null) {
localRepo = USER_HOME + "/.m2/repository";
}
Expand Down Expand Up @@ -1619,4 +1628,101 @@ private void setForkMode() {
forkMode = "auto";
}
}

/**
* Builder for creating {@link Verifier} instances with explicit configuration.
* <p>
* When {@link #localRepository(String)} is set, the local repository is pre-populated
* before {@code findLocalRepo()} runs, so the system property
* {@code maven.repo.local} is never consulted. This eliminates the race condition that
* occurs when multiple {@code Verifier} instances are constructed concurrently in the
* same JVM while {@link Embedded3xLauncher#run} is manipulating the JVM-global system
* properties table ({@code System.setProperties(null)}).
*/
public static class Builder {
private final String basedir;
private String settingsFile;
private String localRepository;
private String forkMode;
private Boolean forkJvm;
private String mvnExecutable;
private String[] defaultCliArguments;

public Builder(String basedir) {
this.basedir = basedir;
}

public Builder settingsFile(String settingsFile) {
this.settingsFile = settingsFile;
return this;
}

/**
* Sets the local repository path explicitly.
* When provided, the system property {@code maven.repo.local} and the settings file
* are both bypassed for local repository resolution, making construction race-free.
*/
public Builder localRepository(String localRepository) {
this.localRepository = localRepository;
return this;
}

/**
* Sets the forkMode explicitly.
* When provided, the system property verifier.forkMode is bypassed.
*/
public Builder forkMode(String forkMode) {
this.forkMode = forkMode;
return this;
}

/**
* Sets the forkJvm explicitly.
*/
public Builder forkJvm(Boolean forkJvm) {
this.forkJvm = forkJvm;
return this;
}

/**
* Sets the maven full path executable explicitly.
* When provided, the maven executable is used directly without any lookup or resolution.
*/
public Builder mvnExecutable(String mvnExecutable) {
this.mvnExecutable = mvnExecutable;
return this;
}

/**
* Sets the default CLI arguments to be used for every execution
*/
public Builder defaultCliArguments(String[] defaultCliArguments) {
this.defaultCliArguments = defaultCliArguments;
return this;
}

public Verifier build() throws VerificationException {
return new Verifier(this);
}
}

private Verifier(Builder builder) throws VerificationException {
this.basedir = builder.basedir;
this.forkMode = builder.forkMode == null ? System.getProperty("verifier.forkMode") : builder.forkMode;
setForkMode();
this.forkJvm = builder.forkJvm;
// Pre-set localRepo before findLocalRepo() so that the settings-file and
// system-property lookups in findLocalRepo() are both skipped when an
// explicit value has been provided via the builder.
this.localRepo = builder.localRepository;
if (builder.settingsFile != null) {
this.settingsFile = builder.settingsFile;
}
findLocalRepo(this.settingsFile);
this.mavenHome = System.getProperty("maven.home");
this.useWrapper = Files.exists(Paths.get(getBasedir(), "mvnw"));
this.defaultCliArguments =
builder.defaultCliArguments == null ? DEFAULT_CLI_ARGUMENTS.clone() : builder.defaultCliArguments;
this.mvnExecutable = builder.mvnExecutable;
}
}