You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/09/07 18:43:53 UTC

(maven) branch master updated: [MNG-7793] Add generated-source metadata to Maven 4 APIs (#12958)

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 3f6be6d7cc [MNG-7793] Add generated-source metadata to Maven 4 APIs (#12958)
3f6be6d7cc is described below

commit 3f6be6d7cc55a0ed40a7d7b34f4de55ca7a4041e
Author: Goutam Adwant <86...@users.noreply.github.com>
AuthorDate: Mon Sep 7 11:43:47 2026 -0700

    [MNG-7793] Add generated-source metadata to Maven 4 APIs (#12958)
    
    Signed-off-by: goutamadwant <wo...@gmail.com>
---
 .../main/java/org/apache/maven/api/SourceRoot.java |  8 +++
 .../apache/maven/api/services/ProjectManager.java  | 19 +++++++
 .../java/org/apache/maven/api/SourceRootTest.java  |  2 +
 api/maven-api-model/src/main/mdo/maven.mdo         | 15 ++++++
 .../maven/internal/impl/DefaultProjectManager.java | 27 ++++++++--
 .../apache/maven/project/ConnectedResource.java    |  3 +-
 .../internal/impl/DefaultProjectManagerTest.java   | 28 ++++++++++
 .../impl/ConsumerPomBuilderTest.java               |  2 +
 .../org/apache/maven/project/MavenProjectTest.java | 13 +++++
 .../apache/maven/project/ResourceIncludeTest.java  | 20 +++++++
 .../test/resources/consumer/multi-module/pom.xml   |  3 +-
 .../org/apache/maven/impl/DefaultSourceRoot.java   | 62 +++++++++++++++++++++-
 .../maven/impl/DefaultModelXmlFactoryTest.java     | 41 ++++++++++++++
 .../apache/maven/impl/DefaultSourceRootTest.java   | 17 ++++++
 14 files changed, 253 insertions(+), 7 deletions(-)

diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
index c8451e66dd..5d34998c8f 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
@@ -317,6 +317,14 @@ default boolean stringFiltering() {
         return false;
     }
 
+    /**
+     * {@return whether the source files are generated by the build}
+     * The default value is {@code false}.
+     */
+    default boolean generated() {
+        return false;
+    }
+
     /**
      * {@return whether the directory described by this source element should be included in the build}
      * The default value is {@code true}.
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java
index 302768585d..8609c3e7ee 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java
@@ -216,6 +216,25 @@ Stream<SourceRoot> getEnabledSourceRoots(
     void addSourceRoot(
             @Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory);
 
+    /**
+     * Resolves and adds the given directory as a source with the given scope, language and generated-source state.
+     * This method does not infer generated-source state from the directory name.
+     *
+     * @param project the project to update
+     * @param scope scope (main or test) of the directory to add
+     * @param language language of the files contained in the directory to add
+     * @param directory the directory to add if not already present in the source
+     * @param generated whether the source files are generated by the build
+     *
+     * @see #getEnabledSourceRoots(Project, ProjectScope, Language)
+     */
+    void addSourceRoot(
+            @Nonnull Project project,
+            @Nonnull ProjectScope scope,
+            @Nonnull Language language,
+            @Nonnull Path directory,
+            boolean generated);
+
     /**
      * Returns an immutable list of project remote repositories (directly specified or inherited).
      * The repositories are ordered by declaration order, with inherited repositories appearing
diff --git a/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java b/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
index a316550aee..a192370839 100644
--- a/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
+++ b/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
@@ -27,6 +27,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -61,6 +62,7 @@ public PathMatcher matcher(Collection<String> defaultIncludes, boolean useDefaul
     @Test
     void testDirectory() {
         assertEquals(Path.of("src", "main", "java"), directory());
+        assertFalse(generated());
 
         scope = ProjectScope.TEST;
         assertEquals(Path.of("src", "test", "java"), directory());
diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo
index 243b784e2a..779076f2b4 100644
--- a/api/maven-api-model/src/main/mdo/maven.mdo
+++ b/api/maven-api-model/src/main/mdo/maven.mdo
@@ -2200,6 +2200,21 @@
           <type>boolean</type>
           <defaultValue>false</defaultValue>
         </field>
+        <field>
+          <name>generated</name>
+          <version>4.2.0+</version>
+          <description>
+            <![CDATA[
+            Whether the source files are generated by the build instead of maintained as project sources.
+            This attribute allows tools that analyze source code to distinguish generated files without
+            relying on directory naming conventions.
+
+            <p>The default value is {@code false}.</p>
+            ]]>
+          </description>
+          <type>boolean</type>
+          <defaultValue>false</defaultValue>
+        </field>
         <field>
           <name>enabled</name>
           <version>4.1.0+</version>
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
index 46356ef69f..cb5758920c 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
@@ -44,6 +44,7 @@
 import org.apache.maven.api.di.SessionScoped;
 import org.apache.maven.api.services.ArtifactManager;
 import org.apache.maven.api.services.ProjectManager;
+import org.apache.maven.impl.DefaultSourceRoot;
 import org.apache.maven.impl.InternalSession;
 import org.apache.maven.impl.MappedList;
 import org.apache.maven.impl.PropertiesAsMap;
@@ -201,11 +202,29 @@ public void addSourceRoot(
             @Nonnull ProjectScope scope,
             @Nonnull Language language,
             @Nonnull Path directory) {
+        addSourceRoot(project, scope, language, directory, false);
+    }
+
+    @Override
+    public void addSourceRoot(
+            @Nonnull Project project,
+            @Nonnull ProjectScope scope,
+            @Nonnull Language language,
+            @Nonnull Path directory,
+            boolean generated) {
         MavenProject prj = getMavenProject(requireNonNull(project, "project" + " cannot be null"));
-        prj.addSourceRoot(
-                requireNonNull(scope, "scope" + " cannot be null"),
-                requireNonNull(language, "language" + " cannot be null"),
-                requireNonNull(directory, "directory" + " cannot be null"));
+        Path resolved = prj.getBaseDirectory()
+                .resolve(requireNonNull(directory, "directory" + " cannot be null"))
+                .normalize();
+        ProjectScope actualScope = requireNonNull(scope, "scope" + " cannot be null");
+        Language actualLanguage = requireNonNull(language, "language" + " cannot be null");
+        boolean exists = prj.getSourceRoots().stream()
+                .anyMatch(source -> source.scope() == actualScope
+                        && source.language() == actualLanguage
+                        && source.directory().equals(resolved));
+        if (!exists) {
+            prj.addSourceRoot(new DefaultSourceRoot(actualScope, actualLanguage, resolved, generated));
+        }
     }
 
     @Override
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java b/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java
index cbb0629b21..c6d6832a7c 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java
@@ -122,7 +122,8 @@ private void updateProjectSourceRoot() {
 
         if (index >= 0) {
             // Replace the SourceRoot at the same position
-            SourceRoot newSourceRoot = new DefaultSourceRoot(project.getBaseDirectory(), scope, this.getDelegate());
+            SourceRoot newSourceRoot = new DefaultSourceRoot(
+                    project.getBaseDirectory(), scope, this.getDelegate(), originalSourceRoot.generated());
             sourcesList.set(index, newSourceRoot);
 
             // Update the project's sources, preserving order
diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java
index 7b8ee0c8b6..53e6d7723e 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java
@@ -20,12 +20,14 @@
 
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.function.Supplier;
 
 import org.apache.maven.api.Language;
 import org.apache.maven.api.ProducedArtifact;
 import org.apache.maven.api.Project;
 import org.apache.maven.api.ProjectScope;
+import org.apache.maven.api.SourceRoot;
 import org.apache.maven.api.services.ArtifactManager;
 import org.apache.maven.impl.DefaultModelVersionParser;
 import org.apache.maven.impl.DefaultSourceRoot;
@@ -35,6 +37,8 @@
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.when;
@@ -49,6 +53,30 @@ class DefaultProjectManagerTest {
 
     private Path artifactPath;
 
+    @Test
+    void addGeneratedSourceRoots() {
+        InternalMavenSession session = Mockito.mock(InternalMavenSession.class);
+        MavenProject mavenProject = new MavenProject();
+        mavenProject.setFile(
+                Path.of("target", "project", "pom.xml").toAbsolutePath().toFile());
+        Project project = new DefaultProject(session, mavenProject);
+        DefaultProjectManager projectManager = new DefaultProjectManager(session, Mockito.mock(ArtifactManager.class));
+
+        projectManager.addSourceRoot(
+                project, ProjectScope.MAIN, Language.JAVA_FAMILY, Path.of("target/generated-sources/main"), true);
+        projectManager.addSourceRoot(
+                project, ProjectScope.TEST, Language.JAVA_FAMILY, Path.of("target/generated-test-sources/test"), true);
+        projectManager.addSourceRoot(project, ProjectScope.MAIN, Language.JAVA_FAMILY, Path.of("src/main/java"));
+        projectManager.addSourceRoot(
+                project, ProjectScope.MAIN, Language.JAVA_FAMILY, Path.of("target/generated-sources/main"), false);
+
+        List<SourceRoot> roots = projectManager.getSourceRoots(project).stream().toList();
+        assertEquals(3, roots.size());
+        assertTrue(roots.get(0).generated());
+        assertTrue(roots.get(1).generated());
+        assertFalse(roots.get(2).generated());
+    }
+
     @Test
     void attachArtifact() {
         InternalMavenSession session = Mockito.mock(InternalMavenSession.class);
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 8ef945c600..5f6661a665 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
@@ -330,6 +330,8 @@ void testMultiModuleConsumerPreserveModelVersion() throws Exception {
 
         assertNotNull(transformed);
         assertNotNull(transformed.getBuild());
+        assertTrue(transformed.getBuild().getSources().get(0).isGenerated());
+        assertFalse(transformed.getBuild().getSources().get(1).isGenerated());
         assertTrue(transformed.getDependencies().isEmpty());
         assertFalse(transformed.getDependencyManagement().getDependencies().isEmpty());
     }
diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/MavenProjectTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/MavenProjectTest.java
index 63ac9d3da9..0adc23af2b 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/project/MavenProjectTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/project/MavenProjectTest.java
@@ -214,6 +214,19 @@ void testAddDotFile() {
         project.addCompileSourceRoot(".");
 
         assertEquals(1, project.getCompileSourceRoots().size());
+        assertTrue(project.getSourceRoots().stream().noneMatch(source -> source.generated()));
+    }
+
+    @Test
+    void testLegacySourceRootsAreNotGenerated() {
+        MavenProject project = new MavenProject();
+        project.setFile(Path.of("target", "project", "pom.xml").toAbsolutePath().toFile());
+
+        project.addCompileSourceRoot("target/generated-sources/main");
+        project.addTestCompileSourceRoot("target/generated-test-sources/test");
+
+        assertEquals(2, project.getSourceRoots().size());
+        assertTrue(project.getSourceRoots().stream().noneMatch(source -> source.generated()));
     }
 
     @Test
diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
index 57ad0dbfc8..8c4a7a355a 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
@@ -195,6 +195,26 @@ void testUnderlyingSourceRootsUpdated() {
         assertTrue(sourceRoot.includes().contains("*.xml"), "Underlying SourceRoot should contain the include");
     }
 
+    @Test
+    void testGeneratedStatePreservedWhenResourceIsUpdated() {
+        project.addSourceRoot(new DefaultSourceRoot(
+                ProjectScope.MAIN, Language.RESOURCES, Path.of("target/generated-resources"), true));
+
+        Resource generatedResource = project.getResources().stream()
+                .filter(resource -> resource.getDirectory().endsWith("generated-resources"))
+                .findFirst()
+                .orElseThrow();
+        generatedResource.addInclude("*.properties");
+
+        org.apache.maven.api.SourceRoot generatedSource = project.getEnabledSourceRoots(
+                        ProjectScope.MAIN, Language.RESOURCES)
+                .filter(source -> source.directory().endsWith("generated-resources"))
+                .findFirst()
+                .orElseThrow();
+        assertTrue(generatedSource.generated());
+        assertTrue(generatedSource.includes().contains("*.properties"));
+    }
+
     /*MNG-11062*/
     @Test
     void testTargetPathPreservedWithConnectedResource() {
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
index 972e7c9be2..1e41a97663 100644
--- a/impl/maven-core/src/test/resources/consumer/multi-module/pom.xml
+++ b/impl/maven-core/src/test/resources/consumer/multi-module/pom.xml
@@ -1,4 +1,4 @@
-<project root="true" xmlns="http://maven.apache.org/POM/4.1.0">
+<project root="true" xmlns="http://maven.apache.org/POM/4.2.0">
   <groupId>org.my.group</groupId>
   <artifactId>parent</artifactId>
   <version>1.0-SNAPSHOT</version>
@@ -31,6 +31,7 @@
     <sources>
       <source>
         <module>org.foo</module>
+        <generated>true</generated>
       </source>
       <source>
         <module>org.foo.bar</module>
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
index feadb91f6c..d155da565a 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
@@ -48,6 +48,7 @@
  * @param excludes            patterns for the files to exclude, or empty if nothing to exclude
  * @param stringFiltering     whether resources are filtered to replace tokens with parameterized values
  * @param targetPathOrNull    an explicit target path, overriding the default value
+ * @param generated           whether the source files are generated by the build
  * @param enabled             whether the directory described by this source element should be included in the build
  */
 public record DefaultSourceRoot(
@@ -60,6 +61,7 @@ public record DefaultSourceRoot(
         @Nonnull List<String> excludes,
         boolean stringFiltering,
         @Nullable Path targetPathOrNull,
+        boolean generated,
         boolean enabled)
         implements SourceRoot {
 
@@ -71,7 +73,48 @@ public record DefaultSourceRoot(
      * @param directory the root directory where the sources are stored
      */
     public DefaultSourceRoot(ProjectScope scope, Language language, Path directory) {
-        this(scope, language, null, null, directory, null, null, false, null, true);
+        this(scope, language, directory, false);
+    }
+
+    /**
+     * Creates a simple instance with no Java module, no target version, and no include or exclude pattern.
+     *
+     * @param scope     in which context the source files will be used (main or test)
+     * @param language  the language of the source files
+     * @param directory the root directory where the sources are stored
+     * @param generated whether the source files are generated by the build
+     */
+    public DefaultSourceRoot(ProjectScope scope, Language language, Path directory, boolean generated) {
+        this(scope, language, null, null, directory, null, null, false, null, generated, true);
+    }
+
+    /**
+     * Creates an instance while retaining the constructor signature used before generated-source metadata was added.
+     */
+    @SuppressWarnings("checkstyle:ParameterNumber")
+    public DefaultSourceRoot(
+            @Nonnull ProjectScope scope,
+            @Nonnull Language language,
+            @Nullable String moduleName,
+            @Nullable Version targetVersionOrNull,
+            @Nonnull Path directory,
+            @Nullable List<String> includes,
+            @Nullable List<String> excludes,
+            boolean stringFiltering,
+            @Nullable Path targetPathOrNull,
+            boolean enabled) {
+        this(
+                scope,
+                language,
+                moduleName,
+                targetVersionOrNull,
+                directory,
+                includes,
+                excludes,
+                stringFiltering,
+                targetPathOrNull,
+                false,
+                enabled);
     }
 
     /**
@@ -86,6 +129,7 @@ public DefaultSourceRoot(ProjectScope scope, Language language, Path directory)
      * @param excludes            patterns for the files to exclude, or {@code null} or empty if nothing to exclude
      * @param stringFiltering     whether resources are filtered to replace tokens with parameterized values
      * @param targetPathOrNull    an explicit target path, overriding the default value
+     * @param generated           whether the source files are generated by the build
      * @param enabled             whether the directory described by this source element should be included in the build
      */
     @SuppressWarnings("checkstyle:ParameterNumber")
@@ -99,6 +143,7 @@ public DefaultSourceRoot(
             @Nullable List<String> excludes,
             boolean stringFiltering,
             @Nullable Path targetPathOrNull,
+            boolean generated,
             boolean enabled) {
         this.scope = Objects.requireNonNull(scope);
         this.language = Objects.requireNonNull(language);
@@ -114,6 +159,7 @@ public DefaultSourceRoot(
         } else {
             this.targetPathOrNull = null;
         }
+        this.generated = generated;
         this.enabled = enabled;
     }
 
@@ -148,6 +194,7 @@ public static DefaultSourceRoot fromModel(
                 source.getExcludes(),
                 source.isStringFiltering(),
                 nonBlank(source.getTargetPath()).map(Path::of).orElse(null),
+                source.isGenerated(),
                 source.isEnabled());
     }
 
@@ -167,6 +214,18 @@ public static DefaultSourceRoot fromModel(
      * @param resource a resource element from the model
      */
     public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource) {
+        this(baseDir, scope, resource, false);
+    }
+
+    /**
+     * Creates a new instance from the given resource while preserving generated-source metadata.
+     *
+     * @param baseDir the base directory for resolving relative paths (used only for the source directory)
+     * @param scope the scope of the resource (main or test)
+     * @param resource a resource element from the model
+     * @param generated whether the source files are generated by the build
+     */
+    public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource, boolean generated) {
         this(
                 scope,
                 Language.RESOURCES,
@@ -179,6 +238,7 @@ public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resour
                 resource.getExcludes(),
                 Boolean.parseBoolean(resource.getFiltering()),
                 nonBlank(resource.getTargetPath()).map(Path::of).orElse(null),
+                generated,
                 true);
     }
 
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
index 8a93cadf9c..6e4f1040f3 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
@@ -73,6 +73,47 @@ void testValidNamespaceWithModelVersion410() throws Exception {
         assertEquals("http://maven.apache.org/POM/4.1.0", model.getNamespaceUri());
     }
 
+    @Test
+    void testGeneratedSourceRoundTrip() throws Exception {
+        String xml = """
+                <project xmlns="http://maven.apache.org/POM/4.2.0">
+                  <modelVersion>4.2.0</modelVersion>
+                  <groupId>g</groupId>
+                  <artifactId>a</artifactId>
+                  <version>1</version>
+                  <build>
+                    <sources>
+                      <source>
+                        <directory>target/generated-sources/java</directory>
+                        <generated>true</generated>
+                      </source>
+                      <source>
+                        <directory>src/main/java</directory>
+                      </source>
+                    </sources>
+                  </build>
+                </project>""";
+
+        Model model = factory.read(XmlReaderRequest.builder()
+                .reader(new StringReader(xml))
+                .strict(true)
+                .build());
+
+        assertTrue(model.getBuild().getSources().get(0).isGenerated());
+        assertFalse(model.getBuild().getSources().get(1).isGenerated());
+
+        StringWriter out = new StringWriter();
+        factory.write(
+                XmlWriterRequest.<Model>builder().writer(out).content(model).build());
+        Model roundTripped = factory.read(XmlReaderRequest.builder()
+                .reader(new StringReader(out.toString()))
+                .strict(true)
+                .build());
+
+        assertTrue(roundTripped.getBuild().getSources().get(0).isGenerated());
+        assertFalse(roundTripped.getBuild().getSources().get(1).isGenerated());
+    }
+
     @Test
     void testInvalidNamespaceWithModelVersion410() {
         String xml = """
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
index 3f43d15af6..bc705ff3f4 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
@@ -86,6 +86,22 @@ void testMainJavaDirectory() {
         assertEquals(Language.JAVA_FAMILY, source.language());
         assertEquals(Path.of("myproject", "src", "main", "java"), source.directory());
         assertTrue(source.targetVersion().isEmpty());
+        assertFalse(source.generated());
+    }
+
+    @Test
+    void testGeneratedSource() {
+        var source = DefaultSourceRoot.fromModel(
+                session,
+                Path.of("myproject"),
+                outputDirectory(),
+                Source.newBuilder()
+                        .directory("target/generated-sources/java")
+                        .generated(true)
+                        .build());
+
+        assertEquals(Path.of("myproject", "target", "generated-sources", "java"), source.directory());
+        assertTrue(source.generated());
     }
 
     @Test
@@ -198,6 +214,7 @@ void testExtractsTargetPathFromResource() {
         assertEquals(Path.of("myproject", "src", "test", "resources"), sourceRoot.directory());
         assertEquals(ProjectScope.TEST, sourceRoot.scope());
         assertEquals(Language.RESOURCES, sourceRoot.language());
+        assertFalse(sourceRoot.generated());
     }
 
     /*MNG-11062*/