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

desruisseaux 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 78ed4a251a Consumer POM of multi-module project should exclude <build> 
and <dependencies> elements (#11639)
78ed4a251a is described below

commit 78ed4a251a86273e4ccd0638643b0787359f259a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Mar 4 20:49:40 2026 +0100

    Consumer POM of multi-module project should exclude <build> and 
<dependencies> elements (#11639)
    
    * Move some of the codes needed by `ProjectSourcesHelper` in an utility 
class that we can reuse in other packages.
    * Consumer POM of multi-module project should exclude <build> and 
<dependencies> elements.
    * Add a test that verifies that `<build>` is preserved when 
`preserveModelVersion=true`.
    
    Co-authored-by: Gerd Aschemann <[email protected]>
---
 .../impl/ConsumerPomArtifactTransformer.java       |   2 +-
 .../impl/DefaultConsumerPomBuilder.java            |  26 ++++-
 .../maven/project/DefaultProjectBuilder.java       |  38 +------
 .../maven/project/SourceHandlingContext.java       | 116 ++++++++++++---------
 .../org/apache/maven/project/SourceQueries.java    |  84 +++++++++++++++
 .../impl/ConsumerPomBuilderTest.java               |  86 +++++++++++----
 .../test/resources/consumer/multi-module/pom.xml   |  41 ++++++++
 7 files changed, 281 insertions(+), 112 deletions(-)

diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
 
b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
index 9444f972a9..f32c962886 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
@@ -96,7 +96,7 @@ public void 
injectTransformedArtifacts(RepositorySystemSession session, MavenPro
         }
     }
 
-    TransformedArtifact createConsumerPomArtifact(
+    private TransformedArtifact createConsumerPomArtifact(
             MavenProject project, Path consumer, RepositorySystemSession 
session) {
         Path actual = project.getFile().toPath();
         Path parent = project.getBaseDirectory();
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
 
b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
index c46d3d5b6d..d631e8fd7e 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
@@ -37,6 +37,7 @@
 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.Parent;
 import org.apache.maven.api.model.Profile;
 import org.apache.maven.api.model.Repository;
 import org.apache.maven.api.model.Scm;
@@ -50,6 +51,7 @@
 import org.apache.maven.impl.InternalSession;
 import org.apache.maven.model.v4.MavenModelVersion;
 import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.SourceQueries;
 import org.eclipse.aether.RepositorySystemSession;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -342,7 +344,7 @@ static Model transformNonPom(Model model, MavenProject 
project) {
         return model;
     }
 
-    static Model transformBom(Model model, MavenProject project) {
+    private static Model transformBom(Model model, MavenProject project) {
         boolean preserveModelVersion = model.isPreserveModelVersion();
 
         Model.Builder builder = prune(
@@ -369,11 +371,25 @@ static Model transformPom(Model model, MavenProject 
project) {
 
         // raw to consumer transform
         model = model.withRoot(false).withModules(null).withSubprojects(null);
-        if (model.getParent() != null) {
-            model = model.withParent(model.getParent().withRelativePath(null));
+        Parent parent = model.getParent();
+        if (parent != null) {
+            model = model.withParent(parent.withRelativePath(null));
+        }
+        var projectSources = project.getBuild().getDelegate().getSources();
+        if (SourceQueries.usesModuleSourceHierarchy(projectSources)) {
+            // Dependencies are dispatched by maven-jar-plugin in the POM 
generated for each module.
+            model = model.withDependencies(null).withPackaging(POM_PACKAGING);
         }
-
         if (!preserveModelVersion) {
+            /*
+             * If the <build> contains <source> elements, it is not compatible 
with the Maven 4.0.0 model.
+             * Remove the full <build> element instead of removing only the 
<sources> element, because the
+             * build without sources does not mean much. Reminder: this 
removal can be disabled by setting
+             * the `preserveModelVersion` XML attribute or 
`preserve.model.version` property to true.
+             */
+            if (SourceQueries.hasEnabledSources(projectSources)) {
+                model = model.withBuild(null);
+            }
             model = model.withPreserveModelVersion(false);
             String modelVersion = new 
MavenModelVersion().getModelVersion(model);
             model = model.withModelVersion(modelVersion);
@@ -381,7 +397,7 @@ static Model transformPom(Model model, MavenProject 
project) {
         return model;
     }
 
-    static void warnNotDowngraded(MavenProject project) {
+    private static void warnNotDowngraded(MavenProject project) {
         LOGGER.warn("The consumer POM for " + project.getId() + " cannot be 
downgraded to 4.0.0. "
                 + "If you intent your build to be consumed with Maven 3 
projects, you need to remove "
                 + "the features that request a newer model version.  If you're 
fine with having the "
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
 
b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index 7b13a9c12a..cdddbdaafe 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -655,7 +655,6 @@ private void initProject(MavenProject project, 
ModelBuilderResult result) {
             // only set those on 2nd phase, ignore on 1st pass
             if (project.getFile() != null) {
                 Build build = project.getBuild().getDelegate();
-                List<org.apache.maven.api.model.Source> sources = 
build.getSources();
                 Path baseDir = project.getBaseDirectory();
                 Function<ProjectScope, String> outputDirectory = (scope) -> {
                     if (scope == ProjectScope.MAIN) {
@@ -666,23 +665,11 @@ private void initProject(MavenProject project, 
ModelBuilderResult result) {
                         return build.getDirectory();
                     }
                 };
-                // Extract modules from sources to detect modular projects
-                Set<String> modules = extractModules(sources);
-                boolean isModularProject = !modules.isEmpty();
-
-                logger.trace(
-                        "Module detection for project {}: found {} module(s) 
{} - modular project: {}.",
-                        project.getId(),
-                        modules.size(),
-                        modules,
-                        isModularProject);
-
                 // Create source handling context for unified tracking of all 
lang/scope combinations
-                SourceHandlingContext sourceContext =
-                        new SourceHandlingContext(project, baseDir, modules, 
isModularProject, result);
+                final SourceHandlingContext sourceContext = new 
SourceHandlingContext(project, result);
 
                 // Process all sources, tracking enabled ones and detecting 
duplicates
-                for (var source : sources) {
+                for (org.apache.maven.api.model.Source source : 
sourceContext.sources) {
                     var sourceRoot = DefaultSourceRoot.fromModel(session, 
baseDir, outputDirectory, source);
                     // Track enabled sources for duplicate detection and 
hasSources() queries
                     // Only add source if it's not a duplicate enabled source 
(first enabled wins)
@@ -711,7 +698,7 @@ private void initProject(MavenProject project, 
ModelBuilderResult result) {
                        implicit fallback (only if they match the default, 
e.g., inherited)
                      - This allows incremental adoption (e.g., custom 
resources + default Java)
                 */
-                if (sources.isEmpty()) {
+                if (sourceContext.sources.isEmpty()) {
                     // Classic fallback: no <sources> configured, use legacy 
directories
                     
project.addScriptSourceRoot(build.getScriptSourceDirectory());
                     project.addCompileSourceRoot(build.getSourceDirectory());
@@ -724,8 +711,7 @@ implicit fallback (only if they match the default, e.g., 
inherited)
                     if (!sourceContext.hasSources(Language.SCRIPT, 
ProjectScope.MAIN)) {
                         
project.addScriptSourceRoot(build.getScriptSourceDirectory());
                     }
-
-                    if (isModularProject) {
+                    if (sourceContext.usesModuleSourceHierarchy()) {
                         // Modular: reject ALL legacy directory configurations
                         failIfLegacyDirectoryPresent(
                                 build.getSourceDirectory(),
@@ -1243,22 +1229,6 @@ public Set<Entry<K, V>> entrySet() {
         }
     }
 
-    /**
-     * Extracts unique module names from the given list of source elements.
-     * A project is considered modular if it has at least one module name.
-     *
-     * @param sources list of source elements from the build
-     * @return set of non-blank module names
-     */
-    private static Set<String> 
extractModules(List<org.apache.maven.api.model.Source> sources) {
-        return sources.stream()
-                .map(org.apache.maven.api.model.Source::getModule)
-                .filter(Objects::nonNull)
-                .map(String::trim)
-                .filter(s -> !s.isBlank())
-                .collect(Collectors.toSet());
-    }
-
     private Model injectLifecycleBindings(
             Model model,
             ModelBuilderRequest request,
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
 
b/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
index 400f9f5dc0..1d7eb393e6 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
@@ -27,6 +27,7 @@
 import org.apache.maven.api.ProjectScope;
 import org.apache.maven.api.SourceRoot;
 import org.apache.maven.api.model.Resource;
+import org.apache.maven.api.model.Source;
 import org.apache.maven.api.services.BuilderProblem.Severity;
 import org.apache.maven.api.services.ModelBuilderResult;
 import org.apache.maven.api.services.ModelProblem.Version;
@@ -37,9 +38,7 @@
 
 /**
  * Handles source configuration for Maven projects with unified tracking for 
all language/scope combinations.
- * <p>
- * This class replaces the previous approach of hardcoded boolean flags 
(hasMain, hasTest, etc.)
- * with a flexible set-based tracking mechanism that works for any language 
and scope combination.
+ * This class uses a flexible set-based tracking mechanism that works for any 
language and scope combination.
  * <p>
  * Key features:
  * <ul>
@@ -51,7 +50,7 @@
  *
  * @since 4.0.0
  */
-class SourceHandlingContext {
+final class SourceHandlingContext {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(SourceHandlingContext.class);
 
@@ -60,26 +59,38 @@ class SourceHandlingContext {
      */
     record SourceKey(Language language, ProjectScope scope, String module, 
Path directory) {}
 
+    /**
+     * The {@code <source>} elements declared in the {@code <build>} elements.
+     */
+    final List<Source> sources;
+
     private final MavenProject project;
-    private final Path baseDir;
     private final Set<String> modules;
-    private final boolean modularProject;
     private final ModelBuilderResult result;
     private final Set<SourceKey> declaredSources;
 
-    SourceHandlingContext(
-            MavenProject project,
-            Path baseDir,
-            Set<String> modules,
-            boolean modularProject,
-            ModelBuilderResult result) {
+    SourceHandlingContext(MavenProject project, ModelBuilderResult result) {
         this.project = project;
-        this.baseDir = baseDir;
-        this.modules = modules;
-        this.modularProject = modularProject;
+        this.sources = project.getBuild().getDelegate().getSources();
+        this.modules = SourceQueries.getModuleNames(sources);
         this.result = result;
         // Each module typically has main, test, main resources, test 
resources = 4 sources
         this.declaredSources = new HashSet<>(4 * modules.size());
+        if (usesModuleSourceHierarchy()) {
+            LOGGER.trace("Found {} module(s) in the \"{}\" project: {}.", 
project.getId(), modules.size(), modules);
+        } else {
+            LOGGER.trace("Project \"{}\" is non-modular.", project.getId());
+        }
+    }
+
+    /**
+     * Whether the project uses module source hierarchy.
+     * Note that this is not synonymous of whether the project is modular,
+     * because it is possible to create a single Java module in a classic 
Maven project
+     * (i.e., using package hierarchy).
+     */
+    boolean usesModuleSourceHierarchy() {
+        return !modules.isEmpty();
     }
 
     /**
@@ -112,7 +123,7 @@ boolean shouldAddSource(SourceRoot sourceRoot) {
         SourceKey key = new SourceKey(
                 sourceRoot.language(), sourceRoot.scope(), 
sourceRoot.module().orElse(null), normalizedDir);
 
-        if (declaredSources.contains(key)) {
+        if (!declaredSources.add(key)) {
             String message = String.format(
                     "Duplicate enabled source detected: lang=%s, scope=%s, 
module=%s, directory=%s. "
                             + "First enabled source wins, this duplicate is 
ignored.",
@@ -130,7 +141,6 @@ boolean shouldAddSource(SourceRoot sourceRoot) {
             return false; // Don't add duplicate enabled source
         }
 
-        declaredSources.add(key);
         LOGGER.debug(
                 "Adding and tracking enabled source: lang={}, scope={}, 
module={}, dir={}",
                 key.language(),
@@ -151,6 +161,13 @@ boolean hasSources(Language language, ProjectScope scope) {
         return declaredSources.stream().anyMatch(key -> 
language.equals(key.language()) && scope.equals(key.scope()));
     }
 
+    /**
+     * {@return the source directory as defined by Maven conventions}
+     */
+    private Path getStandardSourceDirectory() {
+        return project.getBaseDirectory().resolve("src");
+    }
+
     /**
      * Fails the build if modular and classic (non-modular) sources are mixed 
within {@code <sources>}.
      * <p>
@@ -164,30 +181,32 @@ boolean hasSources(Language language, ProjectScope scope) 
{
     void failIfMixedModularAndClassicSources() {
         for (ProjectScope scope : List.of(ProjectScope.MAIN, 
ProjectScope.TEST)) {
             for (Language language : List.of(Language.JAVA_FAMILY, 
Language.RESOURCES)) {
-                boolean hasModular = declaredSources.stream()
-                        .anyMatch(key ->
-                                language.equals(key.language()) && 
scope.equals(key.scope()) && key.module() != null);
-                boolean hasClassic = declaredSources.stream()
-                        .anyMatch(key ->
-                                language.equals(key.language()) && 
scope.equals(key.scope()) && key.module() == null);
-
-                if (hasModular && hasClassic) {
-                    String message = String.format(
-                            "Mixed modular and classic sources detected for 
lang=%s, scope=%s. "
-                                    + "A project must be either fully modular 
(all sources have a module) "
-                                    + "or fully classic (no sources have a 
module). "
-                                    + "The compiler plugin cannot handle mixed 
configurations.",
-                            language.id(), scope.id());
-                    LOGGER.error(message);
-                    result.getProblemCollector()
-                            .reportProblem(new DefaultModelProblem(
-                                    message,
-                                    Severity.ERROR,
-                                    Version.V41,
-                                    project.getModel().getDelegate(),
-                                    -1,
-                                    -1,
-                                    null));
+                boolean hasModular = false;
+                boolean hasClassic = false;
+                for (SourceKey key : declaredSources) {
+                    if (language.equals(key.language()) && 
scope.equals(key.scope())) {
+                        String module = key.module();
+                        hasModular |= (module != null);
+                        hasClassic |= (module == null);
+                        if (hasModular && hasClassic) {
+                            String message = String.format(
+                                    "Mixed modular and classic sources 
detected for lang=%s, scope=%s. "
+                                            + "A project must be either fully 
modular (all sources have a module) "
+                                            + "or fully classic (no sources 
have a module).",
+                                    language.id(), scope.id());
+                            LOGGER.error(message);
+                            result.getProblemCollector()
+                                    .reportProblem(new DefaultModelProblem(
+                                            message,
+                                            Severity.ERROR,
+                                            Version.V41,
+                                            project.getModel().getDelegate(),
+                                            -1,
+                                            -1,
+                                            null));
+                            break;
+                        }
+                    }
                 }
             }
         }
@@ -219,7 +238,7 @@ void handleResourceConfiguration(ProjectScope scope) {
                 ? "<source><lang>resources</lang></source>"
                 : "<source><lang>resources</lang><scope>test</scope></source>";
 
-        if (modularProject) {
+        if (usesModuleSourceHierarchy()) {
             if (hasResourcesInSources) {
                 // Modular project with resources configured via <sources> - 
already added above
                 if (hasExplicitLegacyResources(resources, scopeId)) {
@@ -298,6 +317,7 @@ void handleResourceConfiguration(ProjectScope scope) {
                 // Use legacy resources element
                 LOGGER.debug(
                         "Using explicit or default {} resources ({} resources 
configured).", scopeId, resources.size());
+                Path baseDir = project.getBaseDirectory();
                 for (Resource resource : resources) {
                     project.addSourceRoot(new DefaultSourceRoot(baseDir, 
scope, resource));
                 }
@@ -315,7 +335,7 @@ void handleResourceConfiguration(ProjectScope scope) {
      */
     private DefaultSourceRoot createModularResourceRoot(String module, 
ProjectScope scope) {
         Path resourceDir =
-                
baseDir.resolve("src").resolve(module).resolve(scope.id()).resolve("resources");
+                
getStandardSourceDirectory().resolve(module).resolve(scope.id()).resolve("resources");
 
         return new DefaultSourceRoot(
                 scope,
@@ -345,12 +365,10 @@ private boolean hasExplicitLegacyResources(List<Resource> 
resources, String scop
         }
 
         // Super POM default paths
-        String defaultPath =
-                
baseDir.resolve("src").resolve(scope).resolve("resources").toString();
-        String defaultFilteredPath = baseDir.resolve("src")
-                .resolve(scope)
-                .resolve("resources-filtered")
-                .toString();
+        Path srcDir = getStandardSourceDirectory();
+        String defaultPath = 
srcDir.resolve(scope).resolve("resources").toString();
+        String defaultFilteredPath =
+                srcDir.resolve(scope).resolve("resources-filtered").toString();
 
         // Check if any resource differs from Super POM defaults
         for (Resource resource : resources) {
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/SourceQueries.java 
b/impl/maven-core/src/main/java/org/apache/maven/project/SourceQueries.java
new file mode 100644
index 0000000000..41759d1049
--- /dev/null
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/SourceQueries.java
@@ -0,0 +1,84 @@
+/*
+ * 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.project;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.maven.api.model.Source;
+
+/**
+ * Static utility methods for analyzing {@code <source>} elements of a project.
+ * <p>
+ * <strong>Warning:</strong> This is an internal utility class, not part of 
the public API.
+ * It can be changed or removed without prior notice.
+ *
+ * @since 4.0.0
+ */
+public final class SourceQueries {
+    private SourceQueries() {}
+
+    /**
+     * Returns whether at least one source in the collection has a non-blank 
module name,
+     * indicating a modular source hierarchy.
+     *
+     * @param sources the source elements to check
+     * @return {@code true} if at least one source declares a module
+     */
+    public static boolean usesModuleSourceHierarchy(Collection<Source> 
sources) {
+        return 
sources.stream().map(Source::getModule).filter(Objects::nonNull).anyMatch(s -> 
!s.isBlank());
+    }
+
+    /**
+     * Returns whether at least one source in the collection is enabled.
+     *
+     * @param sources the source elements to check
+     * @return {@code true} if at least one source is enabled
+     */
+    public static boolean hasEnabledSources(Collection<Source> sources) {
+        for (Source source : sources) {
+            if (source.isEnabled()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Extracts unique, non-blank module names from the source elements, 
preserving declaration order.
+     * The following relationship should always be true:
+     *
+     * <pre>getModuleNames(sources).isEmpty() == 
!usesModuleSourceHierarchy(sources)</pre>
+     *
+     * @param sources the source elements to extract module names from
+     * @return set of non-blank module names in declaration order
+     */
+    public static Set<String> getModuleNames(Collection<Source> sources) {
+        var modules = new LinkedHashSet<String>();
+        sources.stream()
+                .map(Source::getModule)
+                .filter(Objects::nonNull)
+                .map(String::strip)
+                .filter(s -> !s.isEmpty())
+                .forEach(modules::add);
+        return modules;
+    }
+}
diff --git 
a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
 
b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
index 11dc8cd9c7..d9744cad5f 100644
--- 
a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
+++ 
b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
@@ -51,6 +51,7 @@
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -88,15 +89,22 @@ protected List<Object> getSessionServices() {
         return services;
     }
 
-    @Test
-    void testTrivialConsumer() throws Exception {
-        InternalMavenSession.from(InternalSession.from(session))
+    /**
+     * Configures {@link #session} with the root directory of a test in {@code 
src/test/resources/consumer}.
+     * Returns the request in case the caller wants to apply more 
configuration.
+     */
+    private MavenExecutionRequest setRootDirectory(String test) {
+        MavenExecutionRequest request = 
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");
+                .getRequest();
+        request.setRootDirectory(Paths.get("src/test/resources/consumer", 
test));
+        return request;
+    }
 
+    /**
+     * Builds the effective model for the given {@code pom.xml} file.
+     */
+    private MavenProject getEffectiveModel(Path file) {
         ModelBuilder.ModelBuilderSession mbs = modelBuilder.newSession();
         
InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class),
 mbs);
         Model orgModel = mbs.build(ModelBuilderRequest.builder()
@@ -108,39 +116,71 @@ void testTrivialConsumer() throws Exception {
 
         MavenProject project = new MavenProject(orgModel);
         project.setOriginalModel(new org.apache.maven.model.Model(orgModel));
+        return project;
+    }
+
+    @Test
+    void testTrivialConsumer() throws Exception {
+        setRootDirectory("trivial");
+        Path file = 
Paths.get("src/test/resources/consumer/trivial/child/pom.xml");
+
+        MavenProject project = getEffectiveModel(file);
         Model model = builder.build(session, project, 
Sources.buildSource(file));
 
         assertNotNull(model);
+        assertNotNull(model.getDependencies());
     }
 
     @Test
     void testSimpleConsumer() throws Exception {
-        MavenExecutionRequest request = 
InternalMavenSession.from(InternalSession.from(session))
-                .getMavenSession()
-                .getRequest();
-        
request.setRootDirectory(Paths.get("src/test/resources/consumer/simple"));
+        MavenExecutionRequest request = setRootDirectory("simple");
         request.getUserProperties().setProperty("changelist", "MNG6957");
-
         Path file = 
Paths.get("src/test/resources/consumer/simple/simple-parent/simple-weather/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(Sources.buildSource(file))
-                        
.requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
-                        .build())
-                .getEffectiveModel();
-
-        MavenProject project = new MavenProject(orgModel);
-        project.setOriginalModel(new org.apache.maven.model.Model(orgModel));
+        MavenProject project = getEffectiveModel(file);
         
request.setRootDirectory(Paths.get("src/test/resources/consumer/simple"));
         Model model = builder.build(session, project, 
Sources.buildSource(file));
 
         assertNotNull(model);
+        assertFalse(model.getDependencies().isEmpty());
         assertTrue(model.getProfiles().isEmpty());
     }
 
+    @Test
+    void testMultiModuleConsumer() throws Exception {
+        setRootDirectory("multi-module");
+        Path file = 
Paths.get("src/test/resources/consumer/multi-module/pom.xml");
+
+        MavenProject project = getEffectiveModel(file);
+        Model model = builder.build(session, project, 
Sources.buildSource(file));
+
+        assertNotNull(model);
+        assertNull(model.getBuild());
+        assertTrue(model.getDependencies().isEmpty());
+        
assertFalse(model.getDependencyManagement().getDependencies().isEmpty());
+    }
+
+    /**
+     * Same test as {@link #testMultiModuleConsumer()}, but verifies that
+     * {@code <build>} is preserved when {@code preserveModelVersion=true}.
+     */
+    @Test
+    void testMultiModuleConsumerPreserveModelVersion() throws Exception {
+        setRootDirectory("multi-module");
+        Path file = 
Paths.get("src/test/resources/consumer/multi-module/pom.xml");
+
+        MavenProject project = getEffectiveModel(file);
+        Model model = getEffectiveModel(file).getModel().getDelegate();
+        model = Model.newBuilder(model, 
true).preserveModelVersion(true).build();
+
+        Model transformed = DefaultConsumerPomBuilder.transformPom(model, 
project);
+
+        assertNotNull(transformed);
+        assertNotNull(transformed.getBuild());
+        assertTrue(transformed.getDependencies().isEmpty());
+        
assertFalse(transformed.getDependencyManagement().getDependencies().isEmpty());
+    }
+
     @Test
     void testScmInheritance() throws Exception {
         Model model = Model.newBuilder()
diff --git a/impl/maven-core/src/test/resources/consumer/multi-module/pom.xml 
b/impl/maven-core/src/test/resources/consumer/multi-module/pom.xml
new file mode 100644
index 0000000000..972e7c9be2
--- /dev/null
+++ b/impl/maven-core/src/test/resources/consumer/multi-module/pom.xml
@@ -0,0 +1,41 @@
+<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>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>2.0.9</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <version>5.10.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <sources>
+      <source>
+        <module>org.foo</module>
+      </source>
+      <source>
+        <module>org.foo.bar</module>
+      </source>
+    </sources>
+  </build>
+
+</project>

Reply via email to