diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java index d18f0aa3576d..745e2c54146b 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java @@ -462,6 +462,15 @@ public final class Constants { @Config(type = "java.lang.Boolean", defaultValue = "true") public static final String MAVEN_CONSUMER_POM = "maven.consumer.pom"; + /** + * User property for controlling "maven personality". If activated Maven will behave + * as previous major version, Maven 3. + * + * @since 4.0.0 + */ + @Config(type = "java.lang.Boolean", defaultValue = "false") + public static final String MAVEN_MAVEN3_PERSONALITY = "maven.maven3Personality"; + /** * User property for disabling version resolver cache. * diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java index 192a4194066c..91f6b9f3503b 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java @@ -35,17 +35,17 @@ public final class Features { private Features() {} /** - * Check if the consumer POM feature is active. + * Check if the personality is "maven3". */ - public static boolean consumerPom(@Nullable Properties userProperties) { - return doGet(userProperties, Constants.MAVEN_CONSUMER_POM, true); + public static boolean mavenMaven3Personality(@Nullable Map userProperties) { + return doGet(userProperties, Constants.MAVEN_MAVEN3_PERSONALITY, false); } /** * Check if the consumer POM feature is active. */ - public static boolean consumerPom(@Nullable Map userProperties, boolean def) { - return doGet(userProperties, Constants.MAVEN_CONSUMER_POM, def); + public static boolean consumerPom(@Nullable Map userProperties) { + return doGet(userProperties, Constants.MAVEN_CONSUMER_POM, !mavenMaven3Personality(userProperties)); } private static boolean doGet(Properties userProperties, String key, boolean def) { diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java index 808eeb00780c..f13a60def826 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java @@ -29,7 +29,7 @@ public interface ModelBuilder extends Service { String MODEL_VERSION_4_1_0 = "4.1.0"; - List VALID_MODEL_VERSIONS = List.of(MODEL_VERSION_4_0_0, MODEL_VERSION_4_1_0); + List KNOWN_MODEL_VERSIONS = List.of(MODEL_VERSION_4_0_0, MODEL_VERSION_4_1_0); ModelBuilderSession newSession(); diff --git a/compat/maven-compat/src/test/java/org/apache/maven/project/AbstractMavenProjectTestCase.java b/compat/maven-compat/src/test/java/org/apache/maven/project/AbstractMavenProjectTestCase.java index 56f62cf65dc5..fcf20ef10501 100644 --- a/compat/maven-compat/src/test/java/org/apache/maven/project/AbstractMavenProjectTestCase.java +++ b/compat/maven-compat/src/test/java/org/apache/maven/project/AbstractMavenProjectTestCase.java @@ -34,6 +34,7 @@ import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.MavenSession; import org.apache.maven.impl.InternalSession; +import org.apache.maven.impl.resolver.scopes.Maven4ScopeManagerConfiguration; import org.apache.maven.internal.impl.DefaultLookup; import org.apache.maven.internal.impl.DefaultSession; import org.apache.maven.internal.impl.InternalMavenSession; @@ -45,6 +46,7 @@ import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.testing.PlexusTest; import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.internal.impl.scope.ScopeManagerImpl; import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.fail; @@ -158,6 +160,7 @@ protected MavenProject getProject(File pom) throws Exception { protected void initRepoSession(ProjectBuildingRequest request) throws Exception { File localRepo = new File(request.getLocalRepository().getBasedir()); DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); + session.setScopeManager(new ScopeManagerImpl(Maven4ScopeManagerConfiguration.INSTANCE)); session.setLocalRepositoryManager(new LegacyLocalRepositoryManager(localRepo)); request.setRepositorySession(session); diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java index c6d3dca433db..6638b6a46fd9 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java @@ -34,6 +34,7 @@ import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Singleton; +import org.apache.maven.api.feature.Features; import org.apache.maven.api.services.TypeRegistry; import org.apache.maven.api.xml.XmlNode; import org.apache.maven.eventspy.internal.EventSpyDispatcher; @@ -155,14 +156,15 @@ public RepositorySystemSession newRepositorySession(MavenExecutionRequest reques public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) { requireNonNull(request, "request"); - MavenSessionBuilderSupplier supplier = new MavenSessionBuilderSupplier(repoSystem); + // this map is read ONLY to get config from (profiles + env + system + user) + Map mergedProps = createMergedProperties(request); + + boolean mavenMaven3Personality = Features.mavenMaven3Personality(mergedProps); + MavenSessionBuilderSupplier supplier = new MavenSessionBuilderSupplier(repoSystem, mavenMaven3Personality); SessionBuilder sessionBuilder = supplier.get(); sessionBuilder.setArtifactTypeRegistry(new TypeRegistryAdapter(typeRegistry)); // dynamic sessionBuilder.setCache(request.getRepositoryCache()); - // this map is read ONLY to get config from (profiles + env + system + user) - Map mergedProps = createMergedProperties(request); - // configProps map is kept "pristine", is written ONLY, the mandatory resolver config Map configProps = new LinkedHashMap<>(); configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent()); @@ -356,7 +358,7 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) // may be overridden String resolverDependencyManagerTransitivity = mergedProps.getOrDefault( - Constants.MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY, Boolean.TRUE.toString()); + Constants.MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY, Boolean.toString(!mavenMaven3Personality)); sessionBuilder.setDependencyManager( supplier.getDependencyManager(Boolean.parseBoolean(resolverDependencyManagerTransitivity))); diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/MavenTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/MavenTransformer.java index 01e6ebbdffe0..ad695d220506 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/MavenTransformer.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/MavenTransformer.java @@ -22,7 +22,7 @@ import javax.inject.Named; import javax.inject.Singleton; -import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer; +import org.apache.maven.internal.transformation.TransformerManager; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.deployment.DeployRequest; import org.eclipse.aether.installation.InstallRequest; @@ -36,20 +36,20 @@ @Singleton @Named final class MavenTransformer implements ArtifactTransformer { - private final ConsumerPomArtifactTransformer consumerPomArtifactTransformer; + private final TransformerManager transformerManager; @Inject - MavenTransformer(ConsumerPomArtifactTransformer consumerPomArtifactTransformer) { - this.consumerPomArtifactTransformer = requireNonNull(consumerPomArtifactTransformer); + MavenTransformer(TransformerManager transformerManager) { + this.transformerManager = requireNonNull(transformerManager); } @Override public InstallRequest transformInstallArtifacts(RepositorySystemSession session, InstallRequest request) { - return consumerPomArtifactTransformer.remapInstallArtifacts(session, request); + return transformerManager.remapInstallArtifacts(session, request); } @Override public DeployRequest transformDeployArtifacts(RepositorySystemSession session, DeployRequest request) { - return consumerPomArtifactTransformer.remapDeployArtifacts(session, request); + return transformerManager.remapDeployArtifacts(session, request); } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/PomArtifactTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/PomArtifactTransformer.java new file mode 100644 index 000000000000..dbccd89858d6 --- /dev/null +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/PomArtifactTransformer.java @@ -0,0 +1,46 @@ +/* + * 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.internal.transformation; + +import javax.xml.stream.XMLStreamException; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.maven.api.services.ModelBuilderException; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.deployment.DeployRequest; +import org.eclipse.aether.installation.InstallRequest; + +/** + * Maven POM transformer. + * TODO: rename this interface to "Transformer" as it can do much more than just transform POM. + * @since TBD + */ +public interface PomArtifactTransformer { + InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request); + + DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request); + + void injectTransformedArtifacts(RepositorySystemSession session, MavenProject currentProject) throws IOException; + + void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt) + throws ModelBuilderException, XMLStreamException, IOException; +} diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformerManager.java similarity index 88% rename from impl/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java rename to impl/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformerManager.java index 94be78f68044..7948080b5120 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformerManager.java @@ -26,16 +26,11 @@ import org.eclipse.aether.installation.InstallRequest; /** - * Consumer POM transformer. + * Maven transformer manager. * - * @since TBD + * @since 4.0.0 */ -public interface ConsumerPomArtifactTransformer { - - String CONSUMER_POM_CLASSIFIER = "consumer"; - - String BUILD_POM_CLASSIFIER = "build"; - +public interface TransformerManager { InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request); DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request); diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java similarity index 82% rename from impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java rename to impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java index d2456a30fc20..4b2722405c16 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java @@ -24,7 +24,6 @@ import javax.xml.stream.XMLStreamException; import java.io.IOException; -import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -37,8 +36,6 @@ import org.apache.maven.api.feature.Features; import org.apache.maven.api.model.Model; import org.apache.maven.api.services.ModelBuilderException; -import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer; -import org.apache.maven.model.v4.MavenStaxWriter; import org.apache.maven.project.MavenProject; import org.apache.maven.project.artifact.ProjectArtifact; import org.eclipse.aether.RepositorySystemSession; @@ -54,30 +51,29 @@ * @since TBD */ @Singleton -@Named("consumer-pom") -class DefaultConsumerPomArtifactTransformer implements ConsumerPomArtifactTransformer { +@Named +class ConsumerPomArtifactTransformer extends TransformerSupport { + private static final String CONSUMER_POM_CLASSIFIER = "consumer"; - private static final String NAMESPACE_FORMAT = "http://maven.apache.org/POM/%s"; - - private static final String SCHEMA_LOCATION_FORMAT = "https://maven.apache.org/xsd/maven-%s.xsd"; + private static final String BUILD_POM_CLASSIFIER = "build"; private final Set toDelete = new CopyOnWriteArraySet<>(); - private final ConsumerPomBuilder builder; + private final PomBuilder builder; @Inject - DefaultConsumerPomArtifactTransformer(ConsumerPomBuilder builder) { + ConsumerPomArtifactTransformer(PomBuilder builder) { this.builder = builder; } - @Override @SuppressWarnings("deprecation") + @Override public void injectTransformedArtifacts(RepositorySystemSession session, MavenProject project) throws IOException { if (project.getFile() == null) { // If there is no build POM there is no reason to inject artifacts for the consumer POM. return; } - if (Features.consumerPom(session.getUserProperties(), true)) { + if (Features.consumerPom(session.getConfigProperties())) { Path buildDir = project.getBuild() != null ? Paths.get(project.getBuild().getDirectory()) : null; if (buildDir != null) { @@ -108,7 +104,8 @@ TransformedArtifact createConsumerPomArtifact( "pom"); } - void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt) + @Override + public void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt) throws ModelBuilderException, XMLStreamException, IOException { Model model = builder.build(session, project, src); write(model, tgt); @@ -173,7 +170,7 @@ private Collection replacePom(Collection artifacts) { main.getExtension(), main.getVersion(), main.getProperties(), - main.getFile())); + main.getPath())); } for (Artifact consumer : consumers) { result.remove(consumer); @@ -184,22 +181,10 @@ private Collection replacePom(Collection artifacts) { consumer.getExtension(), consumer.getVersion(), consumer.getProperties(), - consumer.getFile())); + consumer.getPath())); } artifacts = result; } return artifacts; } - - void write(Model model, Path dest) throws IOException, XMLStreamException { - String version = model.getModelVersion(); - Files.createDirectories(dest.getParent()); - try (Writer w = Files.newBufferedWriter(dest)) { - MavenStaxWriter writer = new MavenStaxWriter(); - writer.setNamespace(String.format(NAMESPACE_FORMAT, version)); - writer.setSchemaLocation(String.format(SCHEMA_LOCATION_FORMAT, version)); - writer.setAddLocationInformation(false); - writer.write(w, model); - } - } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java index 9d74419c989e..4056746ae9ff 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java @@ -53,7 +53,7 @@ import org.slf4j.LoggerFactory; @Named -class DefaultConsumerPomBuilder implements ConsumerPomBuilder { +class DefaultConsumerPomBuilder implements PomBuilder { private static final String BOM_PACKAGING = "bom"; public static final String POM_PACKAGING = "pom"; @@ -198,8 +198,7 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, Path src) request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings); ModelBuilder.ModelBuilderSession mbSession = iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class)); - ModelBuilderResult result = mbSession.build(request.build()); - return result; + return mbSession.build(request.build()); } static Model transformNonPom(Model model, MavenProject project) { diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultTransformerManager.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultTransformerManager.java new file mode 100644 index 000000000000..ad80b59f40db --- /dev/null +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultTransformerManager.java @@ -0,0 +1,70 @@ +/* + * 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.internal.transformation.impl; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.IOException; +import java.util.Map; + +import org.apache.maven.internal.transformation.PomArtifactTransformer; +import org.apache.maven.internal.transformation.TransformerManager; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.deployment.DeployRequest; +import org.eclipse.aether.installation.InstallRequest; + +import static java.util.Objects.requireNonNull; + +@Singleton +@Named +public class DefaultTransformerManager implements TransformerManager { + private final Map transformers; + + @Inject + public DefaultTransformerManager(Map transformers) { + this.transformers = requireNonNull(transformers); + } + + @Override + public InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request) { + for (PomArtifactTransformer transformer : transformers.values()) { + request = transformer.remapInstallArtifacts(session, request); + } + return request; + } + + @Override + public DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request) { + for (PomArtifactTransformer transformer : transformers.values()) { + request = transformer.remapDeployArtifacts(session, request); + } + return request; + } + + @Override + public void injectTransformedArtifacts(RepositorySystemSession repositorySession, MavenProject currentProject) + throws IOException { + for (PomArtifactTransformer transformer : transformers.values()) { + transformer.injectTransformedArtifacts(repositorySession, currentProject); + } + } +} diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/PomBuilder.java similarity index 94% rename from impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilder.java rename to impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/PomBuilder.java index 7de9934e2b2e..4a62e4c6a396 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/PomBuilder.java @@ -30,10 +30,9 @@ /** * This interface is not public and the purpose is to allow easy unit testing - * of {@link DefaultConsumerPomArtifactTransformer}. + * of {@link ConsumerPomArtifactTransformer}. */ -interface ConsumerPomBuilder { - +interface PomBuilder { Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException, IOException, XMLStreamException; } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/PomInlinerTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/PomInlinerTransformer.java new file mode 100644 index 000000000000..f078b53511a1 --- /dev/null +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/PomInlinerTransformer.java @@ -0,0 +1,144 @@ +/* + * 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.internal.transformation.impl; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.xml.stream.XMLStreamException; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.maven.api.feature.Features; +import org.apache.maven.api.model.Model; +import org.apache.maven.api.services.Interpolator; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.deployment.DeployRequest; +import org.eclipse.aether.installation.InstallRequest; + +import static java.util.Objects.requireNonNull; + +/** + * Inliner POM transformer. The goal of this transformer is to fix Maven 3 issue about emitting (installing, deploying) + * unusable POMs when using CI Friendly Versions. + * + * @since TBD + */ +@Singleton +@Named +class PomInlinerTransformer extends TransformerSupport { + private final Interpolator interpolator; + + @Inject + PomInlinerTransformer(Interpolator interpolator) { + this.interpolator = requireNonNull(interpolator); + } + + @Override + public InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request) { + return request.setArtifacts(replacePom(session, request.getArtifacts())); + } + + @Override + public DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request) { + return request.setArtifacts(replacePom(session, request.getArtifacts())); + } + + private Collection replacePom(RepositorySystemSession session, Collection artifacts) { + Set needsInlining = needsInlining(session); + if (needsInlining.isEmpty()) { + return artifacts; + } + ArrayList newArtifacts = new ArrayList<>(artifacts.size()); + for (Artifact artifact : artifacts) { + if ("pom".equals(artifact.getExtension()) + && artifact.getClassifier().isEmpty()) { + try { + Path tmpPom = Files.createTempFile("pom-inliner-", ".xml"); + String originalPom = Files.readString(artifact.getPath()); + String interpolatedPom = interpolator.interpolate( + originalPom, + property -> { + if (needsInlining.contains(property)) { + return (String) + session.getConfigProperties().get(property); + } + return null; + }, + false); + if (!Objects.equals(originalPom, interpolatedPom)) { + Files.writeString(tmpPom, interpolatedPom); + artifact = artifact.setPath(tmpPom); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + newArtifacts.add(artifact); + } + return newArtifacts; + } + + @SuppressWarnings("unchecked") + private Set needsInlining(RepositorySystemSession session) { + return (Set) session.getData() + .computeIfAbsent( + PomInlinerTransformer.class.getName() + ".needsInlining", ConcurrentHashMap::newKeySet); + } + + @Override + public void injectTransformedArtifacts(RepositorySystemSession session, MavenProject project) throws IOException { + if (!Features.consumerPom(session.getConfigProperties())) { + try { + Model model = read(project.getFile().toPath()); + String version = model.getVersion(); + if (version == null && model.getParent() != null) { + version = model.getParent().getVersion(); + } + String newVersion; + if (version != null) { + HashSet usedProperties = new HashSet<>(); + newVersion = interpolator.interpolate(version.trim(), property -> { + if (!session.getConfigProperties().containsKey(property)) { + throw new IllegalArgumentException("Cannot inline property " + property); + } + usedProperties.add(property); + return (String) session.getConfigProperties().get(property); + }); + if (!Objects.equals(version, newVersion)) { + needsInlining(session).addAll(usedProperties); + } + } + } catch (XMLStreamException e) { + throw new IOException("Problem during inlining POM", e); + } + } + } +} diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java index e928e6101896..bcaa67f52df3 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java @@ -22,20 +22,21 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.apache.maven.api.services.ModelBuilderException; import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.internal.transformation.PomArtifactTransformer; import org.apache.maven.internal.transformation.TransformationFailedException; import org.apache.maven.project.MavenProject; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory; +import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper; /** * Transformed artifact is derived with some transformation from source artifact. @@ -45,7 +46,7 @@ class TransformedArtifact extends DefaultArtifact { private static final int SHA1_BUFFER_SIZE = 8192; - private final DefaultConsumerPomArtifactTransformer defaultConsumerPomArtifactTransformer; + private final PomArtifactTransformer pomArtifactTransformer; private final MavenProject project; private final Supplier sourcePathProvider; private final Path target; @@ -54,7 +55,7 @@ class TransformedArtifact extends DefaultArtifact { @SuppressWarnings("checkstyle:ParameterNumber") TransformedArtifact( - DefaultConsumerPomArtifactTransformer defaultConsumerPomArtifactTransformer, + PomArtifactTransformer pomArtifactTransformer, MavenProject project, Path target, RepositorySystemSession session, @@ -71,7 +72,7 @@ class TransformedArtifact extends DefaultArtifact { classifier, new TransformedArtifactHandler( classifier, extension, source.getArtifactHandler().getPackaging())); - this.defaultConsumerPomArtifactTransformer = defaultConsumerPomArtifactTransformer; + this.pomArtifactTransformer = pomArtifactTransformer; this.project = project; this.target = target; this.session = session; @@ -97,12 +98,12 @@ public synchronized File getFile() { return null; } return target.toFile(); - } catch (IOException | NoSuchAlgorithmException | XMLStreamException | ModelBuilderException e) { + } catch (IOException | XMLStreamException | ModelBuilderException e) { throw new TransformationFailedException(e); } } - private String mayUpdate() throws IOException, NoSuchAlgorithmException, XMLStreamException, ModelBuilderException { + private String mayUpdate() throws IOException, XMLStreamException, ModelBuilderException { String result; Path src = sourcePathProvider.get(); if (src == null) { @@ -112,10 +113,11 @@ private String mayUpdate() throws IOException, NoSuchAlgorithmException, XMLStre Files.deleteIfExists(target); result = ""; } else { - String current = sha1(src); + String current = ChecksumAlgorithmHelper.calculate(src, List.of(new Sha1ChecksumAlgorithmFactory())) + .get(Sha1ChecksumAlgorithmFactory.NAME); String existing = sourceState.get(); if (!Files.exists(target) || !Objects.equals(current, existing)) { - defaultConsumerPomArtifactTransformer.transform(project, session, src, target); + pomArtifactTransformer.transform(project, session, src, target); Files.setLastModifiedTime(target, Files.getLastModifiedTime(src)); } result = current; @@ -123,20 +125,4 @@ private String mayUpdate() throws IOException, NoSuchAlgorithmException, XMLStre sourceState.set(result); return result; } - - static String sha1(Path path) throws NoSuchAlgorithmException, IOException { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - try (InputStream fis = Files.newInputStream(path)) { - byte[] buffer = new byte[SHA1_BUFFER_SIZE]; - int read; - while ((read = fis.read(buffer)) != -1) { - md.update(buffer, 0, read); - } - } - StringBuilder result = new StringBuilder(); - for (byte b : md.digest()) { - result.append(String.format("%02x", b)); - } - return result.toString(); - } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformerSupport.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformerSupport.java new file mode 100644 index 000000000000..84e721225fc1 --- /dev/null +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformerSupport.java @@ -0,0 +1,87 @@ +/* + * 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.internal.transformation.impl; + +import javax.xml.stream.XMLStreamException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.maven.api.model.Model; +import org.apache.maven.api.services.ModelBuilderException; +import org.apache.maven.internal.transformation.PomArtifactTransformer; +import org.apache.maven.model.v4.MavenStaxReader; +import org.apache.maven.model.v4.MavenStaxWriter; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.deployment.DeployRequest; +import org.eclipse.aether.installation.InstallRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Support class. + */ +abstract class TransformerSupport implements PomArtifactTransformer { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request) { + return request; + } + + @Override + public DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request) { + return request; + } + + @Override + public void injectTransformedArtifacts(RepositorySystemSession session, MavenProject project) throws IOException {} + + @Override + public void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt) + throws ModelBuilderException, XMLStreamException, IOException { + throw new IllegalStateException("This transformer does not use this call."); + } + + protected static final String NAMESPACE_FORMAT = "http://maven.apache.org/POM/%s"; + + protected static final String SCHEMA_LOCATION_FORMAT = "https://maven.apache.org/xsd/maven-%s.xsd"; + + protected Model read(Path src) throws IOException, XMLStreamException { + try (InputStream is = Files.newInputStream(src)) { + return new MavenStaxReader().read(is, false, null); + } + } + + protected void write(Model model, Path dest) throws IOException, XMLStreamException { + String version = model.getModelVersion(); + Files.createDirectories(dest.getParent()); + try (Writer w = Files.newBufferedWriter(dest)) { + MavenStaxWriter writer = new MavenStaxWriter(); + writer.setNamespace(String.format(NAMESPACE_FORMAT, version)); + writer.setSchemaLocation(String.format(SCHEMA_LOCATION_FORMAT, version)); + writer.setAddLocationInformation(false); + writer.write(w, model); + } + } +} diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java index 2e6efa7d15c0..5877a66df111 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java @@ -33,7 +33,7 @@ import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectExecutionEvent; import org.apache.maven.execution.ProjectExecutionListener; -import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer; +import org.apache.maven.internal.transformation.TransformerManager; import org.apache.maven.lifecycle.MavenExecutionPlan; import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.plugin.MojoExecution; @@ -55,7 +55,7 @@ public class LifecycleModuleBuilder { private final BuilderCommon builderCommon; private final ExecutionEventCatapult eventCatapult; private final ProjectExecutionListener projectExecutionListener; - private final ConsumerPomArtifactTransformer consumerPomArtifactTransformer; + private final TransformerManager transformerManager; @Inject public LifecycleModuleBuilder( @@ -63,12 +63,12 @@ public LifecycleModuleBuilder( BuilderCommon builderCommon, ExecutionEventCatapult eventCatapult, List listeners, - ConsumerPomArtifactTransformer consumerPomArtifactTransformer) { + TransformerManager transformerManager) { this.mojoExecutor = mojoExecutor; this.builderCommon = builderCommon; this.eventCatapult = eventCatapult; this.projectExecutionListener = new CompoundProjectExecutionListener(listeners); - this.consumerPomArtifactTransformer = consumerPomArtifactTransformer; + this.transformerManager = transformerManager; } public void buildProject( @@ -93,7 +93,7 @@ public void buildProject( return; } - consumerPomArtifactTransformer.injectTransformedArtifacts(session.getRepositorySession(), currentProject); + transformerManager.injectTransformedArtifacts(session.getRepositorySession(), currentProject); BuilderCommon.attachToThread(currentProject); diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java index 3bca5cf6b3f3..7e8428a6b168 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java +++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java @@ -58,7 +58,7 @@ import org.apache.maven.impl.util.PhasingExecutor; import org.apache.maven.internal.MultilineMessageHelper; import org.apache.maven.internal.impl.DefaultLifecycleRegistry; -import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer; +import org.apache.maven.internal.transformation.TransformerManager; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.LifecycleNotFoundException; import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; @@ -156,7 +156,7 @@ public class BuildPlanExecutor { private final MojoExecutor mojoExecutor; private final ExecutionEventCatapult eventCatapult; private final ProjectExecutionListener projectExecutionListener; - private final ConsumerPomArtifactTransformer consumerPomArtifactTransformer; + private final TransformerManager transformerManager; private final BuildPlanLogger buildPlanLogger; private final Map mojoExecutionConfigurators; private final MavenPluginManager mavenPluginManager; @@ -169,7 +169,7 @@ public BuildPlanExecutor( @Named("concurrent") MojoExecutor mojoExecutor, ExecutionEventCatapult eventCatapult, List listeners, - ConsumerPomArtifactTransformer consumerPomArtifactTransformer, + TransformerManager transformerManager, BuildPlanLogger buildPlanLogger, Map mojoExecutionConfigurators, MavenPluginManager mavenPluginManager, @@ -178,7 +178,7 @@ public BuildPlanExecutor( this.mojoExecutor = mojoExecutor; this.eventCatapult = eventCatapult; this.projectExecutionListener = new CompoundProjectExecutionListener(listeners); - this.consumerPomArtifactTransformer = consumerPomArtifactTransformer; + this.transformerManager = transformerManager; this.buildPlanLogger = buildPlanLogger; this.mojoExecutionConfigurators = mojoExecutionConfigurators; this.mavenPluginManager = mavenPluginManager; @@ -499,8 +499,7 @@ private void executeStep(BuildStep step) throws IOException, LifecycleExecutionE throw new IllegalStateException(); case SETUP: attachToThread(step); - consumerPomArtifactTransformer.injectTransformedArtifacts( - session.getRepositorySession(), step.project); + transformerManager.injectTransformedArtifacts(session.getRepositorySession(), step.project); projectExecutionListener.beforeProjectExecution(new ProjectExecutionEvent(session, step.project)); eventCatapult.fire(ExecutionEvent.Type.ProjectStarted, session, null); break; diff --git a/impl/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java b/impl/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java index 33e155a266a5..fd35d876018e 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java +++ b/impl/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java @@ -169,7 +169,7 @@ protected void initRepoSession( File localRepoDir = new File(projectBuildingRequest.getLocalRepository().getBasedir()); LocalRepository localRepo = new LocalRepository(localRepoDir, "simple"); - RepositorySystemSession session = new MavenSessionBuilderSupplier(repositorySystem) + RepositorySystemSession session = new MavenSessionBuilderSupplier(repositorySystem, true) .get() .withLocalRepositories(localRepo) .build(); diff --git a/impl/maven-core/src/test/java/org/apache/maven/MavenTestHelper.java b/impl/maven-core/src/test/java/org/apache/maven/MavenTestHelper.java index 3bf131c975b2..4706755f09d6 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/MavenTestHelper.java +++ b/impl/maven-core/src/test/java/org/apache/maven/MavenTestHelper.java @@ -23,15 +23,18 @@ import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.MavenSession; import org.apache.maven.impl.InternalSession; +import org.apache.maven.impl.resolver.scopes.Maven4ScopeManagerConfiguration; import org.apache.maven.internal.impl.DefaultLookup; import org.apache.maven.internal.impl.DefaultSession; import org.codehaus.plexus.PlexusContainer; import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.internal.impl.scope.ScopeManagerImpl; public class MavenTestHelper { public static DefaultRepositorySystemSession createSession( MavenRepositorySystem repositorySystem, PlexusContainer container) { DefaultRepositorySystemSession repoSession = new DefaultRepositorySystemSession(h -> false); + repoSession.setScopeManager(new ScopeManagerImpl(Maven4ScopeManagerConfiguration.INSTANCE)); DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest(); MavenSession mavenSession = new MavenSession(repoSession, request, new DefaultMavenExecutionResult()); DefaultSession session = diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java index 7bb328744c18..82526ddebdbf 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java @@ -115,7 +115,7 @@ class TestApi { @BeforeEach void setup() { // create session with any local repo, is redefined anyway below - RepositorySystemSession rss = new MavenSessionBuilderSupplier(repositorySystem) + RepositorySystemSession rss = new MavenSessionBuilderSupplier(repositorySystem, true) .get() .withLocalRepositoryBaseDirectories(new File("target/test-classes/apiv4-repo").toPath()) .build(); diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java index 4f3847386132..ec08041f24e3 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java @@ -54,7 +54,7 @@ void transform() throws Exception { Model model = new Model(new MavenStaxReader().read(expected)); MavenProject project = new MavenProject(model); project.setOriginalModel(model); - DefaultConsumerPomArtifactTransformer t = new DefaultConsumerPomArtifactTransformer((s, p, f) -> { + ConsumerPomArtifactTransformer t = new ConsumerPomArtifactTransformer((s, p, f) -> { try (InputStream is = Files.newInputStream(f)) { return DefaultConsumerPomBuilder.transformPom(new MavenStaxReader().read(is), project); } @@ -81,7 +81,7 @@ void transformJarConsumerPom() throws Exception { Model model = new Model(new MavenStaxReader().read(expected)); MavenProject project = new MavenProject(model); project.setOriginalModel(model); - DefaultConsumerPomArtifactTransformer t = new DefaultConsumerPomArtifactTransformer((s, p, f) -> { + ConsumerPomArtifactTransformer t = new ConsumerPomArtifactTransformer((s, p, f) -> { try (InputStream is = Files.newInputStream(f)) { return DefaultConsumerPomBuilder.transformNonPom(new MavenStaxReader().read(is), project); } @@ -100,7 +100,7 @@ void injectTransformedArtifactsWithoutPomShouldNotInjectAnyArtifacts() throws IO SessionData sessionDataMock = Mockito.mock(SessionData.class); when(systemSessionMock.getData()).thenReturn(sessionDataMock); - new DefaultConsumerPomArtifactTransformer((session, project, src) -> null) + new ConsumerPomArtifactTransformer((session, project, src) -> null) .injectTransformedArtifacts(systemSessionMock, emptyProject); assertThat(emptyProject.getAttachedArtifacts()).isEmpty(); diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java index 5bd794795259..62f17df33bb0 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java @@ -58,7 +58,7 @@ public class ConsumerPomBuilderTest extends AbstractRepositoryTestCase { @Inject - ConsumerPomBuilder builder; + PomBuilder builder; @Inject ModelBuilder modelBuilder; diff --git a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java index 3ac9d920690f..8d0e5cbdac7b 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java @@ -18,6 +18,7 @@ */ package org.apache.maven.api.services.model; +import org.apache.maven.api.Session; import org.apache.maven.api.model.Model; import org.apache.maven.api.services.ModelProblemCollector; @@ -61,7 +62,7 @@ public interface ModelValidator { * @param validationLevel The validation level. * @param problems The container used to collect problems that were encountered, must not be {@code null}. */ - void validateFileModel(Model model, int validationLevel, ModelProblemCollector problems); + void validateFileModel(Session session, Model model, int validationLevel, ModelProblemCollector problems); /** * Checks the specified (raw) model for missing or invalid values. The raw model is the file model + buildpom filter @@ -71,7 +72,7 @@ public interface ModelValidator { * @param validationLevel The validation level. * @param problems The container used to collect problems that were encountered, must not be {@code null}. */ - void validateRawModel(Model model, int validationLevel, ModelProblemCollector problems); + void validateRawModel(Session session, Model model, int validationLevel, ModelProblemCollector problems); /** * Checks the specified (effective) model for missing or invalid values. The effective model is fully assembled and @@ -81,5 +82,5 @@ public interface ModelValidator { * @param validationLevel The validation level. * @param problems The container used to collect problems that were encountered, must not be {@code null}. */ - void validateEffectiveModel(Model model, int validationLevel, ModelProblemCollector problems); + void validateEffectiveModel(Session session, Model model, int validationLevel, ModelProblemCollector problems); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java index 9cb5e35658a2..4938245a7cc8 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java @@ -58,6 +58,7 @@ import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Singleton; +import org.apache.maven.api.feature.Features; import org.apache.maven.api.model.Activation; import org.apache.maven.api.model.Dependency; import org.apache.maven.api.model.DependencyManagement; @@ -826,6 +827,7 @@ void buildEffectiveModel(Collection importIds) throws ModelBuilderExcept // effective model validation modelValidator.validateEffectiveModel( + session, resultModel, isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL, this); @@ -853,7 +855,7 @@ Model readParent(Model childModel, DefaultProfileActivationContext profileActiva result.setParentModel(parentModel); } else { String superModelVersion = childModel.getModelVersion(); - if (superModelVersion == null || !VALID_MODEL_VERSIONS.contains(superModelVersion)) { + if (superModelVersion == null || !KNOWN_MODEL_VERSIONS.contains(superModelVersion)) { // Maven 3.x is always using 4.0.0 version to load the supermodel, so // do the same when loading a dependency. The model validator will also // check that field later. @@ -1425,9 +1427,15 @@ Model doReadFileModel() throws ModelBuilderException { setSource(model); modelValidator.validateFileModel( + session, model, isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL, this); + InternalSession internalSession = InternalSession.from(session); + if (Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties()) + && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0, model.getModelVersion())) { + add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher model version than 4.0.0 allowed"); + } if (hasFatalErrors()) { throw newModelBuilderException(); } @@ -1507,6 +1515,7 @@ private Model doReadRawModel() throws ModelBuilderException { } modelValidator.validateRawModel( + session, rawModel, isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL, this); diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java index 126de225d4ea..9652387378fd 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.apache.maven.api.Session; import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; @@ -73,8 +74,11 @@ import org.apache.maven.api.services.model.ModelValidator; import org.apache.maven.api.xml.XmlNode; import org.apache.maven.api.xml.XmlService; +import org.apache.maven.impl.InternalSession; import org.apache.maven.model.v4.MavenModelVersion; import org.apache.maven.model.v4.MavenTransformer; +import org.eclipse.aether.scope.DependencyScope; +import org.eclipse.aether.scope.ScopeManager; import static java.util.Objects.requireNonNull; @@ -86,8 +90,6 @@ public class DefaultModelValidator implements ModelValidator { public static final String BUILD_ALLOW_EXPRESSION_IN_EFFECTIVE_PROJECT_VERSION = "maven.build.allowExpressionInEffectiveProjectVersion"; - public static final List VALID_MODEL_VERSIONS = ModelBuilder.VALID_MODEL_VERSIONS; - private static final Pattern EXPRESSION_NAME_PATTERN = Pattern.compile("\\$\\{(.+?)}"); private static final Pattern EXPRESSION_PROJECT_NAME_PATTERN = Pattern.compile("\\$\\{(project.+?)}"); @@ -302,7 +304,7 @@ public DefaultModelValidator() {} @Override @SuppressWarnings("checkstyle:MethodLength") - public void validateFileModel(Model m, int validationLevel, ModelProblemCollector problems) { + public void validateFileModel(Session s, Model m, int validationLevel, ModelProblemCollector problems) { Parent parent = m.getParent(); if (parent != null) { @@ -341,7 +343,7 @@ public void validateFileModel(Model m, int validationLevel, ModelProblemCollecto || parent.getArtifactId() != null && !parent.getArtifactId().isEmpty()) && validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_4_0 - && VALID_MODEL_VERSIONS.contains(m.getModelVersion()) + && ModelBuilder.KNOWN_MODEL_VERSIONS.contains(m.getModelVersion()) && !Objects.equals(m.getModelVersion(), ModelBuilder.MODEL_VERSION_4_0_0)) { addViolation( problems, @@ -372,7 +374,7 @@ public void validateFileModel(Model m, int validationLevel, ModelProblemCollecto } else if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) { validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m); - validateModelVersion(problems, m.getModelVersion(), m, VALID_MODEL_VERSIONS); + validateModelVersion(problems, m.getModelVersion(), m, ModelBuilder.KNOWN_MODEL_VERSIONS); Set modules = new HashSet<>(); for (int i = 0, n = m.getModules().size(); i < n; i++) { @@ -585,7 +587,7 @@ public void validateFileModel(Model m, int validationLevel, ModelProblemCollecto } @Override - public void validateRawModel(Model m, int validationLevel, ModelProblemCollector problems) { + public void validateRawModel(Session s, Model m, int validationLevel, ModelProblemCollector problems) { // Check that the model version is correctly set wrt the model definition, i.e., that the // user does not use an attribute or element that is not available in the modelVersion used. String minVersion = new MavenModelVersion().getModelVersion(m); @@ -830,7 +832,7 @@ private void validateXmlNode( @Override @SuppressWarnings("checkstyle:MethodLength") - public void validateEffectiveModel(Model m, int validationLevel, ModelProblemCollector problems) { + public void validateEffectiveModel(Session s, Model m, int validationLevel, ModelProblemCollector problems) { validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.BASE, m.getModelVersion(), m); validateCoordinatesId("groupId", problems, m.getGroupId(), m); @@ -881,11 +883,11 @@ public void validateEffectiveModel(Model m, int validationLevel, ModelProblemCol Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0); - validateEffectiveDependencies(problems, m, m.getDependencies(), false, validationLevel); + validateEffectiveDependencies(s, problems, m, m.getDependencies(), false, validationLevel); DependencyManagement mgmt = m.getDependencyManagement(); if (mgmt != null) { - validateEffectiveDependencies(problems, m, mgmt.getDependencies(), true, validationLevel); + validateEffectiveDependencies(s, problems, m, mgmt.getDependencies(), true, validationLevel); } if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) { @@ -1155,6 +1157,7 @@ private void validate20RawDependenciesSelfReferencing( } private void validateEffectiveDependencies( + Session s, ModelProblemCollector problems, Model m, List dependencies, @@ -1193,6 +1196,8 @@ private void validateEffectiveDependencies( * TODO Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In * order to don't break backward-compat with those, only warn but don't error out. */ + ScopeManager scopeManager = + InternalSession.from(s).getSession().getScopeManager(); validateEnum( prefix, "scope", @@ -1202,14 +1207,19 @@ private void validateEffectiveDependencies( d.getScope(), SourceHint.dependencyManagementKey(d), d, - "provided", - "compile", - "runtime", - "test", - "system"); + scopeManager.getDependencyScopeUniverse().stream() + .map(DependencyScope::getId) + .distinct() + .toArray(String[]::new)); validateEffectiveModelAgainstDependency(prefix, problems, m, d); } else { + ScopeManager scopeManager = + InternalSession.from(s).getSession().getScopeManager(); + Set scopes = scopeManager.getDependencyScopeUniverse().stream() + .map(DependencyScope::getId) + .collect(Collectors.toCollection(HashSet::new)); + scopes.add("import"); validateEnum( prefix, "scope", @@ -1219,12 +1229,7 @@ private void validateEffectiveDependencies( d.getScope(), SourceHint.dependencyManagementKey(d), d, - "provided", - "compile", - "runtime", - "test", - "system", - "import"); + scopes.toArray(new String[0])); } } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java index 4758bddfd079..314595364431 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java @@ -23,6 +23,7 @@ import org.apache.maven.api.DependencyScope; import org.apache.maven.impl.resolver.artifact.FatArtifactTraverser; +import org.apache.maven.impl.resolver.scopes.Maven3ScopeManagerConfiguration; import org.apache.maven.impl.resolver.scopes.Maven4ScopeManagerConfiguration; import org.apache.maven.impl.resolver.type.DefaultTypeProvider; import org.eclipse.aether.RepositorySystem; @@ -65,11 +66,16 @@ */ public class MavenSessionBuilderSupplier implements Supplier { protected final RepositorySystem repositorySystem; + protected final boolean mavenMaven3Personality; protected final InternalScopeManager scopeManager; - public MavenSessionBuilderSupplier(RepositorySystem repositorySystem) { + public MavenSessionBuilderSupplier(RepositorySystem repositorySystem, boolean mavenMaven3Personality) { this.repositorySystem = requireNonNull(repositorySystem); - this.scopeManager = new ScopeManagerImpl(Maven4ScopeManagerConfiguration.INSTANCE); + this.mavenMaven3Personality = mavenMaven3Personality; + this.scopeManager = new ScopeManagerImpl( + mavenMaven3Personality + ? Maven3ScopeManagerConfiguration.INSTANCE + : Maven4ScopeManagerConfiguration.INSTANCE); } protected DependencyTraverser getDependencyTraverser() { @@ -81,7 +87,7 @@ protected InternalScopeManager getScopeManager() { } protected DependencyManager getDependencyManager() { - return getDependencyManager(true); // same default as in Maven4 + return getDependencyManager(!mavenMaven3Personality); } public DependencyManager getDependencyManager(boolean transitive) { diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/scopes/Maven3ScopeManagerConfiguration.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/scopes/Maven3ScopeManagerConfiguration.java index 8b702bad0e4d..c57edce2d312 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/scopes/Maven3ScopeManagerConfiguration.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/scopes/Maven3ScopeManagerConfiguration.java @@ -24,15 +24,14 @@ import java.util.Collections; import java.util.stream.Collectors; -import org.eclipse.aether.artifact.ArtifactProperties; +import org.apache.maven.api.DependencyScope; +import org.apache.maven.impl.resolver.artifact.MavenArtifactProperties; import org.eclipse.aether.impl.scope.BuildScopeMatrixSource; import org.eclipse.aether.impl.scope.BuildScopeSource; import org.eclipse.aether.impl.scope.CommonBuilds; import org.eclipse.aether.impl.scope.InternalScopeManager; import org.eclipse.aether.impl.scope.ScopeManagerConfiguration; import org.eclipse.aether.internal.impl.scope.ScopeManagerDump; -import org.eclipse.aether.scope.DependencyScope; -import org.eclipse.aether.scope.ResolutionScope; import static org.eclipse.aether.impl.scope.BuildScopeQuery.all; import static org.eclipse.aether.impl.scope.BuildScopeQuery.byBuildPath; @@ -50,11 +49,7 @@ */ public final class Maven3ScopeManagerConfiguration implements ScopeManagerConfiguration { public static final Maven3ScopeManagerConfiguration INSTANCE = new Maven3ScopeManagerConfiguration(); - public static final String DS_COMPILE = "compile"; - public static final String DS_RUNTIME = "runtime"; - public static final String DS_PROVIDED = "provided"; - public static final String DS_SYSTEM = "system"; - public static final String DS_TEST = "test"; + public static final String RS_NONE = "none"; public static final String RS_MAIN_COMPILE = "main-compile"; public static final String RS_MAIN_COMPILE_PLUS_RUNTIME = "main-compilePlusRuntime"; @@ -89,33 +84,37 @@ public BuildScopeSource getBuildScopeSource() { } @Override - public Collection buildDependencyScopes(InternalScopeManager internalScopeManager) { - ArrayList result = new ArrayList<>(); - result.add(internalScopeManager.createDependencyScope(DS_COMPILE, true, all())); + public Collection buildDependencyScopes( + InternalScopeManager internalScopeManager) { + ArrayList result = new ArrayList<>(); + result.add(internalScopeManager.createDependencyScope(DependencyScope.COMPILE.id(), true, all())); result.add(internalScopeManager.createDependencyScope( - DS_RUNTIME, true, byBuildPath(CommonBuilds.BUILD_PATH_RUNTIME))); + DependencyScope.RUNTIME.id(), true, byBuildPath(CommonBuilds.BUILD_PATH_RUNTIME))); result.add(internalScopeManager.createDependencyScope( - DS_PROVIDED, + DependencyScope.PROVIDED.id(), false, union( byBuildPath(CommonBuilds.BUILD_PATH_COMPILE), select(CommonBuilds.PROJECT_PATH_TEST, CommonBuilds.BUILD_PATH_RUNTIME)))); result.add(internalScopeManager.createDependencyScope( - DS_TEST, false, byProjectPath(CommonBuilds.PROJECT_PATH_TEST))); + DependencyScope.TEST.id(), false, byProjectPath(CommonBuilds.PROJECT_PATH_TEST))); result.add(internalScopeManager.createSystemDependencyScope( - DS_SYSTEM, false, all(), ArtifactProperties.LOCAL_PATH)); + DependencyScope.SYSTEM.id(), false, all(), MavenArtifactProperties.LOCAL_PATH)); return result; } @Override - public Collection buildResolutionScopes(InternalScopeManager internalScopeManager) { - Collection allDependencyScopes = internalScopeManager.getDependencyScopeUniverse(); - Collection nonTransitiveDependencyScopes = + public Collection buildResolutionScopes( + InternalScopeManager internalScopeManager) { + Collection allDependencyScopes = + internalScopeManager.getDependencyScopeUniverse(); + Collection nonTransitiveDependencyScopes = allDependencyScopes.stream().filter(s -> !s.isTransitive()).collect(Collectors.toSet()); - DependencyScope system = - internalScopeManager.getDependencyScope(DS_SYSTEM).orElse(null); + org.eclipse.aether.scope.DependencyScope system = internalScopeManager + .getDependencyScope(DependencyScope.SYSTEM.id()) + .orElse(null); - ArrayList result = new ArrayList<>(); + ArrayList result = new ArrayList<>(); result.add(internalScopeManager.createResolutionScope( RS_NONE, InternalScopeManager.Mode.REMOVE, diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java index 06947221f3b5..6dc7a058518c 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java @@ -19,12 +19,18 @@ package org.apache.maven.impl.model; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.apache.maven.api.model.Model; import org.apache.maven.api.services.model.ModelValidator; +import org.apache.maven.impl.InternalSession; import org.apache.maven.impl.model.profile.SimpleProblemCollector; import org.apache.maven.model.v4.MavenStaxReader; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.scope.DependencyScope; +import org.eclipse.aether.scope.ScopeManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,6 +38,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** */ @@ -39,6 +47,8 @@ class DefaultModelValidatorTest { private ModelValidator validator; + private InternalSession session; + private Model read(String pom) throws Exception { String resource = "/poms/validation/" + pom; try (InputStream is = getClass().getResourceAsStream(resource)) { @@ -63,7 +73,7 @@ private SimpleProblemCollector validateRaw(String pom) throws Exception { private SimpleProblemCollector validateEffective(String pom, int level) throws Exception { Model model = read(pom); SimpleProblemCollector problems = new SimpleProblemCollector(); - validator.validateEffectiveModel(model, level, problems); + validator.validateEffectiveModel(session, model, level, problems); return problems; } @@ -71,7 +81,7 @@ private SimpleProblemCollector validateEffective(String pom, int level) throws E private SimpleProblemCollector validateFile(String pom, int level) throws Exception { Model model = read(pom); SimpleProblemCollector problems = new SimpleProblemCollector(); - validator.validateFileModel(model, level, problems); + validator.validateFileModel(session, model, level, problems); return problems; } @@ -79,7 +89,7 @@ private SimpleProblemCollector validateFile(String pom, int level) throws Except private SimpleProblemCollector validateRaw(String pom, int level) throws Exception { Model model = read(pom); SimpleProblemCollector problems = new SimpleProblemCollector(); - validator.validateRawModel(model, level, problems); + validator.validateRawModel(session, model, level, problems); return problems; } @@ -87,9 +97,39 @@ private void assertContains(String msg, String substring) { assertTrue(msg.contains(substring), "\"" + substring + "\" was not found in: " + msg); } + private static class DepScope implements DependencyScope { + private final String id; + + private DepScope(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + + @Override + public boolean isTransitive() { + return false; + } + } + @BeforeEach void setUp() throws Exception { validator = new DefaultModelValidator(); + + Collection scopes = new ArrayList<>(); + scopes.add(new DepScope("compile")); + scopes.add(new DepScope("runtime")); + scopes.add(new DepScope("provided")); + scopes.add(new DepScope("test")); + ScopeManager scopeManager = mock(ScopeManager.class); + when(scopeManager.getDependencyScopeUniverse()).thenReturn(scopes); + RepositorySystemSession repoSession = mock(RepositorySystemSession.class); + when(repoSession.getScopeManager()).thenReturn(scopeManager); + session = mock(InternalSession.class); + when(session.getSession()).thenReturn(repoSession); } @AfterEach diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8744CIFriendlyTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8744CIFriendlyTest.java new file mode 100644 index 000000000000..3e5a285373fc --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8744CIFriendlyTest.java @@ -0,0 +1,79 @@ +/* + * 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.it; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +/** + * The usage of a ${revision} for the version in the pom file and furthermore + * defining the property in the pom file and overwrite it via command line and + * try to build a partial reactor via mvn -pl .. + * MNG-6090. + * Note: this IT is 99% copy of MNG-6090 with one difference: it does not use flatten plugin. + * + * @author Karl Heinz Marbaise khmarbaise@apache.org + */ +public class MavenITmng8744CIFriendlyTest extends AbstractMavenIntegrationTestCase { + + public MavenITmng8744CIFriendlyTest() { + super("[4.0.0-rc-4,)"); + } + + /** + * Check that the resulting run will not fail in case + * of defining the property via command line and + * install the projects and afterwards just build + * a part of the whole reactor. + * + * @throws Exception in case of failure + */ + @Test + public void testitShouldResolveTheInstalledDependencies() throws Exception { + File testDir = extractResources("/mng-8744-ci-friendly"); + + Verifier verifier = newVerifier(testDir.getAbsolutePath(), false); + verifier.setAutoclean(false); + + verifier.addCliArgument("-Drevision=1.2"); + verifier.addCliArgument("-Dmaven.maven3Personality=true"); + verifier.setLogFileName("install-log.txt"); + verifier.addCliArguments("clean", "install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier = newVerifier(testDir.getAbsolutePath(), false); + verifier.setAutoclean(false); + verifier.addCliArgument("clean"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier = newVerifier(testDir.getAbsolutePath(), false); + verifier.setAutoclean(false); + + verifier.addCliArgument("-Drevision=1.2"); + verifier.addCliArgument("-Dmaven.maven3Personality=true"); + verifier.addCliArgument("-pl"); + verifier.addCliArgument("module-3"); + verifier.addCliArgument("package"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + } +} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java index e8b651f49fa8..810926b103de 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java @@ -101,6 +101,7 @@ public TestSuiteOrdering() { * the tests are to finishing. Newer tests are also more likely to fail, so this is * a fail fast technique as well. */ + suite.addTestSuite(MavenITmng8744CIFriendlyTest.class); suite.addTestSuite(MavenITmng8572DITypeHandlerTest.class); suite.addTestSuite(MavenITmng3558PropertyEscapingTest.class); suite.addTestSuite(MavenITmng4559MultipleJvmArgsTest.class); diff --git a/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/.mvn/.gitkeep b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/.mvn/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-1/pom.xml b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-1/pom.xml new file mode 100644 index 000000000000..e5802d8ecd24 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-1/pom.xml @@ -0,0 +1,30 @@ + + + + 4.0.0 + + mng-6090-ci-friendly + base-project + ${revision} + + + module-1 + + diff --git a/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-2/pom.xml b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-2/pom.xml new file mode 100644 index 000000000000..b79e3e9e185d --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-2/pom.xml @@ -0,0 +1,30 @@ + + + + 4.0.0 + + + mng-6090-ci-friendly + base-project + ${revision} + + module-2 + + + + mng-6090-ci-friendly + module-1 + ${project.version} + + + + diff --git a/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-3/jar-with-prod.xml b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-3/jar-with-prod.xml new file mode 100644 index 000000000000..65b31bbc5db9 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-3/jar-with-prod.xml @@ -0,0 +1,17 @@ + + prod + + jar + + false + + + / + false + true + runtime + + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-3/pom.xml b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-3/pom.xml new file mode 100644 index 000000000000..e9a68cce193c --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/module-3/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + + + mng-6090-ci-friendly + base-project + ${revision} + + module-3 + + + + mng-6090-ci-friendly + module-2 + ${project.version} + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.4.0 + + + assemblies + + single + + package + + + jar-with-prod.xml + + + + + + + + + diff --git a/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/pom.xml b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/pom.xml new file mode 100644 index 000000000000..47c310879d3f --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8744-ci-friendly/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + + mng-6090-ci-friendly + base-project + ${revision} + pom + + module-3 + module-1 + module-2 + + + + 1.3.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + + + diff --git a/src/site/markdown/configuration.properties b/src/site/markdown/configuration.properties index ddf017a1aa58..bede9d88da2e 100644 --- a/src/site/markdown/configuration.properties +++ b/src/site/markdown/configuration.properties @@ -20,7 +20,7 @@ # Generated from: maven-resolver-tools/src/main/resources/configuration.properties.vm # To modify this file, edit the template and regenerate. # -props.count = 62 +props.count = 63 props.1.key = maven.build.timestamp.format props.1.configurationType = String props.1.description = Build timestamp format. @@ -152,242 +152,248 @@ props.22.description = The string value output for the warn level. Defaults to W props.22.defaultValue = WARN props.22.since = 4.0.0 props.22.configurationSource = User properties -props.23.key = maven.modelBuilder.parallelism -props.23.configurationType = Integer -props.23.description = ProjectBuilder parallelism. -props.23.defaultValue = cores/2 + 1 +props.23.key = maven.maven3Personality +props.23.configurationType = Boolean +props.23.description = User property for controlling "maven personality". If activated Maven will behave as previous major version, Maven 3. +props.23.defaultValue = false props.23.since = 4.0.0 props.23.configurationSource = User properties -props.24.key = maven.plugin.validation -props.24.configurationType = String -props.24.description = Plugin validation level. -props.24.defaultValue = inline -props.24.since = 3.9.2 +props.24.key = maven.modelBuilder.parallelism +props.24.configurationType = Integer +props.24.description = ProjectBuilder parallelism. +props.24.defaultValue = cores/2 + 1 +props.24.since = 4.0.0 props.24.configurationSource = User properties -props.25.key = maven.plugin.validation.excludes +props.25.key = maven.plugin.validation props.25.configurationType = String -props.25.description = Plugin validation exclusions. -props.25.defaultValue = -props.25.since = 3.9.6 +props.25.description = Plugin validation level. +props.25.defaultValue = inline +props.25.since = 3.9.2 props.25.configurationSource = User properties -props.26.key = maven.project.conf +props.26.key = maven.plugin.validation.excludes props.26.configurationType = String -props.26.description = Maven project configuration directory. -props.26.defaultValue = ${session.rootDirectory}/.mvn -props.26.since = 4.0.0 +props.26.description = Plugin validation exclusions. +props.26.defaultValue = +props.26.since = 3.9.6 props.26.configurationSource = User properties -props.27.key = maven.project.extensions +props.27.key = maven.project.conf props.27.configurationType = String -props.27.description = Maven project extensions. -props.27.defaultValue = ${maven.project.conf}/extensions.xml +props.27.description = Maven project configuration directory. +props.27.defaultValue = ${session.rootDirectory}/.mvn props.27.since = 4.0.0 props.27.configurationSource = User properties -props.28.key = maven.project.settings +props.28.key = maven.project.extensions props.28.configurationType = String -props.28.description = Maven project settings. -props.28.defaultValue = ${maven.project.conf}/settings.xml +props.28.description = Maven project extensions. +props.28.defaultValue = ${maven.project.conf}/extensions.xml props.28.since = 4.0.0 props.28.configurationSource = User properties -props.29.key = maven.relocations.entries +props.29.key = maven.project.settings props.29.configurationType = String -props.29.description = 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. -props.29.defaultValue = +props.29.description = Maven project settings. +props.29.defaultValue = ${maven.project.conf}/settings.xml props.29.since = 4.0.0 props.29.configurationSource = User properties -props.30.key = maven.repo.central +props.30.key = maven.relocations.entries props.30.configurationType = String -props.30.description = Maven central repository URL. The property will have the value of the MAVEN_REPO_CENTRAL environment variable if it is defined. -props.30.defaultValue = https://repo.maven.apache.org/maven2 +props.30.description = 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. +props.30.defaultValue = props.30.since = 4.0.0 props.30.configurationSource = User properties -props.31.key = maven.repo.local +props.31.key = maven.repo.central props.31.configurationType = String -props.31.description = Maven local repository. -props.31.defaultValue = ${maven.user.conf}/repository -props.31.since = 3.0.0 +props.31.description = Maven central repository URL. The property will have the value of the MAVEN_REPO_CENTRAL environment variable if it is defined. +props.31.defaultValue = https://repo.maven.apache.org/maven2 +props.31.since = 4.0.0 props.31.configurationSource = User properties -props.32.key = maven.repo.local.head +props.32.key = maven.repo.local props.32.configurationType = String -props.32.description = User property for chained LRM: the new "head" local repository to use, and "push" the existing into tail. Similar to maven.repo.local.tail, this property may contain comma separated list of paths to be used as local repositories (combine with chained local repository), but while latter is "appending" this one is "prepending". -props.32.defaultValue = -props.32.since = 4.0.0 +props.32.description = Maven local repository. +props.32.defaultValue = ${maven.user.conf}/repository +props.32.since = 3.0.0 props.32.configurationSource = User properties -props.33.key = maven.repo.local.recordReverseTree +props.33.key = maven.repo.local.head props.33.configurationType = String -props.33.description = 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. -props.33.defaultValue = false -props.33.since = 3.9.0 +props.33.description = User property for chained LRM: the new "head" local repository to use, and "push" the existing into tail. Similar to maven.repo.local.tail, this property may contain comma separated list of paths to be used as local repositories (combine with chained local repository), but while latter is "appending" this one is "prepending". +props.33.defaultValue = +props.33.since = 4.0.0 props.33.configurationSource = User properties -props.34.key = maven.repo.local.tail +props.34.key = maven.repo.local.recordReverseTree props.34.configurationType = String -props.34.description = User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with org.eclipse.aether.util.repository.ChainedLocalRepositoryManager. Default value: null, no chained LRM is used. -props.34.defaultValue = +props.34.description = 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. +props.34.defaultValue = false props.34.since = 3.9.0 props.34.configurationSource = User properties -props.35.key = maven.repo.local.tail.ignoreAvailability +props.35.key = maven.repo.local.tail props.35.configurationType = String -props.35.description = User property for chained LRM: whether to ignore "availability check" in tail or not. Usually you do want to ignore it. This property is mapped onto corresponding Resolver 2.x property, is like a synonym for it. Default value: true. +props.35.description = User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with org.eclipse.aether.util.repository.ChainedLocalRepositoryManager. Default value: null, no chained LRM is used. props.35.defaultValue = props.35.since = 3.9.0 props.35.configurationSource = User properties -props.36.key = maven.resolver.dependencyManagerTransitivity +props.36.key = maven.repo.local.tail.ignoreAvailability props.36.configurationType = String -props.36.description = 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". -props.36.defaultValue = true -props.36.since = 4.0.0 +props.36.description = User property for chained LRM: whether to ignore "availability check" in tail or not. Usually you do want to ignore it. This property is mapped onto corresponding Resolver 2.x property, is like a synonym for it. Default value: true. +props.36.defaultValue = +props.36.since = 3.9.0 props.36.configurationSource = User properties -props.37.key = maven.resolver.transport +props.37.key = maven.resolver.dependencyManagerTransitivity props.37.configurationType = String -props.37.description = Resolver transport to use. Can be default, wagon, apache, jdk or auto. -props.37.defaultValue = default +props.37.description = 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". +props.37.defaultValue = true props.37.since = 4.0.0 props.37.configurationSource = User properties -props.38.key = maven.session.versionFilter +props.38.key = maven.resolver.transport props.38.configurationType = String -props.38.description = User property for version filter expression used in session, applied to resolving ranges: 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. Value in this property builds org.eclipse.aether.collection.VersionFilter instance. -props.38.defaultValue = +props.38.description = Resolver transport to use. Can be default, wagon, apache, jdk or auto. +props.38.defaultValue = default props.38.since = 4.0.0 props.38.configurationSource = User properties -props.39.key = maven.settings.security +props.39.key = maven.session.versionFilter props.39.configurationType = String -props.39.description = -props.39.defaultValue = ${maven.user.conf}/settings-security4.xml +props.39.description = User property for version filter expression used in session, applied to resolving ranges: 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. Value in this property builds org.eclipse.aether.collection.VersionFilter instance. +props.39.defaultValue = +props.39.since = 4.0.0 props.39.configurationSource = User properties -props.40.key = maven.startInstant -props.40.configurationType = java.time.Instant -props.40.description = User property used to store the build timestamp. -props.40.defaultValue = -props.40.since = 4.0.0 +props.40.key = maven.settings.security +props.40.configurationType = String +props.40.description = +props.40.defaultValue = ${maven.user.conf}/settings-security4.xml props.40.configurationSource = User properties -props.41.key = maven.style.color -props.41.configurationType = String -props.41.description = Maven output color mode. Allowed values are auto, always, never. -props.41.defaultValue = auto +props.41.key = maven.startInstant +props.41.configurationType = java.time.Instant +props.41.description = User property used to store the build timestamp. +props.41.defaultValue = props.41.since = 4.0.0 props.41.configurationSource = User properties -props.42.key = maven.style.debug +props.42.key = maven.style.color props.42.configurationType = String -props.42.description = Color style for debug messages. -props.42.defaultValue = bold,f:cyan +props.42.description = Maven output color mode. Allowed values are auto, always, never. +props.42.defaultValue = auto props.42.since = 4.0.0 props.42.configurationSource = User properties -props.43.key = maven.style.error +props.43.key = maven.style.debug props.43.configurationType = String -props.43.description = Color style for error messages. -props.43.defaultValue = bold,f:red +props.43.description = Color style for debug messages. +props.43.defaultValue = bold,f:cyan props.43.since = 4.0.0 props.43.configurationSource = User properties -props.44.key = maven.style.failure +props.44.key = maven.style.error props.44.configurationType = String -props.44.description = Color style for failure messages. +props.44.description = Color style for error messages. props.44.defaultValue = bold,f:red props.44.since = 4.0.0 props.44.configurationSource = User properties -props.45.key = maven.style.info +props.45.key = maven.style.failure props.45.configurationType = String -props.45.description = Color style for info messages. -props.45.defaultValue = bold,f:blue +props.45.description = Color style for failure messages. +props.45.defaultValue = bold,f:red props.45.since = 4.0.0 props.45.configurationSource = User properties -props.46.key = maven.style.mojo +props.46.key = maven.style.info props.46.configurationType = String -props.46.description = Color style for mojo messages. -props.46.defaultValue = f:green +props.46.description = Color style for info messages. +props.46.defaultValue = bold,f:blue props.46.since = 4.0.0 props.46.configurationSource = User properties -props.47.key = maven.style.project +props.47.key = maven.style.mojo props.47.configurationType = String -props.47.description = Color style for project messages. -props.47.defaultValue = f:cyan +props.47.description = Color style for mojo messages. +props.47.defaultValue = f:green props.47.since = 4.0.0 props.47.configurationSource = User properties -props.48.key = maven.style.strong +props.48.key = maven.style.project props.48.configurationType = String -props.48.description = Color style for strong messages. -props.48.defaultValue = bold +props.48.description = Color style for project messages. +props.48.defaultValue = f:cyan props.48.since = 4.0.0 props.48.configurationSource = User properties -props.49.key = maven.style.success +props.49.key = maven.style.strong props.49.configurationType = String -props.49.description = Color style for success messages. -props.49.defaultValue = bold,f:green +props.49.description = Color style for strong messages. +props.49.defaultValue = bold props.49.since = 4.0.0 props.49.configurationSource = User properties -props.50.key = maven.style.trace +props.50.key = maven.style.success props.50.configurationType = String -props.50.description = Color style for trace messages. -props.50.defaultValue = bold,f:magenta +props.50.description = Color style for success messages. +props.50.defaultValue = bold,f:green props.50.since = 4.0.0 props.50.configurationSource = User properties -props.51.key = maven.style.transfer +props.51.key = maven.style.trace props.51.configurationType = String -props.51.description = Color style for transfer messages. -props.51.defaultValue = f:bright-black +props.51.description = Color style for trace messages. +props.51.defaultValue = bold,f:magenta props.51.since = 4.0.0 props.51.configurationSource = User properties -props.52.key = maven.style.warning +props.52.key = maven.style.transfer props.52.configurationType = String -props.52.description = Color style for warning messages. -props.52.defaultValue = bold,f:yellow +props.52.description = Color style for transfer messages. +props.52.defaultValue = f:bright-black props.52.since = 4.0.0 props.52.configurationSource = User properties -props.53.key = maven.user.conf +props.53.key = maven.style.warning props.53.configurationType = String -props.53.description = Maven user configuration directory. -props.53.defaultValue = ${user.home}/.m2 +props.53.description = Color style for warning messages. +props.53.defaultValue = bold,f:yellow props.53.since = 4.0.0 props.53.configurationSource = User properties -props.54.key = maven.user.extensions +props.54.key = maven.user.conf props.54.configurationType = String -props.54.description = Maven user extensions. -props.54.defaultValue = ${maven.user.conf}/extensions.xml +props.54.description = Maven user configuration directory. +props.54.defaultValue = ${user.home}/.m2 props.54.since = 4.0.0 props.54.configurationSource = User properties -props.55.key = maven.user.settings +props.55.key = maven.user.extensions props.55.configurationType = String -props.55.description = Maven user settings. -props.55.defaultValue = ${maven.user.conf}/settings.xml +props.55.description = Maven user extensions. +props.55.defaultValue = ${maven.user.conf}/extensions.xml props.55.since = 4.0.0 props.55.configurationSource = User properties -props.56.key = maven.user.toolchains +props.56.key = maven.user.settings props.56.configurationType = String -props.56.description = Maven user toolchains. -props.56.defaultValue = ${maven.user.conf}/toolchains.xml +props.56.description = Maven user settings. +props.56.defaultValue = ${maven.user.conf}/settings.xml props.56.since = 4.0.0 props.56.configurationSource = User properties -props.57.key = maven.version +props.57.key = maven.user.toolchains props.57.configurationType = String -props.57.description = Maven version. -props.57.defaultValue = -props.57.since = 3.0.0 -props.57.configurationSource = system_properties -props.58.key = maven.version.major +props.57.description = Maven user toolchains. +props.57.defaultValue = ${maven.user.conf}/toolchains.xml +props.57.since = 4.0.0 +props.57.configurationSource = User properties +props.58.key = maven.version props.58.configurationType = String -props.58.description = Maven major version: contains the major segment of this Maven version. +props.58.description = Maven version. props.58.defaultValue = -props.58.since = 4.0.0 +props.58.since = 3.0.0 props.58.configurationSource = system_properties -props.59.key = maven.version.minor +props.59.key = maven.version.major props.59.configurationType = String -props.59.description = Maven minor version: contains the minor segment of this Maven version. +props.59.description = Maven major version: contains the major segment of this Maven version. props.59.defaultValue = props.59.since = 4.0.0 props.59.configurationSource = system_properties -props.60.key = maven.version.patch +props.60.key = maven.version.minor props.60.configurationType = String -props.60.description = Maven patch version: contains the patch segment of this Maven version. +props.60.description = Maven minor version: contains the minor segment of this Maven version. props.60.defaultValue = props.60.since = 4.0.0 props.60.configurationSource = system_properties -props.61.key = maven.version.snapshot +props.61.key = maven.version.patch props.61.configurationType = String -props.61.description = Maven snapshot: contains "true" if this Maven is a snapshot version. +props.61.description = Maven patch version: contains the patch segment of this Maven version. props.61.defaultValue = props.61.since = 4.0.0 props.61.configurationSource = system_properties -props.62.key = maven.versionResolver.noCache -props.62.configurationType = Boolean -props.62.description = User property for disabling version resolver cache. -props.62.defaultValue = false -props.62.since = 3.0.0 -props.62.configurationSource = User properties +props.62.key = maven.version.snapshot +props.62.configurationType = String +props.62.description = Maven snapshot: contains "true" if this Maven is a snapshot version. +props.62.defaultValue = +props.62.since = 4.0.0 +props.62.configurationSource = system_properties +props.63.key = maven.versionResolver.noCache +props.63.configurationType = Boolean +props.63.description = User property for disabling version resolver cache. +props.63.defaultValue = false +props.63.since = 3.0.0 +props.63.configurationSource = User properties diff --git a/src/site/markdown/configuration.yaml b/src/site/markdown/configuration.yaml index 7cfdccc5b1c2..a32cb5ddbafa 100644 --- a/src/site/markdown/configuration.yaml +++ b/src/site/markdown/configuration.yaml @@ -152,6 +152,12 @@ props: defaultValue: WARN since: 4.0.0 configurationSource: User properties + - key: maven.maven3Personality + configurationType: Boolean + description: "User property for controlling \"maven personality\". If activated Maven will behave as previous major version, Maven 3." + defaultValue: false + since: 4.0.0 + configurationSource: User properties - key: maven.modelBuilder.parallelism configurationType: Integer description: "ProjectBuilder parallelism." diff --git a/src/site/markdown/maven-configuration.md b/src/site/markdown/maven-configuration.md index f6ff067e9c07..4671ea007afa 100644 --- a/src/site/markdown/maven-configuration.md +++ b/src/site/markdown/maven-configuration.md @@ -53,6 +53,7 @@ To modify this file, edit the template and regenerate. | `maven.logger.showThreadId` | `Boolean` | If you would like to output the current thread id, then set to true. Defaults to false. | `false` | 4.0.0 | User properties | | `maven.logger.showThreadName` | `Boolean` | Set to true if you want to output the current thread name. Defaults to true. | `true` | 4.0.0 | User properties | | `maven.logger.warnLevelString` | `String` | The string value output for the warn level. Defaults to WARN. | `WARN` | 4.0.0 | User properties | +| `maven.maven3Personality` | `Boolean` | User property for controlling "maven personality". If activated Maven will behave as previous major version, Maven 3. | `false` | 4.0.0 | User properties | | `maven.modelBuilder.parallelism` | `Integer` | ProjectBuilder parallelism. | `cores/2 + 1` | 4.0.0 | User properties | | `maven.plugin.validation` | `String` | Plugin validation level. | `inline` | 3.9.2 | User properties | | `maven.plugin.validation.excludes` | `String` | Plugin validation exclusions. | - | 3.9.6 | User properties |