properties) {
- this(time, properties != null ? properties.get(BUILD_TIMESTAMP_FORMAT_PROPERTY) : null);
+ this(time, properties != null ? properties.get(Constants.MAVEN_BUILD_TIMESTAMP_FORMAT) : null);
}
/**
@@ -58,7 +58,7 @@ public class MavenBuildTimestamp {
*/
@Deprecated
public MavenBuildTimestamp(Instant time, Properties properties) {
- this(time, properties != null ? properties.getProperty(BUILD_TIMESTAMP_FORMAT_PROPERTY) : null);
+ this(time, properties != null ? properties.getProperty(Constants.MAVEN_BUILD_TIMESTAMP_FORMAT) : null);
}
public MavenBuildTimestamp(Instant time, String timestampFormat) {
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/relocation/UserPropertiesArtifactRelocationSource.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/relocation/UserPropertiesArtifactRelocationSource.java
index 4493ecc5d6..99b810f83f 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/relocation/UserPropertiesArtifactRelocationSource.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/relocation/UserPropertiesArtifactRelocationSource.java
@@ -25,6 +25,7 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.maven.api.Constants;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Priority;
import org.apache.maven.api.di.Singleton;
@@ -51,8 +52,6 @@ public final class UserPropertiesArtifactRelocationSource implements MavenArtifa
public static final String NAME = "userProperties";
private static final Logger LOGGER = LoggerFactory.getLogger(UserPropertiesArtifactRelocationSource.class);
- private static final String CONFIG_PROP_RELOCATIONS_ENTRIES = "maven.relocations.entries";
-
private static final Artifact SENTINEL = new DefaultArtifact("org.apache.maven.banned:user-relocation:1.0");
@Override
@@ -153,7 +152,7 @@ public final class UserPropertiesArtifactRelocationSource implements MavenArtifa
}
private Relocations parseRelocations(RepositorySystemSession session) {
- String relocationsEntries = (String) session.getConfigProperties().get(CONFIG_PROP_RELOCATIONS_ENTRIES);
+ String relocationsEntries = (String) session.getConfigProperties().get(Constants.MAVEN_RELOCATIONS_ENTRIES);
if (relocationsEntries == null) {
return null;
}
diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
index c8fe844c1c..aa9748ca51 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
@@ -97,11 +97,11 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
private File projectSettingsFile;
- private File globalSettingsFile;
+ private File installationSettingsFile;
private File userToolchainsFile;
- private File globalToolchainsFile;
+ private File installationToolchainsFile;
// ----------------------------------------------------------------------------
// Request
@@ -190,9 +190,9 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
copy.setPluginGroups(original.getPluginGroups());
copy.setProjectPresent(original.isProjectPresent());
copy.setUserSettingsFile(original.getUserSettingsFile());
- copy.setGlobalSettingsFile(original.getGlobalSettingsFile());
+ copy.setInstallationSettingsFile(original.getInstallationSettingsFile());
copy.setUserToolchainsFile(original.getUserToolchainsFile());
- copy.setGlobalToolchainsFile(original.getGlobalToolchainsFile());
+ copy.setInstallationToolchainsFile(original.getInstallationToolchainsFile());
copy.setBaseDirectory((original.getBaseDirectory() != null) ? new File(original.getBaseDirectory()) : null);
copy.setGoals(original.getGoals());
copy.setRecursive(original.isRecursive());
@@ -866,13 +866,25 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
}
@Override
+ @Deprecated
public File getGlobalSettingsFile() {
- return globalSettingsFile;
+ return getInstallationSettingsFile();
}
@Override
+ @Deprecated
public MavenExecutionRequest setGlobalSettingsFile(File globalSettingsFile) {
- this.globalSettingsFile = globalSettingsFile;
+ return setInstallationSettingsFile(globalSettingsFile);
+ }
+
+ @Override
+ public File getInstallationSettingsFile() {
+ return installationSettingsFile;
+ }
+
+ @Override
+ public MavenExecutionRequest setInstallationSettingsFile(File installationSettingsFile) {
+ this.installationSettingsFile = installationSettingsFile;
return this;
}
@@ -890,13 +902,26 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
}
@Override
+ @Deprecated
public File getGlobalToolchainsFile() {
- return globalToolchainsFile;
+ return installationToolchainsFile;
}
@Override
- public MavenExecutionRequest setGlobalToolchainsFile(File globalToolchainsFile) {
- this.globalToolchainsFile = globalToolchainsFile;
+ @Deprecated
+ public MavenExecutionRequest setGlobalToolchainsFile(File installationToolchainsFile) {
+ this.installationToolchainsFile = installationToolchainsFile;
+ return this;
+ }
+
+ @Override
+ public File getInstallationToolchainsFile() {
+ return installationToolchainsFile;
+ }
+
+ @Override
+ public MavenExecutionRequest setInstallationToolchainsFile(File installationToolchainsFile) {
+ this.installationToolchainsFile = installationToolchainsFile;
return this;
}
diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequestPopulator.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequestPopulator.java
index ab548a37ae..5f217daa50 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequestPopulator.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequestPopulator.java
@@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.maven.api.Constants;
import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.bridge.MavenRepositorySystem;
@@ -125,7 +126,11 @@ public class DefaultMavenExecutionRequestPopulator implements MavenExecutionRequ
}
if (localRepositoryPath == null || localRepositoryPath.isEmpty()) {
- localRepositoryPath = new File(System.getProperty("user.home"), ".m2/repository").getAbsolutePath();
+ String path = request.getUserProperties().getProperty(Constants.MAVEN_USER_CONF);
+ if (path == null) {
+ path = request.getSystemProperties().getProperty("user.home") + File.separator + ".m2";
+ }
+ localRepositoryPath = new File(path, "repository").getAbsolutePath();
}
try {
diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
index 1904dff7a6..f95f48b26c 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
@@ -426,10 +426,16 @@ public interface MavenExecutionRequest {
MavenExecutionRequest setProjectSettingsFile(File projectSettingsFile);
+ @Deprecated
File getGlobalSettingsFile();
+ @Deprecated
MavenExecutionRequest setGlobalSettingsFile(File globalSettingsFile);
+ File getInstallationSettingsFile();
+
+ MavenExecutionRequest setInstallationSettingsFile(File installationSettingsFile);
+
MavenExecutionRequest addRemoteRepository(ArtifactRepository repository);
MavenExecutionRequest addPluginArtifactRepository(ArtifactRepository repository);
@@ -467,7 +473,9 @@ public interface MavenExecutionRequest {
*
* @return the global toolchains file
* @since 3.3.0
+ * @deprecated use {@link #getInstallationToolchainsFile()}
*/
+ @Deprecated
File getGlobalToolchainsFile();
/**
@@ -475,9 +483,27 @@ public interface MavenExecutionRequest {
* @param globalToolchainsFile the global toolchains file
* @return this request
* @since 3.3.0
+ * @deprecated use {@link #setInstallationToolchainsFile(File)}
*/
+ @Deprecated
MavenExecutionRequest setGlobalToolchainsFile(File globalToolchainsFile);
+ /**
+ *
+ *
+ * @return the installation toolchains file
+ * @since 4.0.0
+ */
+ File getInstallationToolchainsFile();
+
+ /**
+ *
+ * @param installationToolchainsFile the installation toolchains file
+ * @return this request
+ * @since 4.0.0
+ */
+ MavenExecutionRequest setInstallationToolchainsFile(File installationToolchainsFile);
+
ExecutionListener getExecutionListener();
MavenExecutionRequest setExecutionListener(ExecutionListener executionListener);
diff --git a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
index 05d1d02bfe..f49ef3b727 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
@@ -30,6 +30,7 @@ import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import org.apache.maven.api.Constants;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
@@ -68,7 +69,6 @@ import org.eclipse.aether.util.graph.version.LowestVersionFilter;
import org.eclipse.aether.util.graph.version.PredicateVersionFilter;
import org.eclipse.aether.util.listener.ChainedRepositoryListener;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
-import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector;
@@ -89,66 +89,14 @@ import static java.util.Objects.requireNonNull;
@Named
@Singleton
public class DefaultRepositorySystemSessionFactory implements RepositorySystemSessionFactory {
- /**
- * User property for version filters expression, a semicolon separated list of filters to apply. By default, no version
- * filter is applied (like in Maven 3).
- *
- * Supported filters:
- *
- * "h" or "h(num)" - highest version or top list of highest ones filter
- * "l" or "l(num)" - lowest version or bottom list of lowest ones filter
- * "s" - contextual snapshot filter
- * "e(G:A:V)" - predicate filter (leaves out G:A:V from range, if hit, V can be range)
- *
- * Example filter expression: {@code "h(5);s;e(org.foo:bar:1)} will cause: ranges are filtered for "top 5" (instead
- * full range), snapshots are banned if root project is not a snapshot, and if range for {@code org.foo:bar} is
- * being processed, version 1 is omitted.
- *
- * @since 4.0.0
- */
- private static final String MAVEN_VERSION_FILTERS = "maven.versionFilters";
- /**
- * User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with
- * {@link ChainedLocalRepositoryManager}.
- * Default value: {@code null}, no chained LRM is used.
- *
- * @since 3.9.0
- */
- private static final String MAVEN_REPO_LOCAL_TAIL = "maven.repo.local.tail";
+ public static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default";
- /**
- * User property for reverse dependency tree. If enabled, Maven will record ".tracking" directory into local
- * repository with "reverse dependency tree", essentially explaining WHY given artifact is present in local
- * repository.
- * Default: {@code false}, will not record anything.
- *
- * @since 3.9.0
- */
- private static final String MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE = "maven.repo.local.recordReverseTree";
+ public static final String MAVEN_RESOLVER_TRANSPORT_WAGON = "wagon";
- /**
- * User property for selecting dependency manager behaviour regarding transitive dependencies and dependency
- * management entries in their POMs. Maven 3 targeted full backward compatibility with Maven2, hence it ignored
- * dependency management entries in transitive dependency POMs. Maven 4 enables "transitivity" by default, hence
- * unlike Maven2, obeys dependency management entries deep in dependency graph as well.
- *
- * Default: {@code "true"}.
- *
- * @since 4.0.0
- */
- private static final String MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY_KEY =
- "maven.resolver.dependencyManagerTransitivity";
+ public static final String MAVEN_RESOLVER_TRANSPORT_APACHE = "apache";
- private static final String MAVEN_RESOLVER_TRANSPORT_KEY = "maven.resolver.transport";
-
- private static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default";
-
- private static final String MAVEN_RESOLVER_TRANSPORT_WAGON = "wagon";
-
- private static final String MAVEN_RESOLVER_TRANSPORT_APACHE = "apache";
-
- private static final String MAVEN_RESOLVER_TRANSPORT_JDK = "jdk";
+ public static final String MAVEN_RESOLVER_TRANSPORT_JDK = "jdk";
/**
* This name for Apache HttpClient transport is deprecated.
@@ -158,7 +106,7 @@ public class DefaultRepositorySystemSessionFactory implements RepositorySystemSe
@Deprecated
private static final String MAVEN_RESOLVER_TRANSPORT_NATIVE = "native";
- private static final String MAVEN_RESOLVER_TRANSPORT_AUTO = "auto";
+ public static final String MAVEN_RESOLVER_TRANSPORT_AUTO = "auto";
private static final String WAGON_TRANSPORTER_PRIORITY_KEY = "aether.priority.WagonTransporterFactory";
@@ -257,7 +205,7 @@ public class DefaultRepositorySystemSessionFactory implements RepositorySystemSe
sessionBuilder.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(
request.isIgnoreMissingArtifactDescriptor(), request.isIgnoreInvalidArtifactDescriptor()));
- VersionFilter versionFilter = buildVersionFilter(mergedProps.get(MAVEN_VERSION_FILTERS));
+ VersionFilter versionFilter = buildVersionFilter(mergedProps.get(Constants.MAVEN_VERSION_FILTERS));
if (versionFilter != null) {
sessionBuilder.setVersionFilter(versionFilter);
}
@@ -390,7 +338,8 @@ public class DefaultRepositorySystemSessionFactory implements RepositorySystemSe
}
sessionBuilder.setAuthenticationSelector(authSelector);
- Object transport = mergedProps.getOrDefault(MAVEN_RESOLVER_TRANSPORT_KEY, MAVEN_RESOLVER_TRANSPORT_DEFAULT);
+ Object transport =
+ mergedProps.getOrDefault(Constants.MAVEN_RESOLVER_TRANSPORT, MAVEN_RESOLVER_TRANSPORT_DEFAULT);
if (MAVEN_RESOLVER_TRANSPORT_DEFAULT.equals(transport)) {
// The "default" mode (user did not set anything) from now on defaults to AUTO
} else if (MAVEN_RESOLVER_TRANSPORT_JDK.equals(transport)) {
@@ -425,21 +374,21 @@ public class DefaultRepositorySystemSessionFactory implements RepositorySystemSe
RepositoryListener repositoryListener = eventSpyDispatcher.chainListener(new LoggingRepositoryListener(logger));
boolean recordReverseTree = Boolean.parseBoolean(
- mergedProps.getOrDefault(MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE, Boolean.FALSE.toString()));
+ mergedProps.getOrDefault(Constants.MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE, Boolean.FALSE.toString()));
if (recordReverseTree) {
repositoryListener = new ChainedRepositoryListener(repositoryListener, new ReverseTreeRepositoryListener());
}
sessionBuilder.setRepositoryListener(repositoryListener);
// may be overridden
- String resolverDependencyManagerTransitivity =
- mergedProps.getOrDefault(MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY_KEY, Boolean.TRUE.toString());
+ String resolverDependencyManagerTransitivity = mergedProps.getOrDefault(
+ Constants.MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY, Boolean.TRUE.toString());
sessionBuilder.setDependencyManager(
supplier.getDependencyManager(Boolean.parseBoolean(resolverDependencyManagerTransitivity)));
ArrayList paths = new ArrayList<>();
paths.add(Paths.get(request.getLocalRepository().getBasedir()));
- String localRepoTail = mergedProps.get(MAVEN_REPO_LOCAL_TAIL);
+ String localRepoTail = mergedProps.get(Constants.MAVEN_REPO_LOCAL_TAIL);
if (localRepoTail != null) {
Arrays.stream(localRepoTail.split(","))
.filter(p -> p != null && !p.trim().isEmpty())
diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java
index cf15292df8..35cae50a4c 100644
--- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java
+++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java
@@ -38,6 +38,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
+import org.apache.maven.api.Constants;
import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenSession;
@@ -66,11 +67,7 @@ public final class DefaultPluginValidationManager extends AbstractEventSpy imple
private static final String PLUGIN_EXCLUDES_KEY = DefaultPluginValidationManager.class.getName() + ".excludes";
- private static final String MAVEN_PLUGIN_VALIDATION_KEY = "maven.plugin.validation";
-
- private static final String MAVEN_PLUGIN_VALIDATION_EXCLUDES_KEY = "maven.plugin.validation.excludes";
-
- private static final ValidationReportLevel DEFAULT_VALIDATION_LEVEL = ValidationReportLevel.INLINE;
+ public static final ValidationReportLevel DEFAULT_VALIDATION_LEVEL = ValidationReportLevel.INLINE;
private static final Collection INLINE_VALIDATION_LEVEL = Collections.unmodifiableCollection(
Arrays.asList(ValidationReportLevel.INLINE, ValidationReportLevel.BRIEF));
@@ -106,7 +103,7 @@ public final class DefaultPluginValidationManager extends AbstractEventSpy imple
}
private List parsePluginExcludes(RepositorySystemSession session) {
- String excludes = ConfigUtils.getString(session, null, MAVEN_PLUGIN_VALIDATION_EXCLUDES_KEY);
+ String excludes = ConfigUtils.getString(session, null, Constants.MAVEN_PLUGIN_VALIDATION_EXCLUDES);
if (excludes == null || excludes.isEmpty()) {
return Collections.emptyList();
}
@@ -122,7 +119,7 @@ public final class DefaultPluginValidationManager extends AbstractEventSpy imple
}
private ValidationReportLevel parseValidationReportLevel(RepositorySystemSession session) {
- String level = ConfigUtils.getString(session, null, MAVEN_PLUGIN_VALIDATION_KEY);
+ String level = ConfigUtils.getString(session, null, Constants.MAVEN_PLUGIN_VALIDATION);
if (level == null || level.isEmpty()) {
return DEFAULT_VALIDATION_LEVEL;
}
@@ -131,7 +128,7 @@ public final class DefaultPluginValidationManager extends AbstractEventSpy imple
} catch (IllegalArgumentException e) {
logger.warn(
"Invalid value specified for property {}: '{}'. Supported values are (case insensitive): {}",
- MAVEN_PLUGIN_VALIDATION_KEY,
+ Constants.MAVEN_PLUGIN_VALIDATION,
level,
Arrays.toString(ValidationReportLevel.values()));
return DEFAULT_VALIDATION_LEVEL;
diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index 131c9a69d9..67bffa2799 100644
--- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -57,6 +57,7 @@ import java.util.stream.Stream;
import org.apache.maven.ProjectCycleException;
import org.apache.maven.RepositoryUtils;
+import org.apache.maven.api.Constants;
import org.apache.maven.api.Session;
import org.apache.maven.api.SessionData;
import org.apache.maven.api.feature.Features;
@@ -118,7 +119,7 @@ import org.slf4j.LoggerFactory;
@Named
@Singleton
public class DefaultProjectBuilder implements ProjectBuilder {
- public static final String BUILDER_PARALLELISM = "maven.projectBuilder.parallelism";
+
public static final int DEFAULT_BUILDER_PARALLELISM = Runtime.getRuntime().availableProcessors() / 2 + 1;
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -382,10 +383,7 @@ public class DefaultProjectBuilder implements ProjectBuilder {
private int getParallelism(ProjectBuildingRequest request) {
int parallelism = DEFAULT_BUILDER_PARALLELISM;
try {
- String str = request.getUserProperties().getProperty(BUILDER_PARALLELISM);
- if (str == null) {
- str = request.getSystemProperties().getProperty(BUILDER_PARALLELISM);
- }
+ String str = request.getUserProperties().getProperty(Constants.MAVEN_PROJECT_BUILDER_PARALLELISM);
if (str != null) {
parallelism = Integer.parseInt(str);
}
diff --git a/maven-docgen/pom.xml b/maven-docgen/pom.xml
new file mode 100644
index 0000000000..1e84e830f5
--- /dev/null
+++ b/maven-docgen/pom.xml
@@ -0,0 +1,118 @@
+
+
+
+ 4.0.0
+
+ org.apache.maven
+ maven
+ 4.0.0-beta-4-SNAPSHOT
+
+
+ maven-docgen
+
+ Maven Documentation Generator
+
+
+ true
+
+
+
+
+
+ org.apache.maven
+ maven-api-impl
+
+
+ org.apache.maven
+ maven-core
+
+
+ org.apache.maven
+ maven-embedder
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.ow2.asm
+ asm
+
+
+ org.jboss.forge.roaster
+ roaster-api
+ 2.29.0.Final
+
+
+ org.jboss.forge.roaster
+ roaster-jdt
+ 2.29.0.Final
+
+
+ org.apache.velocity
+ velocity-engine-core
+ 2.3
+
+
+ org.codehaus.plexus
+ plexus-utils
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ runtime
+
+
+ org.apache.commons
+ commons-lang3
+ 3.14.0
+ runtime
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.3.0
+
+
+ render-configuration-page
+
+ java
+
+ verify
+
+ org.apache.maven.tools.CollectConfiguration
+
+ ${basedir}/..
+ ${basedir}/../src/site/markdown/configuration.md
+
+
+
+
+
+
+
+
diff --git a/maven-docgen/src/main/java/org/apache/maven/tools/CollectConfiguration.java b/maven-docgen/src/main/java/org/apache/maven/tools/CollectConfiguration.java
new file mode 100644
index 0000000000..01554d540b
--- /dev/null
+++ b/maven-docgen/src/main/java/org/apache/maven/tools/CollectConfiguration.java
@@ -0,0 +1,363 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.spi.ToolProvider;
+
+import org.apache.maven.api.annotations.Config;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
+import org.codehaus.plexus.util.io.CachingWriter;
+import org.jboss.forge.roaster.Roaster;
+import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.AST;
+import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.ASTNode;
+import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.Javadoc;
+import org.jboss.forge.roaster.model.JavaDocCapable;
+import org.jboss.forge.roaster.model.JavaDocTag;
+import org.jboss.forge.roaster.model.JavaType;
+import org.jboss.forge.roaster.model.impl.JavaDocImpl;
+import org.jboss.forge.roaster.model.source.FieldSource;
+import org.jboss.forge.roaster.model.source.JavaClassSource;
+import org.jboss.forge.roaster.model.source.JavaDocSource;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CollectConfiguration {
+
+ public static void main(String[] args) throws Exception {
+ try {
+ Path start = Paths.get(args.length > 0 ? args[0] : ".");
+ Path output = Paths.get(args.length > 1 ? args[1] : "output");
+
+ TreeMap discoveredKeys = new TreeMap<>();
+
+ Files.walk(start)
+ .map(Path::toAbsolutePath)
+ .filter(p -> p.getFileName().toString().endsWith(".class"))
+ .filter(p -> p.toString().contains("/target/classes/"))
+ .forEach(p -> {
+ processClass(p, discoveredKeys);
+ });
+
+ VelocityEngine velocityEngine = new VelocityEngine();
+ Properties properties = new Properties();
+ properties.setProperty("resource.loaders", "classpath");
+ properties.setProperty("resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
+ velocityEngine.init(properties);
+
+ VelocityContext context = new VelocityContext();
+ context.put("keys", discoveredKeys.values());
+
+ try (Writer fileWriter = new CachingWriter(output, StandardCharsets.UTF_8)) {
+ velocityEngine.getTemplate("page.vm").merge(context, fileWriter);
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw t;
+ }
+ }
+
+ private static void processClass(Path path, Map discoveredKeys) {
+ try {
+ ClassReader classReader = new ClassReader(Files.newInputStream(path));
+ classReader.accept(
+ new ClassVisitor(Opcodes.ASM9) {
+ @Override
+ public FieldVisitor visitField(
+ int fieldAccess,
+ String fieldName,
+ String fieldDescriptor,
+ String fieldSignature,
+ Object fieldValue) {
+ return new FieldVisitor(Opcodes.ASM9) {
+ @Override
+ public AnnotationVisitor visitAnnotation(
+ String annotationDescriptor, boolean annotationVisible) {
+ if (annotationDescriptor.equals("Lorg/apache/maven/api/annotations/Config;")) {
+ return new AnnotationVisitor(Opcodes.ASM9) {
+ Map values = new HashMap<>();
+
+ @Override
+ public void visit(String name, Object value) {
+ values.put(name, value);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ values.put(name, value);
+ }
+
+ @Override
+ public void visitEnd() {
+ JavaType> jtype = parse(Paths.get(path.toString()
+ .replace("/target/classes/", "/src/main/java/")
+ .replace(".class", ".java")));
+ FieldSource f =
+ ((JavaClassSource) jtype).getField(fieldName);
+
+ String fqName = null;
+ String desc = cloneJavadoc(f.getJavaDoc())
+ .removeAllTags()
+ .getFullText()
+ .replace("*", "\\*");
+ String since = getSince(f);
+ String source =
+ switch ((values.get("source") != null
+ ? (String) values.get("source")
+ : Config.Source.USER_PROPERTIES.toString())
+ .toLowerCase()) {
+ case "model" -> "Model properties";
+ case "user_properties" -> "User properties";
+ default -> throw new IllegalStateException();
+ };
+ String type =
+ switch ((values.get("type") != null
+ ? (String) values.get("type")
+ : "java.lang.String")) {
+ case "java.lang.String" -> "String";
+ case "java.lang.Integer" -> "Integer";
+ default -> throw new IllegalStateException();
+ };
+ discoveredKeys.put(
+ fieldValue.toString(),
+ new ConfigurationKey(
+ fieldValue.toString(),
+ values.get("defaultValue") != null
+ ? values.get("defaultValue")
+ .toString()
+ : null,
+ fqName,
+ desc,
+ since,
+ source,
+ type));
+ }
+ };
+ }
+ return null;
+ }
+ };
+ }
+ },
+ 0);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static JavaDocSource cloneJavadoc(JavaDocSource> javaDoc) {
+ Javadoc jd = (Javadoc) javaDoc.getInternal();
+ return new JavaDocImpl(javaDoc.getOrigin(), (Javadoc)
+ ASTNode.copySubtree(AST.newAST(jd.getAST().apiLevel()), jd));
+ }
+
+ private static String unquote(String s) {
+ return (s.startsWith("\"") && s.endsWith("\"")) ? s.substring(1, s.length() - 1) : s;
+ }
+
+ private static JavaType> parse(Path path) {
+ try {
+ return Roaster.parse(path.toFile());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static boolean toBoolean(String value) {
+ return ("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value));
+ }
+
+ /**
+ * Would be record, but... Velocity have no idea what it is nor how to handle it.
+ */
+ public static class ConfigurationKey {
+ private final String key;
+ private final String defaultValue;
+ private final String fqName;
+ private final String description;
+ private final String since;
+ private final String configurationSource;
+ private final String configurationType;
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ public ConfigurationKey(
+ String key,
+ String defaultValue,
+ String fqName,
+ String description,
+ String since,
+ String configurationSource,
+ String configurationType) {
+ this.key = key;
+ this.defaultValue = defaultValue;
+ this.fqName = fqName;
+ this.description = description;
+ this.since = since;
+ this.configurationSource = configurationSource;
+ this.configurationType = configurationType;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public String getFqName() {
+ return fqName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getSince() {
+ return since;
+ }
+
+ public String getConfigurationSource() {
+ return configurationSource;
+ }
+
+ public String getConfigurationType() {
+ return configurationType;
+ }
+ }
+
+ private static String nvl(String string, String def) {
+ return string == null ? def : string;
+ }
+
+ private static boolean hasConfigurationSource(JavaDocCapable> javaDocCapable) {
+ return getTag(javaDocCapable, "@configurationSource") != null;
+ }
+
+ private static String getConfigurationType(JavaDocCapable> javaDocCapable) {
+ String type = getTag(javaDocCapable, "@configurationType");
+ if (type != null) {
+ String linkPrefix = "{@link ";
+ String linkSuffix = "}";
+ if (type.startsWith(linkPrefix) && type.endsWith(linkSuffix)) {
+ type = type.substring(linkPrefix.length(), type.length() - linkSuffix.length());
+ }
+ String javaLangPackage = "java.lang.";
+ if (type.startsWith(javaLangPackage)) {
+ type = type.substring(javaLangPackage.length());
+ }
+ }
+ return nvl(type, "n/a");
+ }
+
+ private static String getConfigurationSource(JavaDocCapable> javaDocCapable) {
+ String source = getTag(javaDocCapable, "@configurationSource");
+ if ("{@link RepositorySystemSession#getConfigProperties()}".equals(source)) {
+ return "Session Configuration";
+ } else if ("{@link System#getProperty(String,String)}".equals(source)) {
+ return "Java System Properties";
+ } else if ("{@link org.apache.maven.api.model.Model#getProperties()}".equals(source)) {
+ return "Model Properties";
+ } else if ("{@link Session#getUserProperties()}".equals(source)) {
+ return "Session Properties";
+ } else {
+ return source;
+ }
+ }
+
+ private static String getSince(JavaDocCapable> javaDocCapable) {
+ List tags;
+ if (javaDocCapable != null) {
+ if (javaDocCapable instanceof FieldSource> fieldSource) {
+ tags = fieldSource.getJavaDoc().getTags("@since");
+ if (tags.isEmpty()) {
+ return getSince(fieldSource.getOrigin());
+ } else {
+ return tags.get(0).getValue();
+ }
+ } else if (javaDocCapable instanceof JavaClassSource classSource) {
+ tags = classSource.getJavaDoc().getTags("@since");
+ if (!tags.isEmpty()) {
+ return tags.get(0).getValue();
+ }
+ }
+ }
+ return "";
+ }
+
+ private static String getTag(JavaDocCapable> javaDocCapable, String tagName) {
+ List tags;
+ if (javaDocCapable != null) {
+ if (javaDocCapable instanceof FieldSource> fieldSource) {
+ tags = fieldSource.getJavaDoc().getTags(tagName);
+ if (tags.isEmpty()) {
+ return getTag(fieldSource.getOrigin(), tagName);
+ } else {
+ return tags.get(0).getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ private static final Pattern CONSTANT_PATTERN = Pattern.compile(".*static final.* ([A-Z_]+) = (.*);");
+
+ private static final ToolProvider JAVAP = ToolProvider.findFirst("javap").orElseThrow();
+
+ /**
+ * Builds "constant table" for one single class.
+ *
+ * Limitations:
+ * - works only for single class (no inherited constants)
+ * - does not work for fields that are Enum.name()
+ * - more to come
+ */
+ private static Map extractConstants(Path file) {
+ StringWriter out = new StringWriter();
+ JAVAP.run(new PrintWriter(out), new PrintWriter(System.err), "-constants", file.toString());
+ Map result = new HashMap<>();
+ out.getBuffer().toString().lines().forEach(l -> {
+ Matcher matcher = CONSTANT_PATTERN.matcher(l);
+ if (matcher.matches()) {
+ result.put(matcher.group(1), matcher.group(2));
+ }
+ });
+ return result;
+ }
+}
diff --git a/maven-docgen/src/main/resources/page.vm b/maven-docgen/src/main/resources/page.vm
new file mode 100644
index 0000000000..8cd78064ac
--- /dev/null
+++ b/maven-docgen/src/main/resources/page.vm
@@ -0,0 +1,55 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied. See the License for the
+## specific language governing permissions and limitations
+## under the License.
+##
+#[[
+# Configuration Options
+
+
+]]#
+
+#macro(yesno $val)
+ #if ($val) Yes #else No #end
+#end
+
+#macro(value $val)
+#if ($val) `$val` #else - #end
+#end
+
+| No | Key | Type | Description | Default Value | Since | Source |
+| --- | --- | --- | --- | --- | --- | --- |
+#foreach($key in $keys)
+| $foreach.count. | `$key.key` | `$key.configurationType` | $key.description | #value( $key.defaultValue ) | $key.since | $key.configurationSource |
+#end
+
diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml
index e6a1d13d15..0c823eb6c6 100644
--- a/maven-embedder/pom.xml
+++ b/maven-embedder/pom.xml
@@ -167,6 +167,12 @@ under the License.
mockito-core
test
+
+ com.google.jimfs
+ jimfs
+ 1.3.0
+ test
+
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
index 81ff7000b6..2074d30b51 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
@@ -79,12 +79,18 @@ public class CLIManager {
public static final String ALTERNATE_PROJECT_SETTINGS = "ps";
+ @Deprecated
public static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
+ public static final String ALTERNATE_INSTALLATION_SETTINGS = "is";
+
public static final char ALTERNATE_USER_TOOLCHAINS = 't';
+ @Deprecated
public static final String ALTERNATE_GLOBAL_TOOLCHAINS = "gt";
+ public static final String ALTERNATE_INSTALLATION_TOOLCHAINS = "it";
+
public static final String FAIL_FAST = "ff";
public static final String FAIL_ON_SEVERITY = "fos";
@@ -222,6 +228,12 @@ public class CLIManager {
.desc("Alternate path for the global settings file")
.hasArg()
.build());
+ options.addOption(Option.builder(ALTERNATE_INSTALLATION_SETTINGS)
+ .longOpt("install-settings")
+ .desc("Alternate path for the installation settings file")
+ .hasArg()
+ .deprecated()
+ .build());
options.addOption(Option.builder(Character.toString(ALTERNATE_USER_TOOLCHAINS))
.longOpt("toolchains")
.desc("Alternate path for the user toolchains file")
@@ -232,6 +244,12 @@ public class CLIManager {
.desc("Alternate path for the global toolchains file")
.hasArg()
.build());
+ options.addOption(Option.builder(ALTERNATE_INSTALLATION_TOOLCHAINS)
+ .longOpt("install-toolchains")
+ .desc("Alternate path for the installation toolchains file")
+ .hasArg()
+ .deprecated()
+ .build());
options.addOption(Option.builder(FAIL_ON_SEVERITY)
.longOpt("fail-on-severity")
.desc("Configure which severity of logging should cause the build to fail")
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index 5239b9b4e6..b7db768555 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -28,9 +28,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -43,6 +44,7 @@ import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -55,6 +57,7 @@ import org.apache.commons.cli.UnrecognizedOptionException;
import org.apache.maven.BuildAbort;
import org.apache.maven.InternalErrorException;
import org.apache.maven.Maven;
+import org.apache.maven.api.Constants;
import org.apache.maven.api.services.MessageBuilder;
import org.apache.maven.api.services.MessageBuilderFactory;
import org.apache.maven.building.FileSource;
@@ -71,6 +74,7 @@ import org.apache.maven.cli.logging.Slf4jConfiguration;
import org.apache.maven.cli.logging.Slf4jConfigurationFactory;
import org.apache.maven.cli.logging.Slf4jLoggerManager;
import org.apache.maven.cli.logging.Slf4jStdoutLogger;
+import org.apache.maven.cli.props.MavenPropertiesLoader;
import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
import org.apache.maven.cli.transfer.QuietMavenTransferListener;
import org.apache.maven.cli.transfer.SimplexTransferListener;
@@ -131,6 +135,8 @@ import org.sonatype.plexus.components.sec.dispatcher.SecUtil;
import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
import static java.util.Comparator.comparing;
+import static org.apache.maven.api.Constants.MAVEN_HOME;
+import static org.apache.maven.api.Constants.MAVEN_INSTALLATION_CONF;
import static org.apache.maven.cli.CLIManager.BATCH_MODE;
import static org.apache.maven.cli.CLIManager.COLOR;
import static org.apache.maven.cli.CLIManager.FORCE_INTERACTIVE;
@@ -142,29 +148,11 @@ import static org.apache.maven.cli.ResolveFile.resolveFile;
/**
*/
public class MavenCli {
- public static final String LOCAL_REPO_PROPERTY = "maven.repo.local";
public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory";
- public static final String USER_HOME = System.getProperty("user.home");
-
- public static final File USER_MAVEN_CONFIGURATION_HOME = new File(USER_HOME, ".m2");
-
- public static final File DEFAULT_USER_TOOLCHAINS_FILE = new File(USER_MAVEN_CONFIGURATION_HOME, "toolchains.xml");
-
- public static final File DEFAULT_GLOBAL_TOOLCHAINS_FILE =
- new File(System.getProperty("maven.conf"), "toolchains.xml");
-
- private static final String EXT_CLASS_PATH = "maven.ext.class.path";
-
- private static final String EXTENSIONS_FILENAME = "extensions.xml";
-
- private static final String MVN_EXTENSIONS_FILENAME = ".mvn/" + EXTENSIONS_FILENAME;
-
private static final String MVN_MAVEN_CONFIG = ".mvn/maven.config";
- public static final String STYLE_COLOR_PROPERTY = "style.color";
-
private ClassWorld classWorld;
private LoggerManager plexusLoggerManager;
@@ -191,6 +179,8 @@ public class MavenCli {
private MessageBuilderFactory messageBuilderFactory;
+ private FileSystem fileSystem = FileSystems.getDefault();
+
private static final Pattern NEXT_LINE = Pattern.compile("\r?\n");
public MavenCli() {
@@ -340,7 +330,7 @@ public class MavenCli {
// We need to locate the top level project which may be pointed at using
// the -f/--file option. However, the command line isn't parsed yet, so
// we need to iterate through the args to find it and act upon it.
- Path topDirectory = Paths.get(cliRequest.workingDirectory);
+ Path topDirectory = fileSystem.getPath(cliRequest.workingDirectory);
boolean isAltFile = false;
for (String arg : cliRequest.args) {
if (isAltFile) {
@@ -380,10 +370,12 @@ public class MavenCli {
// Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative
// Windows paths.
//
- String mavenHome = System.getProperty("maven.home");
+ String mavenHome = System.getProperty(Constants.MAVEN_HOME);
if (mavenHome != null) {
- System.setProperty("maven.home", new File(mavenHome).getAbsolutePath());
+ System.setProperty(
+ Constants.MAVEN_HOME,
+ getCanonicalPath(fileSystem.getPath(mavenHome)).toString());
}
}
@@ -510,7 +502,8 @@ public class MavenCli {
cliRequest.showErrors = cliRequest.verbose || commandLine.hasOption(CLIManager.ERRORS);
// LOG COLOR
- String styleColor = cliRequest.getUserProperties().getProperty(STYLE_COLOR_PROPERTY, "auto");
+ String styleColor = cliRequest.getUserProperties().getProperty("style.color", "auto");
+ styleColor = cliRequest.getUserProperties().getProperty(Constants.MAVEN_STYLE_COLOR_PROPERTY, styleColor);
styleColor = commandLine.getOptionValue(COLOR, styleColor);
if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) {
MessageUtils.setColorEnabled(true);
@@ -763,16 +756,17 @@ public class MavenCli {
return Collections.emptyList();
}
- File extensionsFile = new File(cliRequest.multiModuleProjectDirectory, MVN_EXTENSIONS_FILENAME);
- File userHomeExtensionsFile = new File(USER_MAVEN_CONFIGURATION_HOME, EXTENSIONS_FILENAME);
-
List extensions = new ArrayList<>();
- if (extensionsFile.isFile()) {
- extensions.addAll(readCoreExtensionsDescriptor(extensionsFile));
- }
- if (userHomeExtensionsFile.isFile()) {
- extensions.addAll(readCoreExtensionsDescriptor(userHomeExtensionsFile));
- }
+
+ String installationExtensionsFile =
+ cliRequest.getUserProperties().getProperty(Constants.MAVEN_INSTALLATION_EXTENSIONS);
+ extensions.addAll(readCoreExtensionsDescriptor(installationExtensionsFile));
+
+ String projectExtensionsFile = cliRequest.getUserProperties().getProperty(Constants.MAVEN_PROJECT_EXTENSIONS);
+ extensions.addAll(readCoreExtensionsDescriptor(projectExtensionsFile));
+
+ String userExtensionsFile = cliRequest.getUserProperties().getProperty(Constants.MAVEN_USER_EXTENSIONS);
+ extensions.addAll(readCoreExtensionsDescriptor(userExtensionsFile));
if (extensions.isEmpty()) {
return Collections.emptyList();
@@ -824,13 +818,17 @@ public class MavenCli {
}
}
- private List readCoreExtensionsDescriptor(File extensionsFile)
+ private List readCoreExtensionsDescriptor(String extensionsFile)
throws IOException, XMLStreamException {
- CoreExtensionsStaxReader parser = new CoreExtensionsStaxReader();
-
- try (InputStream is = Files.newInputStream(extensionsFile.toPath())) {
- return parser.read(is, true).getExtensions();
+ if (extensionsFile != null) {
+ Path extensionsPath = Path.of(extensionsFile);
+ if (Files.exists(extensionsPath)) {
+ try (InputStream is = Files.newInputStream(extensionsPath)) {
+ return new CoreExtensionsStaxReader().read(is, true).getExtensions();
+ }
+ }
}
+ return List.of();
}
private ClassRealm setupContainerRealm(
@@ -874,9 +872,16 @@ public class MavenCli {
}
private List parseExtClasspath(CliRequest cliRequest) {
- String extClassPath = cliRequest.userProperties.getProperty(EXT_CLASS_PATH);
+ String extClassPath = cliRequest.userProperties.getProperty(Constants.MAVEN_EXT_CLASS_PATH);
if (extClassPath == null) {
- extClassPath = cliRequest.systemProperties.getProperty(EXT_CLASS_PATH);
+ extClassPath = cliRequest.systemProperties.getProperty(Constants.MAVEN_EXT_CLASS_PATH);
+ if (extClassPath != null) {
+ slf4jLogger.warn(
+ "The property '{}' has been set using a JVM system property which is deprecated. "
+ + "The property can be passed as a Maven argument or in the Maven project configuration file,"
+ + "usually located at ${session.rootDirectory}/.mvn/maven.properties.",
+ Constants.MAVEN_EXT_CLASS_PATH);
+ }
}
List jars = new ArrayList<>();
@@ -1193,7 +1198,7 @@ public class MavenCli {
}
void toolchains(CliRequest cliRequest) throws Exception {
- File userToolchainsFile;
+ File userToolchainsFile = null;
if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_USER_TOOLCHAINS)) {
userToolchainsFile = new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_USER_TOOLCHAINS));
@@ -1204,40 +1209,57 @@ public class MavenCli {
"The specified user toolchains file does not exist: " + userToolchainsFile);
}
} else {
- userToolchainsFile = DEFAULT_USER_TOOLCHAINS_FILE;
+ String userToolchainsFileStr = cliRequest.getUserProperties().getProperty(Constants.MAVEN_USER_TOOLCHAINS);
+ if (userToolchainsFileStr != null) {
+ userToolchainsFile = new File(userToolchainsFileStr);
+ }
}
- File globalToolchainsFile;
+ File installationToolchainsFile = null;
- if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS)) {
- globalToolchainsFile =
- new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS));
- globalToolchainsFile = resolveFile(globalToolchainsFile, cliRequest.workingDirectory);
+ if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_INSTALLATION_TOOLCHAINS)) {
+ installationToolchainsFile =
+ new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_INSTALLATION_TOOLCHAINS));
+ installationToolchainsFile = resolveFile(installationToolchainsFile, cliRequest.workingDirectory);
- if (!globalToolchainsFile.isFile()) {
+ if (!installationToolchainsFile.isFile()) {
throw new FileNotFoundException(
- "The specified global toolchains file does not exist: " + globalToolchainsFile);
+ "The specified installation toolchains file does not exist: " + installationToolchainsFile);
+ }
+ } else if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS)) {
+ installationToolchainsFile =
+ new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS));
+ installationToolchainsFile = resolveFile(installationToolchainsFile, cliRequest.workingDirectory);
+
+ if (!installationToolchainsFile.isFile()) {
+ throw new FileNotFoundException(
+ "The specified installation toolchains file does not exist: " + installationToolchainsFile);
}
} else {
- globalToolchainsFile = DEFAULT_GLOBAL_TOOLCHAINS_FILE;
+ String installationToolchainsFileStr =
+ cliRequest.getUserProperties().getProperty(Constants.MAVEN_INSTALLATION_TOOLCHAINS);
+ if (installationToolchainsFileStr != null) {
+ installationToolchainsFile = new File(installationToolchainsFileStr);
+ installationToolchainsFile = resolveFile(installationToolchainsFile, cliRequest.workingDirectory);
+ }
}
- cliRequest.request.setGlobalToolchainsFile(globalToolchainsFile);
+ cliRequest.request.setInstallationToolchainsFile(installationToolchainsFile);
cliRequest.request.setUserToolchainsFile(userToolchainsFile);
DefaultToolchainsBuildingRequest toolchainsRequest = new DefaultToolchainsBuildingRequest();
- if (globalToolchainsFile.isFile()) {
- toolchainsRequest.setGlobalToolchainsSource(new FileSource(globalToolchainsFile));
+ if (installationToolchainsFile != null && installationToolchainsFile.isFile()) {
+ toolchainsRequest.setGlobalToolchainsSource(new FileSource(installationToolchainsFile));
}
- if (userToolchainsFile.isFile()) {
+ if (userToolchainsFile != null && userToolchainsFile.isFile()) {
toolchainsRequest.setUserToolchainsSource(new FileSource(userToolchainsFile));
}
eventSpyDispatcher.onEvent(toolchainsRequest);
slf4jLogger.debug(
- "Reading global toolchains from '{}'",
- getLocation(toolchainsRequest.getGlobalToolchainsSource(), globalToolchainsFile));
+ "Reading installation toolchains from '{}'",
+ getLocation(toolchainsRequest.getGlobalToolchainsSource(), installationToolchainsFile));
slf4jLogger.debug(
"Reading user toolchains from '{}'",
getLocation(toolchainsRequest.getUserToolchainsSource(), userToolchainsFile));
@@ -1380,14 +1402,18 @@ public class MavenCli {
}
private String determineLocalRepositoryPath(final MavenExecutionRequest request) {
- String userDefinedLocalRepo = request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
- if (userDefinedLocalRepo != null) {
- return userDefinedLocalRepo;
+ String userDefinedLocalRepo = request.getUserProperties().getProperty(Constants.MAVEN_REPO_LOCAL);
+ if (userDefinedLocalRepo == null) {
+ userDefinedLocalRepo = request.getSystemProperties().getProperty(Constants.MAVEN_REPO_LOCAL);
+ if (userDefinedLocalRepo != null) {
+ slf4jLogger.warn(
+ "The property '{}' has been set using a JVM system property which is deprecated. "
+ + "The property can be passed as a Maven argument or in the Maven project configuration file,"
+ + "usually located at ${session.rootDirectory}/.mvn/maven.properties.",
+ Constants.MAVEN_REPO_LOCAL);
+ }
}
-
- // TODO Investigate why this can also be a Java system property and not just a Maven user property like
- // other properties
- return request.getSystemProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
+ return userDefinedLocalRepo;
}
private File determinePom(final CommandLine commandLine, final String workingDirectory, final File baseDirectory) {
@@ -1601,20 +1627,15 @@ public class MavenCli {
// Properties handling
// ----------------------------------------------------------------------
- static void populateProperties(
+ void populateProperties(
CommandLine commandLine, Properties paths, Properties systemProperties, Properties userProperties)
throws Exception {
+
+ // ----------------------------------------------------------------------
+ // Load environment and system properties
+ // ----------------------------------------------------------------------
+
EnvironmentUtils.addEnvVars(systemProperties);
-
- // ----------------------------------------------------------------------
- // Options that are set on the command line become system properties
- // and therefore are set in the session properties. System properties
- // are most dominant.
- // ----------------------------------------------------------------------
-
- final Properties userSpecifiedProperties =
- commandLine.getOptionProperties(String.valueOf(CLIManager.SET_USER_PROPERTY));
-
SystemProperties.addSystemProperties(systemProperties);
// ----------------------------------------------------------------------
@@ -1630,20 +1651,65 @@ public class MavenCli {
String mavenBuildVersion = CLIReportingUtils.createMavenVersionString(buildProperties);
systemProperties.setProperty("maven.build.version", mavenBuildVersion);
- BasicInterpolator interpolator =
- createInterpolator(paths, systemProperties, userProperties, userSpecifiedProperties);
- for (Map.Entry e : userSpecifiedProperties.entrySet()) {
- String name = (String) e.getKey();
- String value = interpolator.interpolate((String) e.getValue());
- userProperties.setProperty(name, value);
- // ----------------------------------------------------------------------
- // I'm leaving the setting of system properties here as not to break
- // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
- // ----------------------------------------------------------------------
- if (System.getProperty(name) == null) {
- System.setProperty(name, value);
- }
+ // ----------------------------------------------------------------------
+ // Options that are set on the command line become system properties
+ // and therefore are set in the session properties. System properties
+ // are most dominant.
+ // ----------------------------------------------------------------------
+
+ Properties userSpecifiedProperties =
+ commandLine.getOptionProperties(String.valueOf(CLIManager.SET_USER_PROPERTY));
+ userProperties.putAll(userSpecifiedProperties);
+
+ // ----------------------------------------------------------------------
+ // Load config files
+ // ----------------------------------------------------------------------
+ Function callback =
+ or(paths::getProperty, prefix("cli.", commandLine::getOptionValue), systemProperties::getProperty);
+
+ Path mavenConf;
+ if (systemProperties.getProperty(MAVEN_INSTALLATION_CONF) != null) {
+ mavenConf = fileSystem.getPath(systemProperties.getProperty(MAVEN_INSTALLATION_CONF));
+ } else if (systemProperties.getProperty("maven.conf") != null) {
+ mavenConf = fileSystem.getPath(systemProperties.getProperty("maven.conf"));
+ } else if (systemProperties.getProperty(MAVEN_HOME) != null) {
+ mavenConf = fileSystem.getPath(systemProperties.getProperty(MAVEN_HOME), "conf");
+ } else {
+ mavenConf = fileSystem.getPath("");
}
+ Path propertiesFile = mavenConf.resolve("maven.properties");
+ MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, callback, false);
+
+ // ----------------------------------------------------------------------
+ // I'm leaving the setting of system properties here as not to break
+ // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
+ // ----------------------------------------------------------------------
+ Set sys = SystemProperties.getSystemProperties().stringPropertyNames();
+ userProperties.stringPropertyNames().stream()
+ .filter(k -> !sys.contains(k))
+ .forEach(k -> System.setProperty(k, userProperties.getProperty(k)));
+ }
+
+ private static Function prefix(String prefix, Function cb) {
+ return s -> {
+ String v = null;
+ if (s.startsWith(prefix)) {
+ v = cb.apply(s.substring(prefix.length()));
+ }
+ return v;
+ };
+ }
+
+ private static Function or(Function... callbacks) {
+ return s -> {
+ for (Function cb : callbacks) {
+ String r = cb.apply(s);
+ if (r != null) {
+ return r;
+ }
+ }
+ return null;
+ };
}
private static BasicInterpolator createInterpolator(Properties... properties) {
@@ -1709,4 +1775,8 @@ public class MavenCli {
protected ModelProcessor createModelProcessor(PlexusContainer container) throws ComponentLookupException {
return container.lookup(ModelProcessor.class);
}
+
+ public void setFileSystem(FileSystem fileSystem) {
+ this.fileSystem = fileSystem;
+ }
}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java b/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java
index 902d0c19ff..a318779e70 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java
@@ -69,7 +69,8 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
public static final File DEFAULT_PROJECT_SETTINGS_FILE = new File(".mvn", "settings.xml");
- public static final File DEFAULT_GLOBAL_SETTINGS_FILE = new File(System.getProperty("maven.conf"), "settings.xml");
+ public static final File DEFAULT_INSTALLATION_SETTINGS_FILE =
+ new File(System.getProperty("maven.conf"), "settings.xml");
private static final Logger LOGGER = LoggerFactory.getLogger(SettingsXmlConfigurationProcessor.class);
@@ -119,26 +120,34 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
projectSettingsFile = null;
}
- File globalSettingsFile;
+ File installationSettingsFile;
- if (commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_SETTINGS)) {
- globalSettingsFile = new File(commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_SETTINGS));
- globalSettingsFile = resolveFile(globalSettingsFile, workingDirectory);
+ if (commandLine.hasOption(CLIManager.ALTERNATE_INSTALLATION_SETTINGS)) {
+ installationSettingsFile = new File(commandLine.getOptionValue(CLIManager.ALTERNATE_INSTALLATION_SETTINGS));
+ installationSettingsFile = resolveFile(installationSettingsFile, workingDirectory);
- if (!globalSettingsFile.isFile()) {
+ if (!installationSettingsFile.isFile()) {
throw new FileNotFoundException(
- "The specified global settings file does not exist: " + globalSettingsFile);
+ "The specified installation settings file does not exist: " + installationSettingsFile);
+ }
+ } else if (commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_SETTINGS)) {
+ installationSettingsFile = new File(commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_SETTINGS));
+ installationSettingsFile = resolveFile(installationSettingsFile, workingDirectory);
+
+ if (!installationSettingsFile.isFile()) {
+ throw new FileNotFoundException(
+ "The specified installation settings file does not exist: " + installationSettingsFile);
}
} else {
- globalSettingsFile = DEFAULT_GLOBAL_SETTINGS_FILE;
+ installationSettingsFile = DEFAULT_INSTALLATION_SETTINGS_FILE;
}
- request.setGlobalSettingsFile(globalSettingsFile);
+ request.setInstallationSettingsFile(installationSettingsFile);
request.setProjectSettingsFile(projectSettingsFile);
request.setUserSettingsFile(userSettingsFile);
SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest();
- settingsRequest.setGlobalSettingsFile(globalSettingsFile);
+ settingsRequest.setGlobalSettingsFile(installationSettingsFile);
settingsRequest.setProjectSettingsFile(projectSettingsFile);
settingsRequest.setUserSettingsFile(userSettingsFile);
settingsRequest.setSystemProperties(cliRequest.getSystemProperties());
@@ -155,7 +164,7 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
}
LOGGER.debug(
- "Reading global settings from '{}'",
+ "Reading installation settings from '{}'",
getLocation(settingsRequest.getGlobalSettingsSource(), settingsRequest.getGlobalSettingsFile()));
LOGGER.debug(
"Reading project settings from '{}'",
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/props/InterpolationHelper.java b/maven-embedder/src/main/java/org/apache/maven/cli/props/InterpolationHelper.java
new file mode 100644
index 0000000000..ceeeba2d9c
--- /dev/null
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/props/InterpolationHelper.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.props;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+public class InterpolationHelper {
+
+ private InterpolationHelper() {}
+
+ private static final char ESCAPE_CHAR = '\\';
+ private static final String DELIM_START = "${";
+ private static final String DELIM_STOP = "}";
+ private static final String MARKER = "$__";
+
+ /**
+ * Perform substitution on a property set
+ *
+ * @param properties the property set to perform substitution on
+ * @param callback Callback for substitution
+ */
+ public static void performSubstitution(Map properties, Function callback) {
+ performSubstitution(properties, callback, true, true);
+ }
+
+ /**
+ * Perform substitution on a property set
+ *
+ * @param properties the property set to perform substitution on
+ * @param callback the callback to obtain substitution values
+ * @param substituteFromConfig If substitute from configuration
+ * @param defaultsToEmptyString sets an empty string if a replacement value is not found, leaves intact otherwise
+ */
+ public static void performSubstitution(
+ Map properties,
+ Function callback,
+ boolean substituteFromConfig,
+ boolean defaultsToEmptyString) {
+ Map org = new HashMap<>(properties);
+ for (String name : properties.keySet()) {
+ properties.compute(
+ name,
+ (k, value) ->
+ substVars(value, name, null, org, callback, substituteFromConfig, defaultsToEmptyString));
+ }
+ }
+
+ /**
+ *
+ * This method performs property variable substitution on the
+ * specified value. If the specified value contains the syntax
+ * {@code ${<prop-name>}}, where {@code <prop-name>}
+ * refers to either a configuration property or a system property,
+ * then the corresponding property value is substituted for the variable
+ * placeholder. Multiple variable placeholders may exist in the
+ * specified value as well as nested variable placeholders, which
+ * are substituted from inner most to outer most. Configuration
+ * properties override system properties.
+ *
+ *
+ * @param val The string on which to perform property substitution.
+ * @param currentKey The key of the property being evaluated used to
+ * detect cycles.
+ * @param cycleMap Map of variable references used to detect nested cycles.
+ * @param configProps Set of configuration properties.
+ * @return The value of the specified string after system property substitution.
+ * @throws IllegalArgumentException If there was a syntax error in the
+ * property placeholder syntax or a recursive variable reference.
+ **/
+ public static String substVars(
+ String val, String currentKey, Map cycleMap, Map configProps) {
+ return substVars(val, currentKey, cycleMap, configProps, null);
+ }
+
+ /**
+ *
+ * This method performs property variable substitution on the
+ * specified value. If the specified value contains the syntax
+ * {@code ${<prop-name>}}, where {@code <prop-name>}
+ * refers to either a configuration property or a system property,
+ * then the corresponding property value is substituted for the variable
+ * placeholder. Multiple variable placeholders may exist in the
+ * specified value as well as nested variable placeholders, which
+ * are substituted from inner most to outer most. Configuration
+ * properties override system properties.
+ *
+ *
+ * @param val The string on which to perform property substitution.
+ * @param currentKey The key of the property being evaluated used to
+ * detect cycles.
+ * @param cycleMap Map of variable references used to detect nested cycles.
+ * @param configProps Set of configuration properties.
+ * @param callback the callback to obtain substitution values
+ * @return The value of the specified string after system property substitution.
+ * @throws IllegalArgumentException If there was a syntax error in the
+ * property placeholder syntax or a recursive variable reference.
+ **/
+ public static String substVars(
+ String val,
+ String currentKey,
+ Map cycleMap,
+ Map configProps,
+ Function callback) {
+ return substVars(val, currentKey, cycleMap, configProps, callback, true, false);
+ }
+
+ /**
+ *
+ * This method performs property variable substitution on the
+ * specified value. If the specified value contains the syntax
+ * {@code ${<prop-name>}}, where {@code <prop-name>}
+ * refers to either a configuration property or a system property,
+ * then the corresponding property value is substituted for the variable
+ * placeholder. Multiple variable placeholders may exist in the
+ * specified value as well as nested variable placeholders, which
+ * are substituted from inner most to outer most. Configuration
+ * properties override system properties.
+ *
+ *
+ * @param val The string on which to perform property substitution.
+ * @param currentKey The key of the property being evaluated used to
+ * detect cycles.
+ * @param cycleMap Map of variable references used to detect nested cycles.
+ * @param configProps Set of configuration properties.
+ * @param callback the callback to obtain substitution values
+ * @param substituteFromConfig If substitute from configuration
+ * @param defaultsToEmptyString sets an empty string if a replacement value is not found, leaves intact otherwise
+ * @return The value of the specified string after system property substitution.
+ * @throws IllegalArgumentException If there was a syntax error in the
+ * property placeholder syntax or a recursive variable reference.
+ **/
+ public static String substVars(
+ String val,
+ String currentKey,
+ Map cycleMap,
+ Map configProps,
+ Function callback,
+ boolean substituteFromConfig,
+ boolean defaultsToEmptyString) {
+ return unescape(doSubstVars(
+ val, currentKey, cycleMap, configProps, callback, substituteFromConfig, defaultsToEmptyString));
+ }
+
+ private static String doSubstVars(
+ String val,
+ String currentKey,
+ Map cycleMap,
+ Map configProps,
+ Function callback,
+ boolean substituteFromConfig,
+ boolean defaultsToEmptyString) {
+ if (cycleMap == null) {
+ cycleMap = new HashMap<>();
+ }
+
+ // Put the current key in the cycle map.
+ cycleMap.put(currentKey, currentKey);
+
+ // Assume we have a value that is something like:
+ // "leading ${foo.${bar}} middle ${baz} trailing"
+
+ // Find the first ending '}' variable delimiter, which
+ // will correspond to the first deepest nested variable
+ // placeholder.
+ int startDelim;
+ int stopDelim = -1;
+ do {
+ stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
+ while (stopDelim > 0 && val.charAt(stopDelim - 1) == ESCAPE_CHAR) {
+ stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
+ }
+
+ // Find the matching starting "${" variable delimiter
+ // by looping until we find a start delimiter that is
+ // greater than the stop delimiter we have found.
+ startDelim = val.indexOf(DELIM_START);
+ while (stopDelim >= 0) {
+ int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
+ if ((idx < 0) || (idx > stopDelim)) {
+ break;
+ } else if (idx < stopDelim) {
+ startDelim = idx;
+ }
+ }
+ } while (startDelim >= 0 && stopDelim >= 0 && stopDelim < startDelim + DELIM_START.length());
+
+ // If we do not have a start or stop delimiter, then just
+ // return the existing value.
+ if ((startDelim < 0) || (stopDelim < 0)) {
+ cycleMap.remove(currentKey);
+ return val;
+ }
+
+ // At this point, we have found a variable placeholder so
+ // we must perform a variable substitution on it.
+ // Using the start and stop delimiter indices, extract
+ // the first, deepest nested variable placeholder.
+ String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
+ String org = variable;
+
+ // Strip expansion modifiers
+ int idx1 = variable.lastIndexOf(":-");
+ int idx2 = variable.lastIndexOf(":+");
+ int idx = idx1 >= 0 && idx2 >= 0 ? Math.min(idx1, idx2) : idx1 >= 0 ? idx1 : idx2;
+ String op = null;
+ if (idx >= 0) {
+ op = variable.substring(idx);
+ variable = variable.substring(0, idx);
+ }
+
+ // Verify that this is not a recursive variable reference.
+ if (cycleMap.get(variable) != null) {
+ throw new IllegalArgumentException("recursive variable reference: " + variable);
+ }
+
+ String substValue = null;
+ // Get the value of the deepest nested variable placeholder.
+ // Try to configuration properties first.
+ if (substituteFromConfig && configProps != null) {
+ substValue = configProps.get(variable);
+ }
+ if (substValue == null) {
+ if (!variable.isEmpty()) {
+ if (callback != null) {
+ substValue = callback.apply(variable);
+ }
+ }
+ }
+
+ if (op != null) {
+ if (op.startsWith(":-")) {
+ if (substValue == null || substValue.isEmpty()) {
+ substValue = op.substring(":-".length());
+ }
+ } else if (op.startsWith(":+")) {
+ if (substValue != null && !substValue.isEmpty()) {
+ substValue = op.substring(":+".length());
+ }
+ } else {
+ throw new IllegalArgumentException("Bad substitution: ${" + org + "}");
+ }
+ }
+
+ if (substValue == null) {
+ if (defaultsToEmptyString) {
+ substValue = "";
+ } else {
+ // alters the original token to avoid infinite recursion
+ // altered tokens are reverted in substVarsPreserveUnresolved()
+ substValue = MARKER + "{" + variable + "}";
+ }
+ }
+
+ // Remove the found variable from the cycle map, since
+ // it may appear more than once in the value and we don't
+ // want such situations to appear as a recursive reference.
+ cycleMap.remove(variable);
+
+ // Append the leading characters, the substituted value of
+ // the variable, and the trailing characters to get the new
+ // value.
+ val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length());
+
+ // Now perform substitution again, since there could still
+ // be substitutions to make.
+ val = doSubstVars(
+ val, currentKey, cycleMap, configProps, callback, substituteFromConfig, defaultsToEmptyString);
+
+ cycleMap.remove(currentKey);
+
+ // Return the value.
+ return val;
+ }
+
+ public static String escape(String val) {
+ return val.replace("$", MARKER);
+ }
+
+ private static String unescape(String val) {
+ val = val.replaceAll("\\" + MARKER, "\\$");
+ int escape = val.indexOf(ESCAPE_CHAR);
+ while (escape >= 0 && escape < val.length() - 1) {
+ char c = val.charAt(escape + 1);
+ if (c == '{' || c == '}' || c == ESCAPE_CHAR) {
+ val = val.substring(0, escape) + val.substring(escape + 1);
+ }
+ escape = val.indexOf(ESCAPE_CHAR, escape + 1);
+ }
+ return val;
+ }
+}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java b/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java
new file mode 100644
index 0000000000..64ff7cf77f
--- /dev/null
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java
@@ -0,0 +1,1151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.props;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Enhancement of the standard Properties
+ * managing the maintain of comments, etc.
+ */
+public class MavenProperties extends AbstractMap {
+
+ /** Constant for the supported comment characters.*/
+ private static final String COMMENT_CHARS = "#!";
+
+ /** The list of possible key/value separators */
+ private static final char[] SEPARATORS = new char[] {'=', ':'};
+
+ /** The white space characters used as key/value separators. */
+ private static final char[] WHITE_SPACE = new char[] {' ', '\t', '\f'};
+
+ /**
+ * Unless standard java props, use UTF-8
+ */
+ static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
+
+ /** Constant for the platform specific line separator.*/
+ private static final String LINE_SEPARATOR = System.lineSeparator();
+
+ /** Constant for the radix of hex numbers.*/
+ private static final int HEX_RADIX = 16;
+
+ /** Constant for the length of a unicode literal.*/
+ private static final int UNICODE_LEN = 4;
+
+ private final Map storage = new LinkedHashMap<>();
+ private final Map layout = new LinkedHashMap<>();
+ private List header;
+ private List footer;
+ private Path location;
+ private Function callback;
+ boolean substitute = true;
+ boolean typed;
+
+ public MavenProperties() {}
+
+ public MavenProperties(Path location) throws IOException {
+ this(location, null);
+ }
+
+ public MavenProperties(Path location, Function callback) throws IOException {
+ this.location = location;
+ this.callback = callback;
+ if (Files.exists(location)) {
+ load(location);
+ }
+ }
+
+ public MavenProperties(boolean substitute) {
+ this.substitute = substitute;
+ }
+
+ public MavenProperties(Path location, boolean substitute) {
+ this.location = location;
+ this.substitute = substitute;
+ }
+
+ public void load(Path location) throws IOException {
+ try (InputStream is = Files.newInputStream(location)) {
+ load(is);
+ }
+ }
+
+ public void load(URL location) throws IOException {
+ try (InputStream is = location.openStream()) {
+ load(is);
+ }
+ }
+
+ public void load(InputStream is) throws IOException {
+ load(new InputStreamReader(is, DEFAULT_ENCODING));
+ }
+
+ public void load(Reader reader) throws IOException {
+ loadLayout(reader, false);
+ }
+
+ public void save() throws IOException {
+ save(this.location);
+ }
+
+ public void save(Path location) throws IOException {
+ try (OutputStream os = Files.newOutputStream(location)) {
+ save(os);
+ }
+ }
+
+ public void save(OutputStream os) throws IOException {
+ save(new OutputStreamWriter(os, DEFAULT_ENCODING));
+ }
+
+ public void save(Writer writer) throws IOException {
+ saveLayout(writer, typed);
+ }
+
+ /**
+ * Store a properties into a output stream, preserving comments, special character, etc.
+ * This method is mainly to be compatible with the java.util.Properties class.
+ *
+ * @param os an output stream.
+ * @param comment this parameter is ignored as this Properties
+ * @throws IOException If storing fails
+ */
+ public void store(OutputStream os, String comment) throws IOException {
+ this.save(os);
+ }
+
+ /**
+ * Searches for the property with the specified key in this property list.
+ *
+ * @param key the property key.
+ * @return the value in this property list with the specified key value.
+ */
+ public String getProperty(String key) {
+ return this.get(key);
+ }
+
+ /**
+ * Searches for the property with the specified key in this property list. If the key is not found in this property
+ * list, the default property list, and its defaults, recursively, are then checked. The method returns the default
+ * value argument if the property is not found.
+ *
+ * @param key the property key.
+ * @param defaultValue a default value.
+ * @return The property value of the default value
+ */
+ public String getProperty(String key, String defaultValue) {
+ if (this.get(key) != null) {
+ return this.get(key);
+ }
+ return defaultValue;
+ }
+
+ @Override
+ public Set> entrySet() {
+ return new AbstractSet<>() {
+ @Override
+ public Iterator> iterator() {
+ return new Iterator<>() {
+ final Iterator> keyIterator =
+ storage.entrySet().iterator();
+
+ public boolean hasNext() {
+ return keyIterator.hasNext();
+ }
+
+ public Entry next() {
+ final Entry entry = keyIterator.next();
+ return new Entry() {
+ public String getKey() {
+ return entry.getKey();
+ }
+
+ public String getValue() {
+ return entry.getValue();
+ }
+
+ public String setValue(String value) {
+ String old = entry.setValue(value);
+ if (old == null || !old.equals(value)) {
+ Layout l = layout.get(entry.getKey());
+ if (l != null) {
+ l.clearValue();
+ }
+ }
+ return old;
+ }
+ };
+ }
+
+ public void remove() {
+ keyIterator.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return storage.size();
+ }
+ };
+ }
+
+ /**
+ * Returns an enumeration of all the keys in this property list, including distinct keys in the default property
+ * list if a key of the same name has not already been found from the main properties list.
+ *
+ * @return an enumeration of all the keys in this property list, including the keys in the default property list.
+ */
+ public Enumeration> propertyNames() {
+ return Collections.enumeration(storage.keySet());
+ }
+
+ /**
+ * Calls the map method put. Provided for parallelism with the getProperty method.
+ * Enforces use of strings for property keys and values. The value returned is the result of the map call to put.
+ *
+ * @param key the key to be placed into this property list.
+ * @param value the value corresponding to the key.
+ * @return the previous value of the specified key in this property list, or null if it did not have one.
+ */
+ public Object setProperty(String key, String value) {
+ return this.put(key, value);
+ }
+
+ @Override
+ public String put(String key, String value) {
+ String old = storage.put(key, value);
+ if (old == null || !old.equals(value)) {
+ Layout l = layout.get(key);
+ if (l != null) {
+ l.clearValue();
+ }
+ }
+ return old;
+ }
+
+ void putAllSubstituted(Map extends String, ? extends String> m) {
+ storage.putAll(m);
+ }
+
+ public String put(String key, List commentLines, List valueLines) {
+ commentLines = new ArrayList<>(commentLines);
+ valueLines = new ArrayList<>(valueLines);
+ String escapedKey = escapeKey(key);
+ StringBuilder sb = new StringBuilder();
+ // int lastLine = valueLines.size() - 1;
+ if (valueLines.isEmpty()) {
+ valueLines.add(escapedKey + "=");
+ sb.append(escapedKey).append("=");
+ } else {
+ String val0 = valueLines.get(0);
+ String rv0 = typed ? val0 : escapeJava(val0);
+ if (!val0.trim().startsWith(escapedKey)) {
+ valueLines.set(0, escapedKey + " = " + rv0 /*+ (0 < lastLine? "\\": "")*/);
+ sb.append(escapedKey).append(" = ").append(rv0);
+ } else {
+ valueLines.set(0, rv0 /*+ (0 < lastLine? "\\": "")*/);
+ sb.append(rv0);
+ }
+ }
+ for (int i = 1; i < valueLines.size(); i++) {
+ String val = valueLines.get(i);
+ valueLines.set(i, typed ? val : escapeJava(val) /*+ (i < lastLine? "\\": "")*/);
+ while (!val.isEmpty() && Character.isWhitespace(val.charAt(0))) {
+ val = val.substring(1);
+ }
+ sb.append(val);
+ }
+ String[] property = PropertiesReader.parseProperty(sb.toString());
+ this.layout.put(key, new Layout(commentLines, valueLines));
+ return storage.put(key, property[1]);
+ }
+
+ public String put(String key, List commentLines, String value) {
+ commentLines = new ArrayList<>(commentLines);
+ this.layout.put(key, new Layout(commentLines, null));
+ return storage.put(key, value);
+ }
+
+ public String put(String key, String comment, String value) {
+ return put(key, Collections.singletonList(comment), value);
+ }
+
+ public boolean update(Map props) {
+ MavenProperties properties;
+ if (props instanceof MavenProperties) {
+ properties = (MavenProperties) props;
+ } else {
+ properties = new MavenProperties();
+ properties.putAll(props);
+ }
+ return update(properties);
+ }
+
+ public boolean update(MavenProperties properties) {
+ boolean modified = false;
+ // Remove "removed" properties from the cfg file
+ for (String key : new ArrayList(this.keySet())) {
+ if (!properties.containsKey(key)) {
+ this.remove(key);
+ modified = true;
+ }
+ }
+ // Update existing keys
+ for (String key : properties.keySet()) {
+ String v = this.get(key);
+ List comments = properties.getComments(key);
+ List value = properties.getRaw(key);
+ if (v == null) {
+ this.put(key, comments, value);
+ modified = true;
+ } else if (!v.equals(properties.get(key))) {
+ if (comments.isEmpty()) {
+ comments = this.getComments(key);
+ }
+ this.put(key, comments, value);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ public List getRaw(String key) {
+ if (layout.containsKey(key)) {
+ if (layout.get(key).getValueLines() != null) {
+ return new ArrayList(layout.get(key).getValueLines());
+ }
+ }
+ List result = new ArrayList();
+ if (storage.containsKey(key)) {
+ result.add(storage.get(key));
+ }
+ return result;
+ }
+
+ public List getComments(String key) {
+ if (layout.containsKey(key)) {
+ if (layout.get(key).getCommentLines() != null) {
+ return new ArrayList(layout.get(key).getCommentLines());
+ }
+ }
+ return new ArrayList();
+ }
+
+ @Override
+ public String remove(Object key) {
+ Layout l = layout.get(key);
+ if (l != null) {
+ l.clearValue();
+ }
+ return storage.remove(key);
+ }
+
+ @Override
+ public void clear() {
+ for (Layout l : layout.values()) {
+ l.clearValue();
+ }
+ storage.clear();
+ }
+
+ /**
+ * Return the comment header.
+ *
+ * @return the comment header
+ */
+ public List getHeader() {
+ return header;
+ }
+
+ /**
+ * Set the comment header.
+ *
+ * @param header the header to use
+ */
+ public void setHeader(List header) {
+ this.header = header;
+ }
+
+ /**
+ * Return the comment footer.
+ *
+ * @return the comment footer
+ */
+ public List getFooter() {
+ return footer;
+ }
+
+ /**
+ * Set the comment footer.
+ *
+ * @param footer the footer to use
+ */
+ public void setFooter(List footer) {
+ this.footer = footer;
+ }
+
+ /**
+ * Reads a properties file and stores its internal structure. The found
+ * properties will be added to the associated configuration object.
+ *
+ * @param in the reader to the properties file
+ * @throws IOException if an error occurs
+ */
+ protected void loadLayout(Reader in, boolean maybeTyped) throws IOException {
+ PropertiesReader reader = new PropertiesReader(in, maybeTyped);
+ boolean hasProperty = false;
+ while (reader.nextProperty()) {
+ hasProperty = true;
+ storage.put(reader.getPropertyName(), reader.getPropertyValue());
+ int idx = checkHeaderComment(reader.getCommentLines());
+ layout.put(
+ reader.getPropertyName(),
+ new Layout(
+ idx < reader.getCommentLines().size()
+ ? new ArrayList<>(reader.getCommentLines()
+ .subList(
+ idx,
+ reader.getCommentLines().size()))
+ : null,
+ new ArrayList<>(reader.getValueLines())));
+ }
+ typed = maybeTyped && reader.typed != null && reader.typed;
+ if (!typed) {
+ for (Entry e : storage.entrySet()) {
+ e.setValue(unescapeJava(e.getValue()));
+ }
+ }
+ if (hasProperty) {
+ footer = new ArrayList<>(reader.getCommentLines());
+ } else {
+ header = new ArrayList<>(reader.getCommentLines());
+ }
+ if (substitute) {
+ substitute();
+ }
+ }
+
+ public void substitute() {
+ substitute(callback);
+ }
+
+ public void substitute(Function callback) {
+ InterpolationHelper.performSubstitution(storage, callback);
+ }
+
+ /**
+ * Writes the properties file to the given writer, preserving as much of its
+ * structure as possible.
+ *
+ * @param out the writer
+ * @throws IOException if an error occurs
+ */
+ protected void saveLayout(Writer out, boolean typed) throws IOException {
+ PropertiesWriter writer = new PropertiesWriter(out, typed);
+ if (header != null) {
+ for (String s : header) {
+ writer.writeln(s);
+ }
+ }
+
+ for (String key : storage.keySet()) {
+ Layout l = layout.get(key);
+ if (l != null && l.getCommentLines() != null) {
+ for (String s : l.getCommentLines()) {
+ writer.writeln(s);
+ }
+ }
+ if (l != null && l.getValueLines() != null) {
+ for (int i = 0; i < l.getValueLines().size(); i++) {
+ String s = l.getValueLines().get(i);
+ if (i < l.getValueLines().size() - 1) {
+ writer.writeln(s + "\\");
+ } else {
+ writer.writeln(s);
+ }
+ }
+ } else {
+ writer.writeProperty(key, storage.get(key));
+ }
+ }
+ if (footer != null) {
+ for (String s : footer) {
+ writer.writeln(s);
+ }
+ }
+ writer.flush();
+ }
+
+ /**
+ * Checks if parts of the passed in comment can be used as header comment.
+ * This method checks whether a header comment can be defined (i.e. whether
+ * this is the first comment in the loaded file). If this is the case, it is
+ * searched for the lates blank line. This line will mark the end of the
+ * header comment. The return value is the index of the first line in the
+ * passed in list, which does not belong to the header comment.
+ *
+ * @param commentLines the comment lines
+ * @return the index of the next line after the header comment
+ */
+ private int checkHeaderComment(List commentLines) {
+ if (getHeader() == null && layout.isEmpty()) {
+ // This is the first comment. Search for blank lines.
+ int index = commentLines.size() - 1;
+ while (index >= 0 && !commentLines.get(index).isEmpty()) {
+ index--;
+ }
+ setHeader(new ArrayList(commentLines.subList(0, index + 1)));
+ return index + 1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Tests whether a line is a comment, i.e. whether it starts with a comment
+ * character.
+ *
+ * @param line the line
+ * @return a flag if this is a comment line
+ */
+ static boolean isCommentLine(String line) {
+ String s = line.trim();
+ // blank lines are also treated as comment lines
+ return s.isEmpty() || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
+ }
+
+ /**
+ * Unescapes any Java literals found in the String
to a
+ * Writer
.
This is a slightly modified version of the
+ * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
+ * drop escaped separators (i.e '\,').
+ *
+ * @param str the String
to unescape, may be null
+ * @return the processed string
+ * @throws IllegalArgumentException if the Writer is null
+ */
+ protected static String unescapeJava(String str) {
+ if (str == null) {
+ return null;
+ }
+ int sz = str.length();
+ StringBuilder out = new StringBuilder(sz);
+ StringBuilder unicode = new StringBuilder(UNICODE_LEN);
+ boolean hadSlash = false;
+ boolean inUnicode = false;
+ for (int i = 0; i < sz; i++) {
+ char ch = str.charAt(i);
+ if (inUnicode) {
+ // if in unicode, then we're reading unicode
+ // values in somehow
+ unicode.append(ch);
+ if (unicode.length() == UNICODE_LEN) {
+ // unicode now contains the four hex digits
+ // which represents our unicode character
+ try {
+ int value = Integer.parseInt(unicode.toString(), HEX_RADIX);
+ out.append((char) value);
+ unicode.setLength(0);
+ inUnicode = false;
+ hadSlash = false;
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("Unable to parse unicode value: " + unicode, nfe);
+ }
+ }
+ continue;
+ }
+
+ if (hadSlash) {
+ // handle an escaped value
+ hadSlash = false;
+ switch (ch) {
+ case '\\':
+ out.append('\\');
+ break;
+ case '\'':
+ out.append('\'');
+ break;
+ case '\"':
+ out.append('"');
+ break;
+ case 'r':
+ out.append('\r');
+ break;
+ case 'f':
+ out.append('\f');
+ break;
+ case 't':
+ out.append('\t');
+ break;
+ case 'n':
+ out.append('\n');
+ break;
+ case 'b':
+ out.append('\b');
+ break;
+ case 'u':
+ // uh-oh, we're in unicode country....
+ inUnicode = true;
+ break;
+ default:
+ out.append(ch);
+ break;
+ }
+ continue;
+ } else if (ch == '\\') {
+ hadSlash = true;
+ continue;
+ }
+ out.append(ch);
+ }
+
+ if (hadSlash) {
+ // then we're in the weird case of a \ at the end of the
+ // string, let's output it anyway.
+ out.append('\\');
+ }
+
+ return out.toString();
+ }
+
+ /**
+ * Escapes the characters in a String
using Java String rules.
+ *
+ * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)
+ *
+ * So a tab becomes the characters '\\'
and
+ * 't'
.
+ *
+ * The only difference between Java strings and JavaScript strings
+ * is that in JavaScript, a single quote must be escaped.
+ *
+ * Example:
+ *
+ * input string: He didn't say, "Stop!"
+ * output string: He didn't say, \"Stop!\"
+ *
+ *
+ *
+ * @param str String to escape values in, may be null
+ * @return String with escaped values, null
if null string input
+ */
+ @SuppressWarnings("checkstyle:MagicNumber")
+ protected static String escapeJava(String str) {
+ if (str == null) {
+ return null;
+ }
+ int sz = str.length();
+ StringBuilder out = new StringBuilder(sz * 2);
+ for (int i = 0; i < sz; i++) {
+ char ch = str.charAt(i);
+ // handle unicode
+ if (ch > 0xfff) {
+ out.append("\\u").append(hex(ch));
+ } else if (ch > 0xff) {
+ out.append("\\u0").append(hex(ch));
+ } else if (ch > 0x7f) {
+ out.append("\\u00").append(hex(ch));
+ } else if (ch < 32) {
+ switch (ch) {
+ case '\b':
+ out.append('\\');
+ out.append('b');
+ break;
+ case '\n':
+ out.append('\\');
+ out.append('n');
+ break;
+ case '\t':
+ out.append('\\');
+ out.append('t');
+ break;
+ case '\f':
+ out.append('\\');
+ out.append('f');
+ break;
+ case '\r':
+ out.append('\\');
+ out.append('r');
+ break;
+ default:
+ if (ch > 0xf) {
+ out.append("\\u00").append(hex(ch));
+ } else {
+ out.append("\\u000").append(hex(ch));
+ }
+ break;
+ }
+ } else {
+ switch (ch) {
+ case '"':
+ out.append('\\');
+ out.append('"');
+ break;
+ case '\\':
+ out.append('\\');
+ out.append('\\');
+ break;
+ default:
+ out.append(ch);
+ break;
+ }
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Returns an upper case hexadecimal String
for the given
+ * character.
+ *
+ * @param ch The character to convert.
+ * @return An upper case hexadecimal String
+ */
+ protected static String hex(char ch) {
+ return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ *
+ * The method returns false
if a null
array is passed in.
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return true
if the array contains the object
+ */
+ public static boolean contains(char[] array, char valueToFind) {
+ if (array == null) {
+ return false;
+ }
+ for (char c : array) {
+ if (valueToFind == c) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Escape the separators in the key.
+ *
+ * @param key the key
+ * @return the escaped key
+ */
+ private static String escapeKey(String key) {
+ StringBuilder newkey = new StringBuilder();
+
+ for (int i = 0; i < key.length(); i++) {
+ char c = key.charAt(i);
+
+ if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) {
+ // escape the separator
+ newkey.append('\\');
+ newkey.append(c);
+ } else {
+ newkey.append(c);
+ }
+ }
+
+ return newkey.toString();
+ }
+
+ /**
+ * This class is used to read properties lines. These lines do
+ * not terminate with new-line chars but rather when there is no
+ * backslash sign a the end of the line. This is used to
+ * concatenate multiple lines for readability.
+ */
+ public static class PropertiesReader extends LineNumberReader {
+ /** Stores the comment lines for the currently processed property.*/
+ private final List commentLines;
+
+ /** Stores the value lines for the currently processed property.*/
+ private final List valueLines;
+
+ /** Stores the name of the last read property.*/
+ private String propertyName;
+
+ /** Stores the value of the last read property.*/
+ private String propertyValue;
+
+ private boolean maybeTyped;
+
+ /** Stores if the properties are typed or not */
+ Boolean typed;
+
+ /**
+ * Creates a new instance of PropertiesReader
and sets
+ * the underlaying reader and the list delimiter.
+ *
+ * @param reader the reader
+ */
+ public PropertiesReader(Reader reader, boolean maybeTyped) {
+ super(reader);
+ commentLines = new ArrayList<>();
+ valueLines = new ArrayList<>();
+ this.maybeTyped = maybeTyped;
+ }
+
+ /**
+ * Reads a property line. Returns null if Stream is
+ * at EOF. Concatenates lines ending with "\".
+ * Skips lines beginning with "#" or "!" and empty lines.
+ * The return value is a property definition (<name>
+ * = <value>
)
+ *
+ * @return A string containing a property value or null
+ *
+ * @throws IOException in case of an I/O error
+ */
+ public String readProperty() throws IOException {
+ commentLines.clear();
+ valueLines.clear();
+ StringBuilder buffer = new StringBuilder();
+
+ while (true) {
+ String line = readLine();
+ if (line == null) {
+ // EOF
+ return null;
+ }
+
+ if (isCommentLine(line)) {
+ commentLines.add(line);
+ continue;
+ }
+
+ boolean combine = checkCombineLines(line);
+ if (combine) {
+ line = line.substring(0, line.length() - 1);
+ }
+ valueLines.add(line);
+ while (line.length() > 0 && contains(WHITE_SPACE, line.charAt(0))) {
+ line = line.substring(1, line.length());
+ }
+ buffer.append(line);
+ if (!combine) {
+ break;
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Parses the next property from the input stream and stores the found
+ * name and value in internal fields. These fields can be obtained using
+ * the provided getter methods. The return value indicates whether EOF
+ * was reached (false ) or whether further properties are
+ * available (true ).
+ *
+ * @return a flag if further properties are available
+ * @throws IOException if an error occurs
+ */
+ public boolean nextProperty() throws IOException {
+ String line = readProperty();
+
+ if (line == null) {
+ return false; // EOF
+ }
+
+ // parse the line
+ String[] property = parseProperty(line);
+ boolean typed = false;
+ if (maybeTyped && property[1].length() >= 2) {
+ typed = property[1].matches(
+ "\\s*[TILFDXSCBilfdxscb]?(\\[[\\S\\s]*\\]|\\([\\S\\s]*\\)|\\{[\\S\\s]*\\}|\"[\\S\\s]*\")\\s*");
+ }
+ if (this.typed == null) {
+ this.typed = typed;
+ } else {
+ this.typed = this.typed & typed;
+ }
+ propertyName = unescapeJava(property[0]);
+ propertyValue = property[1];
+ return true;
+ }
+
+ /**
+ * Returns the comment lines that have been read for the last property.
+ *
+ * @return the comment lines for the last property returned by
+ * readProperty()
+ */
+ public List getCommentLines() {
+ return commentLines;
+ }
+
+ /**
+ * Returns the value lines that have been read for the last property.
+ *
+ * @return the raw value lines for the last property returned by
+ * readProperty()
+ */
+ public List getValueLines() {
+ return valueLines;
+ }
+
+ /**
+ * Returns the name of the last read property. This method can be called
+ * after {@link #nextProperty()}
was invoked and its
+ * return value was true .
+ *
+ * @return the name of the last read property
+ */
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * Returns the value of the last read property. This method can be
+ * called after {@link #nextProperty()}
was invoked and
+ * its return value was true .
+ *
+ * @return the value of the last read property
+ */
+ public String getPropertyValue() {
+ return propertyValue;
+ }
+
+ /**
+ * Checks if the passed in line should be combined with the following.
+ * This is true, if the line ends with an odd number of backslashes.
+ *
+ * @param line the line
+ * @return a flag if the lines should be combined
+ */
+ private static boolean checkCombineLines(String line) {
+ int bsCount = 0;
+ for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) {
+ bsCount++;
+ }
+
+ return bsCount % 2 != 0;
+ }
+
+ /**
+ * Parse a property line and return the key and the value in an array.
+ *
+ * @param line the line to parse
+ * @return an array with the property's key and value
+ */
+ private static String[] parseProperty(String line) {
+ // sorry for this spaghetti code, please replace it as soon as
+ // possible with a regexp when the Java 1.3 requirement is dropped
+
+ String[] result = new String[2];
+ StringBuilder key = new StringBuilder();
+ StringBuilder value = new StringBuilder();
+
+ // state of the automaton:
+ // 0: key parsing
+ // 1: antislash found while parsing the key
+ // 2: separator crossing
+ // 3: white spaces
+ // 4: value parsing
+ int state = 0;
+
+ for (int pos = 0; pos < line.length(); pos++) {
+ char c = line.charAt(pos);
+
+ switch (state) {
+ case 0:
+ if (c == '\\') {
+ state = 1;
+ } else if (contains(WHITE_SPACE, c)) {
+ // switch to the separator crossing state
+ state = 2;
+ } else if (contains(SEPARATORS, c)) {
+ // switch to the value parsing state
+ state = 3;
+ } else {
+ key.append(c);
+ }
+
+ break;
+
+ case 1:
+ if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) {
+ // this is an escaped separator or white space
+ key.append(c);
+ } else {
+ // another escaped character, the '\' is preserved
+ key.append('\\');
+ key.append(c);
+ }
+
+ // return to the key parsing state
+ state = 0;
+
+ break;
+
+ case 2:
+ if (contains(WHITE_SPACE, c)) {
+ // do nothing, eat all white spaces
+ state = 2;
+ } else if (contains(SEPARATORS, c)) {
+ // switch to the value parsing state
+ state = 3;
+ } else {
+ // any other character indicates we encoutered the beginning of the value
+ value.append(c);
+
+ // switch to the value parsing state
+ state = 4;
+ }
+
+ break;
+
+ case 3:
+ if (contains(WHITE_SPACE, c)) {
+ // do nothing, eat all white spaces
+ state = 3;
+ } else {
+ // any other character indicates we encoutered the beginning of the value
+ value.append(c);
+
+ // switch to the value parsing state
+ state = 4;
+ }
+
+ break;
+
+ case 4:
+ value.append(c);
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ result[0] = key.toString();
+ result[1] = value.toString();
+
+ return result;
+ }
+ } // class PropertiesReader
+
+ /**
+ * This class is used to write properties lines.
+ */
+ public static class PropertiesWriter extends FilterWriter {
+ private boolean typed;
+
+ /**
+ * Constructor.
+ *
+ * @param writer a Writer object providing the underlying stream
+ */
+ public PropertiesWriter(Writer writer, boolean typed) {
+ super(writer);
+ this.typed = typed;
+ }
+
+ /**
+ * Writes the given property and its value.
+ *
+ * @param key the property key
+ * @param value the property value
+ * @throws IOException if an error occurs
+ */
+ public void writeProperty(String key, String value) throws IOException {
+ write(escapeKey(key));
+ write(" = ");
+ write(typed ? value : escapeJava(value));
+ writeln(null);
+ }
+
+ /**
+ * Helper method for writing a line with the platform specific line
+ * ending.
+ *
+ * @param s the content of the line (may be null )
+ * @throws IOException if an error occurs
+ */
+ public void writeln(String s) throws IOException {
+ if (s != null) {
+ write(s);
+ }
+ write(LINE_SEPARATOR);
+ }
+ } // class PropertiesWriter
+
+ /**
+ * TODO
+ */
+ protected static class Layout {
+
+ private List commentLines;
+ private List valueLines;
+
+ public Layout() {}
+
+ public Layout(List commentLines, List valueLines) {
+ this.commentLines = commentLines;
+ this.valueLines = valueLines;
+ }
+
+ public List getCommentLines() {
+ return commentLines;
+ }
+
+ public void setCommentLines(List commentLines) {
+ this.commentLines = commentLines;
+ }
+
+ public List getValueLines() {
+ return valueLines;
+ }
+
+ public void setValueLines(List valueLines) {
+ this.valueLines = valueLines;
+ }
+
+ public void clearValue() {
+ this.valueLines = null;
+ }
+ } // class Layout
+}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java b/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java
new file mode 100644
index 0000000000..0e37deb992
--- /dev/null
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.props;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+import java.util.function.Function;
+
+import static org.apache.maven.cli.props.InterpolationHelper.substVars;
+
+public class MavenPropertiesLoader {
+
+ public static final String INCLUDES_PROPERTY = "${includes}"; // includes
+
+ public static final String OVERRIDE_PREFIX =
+ "maven.override."; // prefix that marks that system property should override defaults.
+
+ public static void loadProperties(
+ java.util.Properties properties, Path path, Function callback, boolean escape)
+ throws IOException {
+ MavenProperties sp = new MavenProperties(false);
+ if (Files.exists(path)) {
+ sp.load(path);
+ }
+ properties.forEach(
+ (k, v) -> sp.put(k.toString(), escape ? InterpolationHelper.escape(v.toString()) : v.toString()));
+ loadIncludes(path, sp, callback);
+ substitute(sp, callback);
+ sp.forEach(properties::setProperty);
+ }
+
+ public static void substitute(MavenProperties props, Function callback) {
+ for (Enumeration> e = props.propertyNames(); e.hasMoreElements(); ) {
+ String name = (String) e.nextElement();
+ String value = props.getProperty(name);
+ if (value == null) {
+ value = callback.apply(name);
+ }
+ if (name.startsWith(OVERRIDE_PREFIX)) {
+ String overrideName = name.substring(OVERRIDE_PREFIX.length());
+ props.put(overrideName, substVars(value, name, null, props, callback));
+ } else {
+ props.put(name, substVars(value, name, null, props, callback));
+ }
+ }
+ props.keySet().removeIf(k -> k.startsWith(OVERRIDE_PREFIX));
+ }
+
+ private static MavenProperties loadPropertiesFile(
+ Path path, boolean failIfNotFound, Function callback) throws IOException {
+ MavenProperties configProps = new MavenProperties(null, false);
+ if (Files.exists(path) || failIfNotFound) {
+ configProps.load(path);
+ loadIncludes(path, configProps, callback);
+ trimValues(configProps);
+ }
+ return configProps;
+ }
+
+ private static void loadIncludes(Path configProp, MavenProperties configProps, Function callback)
+ throws IOException {
+ String includes = configProps.get(INCLUDES_PROPERTY);
+ if (includes != null) {
+ includes = substVars(includes, INCLUDES_PROPERTY, null, configProps, callback);
+ StringTokenizer st = new StringTokenizer(includes, "?\",", true);
+ if (st.countTokens() > 0) {
+ String location;
+ do {
+ location = nextLocation(st);
+ if (location != null) {
+ boolean mandatory = true;
+ if (location.startsWith("?")) {
+ mandatory = false;
+ location = location.substring(1);
+ }
+ Path path = configProp.resolveSibling(location);
+ MavenProperties props = loadPropertiesFile(path, mandatory, s -> {
+ var v = callback.apply(s);
+ return v != null ? v : configProps.getProperty(s);
+ });
+ configProps.putAll(props);
+ }
+ } while (location != null);
+ }
+ }
+ configProps.remove(INCLUDES_PROPERTY);
+ }
+
+ private static void trimValues(MavenProperties configProps) {
+ configProps.replaceAll((k, v) -> v.trim());
+ }
+
+ private static String nextLocation(StringTokenizer st) {
+ boolean optional = false;
+ String retVal = null;
+
+ if (st.countTokens() > 0) {
+ String tokenList = "?\",";
+ StringBuilder tokBuf = new StringBuilder(10);
+ String tok;
+ boolean inQuote = false;
+ boolean tokStarted = false;
+ boolean exit = false;
+ while ((st.hasMoreTokens()) && (!exit)) {
+ tok = st.nextToken(tokenList);
+ switch (tok) {
+ case "\"":
+ inQuote = !inQuote;
+ if (inQuote) {
+ tokenList = "\"";
+ } else {
+ tokenList = "\" ";
+ }
+ break;
+ case ",":
+ if (tokStarted) {
+ retVal = tokBuf.toString();
+ tokStarted = false;
+ tokBuf = new StringBuilder(10);
+ exit = true;
+ }
+ break;
+ case "?":
+ optional = true;
+ break;
+ default:
+ tokStarted = true;
+ tokBuf.append(tok.trim());
+ break;
+ }
+ }
+
+ // Handle case where end of token stream and
+ // still got data
+ if ((!exit) && (tokStarted)) {
+ retVal = tokBuf.toString();
+ }
+ }
+
+ return optional ? "?" + retVal : retVal;
+ }
+}
diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index d9da65ff3d..69c5feb8d9 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -22,12 +22,16 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
@@ -35,6 +39,7 @@ import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.maven.Maven;
+import org.apache.maven.api.Constants;
import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
import org.apache.maven.cli.transfer.QuietMavenTransferListener;
import org.apache.maven.cli.transfer.SimplexTransferListener;
@@ -51,6 +56,7 @@ import org.apache.maven.toolchain.building.ToolchainsBuildingResult;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.eclipse.aether.transfer.TransferListener;
+import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -453,24 +459,25 @@ class MavenCliTest {
}
@Test
- void verifyLocalRepositoryPath() {
+ void verifyLocalRepositoryPath() throws Exception {
MavenCli cli = new MavenCli();
CliRequest request = new CliRequest(new String[] {}, null);
request.commandLine = new CommandLine.Builder().build();
MavenExecutionRequest executionRequest;
// Use default
+ cli.cli(request);
executionRequest = cli.populateRequest(request);
assertThat(executionRequest.getLocalRepositoryPath(), is(nullValue()));
// System-properties override default
- request.getSystemProperties().setProperty(MavenCli.LOCAL_REPO_PROPERTY, "." + File.separatorChar + "custom1");
+ request.getSystemProperties().setProperty(Constants.MAVEN_REPO_LOCAL, "." + File.separatorChar + "custom1");
executionRequest = cli.populateRequest(request);
assertThat(executionRequest.getLocalRepositoryPath(), is(notNullValue()));
assertThat(executionRequest.getLocalRepositoryPath().toString(), is("." + File.separatorChar + "custom1"));
// User-properties override system properties
- request.getUserProperties().setProperty(MavenCli.LOCAL_REPO_PROPERTY, "." + File.separatorChar + "custom2");
+ request.getUserProperties().setProperty(Constants.MAVEN_REPO_LOCAL, "." + File.separatorChar + "custom2");
executionRequest = cli.populateRequest(request);
assertThat(executionRequest.getLocalRepositoryPath(), is(notNullValue()));
assertThat(executionRequest.getLocalRepositoryPath().toString(), is("." + File.separatorChar + "custom2"));
@@ -578,9 +585,27 @@ class MavenCliTest {
@Test
public void testPropertiesInterpolation() throws Exception {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.windows());
+
+ Path mavenHome = fs.getPath("C:\\maven");
+ Files.createDirectories(mavenHome);
+ Path mavenConf = mavenHome.resolve("conf");
+ Files.createDirectories(mavenConf);
+ Path mavenUserProps = mavenConf.resolve("maven.properties");
+ Files.writeString(mavenUserProps, "${includes} = ?${session.rootDirectory}/.mvn/maven.properties\n");
+ Path rootDirectory = fs.getPath("C:\\myRootDirectory");
+ Path topDirectory = rootDirectory.resolve("myTopDirectory");
+ Path mvn = rootDirectory.resolve(".mvn");
+ Files.createDirectories(mvn);
+ Files.writeString(
+ mvn.resolve("maven.properties"),
+ "${includes} = env-${envName}.properties\nfro = ${bar}z\n" + "bar = chti${java.version}\n");
+ Files.writeString(mvn.resolve("env-test.properties"), "\n");
+
// Arrange
CliRequest request = new CliRequest(
new String[] {
+ "-DenvName=test",
"-Dfoo=bar",
"-DvalFound=s${foo}i",
"-DvalNotFound=s${foz}i",
@@ -592,20 +617,28 @@ class MavenCliTest {
"validate"
},
null);
- request.rootDirectory = Paths.get("myRootDirectory");
- request.topDirectory = Paths.get("myTopDirectory");
+ request.rootDirectory = rootDirectory;
+ request.topDirectory = topDirectory;
+ System.setProperty("maven.installation.conf", mavenConf.toString());
// Act
+ cli.setFileSystem(fs);
cli.cli(request);
cli.properties(request);
// Assert
+ assertThat(request.getUserProperties().getProperty("fro"), CoreMatchers.startsWith("chti"));
assertThat(request.getUserProperties().getProperty("valFound"), is("sbari"));
assertThat(request.getUserProperties().getProperty("valNotFound"), is("s${foz}i"));
- assertThat(request.getUserProperties().getProperty("valRootDirectory"), is("myRootDirectory/.mvn/foo"));
- assertThat(request.getUserProperties().getProperty("valTopDirectory"), is("myTopDirectory/pom.xml"));
- assertThat(request.getCommandLine().getOptionValue('f'), is("myRootDirectory/my-child"));
+ assertThat(request.getUserProperties().getProperty("valRootDirectory"), is("C:\\myRootDirectory/.mvn/foo"));
+ assertThat(
+ request.getUserProperties().getProperty("valTopDirectory"),
+ is("C:\\myRootDirectory\\myTopDirectory/pom.xml"));
+ assertThat(request.getCommandLine().getOptionValue('f'), is("C:\\myRootDirectory/my-child"));
assertThat(request.getCommandLine().getArgs(), equalTo(new String[] {"prefix:3.0.0:bar", "validate"}));
+
+ Path p = fs.getPath(request.getUserProperties().getProperty("valTopDirectory"));
+ assertThat(p.toString(), is("C:\\myRootDirectory\\myTopDirectory\\pom.xml"));
}
@Test
diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesLoaderTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesLoaderTest.java
new file mode 100644
index 0000000000..991e597cad
--- /dev/null
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesLoaderTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.props;
+
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class MavenPropertiesLoaderTest {
+
+ @Test
+ void testIncludes() throws Exception {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+
+ Path mavenHome = fs.getPath("/maven");
+ Files.createDirectories(mavenHome);
+ Path mavenConf = mavenHome.resolve("conf");
+ Files.createDirectories(mavenConf);
+ Path mavenUserProps = mavenConf.resolve("maven.properties");
+ Files.writeString(mavenUserProps, "${includes} = ?\"/user/ma ven.properties\", ?/foo/bar\n");
+ Path userDirectory = fs.getPath("/user");
+ Files.createDirectories(userDirectory);
+ Path propsPath = userDirectory.resolve("ma ven.properties");
+ Files.writeString(propsPath, "${includes} = another.properties\nfro = ${bar}z\n");
+
+ Properties p = new Properties();
+ p.put("java.version", "11");
+ assertThrows(
+ NoSuchFileException.class, () -> MavenPropertiesLoader.loadProperties(p, mavenUserProps, null, false));
+
+ Path another = propsPath.resolveSibling("another.properties");
+ Files.writeString(another, "bar = chti${java.version}\n");
+ MavenPropertiesLoader.loadProperties(p, mavenUserProps, null, false);
+ assertEquals("chti11z", p.getProperty("fro"));
+ }
+
+ @Test
+ void testIncludes3() throws Exception {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+
+ Path mavenHome = fs.getPath("/maven");
+ Files.createDirectories(mavenHome);
+ Path mavenConf = mavenHome.resolve("conf");
+ Files.createDirectories(mavenConf);
+ Path mavenUserProps = mavenConf.resolve("maven.properties");
+ Files.writeString(mavenUserProps, "${includes} = ?\"${user.home}/maven.properties\"\n");
+ Path userDirectory = fs.getPath("/user");
+ Files.createDirectories(userDirectory);
+ Path propsPath = userDirectory.resolve("maven.properties");
+ Files.writeString(propsPath, "${includes} = default.properties,?env-${env.envName}.properties\n");
+ Path defPath = userDirectory.resolve("default.properties");
+ Files.writeString(defPath, "foo=bar");
+ Path envPath = userDirectory.resolve("env-ci.properties");
+ Files.writeString(envPath, "foo=bar-env\nfoo-env=bar\n");
+
+ Properties p = new Properties();
+ p.put("user.home", userDirectory.toString());
+ MavenPropertiesLoader.loadProperties(p, mavenUserProps, p::getProperty, false);
+ assertEquals("bar", p.getProperty("foo"));
+ assertNull(p.getProperty("foo-env"));
+
+ p = new Properties();
+ p.put("user.home", userDirectory.toString());
+ p.put("env.envName", "ci");
+ MavenPropertiesLoader.loadProperties(p, mavenUserProps, p::getProperty, false);
+ assertEquals("bar-env", p.getProperty("foo"));
+ assertEquals("bar", p.getProperty("foo-env"));
+ }
+}
diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java
new file mode 100644
index 0000000000..3c6a1fd00e
--- /dev/null
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.props;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Unit tests on MavenProperties
.
+ */
+public class MavenPropertiesTest {
+
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+ private static final String COMMENT = "# comment";
+ private static final String KEY1 = "mvn:foo/bar";
+ private static final String KEY1A = "mvn\\:foo/bar";
+ private static final String KEY2 = "foo:bar:version:type:classifier";
+ private static final String KEY2A = "foo\\:bar\\:version\\:type\\:classifier";
+ private static final String VALUE1 = "value";
+
+ private MavenProperties properties;
+
+ static final String TEST_PROPERTIES =
+ """
+ #
+ # test.properties
+ # Used in the PropertiesTest
+ #
+ test=test
+ """;
+
+ /*
+ * (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @BeforeEach
+ public void setUp() throws Exception {
+ properties = new MavenProperties();
+ properties.load(new StringReader(TEST_PROPERTIES));
+ }
+
+ @Test
+ public void testSpaces() throws Exception {
+ String config = "\n" + "\n"
+ + " \n"
+ + " \n"
+ + " \\ \\r \\n \\t \\f\n"
+ + " \n"
+ + " \n"
+ + "! dshfjklahfjkldashgjl;as\n"
+ + " #jdfagdfjagkdjfghksdajfd\n"
+ + " \n"
+ + "!!properties\n"
+ + "\n"
+ + "a=a\n"
+ + "b bb as,dn \n"
+ + "c\\r\\ \\t\\nu =:: cu\n"
+ + "bu= b\\\n"
+ + " u\n"
+ + "d=d\\r\\ne=e\n"
+ + "f :f\\\n"
+ + "f\\\n"
+ + " f\n"
+ + "g g\n"
+ + "h\\u0020h\n"
+ + "\\ i=i\n"
+ + "j=\\ j\n"
+ + "space=\\ c\n"
+ + "\n"
+ + "dblbackslash=\\\\\n"
+ + " \n";
+
+ java.util.Properties props1 = new java.util.Properties();
+ props1.load(new StringReader(config));
+
+ MavenProperties props2 = new MavenProperties();
+ props2.load(new StringReader(config));
+
+ String s325 = props1.getProperty(" \r");
+ assertEquals("\n \t \f", s325, "1");
+ String s324 = props1.getProperty("a");
+ assertEquals("a", s324, "2");
+ String s323 = props1.getProperty("b");
+ assertEquals("bb as,dn ", s323, "3");
+ String s322 = props1.getProperty("c\r \t\nu");
+ assertEquals(":: cu", s322, "4");
+ String s321 = props1.getProperty("bu");
+ assertEquals("bu", s321, "5");
+ String s320 = props1.getProperty("d");
+ assertEquals("d\r\ne=e", s320, "6");
+ String s319 = props1.getProperty("f");
+ assertEquals("fff", s319, "7");
+ String s318 = props1.getProperty("g");
+ assertEquals("g", s318, "8");
+ String s317 = props1.getProperty("h h");
+ assertEquals("", s317, "9");
+ String s316 = props1.getProperty(" ");
+ assertEquals("i=i", s316, "10");
+ String s315 = props1.getProperty("j");
+ assertEquals(" j", s315, "11");
+ String s314 = props1.getProperty("space");
+ assertEquals(" c", s314, "12");
+ String s313 = props1.getProperty("dblbackslash");
+ assertEquals("\\", s313, "13");
+
+ String s312 = props2.getProperty(" \r");
+ assertEquals("\n \t \f", s312, "1");
+ String s311 = props2.getProperty("a");
+ assertEquals("a", s311, "2");
+ String s310 = props2.getProperty("b");
+ assertEquals("bb as,dn ", s310, "3");
+ String s39 = props2.getProperty("c\r \t\nu");
+ assertEquals(":: cu", s39, "4");
+ String s38 = props2.getProperty("bu");
+ assertEquals("bu", s38, "5");
+ String s37 = props2.getProperty("d");
+ assertEquals("d\r\ne=e", s37, "6");
+ String s36 = props2.getProperty("f");
+ assertEquals("fff", s36, "7");
+ String s35 = props2.getProperty("g");
+ assertEquals("g", s35, "8");
+ String s34 = props2.getProperty("h h");
+ assertEquals("", s34, "9");
+ String s33 = props2.getProperty(" ");
+ assertEquals("i=i", s33, "10");
+ String s32 = props2.getProperty("j");
+ assertEquals(" j", s32, "11");
+ String s31 = props2.getProperty("space");
+ assertEquals(" c", s31, "12");
+ String s3 = props2.getProperty("dblbackslash");
+ assertEquals("\\", s3, "13");
+ assertEquals(props1, props2);
+ }
+
+ @Test
+ public void testConfigInterpolation() throws IOException {
+ String config = "a=$\\\\\\\\{var}\n" + "ab=${a}b\n" + "abc=${ab}c";
+
+ java.util.Properties props1 = new java.util.Properties();
+ props1.load(new StringReader(config));
+ InterpolationHelper.performSubstitution((Map) props1, null);
+
+ MavenProperties props2 = new MavenProperties();
+ props2.load(new StringReader(config));
+
+ assertEquals(props1, props2);
+ }
+
+ /**
+ *
+ * Test getting property.
+ *
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testGettingProperty() throws Exception {
+ Object o2 = properties.get("test");
+ assertEquals("test", o2);
+ }
+
+ @Test
+ public void testLoadSave() throws IOException {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ pw.println("# ");
+ pw.println("# The Main ");
+ pw.println("# ");
+ pw.println("# Comment ");
+ pw.println("# ");
+ pw.println("");
+ pw.println("# Another comment");
+ pw.println("");
+ pw.println("# A value comment");
+ pw.println("key1 = val1");
+ pw.println("");
+ pw.println("# Another value comment");
+ pw.println("key2 = ${key1}/foo");
+ pw.println("");
+ pw.println("# A third comment");
+ pw.println("key3 = val3");
+ pw.println("");
+
+ MavenProperties props = new MavenProperties();
+ props.load(new StringReader(sw.toString()));
+ props.save(System.err);
+ System.err.println("=====");
+
+ props.put("key2", props.get("key2"));
+ props.put("key3", "foo");
+ props.save(System.err);
+ System.err.println("=====");
+ }
+
+ @Test
+ public void testJavaUtilPropertiesCompatibility() throws Exception {
+ MavenProperties properties = new MavenProperties();
+ properties.load(new StringReader(TEST_PROPERTIES));
+
+ String test = properties.getProperty("test");
+ assertEquals(test, "test");
+
+ String defaultValue = properties.getProperty("notfound", "default");
+ assertEquals(defaultValue, "default");
+
+ properties.setProperty("another", "another");
+ Object o1 = properties.getProperty("another");
+ assertEquals(o1, "another");
+
+ properties.store(System.err, null);
+ System.err.println("====");
+ }
+
+ private static final String RESULT1 = COMMENT + LINE_SEPARATOR + KEY1A + " = " + VALUE1 + LINE_SEPARATOR;
+
+ @Test
+ public void testSaveComment1() throws Exception {
+ properties.put(KEY1, COMMENT, VALUE1);
+ StringWriter sw = new StringWriter();
+ properties.save(sw);
+ String msg = sw.toString();
+ assertTrue(sw.toString().endsWith(RESULT1), msg);
+ }
+
+ private static final String RESULT1A = COMMENT + LINE_SEPARATOR + KEY2A + " = " + VALUE1 + LINE_SEPARATOR;
+
+ @Test
+ public void testSaveComment1a() throws Exception {
+ properties.put(KEY2, COMMENT, VALUE1);
+ StringWriter sw = new StringWriter();
+ properties.save(sw);
+ String msg = sw.toString();
+ assertTrue(sw.toString().endsWith(RESULT1A), msg);
+ }
+
+ private static final String RESULT2 =
+ COMMENT + LINE_SEPARATOR + COMMENT + LINE_SEPARATOR + KEY1A + " = " + VALUE1 + LINE_SEPARATOR;
+
+ @Test
+ public void testSaveComment2() throws Exception {
+ properties.put(KEY1, List.of(new String[] {COMMENT, COMMENT}), VALUE1);
+ StringWriter sw = new StringWriter();
+ properties.save(sw);
+ String msg = sw.toString();
+ assertTrue(sw.toString().endsWith(RESULT2), msg);
+ }
+
+ private static final String RESULT3 = COMMENT + LINE_SEPARATOR + COMMENT + LINE_SEPARATOR + KEY1A + " = " + VALUE1
+ + "\\" + LINE_SEPARATOR + VALUE1 + LINE_SEPARATOR;
+
+ @Test
+ public void testSaveComment3() throws Exception {
+ properties.put(KEY1, List.of(new String[] {COMMENT, COMMENT}), List.of(new String[] {VALUE1, VALUE1}));
+ StringWriter sw = new StringWriter();
+ properties.save(sw);
+ String msg = sw.toString();
+ assertTrue(sw.toString().endsWith(RESULT3), msg);
+ List rawValue = properties.getRaw(KEY1);
+ assertEquals(2, (Object) rawValue.size());
+ assertEquals(KEY1A + " = " + VALUE1, rawValue.get(0));
+ assertEquals(VALUE1, rawValue.get(1));
+ }
+
+ @Test
+ public void testEntrySetValue() throws Exception {
+ properties.put(KEY1, VALUE1);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ properties.save(baos);
+
+ properties = new MavenProperties();
+ properties.load(new ByteArrayInputStream(baos.toByteArray()));
+ Object o22 = properties.get(KEY1);
+ assertEquals(VALUE1, o22);
+ for (Map.Entry entry : properties.entrySet()) {
+ entry.setValue(entry.getValue() + "x");
+ }
+ Object o21 = properties.get(KEY1);
+ assertEquals(VALUE1 + "x", o21);
+
+ baos = new ByteArrayOutputStream();
+ properties.save(baos);
+
+ properties = new MavenProperties();
+ properties.load(new ByteArrayInputStream(baos.toByteArray()));
+ Object o2 = properties.get(KEY1);
+ assertEquals(VALUE1 + "x", o2);
+ }
+
+ @Test
+ public void testMultiValueEscaping() throws IOException {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ pw.println("fruits apple, banana, pear, \\");
+ pw.println(" cantaloupe, watermelon, \\");
+ pw.println(" kiwi, mango");
+
+ java.util.Properties p = new java.util.Properties();
+ p.load(new StringReader(sw.toString()));
+ Object o24 = p.getProperty("fruits");
+ assertEquals("apple, banana, pear, cantaloupe, watermelon, kiwi, mango", o24);
+
+ MavenProperties props = new MavenProperties();
+ props.load(new StringReader(sw.toString()));
+ Object o23 = props.getProperty("fruits");
+ assertEquals("apple, banana, pear, cantaloupe, watermelon, kiwi, mango", o23);
+ List raw = props.getRaw("fruits");
+ assertNotNull(raw);
+ assertEquals(3, (Object) raw.size());
+ assertEquals("fruits apple, banana, pear, ", raw.get(0));
+
+ props = new MavenProperties();
+ props.put(
+ "fruits",
+ props.getComments("fruits"),
+ List.of(
+ "fruits apple, banana, pear, ",
+ " cantaloupe, watermelon, ",
+ " kiwi, mango"));
+ Object o22 = props.getProperty("fruits");
+ assertEquals("apple, banana, pear, cantaloupe, watermelon, kiwi, mango", o22);
+ raw = props.getRaw("fruits");
+ assertNotNull(raw);
+ assertEquals(3, (Object) raw.size());
+ assertEquals("fruits apple, banana, pear, ", raw.get(0));
+
+ sw = new StringWriter();
+ props.save(sw);
+ props = new MavenProperties();
+ props.load(new StringReader(sw.toString()));
+ Object o21 = props.getProperty("fruits");
+ assertEquals("apple, banana, pear, cantaloupe, watermelon, kiwi, mango", o21);
+ raw = props.getRaw("fruits");
+ assertNotNull(raw);
+ assertEquals(3, (Object) raw.size());
+ assertEquals("fruits apple, banana, pear, ", raw.get(0));
+
+ props = new MavenProperties();
+ props.put(
+ "fruits",
+ props.getComments("fruits"),
+ List.of(
+ " apple, banana, pear, ",
+ " cantaloupe, watermelon, ",
+ " kiwi, mango"));
+ Object o2 = props.getProperty("fruits");
+ assertEquals("apple, banana, pear, cantaloupe, watermelon, kiwi, mango", o2);
+ raw = props.getRaw("fruits");
+ assertNotNull(raw);
+ assertEquals(3, (Object) raw.size());
+ assertEquals("fruits = apple, banana, pear, ", raw.get(0));
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ MavenProperties p1 = new MavenProperties();
+ p1.put(
+ "fruits",
+ List.of("#", "# List of fruits", "#"),
+ List.of(
+ " apple, banana, pear, ",
+ " cantaloupe, watermelon, ",
+ " kiwi, mango"));
+ p1.put("trees", List.of("#", "# List of trees", "#"), List.of(" fir, oak, maple"));
+ p1.put("vegetables", List.of("#", "# List of vegetables", "#"), List.of(" potatoes"));
+
+ MavenProperties p2 = new MavenProperties();
+ p2.put(
+ "fruits",
+ List.of("#", "# List of good fruits", "#"),
+ List.of(" apple, banana, pear"));
+ p2.put("trees", "fir, oak, maple");
+ p1.update(p2);
+
+ assertEquals(2, (Object) p1.size());
+ Object o23 = p1.getComments("trees");
+ assertEquals(List.of("#", "# List of trees", "#"), o23);
+ Object o22 = p1.getProperty("trees");
+ assertEquals("fir, oak, maple", o22);
+ Object o21 = p1.getComments("fruits");
+ assertEquals(List.of("#", "# List of good fruits", "#"), o21);
+ Object o2 = p1.getProperty("fruits");
+ assertEquals("apple, banana, pear", o2);
+ }
+
+ @Test
+ public void testSubstitution() throws IOException {
+ String str = "port = 4141" + LINE_SEPARATOR + "host = localhost"
+ + LINE_SEPARATOR + "url = https://${host}:${port}/service"
+ + LINE_SEPARATOR;
+ MavenProperties properties = new MavenProperties();
+ properties.load(new StringReader(str));
+ properties.put("url", "https://localhost:4141/service");
+ StringWriter sw = new StringWriter();
+ properties.save(sw);
+ Object o2 = sw.toString();
+ assertEquals(str, o2);
+ }
+}
diff --git a/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java b/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java
index 6027acd211..26f4a5a888 100644
--- a/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java
+++ b/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java
@@ -85,7 +85,8 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
}
return null;
}))
- .globalSettingsSource(toSource(request.getGlobalSettingsFile(), request.getGlobalSettingsSource()))
+ .installationSettingsSource(
+ toSource(request.getGlobalSettingsFile(), request.getGlobalSettingsSource()))
.projectSettingsSource(
toSource(request.getProjectSettingsFile(), request.getProjectSettingsSource()))
.userSettingsSource(toSource(request.getUserSettingsFile(), request.getUserSettingsSource()))
diff --git a/maven-toolchain-builder/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java b/maven-toolchain-builder/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
index 44a6044c97..f45474d4a7 100644
--- a/maven-toolchain-builder/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
+++ b/maven-toolchain-builder/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
@@ -85,7 +85,7 @@ public class DefaultToolchainsBuilder implements ToolchainsBuilder {
}
return null;
}))
- .globalToolchainsSource(convert(request.getGlobalToolchainsSource()))
+ .installationToolchainsSource(convert(request.getGlobalToolchainsSource()))
.userToolchainsSource(convert(request.getUserToolchainsSource()))
.build());
diff --git a/pom.xml b/pom.xml
index 099993711e..e1185f0438 100644
--- a/pom.xml
+++ b/pom.xml
@@ -123,6 +123,7 @@ under the License.
maven-toolchain-model
maven-toolchain-builder
maven-bom
+ maven-docgen
diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md
new file mode 100644
index 0000000000..f3c4f7897a
--- /dev/null
+++ b/src/site/markdown/configuration.md
@@ -0,0 +1,53 @@
+
+# Configuration Options
+
+
+
+
+
+
+| No | Key | Type | Description | Default Value | Since | Source |
+| --- | --- | --- | --- | --- | --- | --- |
+| 1. | `maven.build.timestamp.format` | `String` | Build timestamp format. | `yyyy-MM-dd'T'HH:mm:ssXXX` | 3.0.0 | Model properties |
+| 2. | `maven.ext.class.path` | `String` | Extensions class path. | - | | User properties |
+| 3. | `maven.home` | `String` | Maven home. | - | 3.0.0 | User properties |
+| 4. | `maven.installation.conf` | `String` | Maven installation configuration directory. | `${maven.home}/conf` | 4.0.0 | User properties |
+| 5. | `maven.installation.extensions` | `String` | Maven installation extensions. | `${maven.installation.conf}/extensions.xml` | 4.0.0 | User properties |
+| 6. | `maven.installation.settings` | `String` | Maven installation settings. | `${maven.installation.conf}/settings.xml` | 4.0.0 | User properties |
+| 7. | `maven.installation.toolchains` | `String` | Maven installation toolchains. | `${maven.installation.conf}/toolchains.xml` | 4.0.0 | User properties |
+| 8. | `maven.plugin.validation` | `String` | Plugin validation level. | `inline` | 3.9.2 | User properties |
+| 9. | `maven.plugin.validation.excludes` | `String` | Plugin validation exclusions. | - | 3.9.6 | User properties |
+| 10. | `maven.project.conf` | `String` | Maven project configuration directory. | `${session.rootDirectory}/.mvn` | 4.0.0 | User properties |
+| 11. | `maven.project.extensions` | `String` | Maven project extensions. | `${maven.project.conf}/extensions.xml` | 4.0.0 | User properties |
+| 12. | `maven.project.settings` | `String` | Maven project settings. | `${maven.project.conf}/settings.xml` | 4.0.0 | User properties |
+| 13. | `maven.projectBuilder.parallelism` | `Integer` | ProjectBuilder parallelism. | `cores/2 + 1` | 4.0.0 | User properties |
+| 14. | `maven.relocations.entries` | `String` | User controlled relocations. This property is a comma separated list of entries with the syntax GAV>GAV
. The first GAV
can contain \*
for any elem (so \*:\*:\*
would mean ALL, something you don't want). The second GAV
is either fully specified, or also can contain \*
, then it behaves as "ordinary relocation": the coordinate is preserved from relocated artifact. Finally, if right hand GAV
is absent (line looks like GAV>
), the left hand matching GAV
is banned fully (from resolving). Note: the >
means project level, while >>
means global (whole session level, so even plugins will get relocated artifacts) relocation. For example, maven.relocations.entries = org.foo:\*:\*>, \\ org.here:\*:\*>org.there:\*:\*, \\ javax.inject:javax.inject:1>>jakarta.inject:jakarta.inject:1.0.5 means: 3 entries, ban org.foo group
(exactly, so org.foo.bar
is allowed), relocate org.here
to org.there
and finally globally relocate (see >>
above) javax.inject:javax.inject:1
to jakarta.inject:jakarta.inject:1.0.5
. | - | 4.0.0 | User properties |
+| 15. | `maven.repo.local` | `String` | Maven local repository. | `${maven.user.conf}/repository` | 3.0.0 | User properties |
+| 16. | `maven.repo.local.recordReverseTree` | `String` | User property for reverse dependency tree. If enabled, Maven will record ".tracking" directory into local repository with "reverse dependency tree", essentially explaining WHY given artifact is present in local repository. Default: false
, will not record anything. | `false` | 3.9.0 | User properties |
+| 17. | `maven.repo.local.tail` | `String` | User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with {@code org.eclipse.aether.util.repository.ChainedLocalRepositoryManager} . Default value: null
, no chained LRM is used. | - | 3.9.0 | User properties |
+| 18. | `maven.resolver.dependencyManagerTransitivity` | `String` | User property for selecting dependency manager behaviour regarding transitive dependencies and dependency management entries in their POMs. Maven 3 targeted full backward compatibility with Maven2, hence it ignored dependency management entries in transitive dependency POMs. Maven 4 enables "transitivity" by default, hence unlike Maven2, obeys dependency management entries deep in dependency graph as well. Default: "true"
. | `true` | 4.0.0 | User properties |
+| 19. | `maven.resolver.transport` | `String` | Resolver transport to use. Can be default
, wagon
, apache
, jdk
or auto
. | `default` | 4.0.0 | User properties |
+| 20. | `maven.style.color` | `String` | Maven output color mode. Allowed values are auto
, always
, never
. | `auto` | 4.0.0 | User properties |
+| 21. | `maven.user.conf` | `String` | Maven user configuration directory. | `${user.home}/.m2` | 4.0.0 | User properties |
+| 22. | `maven.user.extensions` | `String` | Maven user extensions. | `${maven.user.conf}/extensions.xml` | 4.0.0 | User properties |
+| 23. | `maven.user.settings` | `String` | Maven user settings. | `${maven.user.conf}/settings.xml` | 4.0.0 | User properties |
+| 24. | `maven.user.toolchains` | `String` | Maven user toolchains. | `${maven.user.home}/toolchains.xml` | 4.0.0 | User properties |
+| 25. | `maven.versionFilters` | `String` | User property for version filters expression, a semicolon separated list of filters to apply. By default, no version filter is applied (like in Maven 3). Supported filters: "h" or "h(num)" - highest version or top list of highest ones filter "l" or "l(num)" - lowest version or bottom list of lowest ones filter "s" - contextual snapshot filter "e(G:A:V)" - predicate filter (leaves out G:A:V from range, if hit, V can be range) Example filter expression: "h(5);s;e(org.foo:bar:1)
will cause: ranges are filtered for "top 5" (instead full range), snapshots are banned if root project is not a snapshot, and if range for org.foo:bar
is being processed, version 1 is omitted. | - | 4.0.0 | User properties |
+