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

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

commit 89cd556e1555a5a2db8badaec8d7e271f0ae2ca6
Author: Guillaume Nodet <[email protected]>
AuthorDate: Sun Aug 11 23:37:18 2024 +0200

    Introduce subprojects, deprecate modules, discover subprojects
---
 .../apache/maven/api/services/ModelBuilder.java    |  6 ++-
 .../apache/maven/api/services/ModelProblem.java    |  3 +-
 api/maven-api-model/src/main/mdo/maven.mdo         | 13 ++++++-
 .../internal/impl/model/DefaultModelBuilder.java   | 30 ++++++++++++++-
 .../internal/impl/model/DefaultModelValidator.java |  4 ++
 .../impl/DefaultConsumerPomBuilder.java            |  1 +
 .../maven/project/DefaultProjectBuilder.java       | 44 ++++++++++++----------
 .../org/apache/maven/project/MavenProject.java     |  3 ++
 .../project/DefaultMavenProjectBuilderTest.java    | 14 +++++++
 .../projects/subprojects-discover/child/pom.xml    |  8 ++++
 .../projects/subprojects-discover/pom.xml          |  6 +++
 11 files changed, 109 insertions(+), 23 deletions(-)

diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
index aa828d349d..236b31db4c 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
@@ -25,7 +25,11 @@ import org.apache.maven.api.model.Model;
 
 public interface ModelBuilder extends Service {
 
-    List<String> VALID_MODEL_VERSIONS = List.of("4.0.0", "4.1.0");
+    String MODEL_VERSION_4_0_0 = "4.0.0";
+
+    String MODEL_VERSION_4_1_0 = "4.0.0";
+
+    List<String> VALID_MODEL_VERSIONS = List.of(MODEL_VERSION_4_0_0, 
MODEL_VERSION_4_1_0);
 
     ModelBuilderResult build(ModelBuilderRequest request) throws 
ModelBuilderException;
 
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
index 0e20dbee98..f15fe0db01 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
@@ -35,7 +35,8 @@ public interface ModelProblem extends BuilderProblem {
         V20,
         V30,
         V31,
-        V40
+        V40,
+        V41
     }
 
     /**
diff --git a/api/maven-api-model/src/main/mdo/maven.mdo 
b/api/maven-api-model/src/main/mdo/maven.mdo
index 5b22bc9194..54894b3692 100644
--- a/api/maven-api-model/src/main/mdo/maven.mdo
+++ b/api/maven-api-model/src/main/mdo/maven.mdo
@@ -527,7 +527,18 @@
       <fields>
         <field xdoc.separator="blank">
           <name>modules</name>
-          <version>4.0.0+</version>
+          <version>4.0.0/4.1.0</version>
+          <description>
+            @deprecated Use {@link #subprojects} instead.
+          </description>
+          <association>
+            <type>String</type>
+            <multiplicity>*</multiplicity>
+          </association>
+        </field>
+        <field xdoc.separator="blank">
+          <name>subprojects</name>
+          <version>4.1.0</version>
           <description>The subprojects (formerly called modules) to build as a 
part of this
             project. Each subproject listed is a relative path to the 
directory containing the subproject.
             To be consistent with the way default URLs are calculated from 
parent, it is recommended
diff --git 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
index c723950e61..3839704d46 100644
--- 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
+++ 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
@@ -21,6 +21,7 @@ package org.apache.maven.internal.impl.model;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Field;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -40,6 +41,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.apache.maven.api.Session;
+import org.apache.maven.api.Type;
 import org.apache.maven.api.VersionRange;
 import org.apache.maven.api.annotations.Nullable;
 import org.apache.maven.api.di.Inject;
@@ -305,7 +307,7 @@ public class DefaultModelBuilder implements ModelBuilder {
             // Maven 3.x is always using 4.0.0 version to load the supermodel, 
so
             // do the same when loading a dependency.  The model validator 
will also
             // check that field later.
-            superModelVersion = "4.0.0";
+            superModelVersion = MODEL_VERSION_4_0_0;
         }
         ModelData superData = new ModelData(null, 
getSuperModel(superModelVersion));
 
@@ -737,6 +739,32 @@ public class DefaultModelBuilder implements ModelBuilder {
 
         if (modelSource.getPath() != null) {
             model = model.withPomFile(modelSource.getPath());
+
+            // subprojects discovery
+            if (model.getSubprojects().isEmpty()
+                    && model.getModules().isEmpty()
+                    // only discover subprojects if POM > 4.0.0
+                    && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
+                    // and if packaging is POM (we check type, but the session 
is not yet available,
+                    // we would require the project realm if we want to 
support extensions
+                    && Type.POM.equals(model.getPackaging())) {
+                List<String> subprojects = new ArrayList<>();
+                try (Stream<Path> files = 
Files.list(model.getProjectDirectory())) {
+                    for (Path f : files.toList()) {
+                        if (Files.isDirectory(f)) {
+                            Path subproject = 
modelProcessor.locateExistingPom(f);
+                            if (subproject != null) {
+                                subprojects.add(f.getFileName().toString());
+                            }
+                        }
+                    }
+                    if (!subprojects.isEmpty()) {
+                        model = model.withSubprojects(subprojects);
+                    }
+                } catch (IOException e) {
+                    problems.add(Severity.FATAL, ModelProblem.Version.V41, 
"Error discovering subprojects", e);
+                }
+            }
         }
 
         problems.setSource(model);
diff --git 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
index 30dab0bd7c..5a0912536b 100644
--- 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
+++ 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
@@ -644,6 +644,10 @@ public class DefaultModelValidator implements 
ModelValidator {
 
         validateStringNotEmpty("packaging", problems, Severity.ERROR, 
Version.BASE, m.getPackaging(), m);
 
+        // TODO: if the model is a 4.1.0:
+        //   * modules should be empty, else issue a warning
+        //   * validate subprojects
+
         if (!m.getModules().isEmpty()) {
             if (!"pom".equals(m.getPackaging())) {
                 addViolation(
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
index c0ef5bcf3e..dbf7be1afb 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
@@ -294,6 +294,7 @@ class DefaultConsumerPomBuilder implements 
ConsumerPomBuilder {
                         || 
profile.getDependencyManagement().getDependencies().isEmpty())
                 && profile.getDistributionManagement() == null
                 && profile.getModules().isEmpty()
+                && profile.getSubprojects().isEmpty()
                 && profile.getProperties().isEmpty()
                 && profile.getRepositories().isEmpty()
                 && profile.getPluginRepositories().isEmpty()
diff --git 
a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java 
b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index ddd26688bc..a6dae088e5 100644
--- 
a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ 
b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -121,6 +121,7 @@ import org.slf4j.LoggerFactory;
 public class DefaultProjectBuilder implements ProjectBuilder {
     public static final String BUILDER_PARALLELISM = 
"maven.projectBuilder.parallelism";
     public static final int DEFAULT_BUILDER_PARALLELISM = 
Runtime.getRuntime().availableProcessors() / 2 + 1;
+    private static final String POM_4_0_0 = "4.0.0";
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
     private final ModelBuilder modelBuilder;
@@ -275,7 +276,7 @@ public class DefaultProjectBuilder implements 
ProjectBuilder {
 
         boolean root;
 
-        List<InterimResult> modules = Collections.emptyList();
+        List<InterimResult> subprojects = Collections.emptyList();
 
         ProjectBuildingResult projectBuildingResult;
 
@@ -629,20 +630,24 @@ public class DefaultProjectBuilder implements 
ProjectBuilder {
 
             if (recursive) {
                 File basedir = pomFile.getParentFile();
-                List<File> moduleFiles = new ArrayList<>();
-                for (String module : model.getModules()) {
-                    if (module == null || module.isEmpty()) {
+                List<String> subprojects = model.getSubprojects();
+                if (subprojects.isEmpty()) {
+                    subprojects = model.getModules();
+                }
+                List<File> subprojectFiles = new ArrayList<>();
+                for (String subproject : subprojects) {
+                    if (subproject == null || subproject.isEmpty()) {
                         continue;
                     }
 
-                    module = module.replace('\\', 
File.separatorChar).replace('/', File.separatorChar);
+                    subproject = subproject.replace('\\', 
File.separatorChar).replace('/', File.separatorChar);
 
-                    Path modulePath = modelProcessor.locateExistingPom(new 
File(basedir, module).toPath());
-                    File moduleFile = modulePath != null ? modulePath.toFile() 
: null;
+                    Path subprojectPath = modelProcessor.locateExistingPom(new 
File(basedir, subproject).toPath());
+                    File subprojectFile = subprojectPath != null ? 
subprojectPath.toFile() : null;
 
-                    if (moduleFile == null) {
+                    if (subprojectFile == null) {
                         ModelProblem problem = new 
org.apache.maven.internal.impl.model.DefaultModelProblem(
-                                "Child module " + moduleFile + " of " + 
pomFile + " does not exist",
+                                "Child subproject " + subprojectFile + " of " 
+ pomFile + " does not exist",
                                 ModelProblem.Severity.ERROR,
                                 ModelProblem.Version.BASE,
                                 model,
@@ -656,23 +661,24 @@ public class DefaultProjectBuilder implements 
ProjectBuilder {
                     if (Os.IS_WINDOWS) {
                         // we don't canonicalize on unix to avoid interfering 
with symlinks
                         try {
-                            moduleFile = moduleFile.getCanonicalFile();
+                            subprojectFile = subprojectFile.getCanonicalFile();
                         } catch (IOException e) {
-                            moduleFile = moduleFile.getAbsoluteFile();
+                            subprojectFile = subprojectFile.getAbsoluteFile();
                         }
                     } else {
-                        moduleFile = new File(moduleFile.toURI().normalize());
+                        subprojectFile = new 
File(subprojectFile.toURI().normalize());
                     }
 
-                    if (aggregatorFiles.contains(moduleFile)) {
+                    if (aggregatorFiles.contains(subprojectFile)) {
                         StringBuilder buffer = new StringBuilder(256);
                         for (File aggregatorFile : aggregatorFiles) {
                             buffer.append(aggregatorFile).append(" -> ");
                         }
-                        buffer.append(moduleFile);
+                        buffer.append(subprojectFile);
 
                         ModelProblem problem = new 
org.apache.maven.internal.impl.model.DefaultModelProblem(
-                                "Child module " + moduleFile + " of " + 
pomFile + " forms aggregation cycle " + buffer,
+                                "Child subproject " + subprojectFile + " of " 
+ pomFile + " forms aggregation cycle "
+                                        + buffer,
                                 ModelProblem.Severity.ERROR,
                                 ModelProblem.Version.BASE,
                                 model,
@@ -684,11 +690,11 @@ public class DefaultProjectBuilder implements 
ProjectBuilder {
                         continue;
                     }
 
-                    moduleFiles.add(moduleFile);
+                    subprojectFiles.add(subprojectFile);
                 }
 
-                if (!moduleFiles.isEmpty()) {
-                    interimResult.modules = build(projectIndex, moduleFiles, 
aggregatorFiles, false, recursive);
+                if (!subprojectFiles.isEmpty()) {
+                    interimResult.subprojects = build(projectIndex, 
subprojectFiles, aggregatorFiles, false, recursive);
                 }
             }
 
@@ -780,7 +786,7 @@ public class DefaultProjectBuilder implements 
ProjectBuilder {
                             iarte));
                 }
 
-                List<ProjectBuildingResult> results = build(projectIndex, 
interimResult.modules);
+                List<ProjectBuildingResult> results = build(projectIndex, 
interimResult.subprojects);
 
                 project.setExecutionRoot(interimResult.root);
                 project.setCollectedProjects(
diff --git 
a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java 
b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
index 42b08f5288..e155609de5 100644
--- a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
+++ b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
@@ -708,6 +708,9 @@ public class MavenProject implements Cloneable {
     }
 
     public List<String> getModules() {
+        if (!getModel().getDelegate().getSubprojects().isEmpty()) {
+            return getModel().getDelegate().getSubprojects();
+        }
         return getModel().getModules();
     }
 
diff --git 
a/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java
 
b/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java
index 97152f5e4e..116d8a88cd 100644
--- 
a/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java
+++ 
b/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java
@@ -405,4 +405,18 @@ class DefaultMavenProjectBuilderTest extends 
AbstractMavenProjectTestCase {
 
         assertEquals("1.0-SNAPSHOT", mp.getVersion());
     }
+
+    @Test
+    public void testSubprojectDiscovery() throws Exception {
+        File pom = 
getTestFile("src/test/resources/projects/subprojects-discover/pom.xml");
+        ProjectBuildingRequest configuration = newBuildingRequest();
+
+        List<ProjectBuildingResult> results = 
projectBuilder.build(List.of(pom), true, configuration);
+        assertEquals(2, results.size());
+        MavenProject p1 = results.get(0).getProject();
+        MavenProject p2 = results.get(1).getProject();
+        MavenProject parent = p1.getArtifactId().equals("parent") ? p1 : p2;
+        MavenProject child = p1.getArtifactId().equals("parent") ? p2 : p1;
+        assertEquals(List.of("child"), 
parent.getModel().getDelegate().getSubprojects());
+    }
 }
diff --git 
a/maven-core/src/test/resources/projects/subprojects-discover/child/pom.xml 
b/maven-core/src/test/resources/projects/subprojects-discover/child/pom.xml
new file mode 100644
index 0000000000..4ec6c2320f
--- /dev/null
+++ b/maven-core/src/test/resources/projects/subprojects-discover/child/pom.xml
@@ -0,0 +1,8 @@
+<project xmlns="http://maven.apache.org/POM/4.1.0";>
+  <parent>
+    <groupId>subprojects-discover</groupId>
+    <artifactId>parent</artifactId>
+  </parent>
+  <artifactId>child</artifactId>
+  <packaging>jar</packaging>
+</project>
diff --git 
a/maven-core/src/test/resources/projects/subprojects-discover/pom.xml 
b/maven-core/src/test/resources/projects/subprojects-discover/pom.xml
new file mode 100644
index 0000000000..6567ad58f2
--- /dev/null
+++ b/maven-core/src/test/resources/projects/subprojects-discover/pom.xml
@@ -0,0 +1,6 @@
+<project xmlns="http://maven.apache.org/POM/4.1.0";>
+  <groupId>subprojects-discover</groupId>
+  <artifactId>parent</artifactId>
+  <version>1</version>
+  <packaging>pom</packaging>
+</project>

Reply via email to