This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 69bc993b80 [MNG-7945] Fix profile settings being injected into 
consumer POM (#1323)
69bc993b80 is described below

commit 69bc993b8089a2d3d1ddfd6c7d4f8dc6cc205995
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Nov 28 18:17:10 2023 +0100

    [MNG-7945] Fix profile settings being injected into consumer POM (#1323)
---
 .../ConsumerPomArtifactTransformer.java            | 293 +--------------------
 .../transformation/OnChangeTransformer.java        | 104 --------
 .../TransformationFailedException.java             |  30 +++
 .../transformation/TransformedArtifact.java        | 142 ----------
 .../transformation/impl/ConsumerPomBuilder.java    |  40 +++
 .../DefaultConsumerPomArtifactTransformer.java     | 207 +++++++++++++++
 .../impl/DefaultConsumerPomBuilder.java            | 230 ++++++++++++++++
 .../transformation/impl/TransformedArtifact.java   | 148 +++++++++++
 .../impl/TransformedArtifactHandler.java           |  66 +++++
 .../lifecycle/internal/LifecycleModuleBuilder.java |   2 +-
 .../maven/AbstractCoreMavenComponentTestCase.java  |   7 +-
 .../transformation/AbstractRepositoryTestCase.java |  78 ++++++
 .../ConsumerPomArtifactTransformerTest.java        |  19 +-
 .../impl/ConsumerPomBuilderTest.java               |  68 +++++
 .../apache/maven/project/ProjectBuilderTest.java   |   3 +-
 .../src/test/resources/consumer/simple/pom.xml     |  32 +++
 .../consumer/simple/simple-parent/pom.xml          |  51 ++++
 .../simple/simple-parent/simple-testutils/pom.xml  |  27 ++
 .../simple/simple-parent/simple-weather/pom.xml    |  38 +++
 .../simple/simple-parent/simple-webapp/pom.xml     |  47 ++++
 .../simple/simple-parent/utils-parent/pom.xml      |  27 ++
 .../test/resources/consumer/trivial/child/pom.xml  |  18 ++
 .../src/test/resources/consumer/trivial/pom.xml    |  47 ++++
 .../pom.xml}                                       |   0
 .../maven/model/building/DefaultModelBuilder.java  |   2 +-
 .../building/DefaultTransformerContextBuilder.java |  50 ++++
 26 files changed, 1228 insertions(+), 548 deletions(-)

diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
index 383fd64908..ef2901a652 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
@@ -18,45 +18,10 @@
  */
 package org.apache.maven.internal.transformation;
 
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-import javax.xml.stream.XMLStreamException;
-
 import java.io.IOException;
-import java.io.Writer;
-import java.lang.reflect.Method;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.stream.Collectors;
 
-import org.apache.maven.api.Repository;
-import org.apache.maven.api.feature.Features;
-import org.apache.maven.api.model.DistributionManagement;
-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.model.building.FileModelSource;
-import org.apache.maven.model.building.ModelBuilder;
-import org.apache.maven.model.building.ModelBuildingRequest;
-import org.apache.maven.model.building.ModelCache;
-import org.apache.maven.model.building.Result;
-import org.apache.maven.model.building.TransformerContext;
-import org.apache.maven.model.v4.MavenModelVersion;
-import org.apache.maven.model.v4.MavenStaxWriter;
 import org.apache.maven.project.MavenProject;
-import org.apache.maven.project.artifact.ProjectArtifact;
-import org.apache.maven.repository.internal.DefaultModelCache;
 import org.eclipse.aether.RepositorySystemSession;
-import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.deployment.DeployRequest;
 import org.eclipse.aether.installation.InstallRequest;
 
@@ -65,260 +30,12 @@ import org.eclipse.aether.installation.InstallRequest;
  *
  * @since TBD
  */
-@Singleton
-@Named("consumer-pom")
-public final class ConsumerPomArtifactTransformer {
-
-    private static final String BOM_PACKAGING = "bom";
-
-    public static final String POM_PACKAGING = "pom";
-
-    private static final String CONSUMER_POM_CLASSIFIER = "consumer";
-
-    private static final String BUILD_POM_CLASSIFIER = "build";
-
-    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 final Set<Path> toDelete = new CopyOnWriteArraySet<>();
-
-    private final ModelBuilder modelBuilder;
-
-    @Inject
-    ConsumerPomArtifactTransformer(ModelBuilder modelBuilder) {
-        this.modelBuilder = modelBuilder;
-    }
-
-    public void injectTransformedArtifacts(MavenProject project, 
RepositorySystemSession session) 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.buildConsumer(session.getUserProperties())) {
-            Path buildDir =
-                    project.getBuild() != null ? 
Paths.get(project.getBuild().getDirectory()) : null;
-            if (buildDir != null) {
-                Files.createDirectories(buildDir);
-            }
-            Path consumer = buildDir != null
-                    ? Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER + 
"-", ".pom")
-                    : Files.createTempFile(CONSUMER_POM_CLASSIFIER + "-", 
".pom");
-            deferDeleteFile(consumer);
-
-            project.addAttachedArtifact(createConsumerPomArtifact(project, 
consumer, session));
-        } else if (project.getModel().getDelegate().isRoot()) {
-            throw new IllegalStateException(
-                    "The use of the root attribute on the model requires the 
buildconsumer feature to be active");
-        }
-    }
-
-    public ConsumerPomArtifact createConsumerPomArtifact(
-            MavenProject project, Path consumer, RepositorySystemSession 
session) {
-        return new ConsumerPomArtifact(project, consumer, session);
-    }
-
-    private void deferDeleteFile(Path generatedFile) {
-        toDelete.add(generatedFile.toAbsolutePath());
-    }
-
-    @PreDestroy
-    private void doDeleteFiles() {
-        for (Path file : toDelete) {
-            try {
-                Files.delete(file);
-            } catch (IOException e) {
-                // ignore, we did our best...
-            }
-        }
-    }
-
-    public InstallRequest remapInstallArtifacts(RepositorySystemSession 
session, InstallRequest request) {
-        if (Features.buildConsumer(session.getUserProperties()) && 
consumerPomPresent(request.getArtifacts())) {
-            request.setArtifacts(replacePom(request.getArtifacts()));
-        }
-        return request;
-    }
-
-    public DeployRequest remapDeployArtifacts(RepositorySystemSession session, 
DeployRequest request) {
-        if (Features.buildConsumer(session.getUserProperties()) && 
consumerPomPresent(request.getArtifacts())) {
-            request.setArtifacts(replacePom(request.getArtifacts()));
-        }
-        return request;
-    }
-
-    private boolean consumerPomPresent(Collection<Artifact> artifacts) {
-        return artifacts.stream()
-                .anyMatch(a -> "pom".equals(a.getExtension()) && 
CONSUMER_POM_CLASSIFIER.equals(a.getClassifier()));
-    }
-
-    private Collection<Artifact> replacePom(Collection<Artifact> artifacts) {
-        List<Artifact> consumers = new ArrayList<>();
-        List<Artifact> mains = new ArrayList<>();
-        for (Artifact artifact : artifacts) {
-            if ("pom".equals(artifact.getExtension()) || 
artifact.getExtension().startsWith("pom.")) {
-                if (CONSUMER_POM_CLASSIFIER.equals(artifact.getClassifier())) {
-                    consumers.add(artifact);
-                } else if ("".equals(artifact.getClassifier())) {
-                    mains.add(artifact);
-                }
-            }
-        }
-        if (!mains.isEmpty() && !consumers.isEmpty()) {
-            ArrayList<Artifact> result = new ArrayList<>(artifacts);
-            for (Artifact main : mains) {
-                result.remove(main);
-                result.add(new DefaultArtifact(
-                        main.getGroupId(),
-                        main.getArtifactId(),
-                        BUILD_POM_CLASSIFIER,
-                        main.getExtension(),
-                        main.getVersion(),
-                        main.getProperties(),
-                        main.getFile()));
-            }
-            for (Artifact consumer : consumers) {
-                result.remove(consumer);
-                result.add(new DefaultArtifact(
-                        consumer.getGroupId(),
-                        consumer.getArtifactId(),
-                        "",
-                        consumer.getExtension(),
-                        consumer.getVersion(),
-                        consumer.getProperties(),
-                        consumer.getFile()));
-            }
-            artifacts = result;
-        }
-        return artifacts;
-    }
-
-    /**
-     * Consumer POM is transformed from original POM.
-     */
-    class ConsumerPomArtifact extends TransformedArtifact {
-
-        private MavenProject project;
-        private RepositorySystemSession session;
-
-        ConsumerPomArtifact(MavenProject mavenProject, Path target, 
RepositorySystemSession session) {
-            super(
-                    new ProjectArtifact(mavenProject),
-                    () -> mavenProject.getFile().toPath(),
-                    CONSUMER_POM_CLASSIFIER,
-                    "pom",
-                    target);
-            this.project = mavenProject;
-            this.session = session;
-        }
-
-        @Override
-        public void transform(Path src, Path dest) {
-            Model model = project.getModel().getDelegate();
-            transform(src, dest, model);
-        }
-
-        void transform(Path src, Path dest, Model model) {
-            Model consumer = null;
-            String version;
-
-            String packaging = model.getPackaging();
-            if (POM_PACKAGING.equals(packaging)) {
-                // This is a bit of a hack, but all models are cached, so not 
sure why we'd need to parse it again
-                ModelCache cache = DefaultModelCache.newInstance(session);
-                Object modelData = cache.get(new 
FileModelSource(src.toFile()), "raw");
-                if (modelData != null) {
-                    try {
-                        Method getModel = 
modelData.getClass().getMethod("getModel");
-                        getModel.setAccessible(true);
-                        org.apache.maven.model.Model cachedModel =
-                                (org.apache.maven.model.Model) 
getModel.invoke(modelData);
-                        consumer = cachedModel.getDelegate();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-
-                if (consumer == null) {
-                    TransformerContext context =
-                            (TransformerContext) 
session.getData().get(TransformerContext.KEY);
-                    Result<? extends org.apache.maven.model.Model> result = 
modelBuilder.buildRawModel(
-                            src.toFile(), 
ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false, context);
-                    if (result.hasErrors()) {
-                        throw new IllegalStateException(
-                                "Unable to build POM " + src,
-                                
result.getProblems().iterator().next().getException());
-                    }
-                    consumer = result.get().getDelegate();
-                }
-
-                // raw to consumer transform
-                consumer = consumer.withRoot(false).withModules(null);
-                if (consumer.getParent() != null) {
-                    consumer = 
consumer.withParent(consumer.getParent().withRelativePath(null));
-                }
-
-                if (!consumer.isPreserveModelVersion()) {
-                    consumer = consumer.withPreserveModelVersion(false);
-                    version = new 
MavenModelVersion().getModelVersion(consumer);
-                    consumer = consumer.withModelVersion(version);
-                } else {
-                    version = consumer.getModelVersion();
-                }
-            } else {
-                Model.Builder builder = prune(
-                        Model.newBuilder(model, true)
-                                .preserveModelVersion(false)
-                                .root(false)
-                                .parent(null)
-                                .build(null),
-                        model);
-                boolean isBom = BOM_PACKAGING.equals(packaging);
-                if (isBom) {
-                    builder.packaging(POM_PACKAGING);
-                }
-                builder.profiles(model.getProfiles().stream()
-                        .map(p -> prune(Profile.newBuilder(p, true), 
p).build())
-                        .collect(Collectors.toList()));
-                consumer = builder.build();
-                version = new MavenModelVersion().getModelVersion(consumer);
-                consumer = consumer.withModelVersion(version);
-            }
+public interface ConsumerPomArtifactTransformer {
 
-            try {
-                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, consumer);
-                }
-            } catch (XMLStreamException | IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
+    InstallRequest remapInstallArtifacts(RepositorySystemSession session, 
InstallRequest request);
 
-    private static <T extends ModelBase.Builder> T prune(T builder, ModelBase 
model) {
-        builder.properties(null).reporting(null);
-        if (model.getDistributionManagement() != null
-                && model.getDistributionManagement().getRelocation() != null) {
-            // keep relocation only
-            builder.distributionManagement(DistributionManagement.newBuilder()
-                    
.relocation(model.getDistributionManagement().getRelocation())
-                    .build());
-        }
-        // only keep repositories others than 'central'
-        
builder.pluginRepositories(pruneRepositories(model.getPluginRepositories()));
-        builder.repositories(pruneRepositories(model.getRepositories()));
-        return builder;
-    }
+    DeployRequest remapDeployArtifacts(RepositorySystemSession session, 
DeployRequest request);
 
-    private static List<org.apache.maven.api.model.Repository> 
pruneRepositories(
-            List<org.apache.maven.api.model.Repository> repositories) {
-        return repositories.stream()
-                .filter(r -> !Repository.CENTRAL_ID.equals(r.getId()))
-                .collect(Collectors.toList());
-    }
+    void injectTransformedArtifacts(RepositorySystemSession repositorySession, 
MavenProject currentProject)
+            throws IOException;
 }
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/OnChangeTransformer.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/OnChangeTransformer.java
deleted file mode 100644
index d777ed2068..0000000000
--- 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/OnChangeTransformer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-import static java.util.Objects.requireNonNull;
-
-/**
- * Keeps transformed file up-to-date relative to its source file. It manages 
state (i.e. hashing the content) using
- * passed in stateFunction, and transforms when needed using passed in 
transformer bi-consumer.
- * <p>
- * Covered cases:
- * <ul>
- *     <li>when source supplier returns {@code null}, this class will return 
{@code null}.</li>
- *     <li>when source supplier returns non existing path, this class will 
return non existing path.</li>
- *     <li>when source supplier returns existing path, this class will ensure 
transformation is in sync.</li>
- * </ul>
- *
- * @since TBD
- */
-final class OnChangeTransformer implements Supplier<Path> {
-
-    private final Supplier<Path> source;
-
-    private final Path target;
-
-    private final Function<Path, String> stateFunction;
-
-    private final BiConsumer<Path, Path> transformerConsumer;
-
-    private final AtomicReference<String> sourceState;
-
-    OnChangeTransformer(
-            Supplier<Path> source,
-            Path target,
-            Function<Path, String> stateFunction,
-            BiConsumer<Path, Path> transformerConsumer) {
-        this.source = requireNonNull(source);
-        this.target = requireNonNull(target);
-        this.stateFunction = requireNonNull(stateFunction);
-        this.transformerConsumer = requireNonNull(transformerConsumer);
-        this.sourceState = new AtomicReference<>(null);
-    }
-
-    @Override
-    public synchronized Path get() {
-        String state = mayUpdate();
-        if (state == null) {
-            return null;
-        }
-        return target;
-    }
-
-    private String mayUpdate() {
-        String result;
-        try {
-            Path src = source.get();
-            if (src == null) {
-                Files.deleteIfExists(target);
-                result = null;
-            } else if (!Files.exists(src)) {
-                Files.deleteIfExists(target);
-                result = "";
-            } else {
-                String current = stateFunction.apply(src);
-                String existing = sourceState.get();
-                if (!Objects.equals(current, existing)) {
-                    transformerConsumer.accept(src, target);
-                    Files.setLastModifiedTime(target, 
Files.getLastModifiedTime(src));
-                }
-                result = current;
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        sourceState.set(result);
-        return result;
-    }
-}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformationFailedException.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformationFailedException.java
new file mode 100644
index 0000000000..c45dd758b7
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformationFailedException.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Exception that may be thrown by the {@link 
org.apache.maven.artifact.Artifact#getFile()}
+ * implementation.
+ */
+public class TransformationFailedException extends RuntimeException {
+
+    public TransformationFailedException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformedArtifact.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformedArtifact.java
deleted file mode 100644
index 9c88336558..0000000000
--- 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/TransformedArtifact.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 java.io.File;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.util.function.Supplier;
-
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.DefaultArtifact;
-import org.apache.maven.artifact.handler.ArtifactHandler;
-
-import static java.util.Objects.requireNonNull;
-
-/**
- * Transformed artifact is derived with some transformation from source 
artifact.
- *
- * @since TBD
- */
-abstract class TransformedArtifact extends DefaultArtifact {
-
-    private final OnChangeTransformer onChangeTransformer;
-
-    TransformedArtifact(
-            Artifact source, Supplier<Path> sourcePathProvider, String 
classifier, String extension, Path targetPath) {
-        super(
-                source.getGroupId(),
-                source.getArtifactId(),
-                source.getVersionRange(),
-                source.getScope(),
-                extension,
-                classifier,
-                new TransformedArtifactHandler(
-                        classifier, extension, 
source.getArtifactHandler().getPackaging()));
-        this.onChangeTransformer =
-                new OnChangeTransformer(sourcePathProvider, targetPath, 
TransformedArtifact::sha1, this::transform);
-    }
-
-    @Override
-    public boolean isResolved() {
-        return getFile() != null;
-    }
-
-    @Override
-    public void setFile(File file) {
-        throw new IllegalStateException("transformed artifact file cannot be 
set");
-    }
-
-    @Override
-    public File getFile() {
-        Path result = onChangeTransformer.get();
-        if (result == null) {
-            return null;
-        }
-        return result.toFile();
-    }
-
-    private static final int BUFFER_SIZE = 8192;
-
-    private static String sha1(Path path) {
-        try {
-            MessageDigest md = MessageDigest.getInstance("SHA-1");
-            try (InputStream fis = Files.newInputStream(path)) {
-                byte[] buffer = new byte[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();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    protected abstract void transform(Path src, Path dst);
-
-    private static class TransformedArtifactHandler implements ArtifactHandler 
{
-        private final String classifier;
-
-        private final String extension;
-
-        private final String packaging;
-
-        private TransformedArtifactHandler(String classifier, String 
extension, String packaging) {
-            this.classifier = classifier;
-            this.extension = requireNonNull(extension);
-            this.packaging = requireNonNull(packaging);
-        }
-
-        public String getClassifier() {
-            return classifier;
-        }
-
-        public String getDirectory() {
-            return null;
-        }
-
-        public String getExtension() {
-            return extension;
-        }
-
-        public String getLanguage() {
-            return "none";
-        }
-
-        public String getPackaging() {
-            return packaging;
-        }
-
-        public boolean isAddedToClasspath() {
-            return false;
-        }
-
-        public boolean isIncludesDependencies() {
-            return false;
-        }
-    }
-}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilder.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilder.java
new file mode 100644
index 0000000000..f001fdd712
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilder.java
@@ -0,0 +1,40 @@
+/*
+ * 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.nio.file.Path;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.model.building.ModelBuildingException;
+import org.apache.maven.project.MavenProject;
+import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.RepositorySystemSession;
+
+/**
+ * This interface is not public and the purpose is to allow easy unit testing
+ * of {@link DefaultConsumerPomArtifactTransformer}.
+ */
+interface ConsumerPomBuilder {
+
+    Model build(RepositorySystemSession session, MavenProject project, Path 
src)
+            throws ModelBuildingException, ComponentLookupException, 
IOException, XMLStreamException;
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java
new file mode 100644
index 0000000000..569ce22338
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java
@@ -0,0 +1,207 @@
+/*
+ * 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.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+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;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.maven.api.feature.Features;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer;
+import org.apache.maven.model.building.ModelBuildingException;
+import org.apache.maven.model.v4.MavenStaxWriter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.artifact.ProjectArtifact;
+import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.deployment.DeployRequest;
+import org.eclipse.aether.installation.InstallRequest;
+
+/**
+ * Consumer POM transformer.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named("consumer-pom")
+class DefaultConsumerPomArtifactTransformer implements 
ConsumerPomArtifactTransformer {
+
+    private static final String CONSUMER_POM_CLASSIFIER = "consumer";
+
+    private static final String BUILD_POM_CLASSIFIER = "build";
+
+    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 final Set<Path> toDelete = new CopyOnWriteArraySet<>();
+
+    private final ConsumerPomBuilder builder;
+
+    @Inject
+    DefaultConsumerPomArtifactTransformer(ConsumerPomBuilder builder) {
+        this.builder = builder;
+    }
+
+    @SuppressWarnings("deprecation")
+    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.buildConsumer(session.getUserProperties())) {
+            Path buildDir =
+                    project.getBuild() != null ? 
Paths.get(project.getBuild().getDirectory()) : null;
+            if (buildDir != null) {
+                Files.createDirectories(buildDir);
+            }
+            Path consumer = buildDir != null
+                    ? Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER + 
"-", ".pom")
+                    : Files.createTempFile(CONSUMER_POM_CLASSIFIER + "-", 
".pom");
+            deferDeleteFile(consumer);
+
+            project.addAttachedArtifact(createConsumerPomArtifact(project, 
consumer, session));
+        } else if (project.getModel().getDelegate().isRoot()) {
+            throw new IllegalStateException(
+                    "The use of the root attribute on the model requires the 
buildconsumer feature to be active");
+        }
+    }
+
+    TransformedArtifact createConsumerPomArtifact(
+            MavenProject project, Path consumer, RepositorySystemSession 
session) {
+        return new TransformedArtifact(
+                this,
+                project,
+                consumer,
+                session,
+                new ProjectArtifact(project),
+                () -> project.getFile().toPath(),
+                CONSUMER_POM_CLASSIFIER,
+                "pom");
+    }
+
+    void transform(MavenProject project, RepositorySystemSession session, Path 
src, Path tgt)
+            throws ModelBuildingException, ComponentLookupException, 
XMLStreamException, IOException {
+        Model model = builder.build(session, project, src);
+        write(model, tgt);
+    }
+
+    private void deferDeleteFile(Path generatedFile) {
+        toDelete.add(generatedFile.toAbsolutePath());
+    }
+
+    @PreDestroy
+    private void doDeleteFiles() {
+        for (Path file : toDelete) {
+            try {
+                Files.delete(file);
+            } catch (IOException e) {
+                // ignore, we did our best...
+            }
+        }
+    }
+
+    public InstallRequest remapInstallArtifacts(RepositorySystemSession 
session, InstallRequest request) {
+        if (Features.buildConsumer(session.getUserProperties()) && 
consumerPomPresent(request.getArtifacts())) {
+            request.setArtifacts(replacePom(request.getArtifacts()));
+        }
+        return request;
+    }
+
+    public DeployRequest remapDeployArtifacts(RepositorySystemSession session, 
DeployRequest request) {
+        if (Features.buildConsumer(session.getUserProperties()) && 
consumerPomPresent(request.getArtifacts())) {
+            request.setArtifacts(replacePom(request.getArtifacts()));
+        }
+        return request;
+    }
+
+    private boolean consumerPomPresent(Collection<Artifact> artifacts) {
+        return artifacts.stream()
+                .anyMatch(a -> "pom".equals(a.getExtension()) && 
CONSUMER_POM_CLASSIFIER.equals(a.getClassifier()));
+    }
+
+    private Collection<Artifact> replacePom(Collection<Artifact> artifacts) {
+        List<Artifact> consumers = new ArrayList<>();
+        List<Artifact> mains = new ArrayList<>();
+        for (Artifact artifact : artifacts) {
+            if ("pom".equals(artifact.getExtension()) || 
artifact.getExtension().startsWith("pom.")) {
+                if (CONSUMER_POM_CLASSIFIER.equals(artifact.getClassifier())) {
+                    consumers.add(artifact);
+                } else if ("".equals(artifact.getClassifier())) {
+                    mains.add(artifact);
+                }
+            }
+        }
+        if (!mains.isEmpty() && !consumers.isEmpty()) {
+            ArrayList<Artifact> result = new ArrayList<>(artifacts);
+            for (Artifact main : mains) {
+                result.remove(main);
+                result.add(new DefaultArtifact(
+                        main.getGroupId(),
+                        main.getArtifactId(),
+                        BUILD_POM_CLASSIFIER,
+                        main.getExtension(),
+                        main.getVersion(),
+                        main.getProperties(),
+                        main.getFile()));
+            }
+            for (Artifact consumer : consumers) {
+                result.remove(consumer);
+                result.add(new DefaultArtifact(
+                        consumer.getGroupId(),
+                        consumer.getArtifactId(),
+                        "",
+                        consumer.getExtension(),
+                        consumer.getVersion(),
+                        consumer.getProperties(),
+                        consumer.getFile()));
+            }
+            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/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
new file mode 100644
index 0000000000..cf5bafac3d
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
@@ -0,0 +1,230 @@
+/*
+ * 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 java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+import org.apache.maven.api.model.DistributionManagement;
+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.model.building.DefaultModelBuilder;
+import org.apache.maven.model.building.DefaultModelBuilderFactory;
+import org.apache.maven.model.building.DefaultModelBuildingRequest;
+import org.apache.maven.model.building.ModelBuildingException;
+import org.apache.maven.model.building.ModelBuildingRequest;
+import org.apache.maven.model.building.ModelBuildingResult;
+import org.apache.maven.model.building.ModelProblemCollector;
+import org.apache.maven.model.building.ModelProcessor;
+import org.apache.maven.model.composition.DependencyManagementImporter;
+import org.apache.maven.model.inheritance.InheritanceAssembler;
+import org.apache.maven.model.interpolation.ModelInterpolator;
+import org.apache.maven.model.management.DependencyManagementInjector;
+import org.apache.maven.model.management.PluginManagementInjector;
+import org.apache.maven.model.normalization.ModelNormalizer;
+import org.apache.maven.model.path.ModelPathTranslator;
+import org.apache.maven.model.path.ModelUrlNormalizer;
+import org.apache.maven.model.plugin.LifecycleBindingsInjector;
+import org.apache.maven.model.plugin.PluginConfigurationExpander;
+import org.apache.maven.model.plugin.ReportConfigurationExpander;
+import org.apache.maven.model.profile.DefaultProfileSelector;
+import org.apache.maven.model.profile.ProfileActivationContext;
+import org.apache.maven.model.profile.ProfileInjector;
+import org.apache.maven.model.profile.ProfileSelector;
+import org.apache.maven.model.superpom.SuperPomProvider;
+import org.apache.maven.model.v4.MavenModelVersion;
+import org.apache.maven.model.validation.ModelValidator;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.project.ProjectModelResolver;
+import org.apache.maven.repository.internal.ModelCacheFactory;
+import org.codehaus.plexus.PlexusContainer;
+import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.RequestTrace;
+import org.eclipse.aether.impl.RemoteRepositoryManager;
+
+@Named
+class DefaultConsumerPomBuilder implements ConsumerPomBuilder {
+
+    private static final String BOM_PACKAGING = "bom";
+
+    public static final String POM_PACKAGING = "pom";
+
+    @Inject
+    PlexusContainer container;
+
+    @Inject
+    ModelCacheFactory modelCacheFactory;
+
+    public Model build(RepositorySystemSession session, MavenProject project, 
Path src)
+            throws ModelBuildingException, ComponentLookupException {
+        Model model = project.getModel().getDelegate();
+        String packaging = model.getPackaging();
+        if (POM_PACKAGING.equals(packaging)) {
+            return buildPom(session, project, src);
+        } else {
+            return buildNonPom(session, project, src);
+        }
+    }
+
+    protected Model buildPom(RepositorySystemSession session, MavenProject 
project, Path src)
+            throws ModelBuildingException, ComponentLookupException {
+        ModelBuildingResult result = buildModel(session, project, src);
+        Model model = result.getRawModel().getDelegate();
+        return transform(model);
+    }
+
+    protected Model buildNonPom(RepositorySystemSession session, MavenProject 
project, Path src)
+            throws ModelBuildingException, ComponentLookupException {
+        ModelBuildingResult result = buildModel(session, project, src);
+        Model model = result.getEffectiveModel().getDelegate();
+        return transform(model);
+    }
+
+    private ModelBuildingResult buildModel(RepositorySystemSession session, 
MavenProject project, Path src)
+            throws ModelBuildingException, ComponentLookupException {
+        ProfileSelector customSelector = new DefaultProfileSelector() {
+            @Override
+            public List<Profile> getActiveProfilesV4(
+                    Collection<Profile> profiles, ProfileActivationContext 
context, ModelProblemCollector problems) {
+                return new ArrayList<>();
+            }
+        };
+        DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory()
+                .setProfileSelector(customSelector)
+                // apply currently active ModelProcessor etc. to support 
extensions like jgitver
+                .setProfileInjector(lookup(ProfileInjector.class))
+                .setInheritanceAssembler(lookup(InheritanceAssembler.class))
+                
.setDependencyManagementImporter(lookup(DependencyManagementImporter.class))
+                
.setDependencyManagementInjector(lookup(DependencyManagementInjector.class))
+                
.setLifecycleBindingsInjector(lookup(LifecycleBindingsInjector.class))
+                .setModelInterpolator(lookup(ModelInterpolator.class))
+                .setModelNormalizer(lookup(ModelNormalizer.class))
+                .setModelPathTranslator(lookup(ModelPathTranslator.class))
+                .setModelProcessor(lookup(ModelProcessor.class))
+                .setModelUrlNormalizer(lookup(ModelUrlNormalizer.class))
+                .setModelValidator(lookup(ModelValidator.class))
+                
.setPluginConfigurationExpander(lookup(PluginConfigurationExpander.class))
+                
.setPluginManagementInjector(lookup(PluginManagementInjector.class))
+                
.setReportConfigurationExpander(lookup(ReportConfigurationExpander.class))
+                .setSuperPomProvider(lookup(SuperPomProvider.class))
+                .newInstance();
+        DefaultModelBuildingRequest request = new 
DefaultModelBuildingRequest();
+        try {
+            request.setRootDirectory(project.getRootDirectory());
+        } catch (IllegalStateException e) {
+            // ignore if we don't have a root directory
+        }
+        request.setPomFile(src.toFile());
+        
request.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
+        request.setLocationTracking(false);
+        request.setModelResolver(new ProjectModelResolver(
+                session,
+                new RequestTrace(null),
+                lookup(RepositorySystem.class),
+                lookup(RemoteRepositoryManager.class),
+                project.getRemoteProjectRepositories(),
+                ProjectBuildingRequest.RepositoryMerging.POM_DOMINANT,
+                null));
+        
request.setTransformerContextBuilder(modelBuilder.newTransformerContextBuilder());
+        
request.setSystemProperties(toProperties(session.getSystemProperties()));
+        request.setUserProperties(toProperties(session.getUserProperties()));
+        request.setModelCache(modelCacheFactory.createCache(session));
+        return modelBuilder.build(request);
+    }
+
+    private Properties toProperties(Map<String, String> map) {
+        Properties props = new Properties();
+        props.putAll(map);
+        return props;
+    }
+
+    private <T> T lookup(Class<T> clazz) throws ComponentLookupException {
+        return container.lookup(clazz);
+    }
+
+    static Model transform(Model model) {
+        String packaging = model.getPackaging();
+        if (POM_PACKAGING.equals(packaging)) {
+            // raw to consumer transform
+            model = model.withRoot(false).withModules(null);
+            if (model.getParent() != null) {
+                model = 
model.withParent(model.getParent().withRelativePath(null));
+            }
+
+            if (!model.isPreserveModelVersion()) {
+                model = model.withPreserveModelVersion(false);
+                String version = new 
MavenModelVersion().getModelVersion(model);
+                model = model.withModelVersion(version);
+            }
+        } else {
+            Model.Builder builder = prune(
+                    Model.newBuilder(model, true)
+                            .preserveModelVersion(false)
+                            .root(false)
+                            .parent(null)
+                            .build(null),
+                    model);
+            boolean isBom = BOM_PACKAGING.equals(packaging);
+            if (isBom) {
+                builder.packaging(POM_PACKAGING);
+            }
+            builder.profiles(model.getProfiles().stream()
+                    .map(p -> prune(Profile.newBuilder(p, true), p).build())
+                    .collect(Collectors.toList()));
+            model = builder.build();
+            String version = new MavenModelVersion().getModelVersion(model);
+            model = model.withModelVersion(version);
+        }
+        return model;
+    }
+
+    private static <T extends ModelBase.Builder> T prune(T builder, ModelBase 
model) {
+        builder.properties(null).reporting(null);
+        if (model.getDistributionManagement() != null
+                && model.getDistributionManagement().getRelocation() != null) {
+            // keep relocation only
+            builder.distributionManagement(DistributionManagement.newBuilder()
+                    
.relocation(model.getDistributionManagement().getRelocation())
+                    .build());
+        }
+        // only keep repositories other than 'central'
+        
builder.pluginRepositories(pruneRepositories(model.getPluginRepositories()));
+        builder.repositories(pruneRepositories(model.getRepositories()));
+        return builder;
+    }
+
+    private static List<Repository> pruneRepositories(List<Repository> 
repositories) {
+        return repositories.stream()
+                .filter(r -> 
!org.apache.maven.api.Repository.CENTRAL_ID.equals(r.getId()))
+                .collect(Collectors.toList());
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java
new file mode 100644
index 0000000000..5800048bc4
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifact.java
@@ -0,0 +1,148 @@
+/*
+ * 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.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.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.internal.transformation.TransformationFailedException;
+import org.apache.maven.model.building.ModelBuildingException;
+import org.apache.maven.project.MavenProject;
+import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.RepositorySystemSession;
+
+/**
+ * Transformed artifact is derived with some transformation from source 
artifact.
+ *
+ * @since TBD
+ */
+class TransformedArtifact extends DefaultArtifact {
+
+    private static final int SHA1_BUFFER_SIZE = 8192;
+    private final DefaultConsumerPomArtifactTransformer 
defaultConsumerPomArtifactTransformer;
+    private final MavenProject project;
+    private final Supplier<Path> sourcePathProvider;
+    private final Path target;
+    private final RepositorySystemSession session;
+    private final AtomicReference<String> sourceState;
+
+    TransformedArtifact(
+            DefaultConsumerPomArtifactTransformer 
defaultConsumerPomArtifactTransformer,
+            MavenProject project,
+            Path target,
+            RepositorySystemSession session,
+            org.apache.maven.artifact.Artifact source,
+            Supplier<Path> sourcePathProvider,
+            String classifier,
+            String extension) {
+        super(
+                source.getGroupId(),
+                source.getArtifactId(),
+                source.getVersionRange(),
+                source.getScope(),
+                extension,
+                classifier,
+                new TransformedArtifactHandler(
+                        classifier, extension, 
source.getArtifactHandler().getPackaging()));
+        this.defaultConsumerPomArtifactTransformer = 
defaultConsumerPomArtifactTransformer;
+        this.project = project;
+        this.target = target;
+        this.session = session;
+        this.sourcePathProvider = sourcePathProvider;
+        this.sourceState = new AtomicReference<>(null);
+    }
+
+    @Override
+    public boolean isResolved() {
+        return getFile() != null;
+    }
+
+    @Override
+    public void setFile(File file) {
+        throw new UnsupportedOperationException("transformed artifact file 
cannot be set");
+    }
+
+    @Override
+    public synchronized File getFile() {
+        try {
+            String state = mayUpdate();
+            if (state == null) {
+                return null;
+            }
+            return target.toFile();
+        } catch (IOException
+                | NoSuchAlgorithmException
+                | XMLStreamException
+                | ModelBuildingException
+                | ComponentLookupException e) {
+            throw new TransformationFailedException(e);
+        }
+    }
+
+    private String mayUpdate()
+            throws IOException, NoSuchAlgorithmException, XMLStreamException, 
ModelBuildingException,
+                    ComponentLookupException {
+        String result;
+        Path src = sourcePathProvider.get();
+        if (src == null) {
+            Files.deleteIfExists(target);
+            result = null;
+        } else if (!Files.exists(src)) {
+            Files.deleteIfExists(target);
+            result = "";
+        } else {
+            String current = sha1(src);
+            String existing = sourceState.get();
+            if (!Objects.equals(current, existing)) {
+                defaultConsumerPomArtifactTransformer.transform(project, 
session, src, target);
+                Files.setLastModifiedTime(target, 
Files.getLastModifiedTime(src));
+            }
+            result = current;
+        }
+        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/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
new file mode 100644
index 0000000000..80ba4e7cdf
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
@@ -0,0 +1,66 @@
+/*
+ * 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 org.apache.maven.artifact.handler.ArtifactHandler;
+
+import static java.util.Objects.requireNonNull;
+
+class TransformedArtifactHandler implements ArtifactHandler {
+
+    private final String classifier;
+
+    private final String extension;
+
+    private final String packaging;
+
+    TransformedArtifactHandler(String classifier, String extension, String 
packaging) {
+        this.classifier = classifier;
+        this.extension = requireNonNull(extension);
+        this.packaging = requireNonNull(packaging);
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getDirectory() {
+        return null;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public String getLanguage() {
+        return "none";
+    }
+
+    public String getPackaging() {
+        return packaging;
+    }
+
+    public boolean isAddedToClasspath() {
+        return false;
+    }
+
+    public boolean isIncludesDependencies() {
+        return false;
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
 
b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
index 0b9d28daeb..caec1dc9aa 100644
--- 
a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
+++ 
b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
@@ -94,7 +94,7 @@ public class LifecycleModuleBuilder {
                 return;
             }
 
-            
consumerPomArtifactTransformer.injectTransformedArtifacts(currentProject, 
session.getRepositorySession());
+            
consumerPomArtifactTransformer.injectTransformedArtifacts(session.getRepositorySession(),
 currentProject);
 
             BuilderCommon.attachToThread(currentProject);
 
diff --git 
a/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java
 
b/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java
index e08814fd48..51f9969258 100644
--- 
a/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java
+++ 
b/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java
@@ -34,7 +34,7 @@ import 
org.apache.maven.execution.DefaultMavenExecutionRequest;
 import org.apache.maven.execution.DefaultMavenExecutionResult;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
-import org.apache.maven.internal.impl.DefaultSession;
+import org.apache.maven.internal.impl.DefaultSessionFactory;
 import org.apache.maven.model.Build;
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Exclusion;
@@ -146,11 +146,14 @@ public abstract class AbstractCoreMavenComponentTestCase {
 
         initRepoSession(configuration);
 
+        DefaultSessionFactory defaultSessionFactory =
+                new DefaultSessionFactory(mock(RepositorySystem.class), null, 
null, null);
+
         MavenSession session = new MavenSession(
                 getContainer(), configuration.getRepositorySession(), request, 
new DefaultMavenExecutionResult());
         session.setProjects(projects);
         session.setAllProjects(session.getProjects());
-        session.setSession(new DefaultSession(session, 
mock(RepositorySystem.class), null, null, null, null));
+        session.setSession(defaultSessionFactory.getSession(session));
 
         return session;
     }
diff --git 
a/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java
 
b/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java
new file mode 100644
index 0000000000..728e7494ba
--- /dev/null
+++ 
b/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java
@@ -0,0 +1,78 @@
+/*
+ * 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.inject.Inject;
+
+import java.net.MalformedURLException;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.testing.PlexusTest;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositoryListener;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.transfer.TransferListener;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.Mockito;
+
+import static org.codehaus.plexus.testing.PlexusExtension.getTestFile;
+
+@PlexusTest
+public abstract class AbstractRepositoryTestCase {
+    @Inject
+    protected RepositorySystem system;
+
+    @Inject
+    protected PlexusContainer container;
+
+    protected RepositorySystemSession session;
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        session = newMavenRepositorySystemSession(system);
+    }
+
+    protected PlexusContainer getContainer() {
+        return container;
+    }
+
+    public static RepositorySystemSession 
newMavenRepositorySystemSession(RepositorySystem system) {
+        DefaultRepositorySystemSession session = 
MavenRepositorySystemUtils.newSession();
+
+        LocalRepository localRepo = new LocalRepository("target/local-repo");
+        
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, 
localRepo));
+
+        session.setTransferListener(Mockito.mock(TransferListener.class));
+        session.setRepositoryListener(Mockito.mock(RepositoryListener.class));
+
+        return session;
+    }
+
+    public static RemoteRepository newTestRepository() throws 
MalformedURLException {
+        return new RemoteRepository.Builder(
+                        "repo",
+                        "default",
+                        
getTestFile("target/test-classes/repo").toURI().toURL().toString())
+                .build();
+    }
+}
diff --git 
a/maven-core/src/test/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformerTest.java
 
b/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java
similarity index 85%
rename from 
maven-core/src/test/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformerTest.java
rename to 
maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java
index 0d5ca973c3..ceb8f5128c 100644
--- 
a/maven-core/src/test/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformerTest.java
+++ 
b/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformerTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.internal.transformation;
+package org.apache.maven.internal.transformation.impl;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,8 +25,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 
 import org.apache.maven.model.Model;
-import org.apache.maven.model.building.DefaultModelBuilderFactory;
-import org.apache.maven.model.building.ModelBuilder;
 import org.apache.maven.model.building.TransformerContext;
 import org.apache.maven.model.v4.MavenStaxReader;
 import org.apache.maven.project.MavenProject;
@@ -42,8 +40,6 @@ import static org.mockito.Mockito.when;
 
 class ConsumerPomArtifactTransformerTest {
 
-    ModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
-
     @Test
     void transform() throws Exception {
         RepositorySystemSession systemSessionMock = 
Mockito.mock(RepositorySystemSession.class);
@@ -60,9 +56,13 @@ class ConsumerPomArtifactTransformerTest {
         try (InputStream expected = Files.newInputStream(beforePomFile)) {
             Model model = new Model(new MavenStaxReader().read(expected));
             MavenProject project = new MavenProject(model);
-            ConsumerPomArtifactTransformer t = new 
ConsumerPomArtifactTransformer(modelBuilder);
-            t.createConsumerPomArtifact(project, tempFile, systemSessionMock)
-                    .transform(beforePomFile, tempFile, model.getDelegate());
+            DefaultConsumerPomArtifactTransformer t = new 
DefaultConsumerPomArtifactTransformer((s, p, f) -> {
+                try (InputStream is = Files.newInputStream(f)) {
+                    return DefaultConsumerPomBuilder.transform(new 
MavenStaxReader().read(is));
+                }
+            });
+
+            t.transform(project, systemSessionMock, beforePomFile, tempFile);
         }
         
XmlAssert.assertThat(afterPomFile.toFile()).and(tempFile.toFile()).areIdentical();
     }
@@ -76,7 +76,8 @@ class ConsumerPomArtifactTransformerTest {
         when(systemSessionMock.getData()).thenReturn(sessionDataMock);
         when(sessionDataMock.get(any())).thenReturn(new 
NoTransformerContext());
 
-        new 
ConsumerPomArtifactTransformer(modelBuilder).injectTransformedArtifacts(emptyProject,
 systemSessionMock);
+        new DefaultConsumerPomArtifactTransformer((session, project, src) -> 
null)
+                .injectTransformedArtifacts(systemSessionMock, emptyProject);
 
         assertThat(emptyProject.getAttachedArtifacts()).isEmpty();
     }
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
new file mode 100644
index 0000000000..6eac02bd9a
--- /dev/null
+++ 
b/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.artifact.repository.MavenArtifactRepository;
+import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
+import org.apache.maven.internal.transformation.AbstractRepositoryTestCase;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ConsumerPomBuilderTest extends AbstractRepositoryTestCase {
+
+    @Inject
+    ConsumerPomBuilder builder;
+
+    @Test
+    void testTrivialConsumer() throws Exception {
+        MavenProject project = new MavenProject();
+        
project.setRootDirectory(Paths.get("src/test/resources/consumer/trivial"));
+        project.setRemoteArtifactRepositories(Collections.singletonList(new 
MavenArtifactRepository(
+                "central", "http://repo.maven.apache.org/";, new 
DefaultRepositoryLayout(), null, null)));
+        Path file = 
Paths.get("src/test/resources/consumer/trivial/child/pom.xml");
+        Model model = builder.build(session, project, file);
+
+        assertNotNull(model);
+    }
+
+    @Test
+    void testSimpleConsumer() throws Exception {
+        MavenProject project = new MavenProject();
+        
project.setRootDirectory(Paths.get("src/test/resources/consumer/simple"));
+        project.setRemoteArtifactRepositories(Collections.singletonList(new 
MavenArtifactRepository(
+                "central", "http://repo.maven.apache.org/";, new 
DefaultRepositoryLayout(), null, null)));
+        Path file = 
Paths.get("src/test/resources/consumer/simple/simple-parent/simple-weather/pom.xml");
+        ((DefaultRepositorySystemSession) 
session).setUserProperty("changelist", "MNG6957");
+        Model model = builder.build(session, project, file);
+
+        assertNotNull(model);
+        assertTrue(model.getProfiles().isEmpty());
+    }
+}
diff --git 
a/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java 
b/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
index 182df18fac..637d05badd 100644
--- a/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
+++ b/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
@@ -192,8 +192,9 @@ class ProjectBuilderTest extends 
AbstractCoreMavenComponentTestCase {
 
     @Test
     void testReadErroneousMavenProjectContainsReference() throws Exception {
-        File pomFile = new 
File("src/test/resources/projects/artifactMissingVersion.xml").getAbsoluteFile();
+        File pomFile = new 
File("src/test/resources/projects/artifactMissingVersion/pom.xml").getAbsoluteFile();
         MavenSession mavenSession = createMavenSession(null);
+        
mavenSession.getRequest().setRootDirectory(pomFile.getParentFile().toPath());
         ProjectBuildingRequest configuration = new 
DefaultProjectBuildingRequest();
         
configuration.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
         
configuration.setRepositorySession(mavenSession.getRepositorySession());
diff --git a/maven-core/src/test/resources/consumer/simple/pom.xml 
b/maven-core/src/test/resources/consumer/simple/pom.xml
new file mode 100644
index 0000000000..54e3379aff
--- /dev/null
+++ b/maven-core/src/test/resources/consumer/simple/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project root="true" xmlns="http://maven.apache.org/POM/4.1.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 
https://maven.apache.org/xsd/maven-4.1.0-alpha-8.xsd";>
+  <groupId>org.sonatype.mavenbook.multi</groupId>
+  <artifactId>parent</artifactId>
+  <version>0.9-${changelist}-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  <name>Multi Chapter Parent Project</name>
+
+  <!-- Optimized from 
https://github.com/sonatype/maven-example-en/tree/master/examples/ch-multi -->
+  <modules>
+    <module>simple-parent</module>
+  </modules>
+</project>
diff --git 
a/maven-core/src/test/resources/consumer/simple/simple-parent/pom.xml 
b/maven-core/src/test/resources/consumer/simple/simple-parent/pom.xml
new file mode 100644
index 0000000000..cec61450ae
--- /dev/null
+++ b/maven-core/src/test/resources/consumer/simple/simple-parent/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 
https://maven.apache.org/xsd/maven-4.1.0-alpha-8.xsd";>
+  <parent>
+    <groupId>org.sonatype.mavenbook.multi</groupId>
+    <artifactId>parent</artifactId>
+  </parent>
+  <artifactId>simple-parent</artifactId>
+  <packaging>pom</packaging>
+  <name>Multi Chapter Simple Parent Project</name>
+
+  <modules>
+    <module>simple-weather</module>
+    <module>simple-webapp</module>
+
+    <!-- On purpose at the end, project graph is responsible for ordering -->
+    <!-- Build/consumer should not be effected by reverse order -->
+    <module>simple-testutils</module>
+    <module>utils-parent</module>
+  </modules>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>0.1-stub-SNAPSHOT</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+</project>
diff --git 
a/maven-core/src/test/resources/consumer/simple/simple-parent/simple-testutils/pom.xml
 
b/maven-core/src/test/resources/consumer/simple/simple-parent/simple-testutils/pom.xml
new file mode 100644
index 0000000000..ce6a47bf6f
--- /dev/null
+++ 
b/maven-core/src/test/resources/consumer/simple/simple-parent/simple-testutils/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 
https://maven.apache.org/xsd/maven-4.1.0-alpha-8.xsd";>
+  <parent>
+    <groupId>org.sonatype.mavenbook.multi</groupId>
+    <artifactId>utils-parent</artifactId>
+    <relativePath>../utils-parent/pom.xml</relativePath>
+  </parent>
+  <artifactId>simple-testutils</artifactId>
+</project>
diff --git 
a/maven-core/src/test/resources/consumer/simple/simple-parent/simple-weather/pom.xml
 
b/maven-core/src/test/resources/consumer/simple/simple-parent/simple-weather/pom.xml
new file mode 100644
index 0000000000..7b2e0b29db
--- /dev/null
+++ 
b/maven-core/src/test/resources/consumer/simple/simple-parent/simple-weather/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 
https://maven.apache.org/xsd/maven-4.1.0-alpha-8.xsd";>
+  <parent>
+    <groupId>org.sonatype.mavenbook.multi</groupId>
+    <artifactId>simple-parent</artifactId>
+  </parent>
+  <artifactId>simple-weather</artifactId>
+  <packaging>jar</packaging>
+
+  <name>Multi Chapter Simple Weather API</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.sonatype.mavenbook.multi</groupId>
+      <artifactId>simple-testutils</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git 
a/maven-core/src/test/resources/consumer/simple/simple-parent/simple-webapp/pom.xml
 
b/maven-core/src/test/resources/consumer/simple/simple-parent/simple-webapp/pom.xml
new file mode 100644
index 0000000000..655d0ccd17
--- /dev/null
+++ 
b/maven-core/src/test/resources/consumer/simple/simple-parent/simple-webapp/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.sonatype.mavenbook.multi</groupId>
+    <artifactId>simple-parent</artifactId>
+  </parent>
+
+  <artifactId>simple-webapp</artifactId>
+  <name>Multi Chapter Simple Web Application Project</name>
+  <dependencies>
+    <dependency>
+      <groupId>org.sonatype.mavenbook.multi</groupId>
+      <artifactId>simple-weather</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <finalName>simple-webapp</finalName>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-war-plugin</artifactId>
+          <version>3.3.2</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
diff --git 
a/maven-core/src/test/resources/consumer/simple/simple-parent/utils-parent/pom.xml
 
b/maven-core/src/test/resources/consumer/simple/simple-parent/utils-parent/pom.xml
new file mode 100644
index 0000000000..52909d70f9
--- /dev/null
+++ 
b/maven-core/src/test/resources/consumer/simple/simple-parent/utils-parent/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 
https://maven.apache.org/xsd/maven-4.1.0-alpha-8.xsd";>
+  <parent>
+    <groupId>org.sonatype.mavenbook.multi</groupId>
+    <artifactId>simple-parent</artifactId>
+  </parent>
+  <artifactId>utils-parent</artifactId>
+  <packaging>pom</packaging>
+</project>
diff --git a/maven-core/src/test/resources/consumer/trivial/child/pom.xml 
b/maven-core/src/test/resources/consumer/trivial/child/pom.xml
new file mode 100644
index 0000000000..25c05afa1d
--- /dev/null
+++ b/maven-core/src/test/resources/consumer/trivial/child/pom.xml
@@ -0,0 +1,18 @@
+<project xmlns="http://maven.apache.org/POM/4.1.0";>
+  <parent>
+    <groupId>org.my.group</groupId>
+    <artifactId>parent</artifactId>
+  </parent>
+  <artifactId>child</artifactId>
+  <packaging>jar</packaging>
+
+  <properties>
+    <prop-child>bar</prop-child>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+  </dependencies>
+</project>
\ No newline at end of file
diff --git a/maven-core/src/test/resources/consumer/trivial/pom.xml 
b/maven-core/src/test/resources/consumer/trivial/pom.xml
new file mode 100644
index 0000000000..14fab6c9df
--- /dev/null
+++ b/maven-core/src/test/resources/consumer/trivial/pom.xml
@@ -0,0 +1,47 @@
+<project root="true" xmlns="http://maven.apache.org/POM/4.1.0";>
+  <groupId>org.my.group</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <modules>
+    <module>child.xml</module>
+  </modules>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>2.0.9</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <version>5.10.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>release</id>
+      <properties>
+        <prop>foo</prop>
+      </properties>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.11.0</version>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
\ No newline at end of file
diff --git a/maven-core/src/test/resources/projects/artifactMissingVersion.xml 
b/maven-core/src/test/resources/projects/artifactMissingVersion/pom.xml
similarity index 100%
rename from maven-core/src/test/resources/projects/artifactMissingVersion.xml
rename to maven-core/src/test/resources/projects/artifactMissingVersion/pom.xml
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 e1db4c34de..033ca11dae 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
@@ -1045,7 +1045,7 @@ public class DefaultModelBuilder implements ModelBuilder {
         }
     }
 
-    private Model readFileModel(ModelBuildingRequest request, 
DefaultModelProblemCollector problems)
+    Model readFileModel(ModelBuildingRequest request, 
DefaultModelProblemCollector problems)
             throws ModelBuildingException {
         ModelSource modelSource = request.getModelSource();
         org.apache.maven.api.model.Model model = cache(
diff --git 
a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContextBuilder.java
 
b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContextBuilder.java
index 6b5de94484..f86f1aaa7d 100644
--- 
a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContextBuilder.java
+++ 
b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContextBuilder.java
@@ -18,9 +18,12 @@
  */
 package org.apache.maven.model.building;
 
+import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -56,6 +59,9 @@ class DefaultTransformerContextBuilder implements 
TransformerContextBuilder {
         // We must assume the TransformerContext was created using 
this.newTransformerContextBuilder()
         DefaultModelProblemCollector problems = (DefaultModelProblemCollector) 
collector;
         return new TransformerContext() {
+
+            private volatile boolean fullReactorLoaded;
+
             @Override
             public Path locate(Path path) {
                 return context.locate(path);
@@ -91,6 +97,11 @@ class DefaultTransformerContextBuilder implements 
TransformerContextBuilder {
 
             private Model findRawModel(Path from, String groupId, String 
artifactId) {
                 FileModelSource source = getSource(groupId, artifactId);
+                if (source == null) {
+                    // we need to check the whole reactor in case it's a 
dependency
+                    loadFullReactor();
+                    source = getSource(groupId, artifactId);
+                }
                 if (source != null) {
                     if (!addEdge(from, source.getFile().toPath(), problems)) {
                         return null;
@@ -106,6 +117,45 @@ class DefaultTransformerContextBuilder implements 
TransformerContextBuilder {
                 return null;
             }
 
+            private void loadFullReactor() {
+                if (!fullReactorLoaded) {
+                    synchronized (this) {
+                        if (!fullReactorLoaded) {
+                            doLoadFullReactor();
+                            fullReactorLoaded = true;
+                        }
+                    }
+                }
+            }
+
+            private void doLoadFullReactor() {
+                Path rootDirectory = request.getRootDirectory();
+                if (rootDirectory == null) {
+                    return;
+                }
+                List<File> toLoad = new ArrayList<>();
+                File root = 
defaultModelBuilder.getModelProcessor().locateExistingPom(rootDirectory.toFile());
+                toLoad.add(root);
+                while (!toLoad.isEmpty()) {
+                    File pom = toLoad.remove(0);
+                    try {
+                        ModelBuildingRequest gaBuildingRequest =
+                                new 
DefaultModelBuildingRequest(request).setModelSource(new FileModelSource(pom));
+                        Model rawModel = 
defaultModelBuilder.readFileModel(gaBuildingRequest, problems);
+                        for (String module : rawModel.getModules()) {
+                            File moduleFile = defaultModelBuilder
+                                    .getModelProcessor()
+                                    .locateExistingPom(new 
File(pom.getParent(), module));
+                            if (moduleFile != null) {
+                                toLoad.add(moduleFile);
+                            }
+                        }
+                    } catch (ModelBuildingException e) {
+                        // gathered with problem collector
+                    }
+                }
+            }
+
             private Model findRawModel(Path from, Path p) {
                 if (!Files.isRegularFile(p)) {
                     throw new IllegalArgumentException("Not a regular file: " 
+ p);


Reply via email to