diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java index ccd39248b1..b9b3ebed9e 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java @@ -52,6 +52,10 @@ public interface ModelBuilderRequest { * The request is for building a model from a POM file in a project on the filesystem. */ BUILD_POM, + /** + * The request is for building the consumer POM. + */ + CONSUMER_POM, /** * The request is for building a model from a parent POM file from a downloaded artifact. */ diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 7df64f342b..4e29c4ffaf 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -1744,20 +1744,16 @@ relativePath 4.0.0+ - The relative path of the parent {@code pom.xml} file within the checkout. - If not specified, it defaults to {@code ../pom.xml}. + The relative path of the parent subproject POM file or directory within the checkout. + If not specified, it defaults to {@code ..}, i.e. the parent directory. Maven looks for the parent POM first in this location on - the filesystem, then the local repository, and lastly in the remote repo. - {@code relativePath} allows you to select a different location, - for example when your structure is flat, or deeper without an intermediate parent POM. - However, the group ID, artifact ID and version are still required, - and must match the file in the location given, or it will revert to the repository for the POM. - This feature is only for enhancing the development in a local checkout of that project. - Set the value to an empty string in case you want to disable the feature and always resolve - the parent POM from the repositories. + the filesystem if explicitly provided, then in the reactor if groupId and artifactId are provided, + then in the default parent directory, then the local repository, and lastly in the remote repo. + However, if the both relative path and the group ID / artifact ID are provided, + they must match the file in the location given. + Specify either the {@code relativePath} or the {@code groupId}/{@code artifactId}, not both. String - .. diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java index 4de108f1ff..bb1a7e23b7 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java @@ -34,7 +34,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -829,6 +828,14 @@ public class DefaultModelBuilder implements ModelBuilder { } result.setEffectiveModel(resultModel); + // Set the default relative path for the parent in the file model + if (result.getFileModel().getParent() != null + && result.getFileModel().getParent().getRelativePath() == null) { + result.setFileModel(result.getFileModel() + .withParent(result.getFileModel() + .getParent() + .withRelativePath(resultModel.getParent().getRelativePath()))); + } // effective model validation modelValidator.validateEffectiveModel( @@ -849,10 +856,7 @@ public class DefaultModelBuilder implements ModelBuilder { Parent parent = childModel.getParent(); if (parent != null) { - parentModel = readParentLocally(childModel); - if (parentModel == null) { - parentModel = resolveAndReadParentExternally(childModel); - } + parentModel = resolveParent(childModel); if (!"pom".equals(parentModel.getPackaging())) { add( @@ -877,43 +881,56 @@ public class DefaultModelBuilder implements ModelBuilder { return parentModel; } + private Model resolveParent(Model childModel) { + Model parentModel = null; + if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM + || request.getRequestType() == ModelBuilderRequest.RequestType.CONSUMER_POM) { + parentModel = readParentLocally(childModel); + } + if (parentModel == null) { + parentModel = resolveAndReadParentExternally(childModel); + } + return parentModel; + } + private Model readParentLocally(Model childModel) throws ModelBuilderException { - ModelSource candidateSource = getParentPomFile(childModel, request.getSource()); + ModelSource candidateSource = null; + + Parent parent = childModel.getParent(); + String parentPath = parent.getRelativePath(); + if (parentPath != null && !parentPath.isEmpty()) { + candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath); + if (candidateSource == null) { + wrongParentRelativePath(childModel); + return null; + } + } + if (candidateSource == null) { + candidateSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); + } + if (candidateSource == null) { + candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, ".."); + } + if (candidateSource == null) { return null; } Model candidateModel = derive(candidateSource).readAsParentModel(); - // - // TODO jvz Why isn't all this checking the job of the duty of the workspace resolver, we know that we - // have a model that is suitable, yet more checks are done here and the one for the version is problematic - // before because with parents as ranges it will never work in this scenario. - // - String groupId = getGroupId(candidateModel); String artifactId = candidateModel.getArtifactId(); + String version = getVersion(candidateModel); - Parent parent = childModel.getParent(); + // Ensure that relative path and GA match, if both are provided if (groupId == null || !groupId.equals(parent.getGroupId()) || artifactId == null || !artifactId.equals(parent.getArtifactId())) { - StringBuilder buffer = new StringBuilder(256); - buffer.append("'parent.relativePath'"); - if (childModel != getRootModel()) { - buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel)); - } - buffer.append(" points at ").append(groupId).append(':').append(artifactId); - buffer.append(" instead of ").append(parent.getGroupId()).append(':'); - buffer.append(parent.getArtifactId()).append(", please verify your project structure"); - - setSource(childModel); - add(Severity.WARNING, Version.BASE, buffer.toString(), parent.getLocation("")); + mismatchRelativePathAndGA(childModel, groupId, artifactId); return null; } - String version = getVersion(candidateModel); if (version != null && parent.getVersion() != null && !version.equals(parent.getVersion())) { try { VersionRange parentRange = versionParser.parseVersionRange(parent.getVersion()); @@ -946,15 +963,41 @@ public class DefaultModelBuilder implements ModelBuilder { return null; } } - - // - // Here we just need to know that a version is fine to use but this validation we can do in our workspace - // resolver. - // - return candidateModel; } + private void mismatchRelativePathAndGA(Model childModel, String groupId, String artifactId) { + Parent parent = childModel.getParent(); + StringBuilder buffer = new StringBuilder(256); + buffer.append("'parent.relativePath'"); + if (childModel != getRootModel()) { + buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel)); + } + buffer.append(" points at ").append(groupId).append(':').append(artifactId); + buffer.append(" instead of ").append(parent.getGroupId()).append(':'); + buffer.append(parent.getArtifactId()).append(", please verify your project structure"); + + setSource(childModel); + boolean warn = MODEL_VERSION_4_0_0.equals(childModel.getModelVersion()) + || childModel.getParent().getRelativePath() == null; + add(warn ? Severity.WARNING : Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation("")); + } + + private void wrongParentRelativePath(Model childModel) { + Parent parent = childModel.getParent(); + String parentPath = parent.getRelativePath(); + StringBuilder buffer = new StringBuilder(256); + buffer.append("'parent.relativePath'"); + if (childModel != getRootModel()) { + buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel)); + } + buffer.append(" points at '").append(parentPath); + buffer.append("' but no POM could be found, please verify your project structure"); + + setSource(childModel); + add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation("")); + } + Model resolveAndReadParentExternally(Model childModel) throws ModelBuilderException { ModelBuilderRequest request = this.request; setSource(childModel); @@ -981,7 +1024,7 @@ public class DefaultModelBuilder implements ModelBuilder { ModelSource modelSource; try { - modelSource = resolveReactorModel(groupId, artifactId, version); + modelSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); if (modelSource == null) { AtomicReference modified = new AtomicReference<>(); modelSource = modelResolver.resolveModel(request.getSession(), repositories, parent, modified); @@ -1000,13 +1043,8 @@ public class DefaultModelBuilder implements ModelBuilder { buffer.append(" for ").append(ModelProblemUtils.toId(childModel)); } buffer.append(": ").append(e.getMessage()); - if (childModel.getProjectDirectory() != null) { - if (parent.getRelativePath() == null - || parent.getRelativePath().isEmpty()) { - buffer.append(" and 'parent.relativePath' points at no local POM"); - } else { - buffer.append(" and 'parent.relativePath' points at wrong local POM"); - } + if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM) { + buffer.append(" and parent could not be found in reactor"); } add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""), e); @@ -1050,8 +1088,7 @@ public class DefaultModelBuilder implements ModelBuilder { DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel); setSource("(external profiles)"); - List activeExternalProfiles = - profileSelector.getActiveProfiles(request.getProfiles(), profileActivationContext, this); + List activeExternalProfiles = getActiveProfiles(request.getProfiles(), profileActivationContext); result.setActiveExternalProfiles(activeExternalProfiles); @@ -1066,8 +1103,7 @@ public class DefaultModelBuilder implements ModelBuilder { profileActivationContext.setProjectProperties(inputModel.getProperties()); setSource(inputModel); - List activePomProfiles = - profileSelector.getActiveProfiles(inputModel.getProfiles(), profileActivationContext, this); + List activePomProfiles = getActiveProfiles(inputModel.getProfiles(), profileActivationContext); // model normalization setSource(inputModel); @@ -1109,12 +1145,29 @@ public class DefaultModelBuilder implements ModelBuilder { } Model parentModel = readParent(inputModel); + // Now that we have read the parent, we can set the relative + // path correctly if it was not set in the input model + if (inputModel.getParent() != null && inputModel.getParent().getRelativePath() == null) { + String relPath; + if (parentModel.getPomFile() != null + && (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM + || request.getRequestType() == ModelBuilderRequest.RequestType.CONSUMER_POM)) { + relPath = inputModel + .getPomFile() + .getParent() + .relativize(parentModel.getPomFile().getParent()) + .toString(); + } else { + relPath = ".."; + } + inputModel = inputModel.withParent(inputModel.getParent().withRelativePath(relPath)); + } List parentInterpolatedProfiles = interpolateActivations(parentModel.getProfiles(), profileActivationContext, this); // profile injection List parentActivePomProfiles = - profileSelector.getActiveProfiles(parentInterpolatedProfiles, profileActivationContext, this); + getActiveProfiles(parentInterpolatedProfiles, profileActivationContext); Model injectedParentModel = profileInjector .injectProfiles(parentModel, parentActivePomProfiles, request, this) .withProfiles(List.of()); @@ -1131,8 +1184,7 @@ public class DefaultModelBuilder implements ModelBuilder { interpolateActivations(model.getProfiles(), profileActivationContext, this); // profile injection - List activePomProfiles = - profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, this); + List activePomProfiles = getActiveProfiles(interpolatedProfiles, profileActivationContext); result.setActivePomProfiles(activePomProfiles); model = profileInjector.injectProfiles(model, activePomProfiles, request, this); model = profileInjector.injectProfiles(model, activeExternalProfiles, request, this); @@ -1160,6 +1212,15 @@ public class DefaultModelBuilder implements ModelBuilder { return resultModel; } + private List getActiveProfiles( + Collection interpolatedProfiles, DefaultProfileActivationContext profileActivationContext) { + if (request.getRequestType() != ModelBuilderRequest.RequestType.CONSUMER_POM) { + return profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, this); + } else { + return List.of(); + } + } + Model readFileModel() throws ModelBuilderException { Model model = cache(request.getSource(), FILE, this::doReadFileModel); // set the file model in the result outside the cache @@ -1248,7 +1309,8 @@ public class DefaultModelBuilder implements ModelBuilder { } } - if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM) { + if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM + || request.getRequestType() == ModelBuilderRequest.RequestType.CONSUMER_POM) { model = model.withPomFile(modelSource.getPath()); Parent parent = model.getParent(); @@ -1256,10 +1318,11 @@ public class DefaultModelBuilder implements ModelBuilder { String groupId = parent.getGroupId(); String artifactId = parent.getArtifactId(); String version = parent.getVersion(); - String path = Optional.ofNullable(parent.getRelativePath()).orElse(".."); - if (version == null && !path.isEmpty()) { + String path = parent.getRelativePath(); + if ((groupId == null || artifactId == null || version == null) + && (path == null || !path.isEmpty())) { Path pomFile = model.getPomFile(); - Path relativePath = Paths.get(path); + Path relativePath = Paths.get(path != null ? path : ".."); Path pomPath = pomFile.resolveSibling(relativePath).normalize(); if (Files.isDirectory(pomPath)) { pomPath = modelProcessor.locateExistingPom(pomPath); @@ -1267,18 +1330,23 @@ public class DefaultModelBuilder implements ModelBuilder { if (pomPath != null && Files.isRegularFile(pomPath)) { Model parentModel = derive(ModelSource.fromPath(pomPath)).readFileModel(); - if (parentModel != null) { - String parentGroupId = getGroupId(parentModel); - String parentArtifactId = parentModel.getArtifactId(); - String parentVersion = getVersion(parentModel); - if ((groupId == null || groupId.equals(parentGroupId)) - && (artifactId == null || artifactId.equals(parentArtifactId))) { - model = model.withParent(parent.with() - .groupId(parentGroupId) - .artifactId(parentArtifactId) - .version(parentVersion) - .build()); - } + String parentGroupId = getGroupId(parentModel); + String parentArtifactId = parentModel.getArtifactId(); + String parentVersion = getVersion(parentModel); + if ((groupId == null || groupId.equals(parentGroupId)) + && (artifactId == null || artifactId.equals(parentArtifactId)) + && (version == null || version.equals(parentVersion))) { + model = model.withParent(parent.with() + .groupId(parentGroupId) + .artifactId(parentArtifactId) + .version(parentVersion) + .build()); + } else { + mismatchRelativePathAndGA(model, parentGroupId, parentArtifactId); + } + } else { + if (!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) { + wrongParentRelativePath(model); } } } @@ -1369,7 +1437,8 @@ public class DefaultModelBuilder implements ModelBuilder { Model rawModel = readFileModel(); if (!MODEL_VERSION_4_0_0.equals(rawModel.getModelVersion()) - && request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM) { + && (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM + || request.getRequestType() == ModelBuilderRequest.RequestType.CONSUMER_POM)) { rawModel = transformFileToRaw(rawModel); } @@ -1600,7 +1669,7 @@ public class DefaultModelBuilder implements ModelBuilder { .source(importSource) .repositories(repositories) .build(); - DefaultModelBuilderSession modelBuilderSession = new DefaultModelBuilderSession(importRequest); + DefaultModelBuilderSession modelBuilderSession = derive(importRequest); // build the effective model modelBuilderSession.buildEffectiveModel(importIds); importResult = modelBuilderSession.result; @@ -1812,15 +1881,6 @@ public class DefaultModelBuilder implements ModelBuilder { || rawChildModelVersion.equals("${project.parent.version}"); } - private ModelSource getParentPomFile(Model childModel, ModelSource source) { - String parentPath = childModel.getParent().getRelativePath(); - if (parentPath == null || parentPath.isEmpty()) { - return null; - } else { - return source.resolve(modelProcessor::locateExistingPom, parentPath); - } - } - private Model getSuperModel(String modelVersion) { return superPomProvider.getSuperPom(modelVersion); } diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java index 94eb22f1dc..8867a82573 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java @@ -326,6 +326,24 @@ public class DefaultModelValidator implements ModelValidator { "is either LATEST or RELEASE (both of them are being deprecated)", parent); } + + if (parent.getRelativePath() != null + && !parent.getRelativePath().isEmpty() + && (parent.getGroupId() != null && !parent.getGroupId().isEmpty() + || parent.getArtifactId() != null + && !parent.getArtifactId().isEmpty()) + && validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_4_0 + && VALID_MODEL_VERSIONS.contains(m.getModelVersion()) + && !Objects.equals(m.getModelVersion(), ModelBuilder.MODEL_VERSION_4_0_0)) { + addViolation( + problems, + Severity.WARNING, + Version.BASE, + "parent.relativePath", + null, + "only specify relativePath or groupId/artifactId in modelVersion 4.1.0", + parent); + } } if (validationLevel == ModelValidator.VALIDATION_LEVEL_MINIMAL) { diff --git a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java index d2953b2916..265dabdcd7 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java @@ -21,10 +21,14 @@ package org.apache.maven.internal.transformation.impl; import javax.inject.Inject; import javax.inject.Named; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import org.apache.maven.api.SessionData; @@ -35,42 +39,17 @@ import org.apache.maven.api.model.Model; import org.apache.maven.api.model.ModelBase; import org.apache.maven.api.model.Profile; import org.apache.maven.api.model.Repository; -import org.apache.maven.api.services.Interpolator; import org.apache.maven.api.services.ModelBuilder; import org.apache.maven.api.services.ModelBuilderException; import org.apache.maven.api.services.ModelBuilderRequest; import org.apache.maven.api.services.ModelBuilderResult; -import org.apache.maven.api.services.ModelProblemCollector; import org.apache.maven.api.services.ModelSource; -import org.apache.maven.api.services.SuperPomProvider; -import org.apache.maven.api.services.model.DependencyManagementImporter; -import org.apache.maven.api.services.model.DependencyManagementInjector; -import org.apache.maven.api.services.model.InheritanceAssembler; +import org.apache.maven.api.services.Source; import org.apache.maven.api.services.model.LifecycleBindingsInjector; -import org.apache.maven.api.services.model.ModelCacheFactory; -import org.apache.maven.api.services.model.ModelInterpolator; -import org.apache.maven.api.services.model.ModelNormalizer; -import org.apache.maven.api.services.model.ModelPathTranslator; -import org.apache.maven.api.services.model.ModelProcessor; -import org.apache.maven.api.services.model.ModelResolver; -import org.apache.maven.api.services.model.ModelUrlNormalizer; -import org.apache.maven.api.services.model.ModelValidator; -import org.apache.maven.api.services.model.ModelVersionParser; -import org.apache.maven.api.services.model.PluginConfigurationExpander; -import org.apache.maven.api.services.model.PluginManagementInjector; -import org.apache.maven.api.services.model.ProfileActivationContext; -import org.apache.maven.api.services.model.ProfileInjector; -import org.apache.maven.api.services.model.ProfileSelector; -import org.apache.maven.api.spi.ModelTransformer; import org.apache.maven.internal.impl.InternalSession; -import org.apache.maven.internal.impl.model.DefaultModelBuilder; -import org.apache.maven.internal.impl.model.DefaultProfileSelector; -import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator; import org.apache.maven.model.v4.MavenModelVersion; import org.apache.maven.project.MavenProject; import org.eclipse.aether.RepositorySystemSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Named class DefaultConsumerPomBuilder implements ConsumerPomBuilder { @@ -78,74 +57,14 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder { public static final String POM_PACKAGING = "pom"; - private final ProfileInjector profileInjector; - private final InheritanceAssembler inheritanceAssembler; - private final DependencyManagementImporter dependencyManagementImporter; - private final DependencyManagementInjector dependencyManagementInjector; private final LifecycleBindingsInjector lifecycleBindingsInjector; - private final ModelInterpolator modelInterpolator; - private final ModelNormalizer modelNormalizer; - private final ModelPathTranslator modelPathTranslator; - private final ModelProcessor modelProcessor; - private final ModelUrlNormalizer modelUrlNormalizer; - private final ModelValidator modelValidator; - private final PluginConfigurationExpander pluginConfigurationExpander; - private final PluginManagementInjector pluginManagementInjector; - private final SuperPomProvider superPomProvider; - private final ModelVersionParser versionParser; - private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator; - private final List transformers; - private final ModelCacheFactory modelCacheFactory; - private final ModelResolver modelResolver; - private final Interpolator interpolator; @Inject @SuppressWarnings("checkstyle:ParameterNumber") - DefaultConsumerPomBuilder( - ProfileInjector profileInjector, - InheritanceAssembler inheritanceAssembler, - DependencyManagementImporter dependencyManagementImporter, - DependencyManagementInjector dependencyManagementInjector, - LifecycleBindingsInjector lifecycleBindingsInjector, - ModelInterpolator modelInterpolator, - ModelNormalizer modelNormalizer, - ModelPathTranslator modelPathTranslator, - ModelProcessor modelProcessor, - ModelUrlNormalizer modelUrlNormalizer, - ModelValidator modelValidator, - PluginConfigurationExpander pluginConfigurationExpander, - PluginManagementInjector pluginManagementInjector, - SuperPomProvider superPomProvider, - ModelVersionParser versionParser, - ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator, - List transformers, - ModelCacheFactory modelCacheFactory, - ModelResolver modelResolver, - Interpolator interpolator) { - this.profileInjector = profileInjector; - this.inheritanceAssembler = inheritanceAssembler; - this.dependencyManagementImporter = dependencyManagementImporter; - this.dependencyManagementInjector = dependencyManagementInjector; + DefaultConsumerPomBuilder(LifecycleBindingsInjector lifecycleBindingsInjector) { this.lifecycleBindingsInjector = lifecycleBindingsInjector; - this.modelInterpolator = modelInterpolator; - this.modelNormalizer = modelNormalizer; - this.modelPathTranslator = modelPathTranslator; - this.modelProcessor = modelProcessor; - this.modelUrlNormalizer = modelUrlNormalizer; - this.modelValidator = modelValidator; - this.pluginConfigurationExpander = pluginConfigurationExpander; - this.pluginManagementInjector = pluginManagementInjector; - this.superPomProvider = superPomProvider; - this.versionParser = versionParser; - this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator; - this.transformers = transformers; - this.modelCacheFactory = modelCacheFactory; - this.modelResolver = modelResolver; - this.interpolator = interpolator; } - private final Logger logger = LoggerFactory.getLogger(getClass()); - @Override public Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException { Model model = project.getModel().getDelegate(); @@ -174,49 +93,18 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder { private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException { - ProfileSelector customSelector = new DefaultProfileSelector() { - @Override - public List getActiveProfiles( - Collection profiles, ProfileActivationContext context, ModelProblemCollector problems) { - return new ArrayList<>(); - } - }; - // TODO: the custom selector should be used as a flag on the request - DefaultModelBuilder modelBuilder = new DefaultModelBuilder( - modelProcessor, - modelValidator, - modelNormalizer, - modelInterpolator, - modelPathTranslator, - modelUrlNormalizer, - superPomProvider, - inheritanceAssembler, - customSelector, - profileInjector, - pluginManagementInjector, - dependencyManagementInjector, - dependencyManagementImporter, - pluginConfigurationExpander, - profileActivationFilePathInterpolator, - versionParser, - transformers, - modelCacheFactory, - modelResolver, - interpolator); InternalSession iSession = InternalSession.from(session); ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder(); - request.requestType(ModelBuilderRequest.RequestType.BUILD_POM); + request.requestType(ModelBuilderRequest.RequestType.CONSUMER_POM); request.session(iSession); - request.source(ModelSource.fromPath(src)); + // in order to resolve parents, we need to fake being at the correct location + request.source(new PomConsumerModelSource(project.getModel().getPomPath(), src)); request.locationTracking(false); request.systemProperties(session.getSystemProperties()); request.userProperties(session.getUserProperties()); request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings); ModelBuilder.ModelBuilderSession mbSession = iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class)); - if (mbSession == null) { - mbSession = modelBuilder.newSession(); - } return mbSession.build(request.build()); } @@ -320,4 +208,63 @@ class DefaultConsumerPomBuilder implements ConsumerPomBuilder { .filter(r -> !org.apache.maven.api.Repository.CENTRAL_ID.equals(r.getId())) .collect(Collectors.toList()); } + + static class PomConsumerModelSource implements ModelSource { + final Path path; + final Path src; + + PomConsumerModelSource(Path path, Path src) { + this.path = path; + this.src = src; + } + + @Override + public Path getPath() { + return path; + } + + @Override + public InputStream openStream() throws IOException { + return Files.newInputStream(src); + } + + @Override + public String getLocation() { + return src.toString(); + } + + @Override + public Source resolve(String relative) { + return ModelSource.fromPath(path.resolve(relative)); + } + + @Override + public ModelSource resolve(ModelLocator locator, String relative) { + String norm = relative.replace('\\', File.separatorChar).replace('/', File.separatorChar); + Path path = getPath().getParent().resolve(norm); + Path relatedPom = locator.locateExistingPom(path); + if (relatedPom != null) { + return ModelSource.fromPath(relatedPom); + } + return null; + } + + @Override + public boolean equals(Object o) { + return this == o + || o.getClass() == getClass() + && Objects.equals(path, ((PomConsumerModelSource) o).path) + && Objects.equals(src, ((PomConsumerModelSource) o).src); + } + + @Override + public int hashCode() { + return Objects.hash(path, src); + } + + @Override + public String toString() { + return "PomConsumerModelSource[" + "path=" + path + ']'; + } + } } diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java index 236ffe0055..954d3faf6d 100644 --- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java @@ -74,6 +74,7 @@ import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.bridge.MavenRepositorySystem; import org.apache.maven.internal.impl.InternalSession; import org.apache.maven.internal.impl.resolver.ArtifactDescriptorUtils; +import org.apache.maven.model.building.DefaultModelProblem; import org.apache.maven.model.building.FileModelSource; import org.apache.maven.model.building.ModelBuildingRequest; import org.apache.maven.model.building.ModelSource2; @@ -499,6 +500,11 @@ public class DefaultProjectBuilder implements ProjectBuilder { List results = new ArrayList<>(); List allModels = results(result).toList(); for (ModelBuilderResult r : allModels) { + List problems = new ArrayList<>(r.getProblems()); + results(r) + .filter(c -> c != r) + .flatMap(c -> c.getProblems().stream()) + .forEach(problems::remove); if (r.getEffectiveModel() != null) { File pom = r.getSource().getPath().toFile(); MavenProject project = @@ -510,7 +516,7 @@ public class DefaultProjectBuilder implements ProjectBuilder { project.setExecutionRoot(pom.equals(pomFile)); initProject(project, r); project.setCollectedProjects(results(r) - .filter(cr -> cr != r) + .filter(cr -> cr != r && cr.getEffectiveModel() != null) .map(cr -> projectIndex.get(cr.getEffectiveModel().getId())) .collect(Collectors.toList())); @@ -518,10 +524,9 @@ public class DefaultProjectBuilder implements ProjectBuilder { if (request.isResolveDependencies()) { resolutionResult = resolveDependencies(project); } - results.add( - new DefaultProjectBuildingResult(project, convert(result.getProblems()), resolutionResult)); + results.add(new DefaultProjectBuildingResult(project, convert(problems), resolutionResult)); } else { - results.add(new DefaultProjectBuildingResult(null, convert(result.getProblems()), null)); + results.add(new DefaultProjectBuildingResult(null, convert(problems), null)); } } return results; @@ -535,20 +540,21 @@ public class DefaultProjectBuilder implements ProjectBuilder { if (problems == null) { return null; } - return problems.stream() - .map(p -> (org.apache.maven.model.building.ModelProblem) - new org.apache.maven.model.building.DefaultModelProblem( - p.getMessage(), - org.apache.maven.model.building.ModelProblem.Severity.valueOf( - p.getSeverity().name()), - org.apache.maven.model.building.ModelProblem.Version.valueOf( - p.getVersion().name()), - p.getSource(), - p.getLineNumber(), - p.getColumnNumber(), - p.getModelId(), - p.getException())) - .toList(); + return problems.stream().map(p -> convert(p)).toList(); + } + + private static org.apache.maven.model.building.ModelProblem convert(ModelProblem p) { + return new DefaultModelProblem( + p.getMessage(), + org.apache.maven.model.building.ModelProblem.Severity.valueOf( + p.getSeverity().name()), + org.apache.maven.model.building.ModelProblem.Version.valueOf( + p.getVersion().name()), + p.getSource(), + p.getLineNumber(), + p.getColumnNumber(), + p.getModelId(), + p.getException()); } @SuppressWarnings({"checkstyle:methodlength", "deprecation"}) diff --git a/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java index 0cf6c5c92c..2b58df1dc9 100644 --- a/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java +++ b/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java @@ -20,9 +20,6 @@ package org.apache.maven.internal.transformation.impl; import javax.inject.Inject; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -35,17 +32,17 @@ import org.apache.maven.api.SessionData; import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Parent; import org.apache.maven.api.services.ModelBuilder; +import org.apache.maven.api.services.ModelBuilderRequest; import org.apache.maven.api.services.ModelSource; import org.apache.maven.api.services.model.ModelResolver; import org.apache.maven.api.services.model.ModelResolverException; import org.apache.maven.api.spi.ModelTransformer; import org.apache.maven.api.spi.ModelTransformerException; import org.apache.maven.di.Injector; +import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.internal.impl.InternalMavenSession; import org.apache.maven.internal.impl.InternalSession; -import org.apache.maven.internal.impl.model.DefaultModelBuilder; import org.apache.maven.internal.transformation.AbstractRepositoryTestCase; -import org.apache.maven.model.v4.MavenStaxReader; import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,6 +55,9 @@ public class ConsumerPomBuilderTest extends AbstractRepositoryTestCase { @Inject ConsumerPomBuilder builder; + @Inject + ModelBuilder modelBuilder; + @BeforeEach void setupTransformerContext() throws Exception { // We need to hack things a bit here to get the transformer context to work @@ -71,33 +71,30 @@ public class ConsumerPomBuilderTest extends AbstractRepositoryTestCase { // to maven central getContainer().lookup(Injector.class).bindImplicit(MyModelResolver.class); InternalSession iSession = InternalSession.from(session); - // set up the transformers - List transformers = List.of(new CIFriendlyVersionModelTransformer(iSession)); - Field transformersField = DefaultModelBuilder.class.getDeclaredField("transformers"); - transformersField.setAccessible(true); - DefaultModelBuilder modelBuilder = (DefaultModelBuilder) getContainer().lookup(ModelBuilder.class); - transformersField.set(modelBuilder, transformers); - transformersField = DefaultConsumerPomBuilder.class.getDeclaredField("transformers"); - transformersField.setAccessible(true); - transformersField.set(builder, transformers); // set up the model resolver iSession.getData().set(SessionData.key(ModelResolver.class), new MyModelResolver()); } @Test void testTrivialConsumer() throws Exception { - MavenProject project; - Path file = Paths.get("src/test/resources/consumer/trivial/child/pom.xml"); - try (InputStream inputStream = Files.newInputStream(file)) { - org.apache.maven.model.Model model = - new org.apache.maven.model.Model(new MavenStaxReader().read(inputStream)); - project = new MavenProject(model); - project.setOriginalModel(model); - } InternalMavenSession.from(InternalSession.from(session)) .getMavenSession() .getRequest() .setRootDirectory(Paths.get("src/test/resources/consumer/trivial")); + + Path file = Paths.get("src/test/resources/consumer/trivial/child/pom.xml"); + + ModelBuilder.ModelBuilderSession mbs = modelBuilder.newSession(); + InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class), mbs); + Model orgModel = mbs.build(ModelBuilderRequest.builder() + .session(InternalSession.from(session)) + .source(ModelSource.fromPath(file)) + .requestType(ModelBuilderRequest.RequestType.BUILD_POM) + .build()) + .getEffectiveModel(); + + MavenProject project = new MavenProject(orgModel); + project.setOriginalModel(new org.apache.maven.model.Model(orgModel)); Model model = builder.build(session, project, file); assertNotNull(model); @@ -105,24 +102,26 @@ public class ConsumerPomBuilderTest extends AbstractRepositoryTestCase { @Test void testSimpleConsumer() throws Exception { - MavenProject project; + MavenExecutionRequest request = InternalMavenSession.from(InternalSession.from(session)) + .getMavenSession() + .getRequest(); + request.setRootDirectory(Paths.get("src/test/resources/consumer/simple")); + request.getUserProperties().setProperty("changelist", "MNG6957"); + Path file = Paths.get("src/test/resources/consumer/simple/simple-parent/simple-weather/pom.xml"); - InternalMavenSession.from(InternalSession.from(session)) - .getMavenSession() - .getRequest() - .getUserProperties() - .setProperty("changelist", "MNG6957"); - try (InputStream inputStream = Files.newInputStream(file)) { - org.apache.maven.model.Model model = - new org.apache.maven.model.Model(new MavenStaxReader().read(inputStream)); - project = new MavenProject(model); - project.setOriginalModel(model); - } - InternalMavenSession.from(InternalSession.from(session)) - .getMavenSession() - .getRequest() - .setRootDirectory(Paths.get("src/test/resources/consumer/simple")); + ModelBuilder.ModelBuilderSession mbs = modelBuilder.newSession(); + InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class), mbs); + Model orgModel = mbs.build(ModelBuilderRequest.builder() + .session(InternalSession.from(session)) + .source(ModelSource.fromPath(file)) + .requestType(ModelBuilderRequest.RequestType.BUILD_POM) + .build()) + .getEffectiveModel(); + + MavenProject project = new MavenProject(orgModel); + project.setOriginalModel(new org.apache.maven.model.Model(orgModel)); + request.setRootDirectory(Paths.get("src/test/resources/consumer/simple")); Model model = builder.build(session, project, file); assertNotNull(model); diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java index 990c2cfb80..8dd4de675c 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java @@ -1616,8 +1616,10 @@ public class DefaultModelBuilder implements ModelBuilder { } String parentPath = childModel.getParent().getRelativePath(); - - if (parentPath == null || parentPath.isEmpty()) { + if (parentPath == null) { + parentPath = ".."; + childModel.getParent().setRelativePath(parentPath); + } else if (parentPath.isEmpty()) { return null; }