Hi Martin, thanks for your feedback and sorry for the lengthy text and wrong internal linkage. Perhaps you have already noticed, that I wrote it with another tool (IntelliJ as markdown file) and then copy/pasted it to the mail? Obviously this was a bad idea (technically).
Meanwhile I have published the page in the Apache Confluence: https://cwiki.apache.org/confluence/x/84TMFw (sub page of the current Java Modules overview). Should we discuss over there? I am currently hesitating how to create issues out of it. In the first place this is a question of component/algorithm concept and design for me. Having a complete overview with all aspects seemed like a good starting point to me, like an Epic. If we can cut smaller pieces out of it, these may result in (GitHub) issues for implementation. However, the document (Confluence page) may also help other committers later to understand the overall design considerations? Having said that and already having implement the resources handling (PR 11505) I think it is not such a big deal to implement the remaining parts. Regards, Gerd > On 3. Dec 2025, at 16:41, Martin Desruisseaux via dev <[email protected]> > wrote: > > Hello Gerd > > Thanks for your in-depth analysis. > > * Note for other readers: original Gerd's email is below this reply. > It has a lot of content, but I try to digest the main points below. > * Note for Gerd: the email contains links to "http://localhost", which > we cannot see. In addition, I think that the originally intended > formatting has been lost in the email. It may be worth to provide > this content in GitHub issues, maybe with one issue one problem. > > Gerd pointed out the following problems in the current Maven behavior > regarding Java modules. My comments are inline: > > >> Problem 1: Mixed Modular/Non-Modular Sources Accepted Silently > I agree that this problem should be detected by Maven core instead of Maven > compiler plugin, for the reasons stated by Gerd in his email (earlier and > more complete validation, validation done for all plugins, provides line > numbers were problems occur). > > >> Problem 2: Test-Only Modular Projects Get Classic Main Sources Injected >> If src/main/java exists with old/unrelated code → it gets compiled! Modular >> test project silently becomes a mixed modular-test + classic-main project > I see that as a Maven Compiler Plugin bug. Maybe the description of this > problem should move to a GitHub issue on > https://github.com/apache/maven-compiler-plugin/? > > >> Problem 3: Resources Are Not Module-Aware >> When a project uses modular sources, resources should follow the same >> modular layout. Currently, they don't. > I propose to separate this problem in three parts: > > 1. In the current model, users must declare modular resources > explicitly with <source> elements having <lang>resources</lang>. > However, the current Maven Resource Plugin has not yet been updated > for honoring that configuration. This is a first problem to resolve. > 2. The second problem is what to do if the user did not declare > resources explicitly. In the current Maven core implementation, we > inherit the default Maven 3 configuration, which is incorrect for a > modular project. > 3. A third problem is what to do if the user mixes modular and > classical configuration. The current Maven core implementation > raises no warning. > > The https://github.com/apache/maven/pull/11505 pull request seems to address > 2 and 3, but not 1, is that right? If so, should we resolve 1 first? > > >> Problem 4: Legacy Configuration Silently Ignored >> When <sources> is present, legacy elements like <sourceDirectory> are >> silently ignored without warning. > I agree that it is a risk of unpredictable (or at least non-obvious) behavior > and should cause a warning or error. Gerd's email proposes two alternative > approaches: lenient or strict. I would favor the strict approach on the > argument that the unified <source> element is a new feature of Maven 4 (so > there is no backward compatibility constraint), and it is easier to be too > strict at first and relax later if needed than doing the opposite. > > Regarding the proposal in the email copied below, I propose two amendments. > First, I suggest to drop the following rule: > >> R3 Duplicate <source> (same scope/lang/module) ERROR "Duplicate source >> definition for scope='X', lang='Y', module='Z'" > The rational is to support projects having their sources split in many > directories for the same module. This is documented as a replacement for the > `build-helper-maven-plugin` in the "Declaration of many source directories" > section of > https://maven.apache.org/plugins/maven-compiler-plugin-4.x/sources.html (that > particular section is about classical projects, but can applied to modular > projects too). > > I propose to also drop the "Filesystem mismatch" warning, if "filesystem" is > in the sense of `Path.getFileSystem()`. The codes that have been revisited in > the compiler plugin, clean plugin and parts of Maven core try to be careful > in the way that they resolve paths. They should work fine even with mixed > filesystems (if not, it would be bugs to fix). > > Martin > > > Le 30/11/2025 à 14:45, Gerd Aschemann a écrit : >> Hi, >> >> based on my work to migrate a number of Java Modules (aka. Jigsaw/JPMS) to >> Maven (4), I did a deep investigation of the current implementation. I >> already discussed with some of you on Slack about my work and one thing we >> found is the missing handling of resources when (Java) modules are used. >> >> At first I just tried to implement proper resource handling (cf. my >> proposed, yet draft, PR). But the deeper I looked into it, I found Maven >> support for Java Modules incomplete, in particular wrt the old (Maven <= 3) >> conventions. Therefore, I have now created a deep analysis based on some >> typical examples. The analysis culminates in a full matrix of combinations >> of sources/resources etc. I also propose how to handle the different cases. >> >> Please have a look into it and provide feedback about it. The subject is >> rather complex, so I might have missed important view points. >> >> Perhaps other media are better suited to discuss the different cases and >> their consequences (in particular wrt. to warnings and errors)? If its >> helpful, I’d be happy to place the stuff into the Maven Wiki (Confluence). >> >> However, in the end, the work should result in some decisions. I propose the >> following >> Handle model loading and proper handling of sources/resources completely in >> the core (currently, the compiler plugin contributes to some handling, see >> below) >> Implement a strict approach how to handle misconfigurations (this is mostly >> about warnings vs. errors, see below). Personally, I plead for a very strict >> handling (errors) here as the feature is new with Maven 4 and have no need >> to handle legacy code about Java modules. >> Build this handling into the Maven core and provide proper documentation. >> Perhaps redraw some of the existing error handling from the compiler plugin >> as it much better suites into the core (sorry, Martin). >> >> Of course, I hereby volunteer to perform the implementation. I have already >> started with some of it, to be honest. >> >> For phase 1 (proper handling of resources in the context of <sources> and >> <modules> I have implemented a solution (cf. Draft >> PR<https://github.com/apache/maven/pull/11505> on maven-core). The nice >> thing about this implementation is, that even M-Resources-P in version 3.x >> can handle the resources with this Maven core as it is using the proper >> abstractions. >> >> Regards >> -- >> Gerd Aschemann --- Veröffentlichen heißt Verändern (Carmen Thomas) >> +49/173/3264070 [email protected] --http://www.aschemann.net >> >> >> Build Sources Validation >> >> This document captures a discussion about validating <sources> configuration >> in Maven 4.x. >> >> 0. Scope >> >> This document focuses exclusively on the Maven 4.x core implementation >> (master branch of apache/maven<https://github.com/apache/maven>). >> Specifically: >> >> The <sources> element introduced in POM model version 4.1.0 >> The source/resource handling logic in DefaultProjectBuilder.java >> Validation gaps in the core that should be addressed >> 0.1 Compiler Plugin Compensation >> >> Some validation gaps in the core are currently compensated by the >> maven-compiler-plugin, which performs its own validation at compile time, >> e.g., >> >> Mixed modular/non-modular sources → compiler plugin fails with "Mix of >> modular and non-modular sources", cf. >> ToolExecutor.java#L595-L608<https://github.com/apache/maven-compiler-plugin/blob/050664790af28be9107aff6172f8e61adcb8ed8a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java#L595-L608> >> However, relying on plugin-level validation is suboptimal: >> >> Later feedback: Errors appear during compilation, not during POM processing >> No line numbers: Plugin errors cannot reference the exact POM location >> Plugin-specific: Other language plugins (Kotlin, Groovy, Scala) may not have >> equivalent validation >> Incomplete coverage: Some invalid configurations are not caught at all >> (e.g., test-only modular projects) >> The goal should be to implement proper validation in the Maven core, >> providing early, consistent feedback with precise error locations via >> ModelProblem. >> >> 0.2 Origin of This Discussion >> >> This discussion was triggered by investigating how to best implement >> resource handling in the new modular source directory hierarchy. Maven 4.x >> introduces a unified <sources> element that supports modular layouts like: >> >> src/ >> ├── org.foo.moduleA/ >> │ ├── main/ >> │ │ ├── java/ >> │ │ └── resources/ ← Module-specific resources >> │ └── test/ >> │ ├── java/ >> │ └── resources/ >> └── org.foo.moduleB/ >> ├── main/ >> │ ├── java/ >> │ └── resources/ ← Module-specific resources >> └── test/ >> However, the current implementation does not automatically pick up resources >> from the modular paths (src/<module>/main/resources). Instead, it always >> uses the legacy <resources> element which defaults to src/main/resources. >> >> Solution: See Phase >> 1<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented> >> for the implementation that automatically injects module-aware resources. >> >> Current Workaround: Users can configure the maven-resources-plugin with >> explicit executions for each module: >> >> <plugin> >> <groupId>org.apache.maven.plugins</groupId> >> <artifactId>maven-resources-plugin</artifactId> >> <executions> >> <execution> >> <id>copy-resources-moduleA</id> >> <phase>process-resources</phase> >> <goals><goal>copy-resources</goal></goals> >> <configuration> >> <resources> >> <resource> >> <directory>src/org.foo.moduleA/main/resources</directory> >> </resource> >> </resources> >> <outputDirectory>${project.build.outputDirectory}</outputDirectory> >> </configuration> >> </execution> >> <execution> >> <id>copy-resources-moduleB</id> >> <phase>process-resources</phase> >> <goals><goal>copy-resources</goal></goals> >> <configuration> >> <resources> >> <resource> >> <directory>src/org.foo.moduleB/main/resources</directory> >> </resource> >> </resources> >> <outputDirectory>${project.build.outputDirectory}</outputDirectory> >> </configuration> >> </execution> >> </executions> >> </plugin> >> This workaround is verbose and error-prone. The investigation into >> implementing this properly in the core led to the Phase 1 >> implementation<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented> >> and revealed the broader set of validation and configuration handling >> issues documented here. >> >> 1. Problem Statement >> >> Let's investigate some of the problems with the current implementation of >> <sources> handling in Maven 4.x. Maven 4.x introduces a new <sources> >> element that supports modular project layouts (src/<module>/<scope>/<lang>). >> However, the current implementation has several issues when handling the >> interaction between the new <sources> element and legacy configuration >> elements (<sourceDirectory>, <resources>, etc.). >> >> This is not a comprehensive list, but highlights key problems that should be >> addressed in the core. A systematic Analysis and Proposed Solutions follow >> in subsequent sections. >> >> Problem 1: Mixed Modular/Non-Modular Sources Accepted Silently >> >> Mixing modular and non-modular sources in <sources> is an invalid >> configuration that will fail at compile time, but DefaultProjectBuilder >> accepts it silently without early validation. >> >> Example - Mixed configuration: >> >> <build> >> <sources> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.moduleA</module> <!-- Modular --> >> </source> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <!-- No module - classic style --> >> </source> >> </sources> >> </build> >> Expected Behavior Actual Behavior >> Early warning/error about inconsistent configuration Silent acceptance by >> DefaultProjectBuilder >> Clear feedback during project build Delayed failure at compile time >> Current Workaround: The maven-compiler-plugin validates this at compile time >> and fails with: >> >> "Mix of modular and non-modular sources." >> See above (Compiler Plugin >> Compensation<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#compiler-plugin-compensation>) >> for drawbacks of this approach. >> >> Problem 2: Test-Only Modular Projects Get Classic Main Sources Injected >> >> When a project configures only test sources in <sources> (no main sources), >> DefaultProjectBuilder silently injects the classic src/main/java as a main >> source directory. >> >> Example - Integration test module with only test sources: >> >> <build> >> <sources> >> <source> >> <scope>test</scope> >> <lang>java</lang> >> <module>org.foo.integrationTests</module> >> </source> >> </sources> >> </build> >> Expected Behavior Actual Behavior >> No main sources (project is test-only) src/main/java silently added as >> main source >> Test sources from src/org.foo.integrationTests/test/java Test sources >> work correctly >> Root cause: The hasMain boolean remains false when no <source> has >> scope=main + lang=java. Line 697-698 then adds build.getSourceDirectory() >> (defaults to src/main/java from Super POM). >> >> See: >> DefaultProjectBuilder.java#L697-L698<https://github.com/apache/maven/blob/25c80d8ece4252421c9dce5c34fb46a21c7b9f23/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L697-L698> >> Impact: >> >> If src/main/java doesn't exist → probably harmless (empty directory) >> If src/main/java exists with old/unrelated code → it gets compiled! >> Modular test project silently becomes a mixed modular-test + classic-main >> project >> Use cases affected: >> >> Integration test modules (only contain tests) >> Test fixtures modules (shared test utilities) >> BOM/parent projects with test verification but no main code >> Note: Unlike Problem 1, this is not caught by the compiler plugin - it >> silently succeeds but with potentially wrong behavior. >> >> Problem 3: Resources Are Not Module-Aware >> >> When a project uses modular sources, resources should follow the same >> modular layout. Currently, they don't. >> >> Example - Modular Java sources without explicit resources: >> >> <build> >> <sources> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.moduleA</module> >> </source> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.moduleB</module> >> </source> >> </sources> >> </build> >> Expected Behavior Actual Behavior >> Resources from src/org.foo.moduleA/main/resources Resources from >> src/main/resources only >> Resources from src/org.foo.moduleB/main/resources (legacy path, not >> module-aware) >> Impact: Module-specific resources (like module-specific configs) cannot be >> organized per-module. >> >> Solution: See Phase >> 1<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented> >> for the implementation. >> >> Workaround (no longer needed): See Origin of This >> Discussion<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#origin-of-this-discussion> >> for how to configure the maven-resources-plugin with explicit executions >> for each module. >> >> Problem 4: Legacy Configuration Silently Ignored >> >> When <sources> is present, legacy elements like <sourceDirectory> are >> silently ignored without warning. >> >> Example - Explicit sourceDirectory with sources: >> >> <build> >> <sourceDirectory>src/custom/java</sourceDirectory> <!-- User expects this >> to be used --> >> <sources> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.bar</module> >> </source> >> </sources> >> </build> >> Expected Behavior Actual Behavior >> Warning: "sourceDirectory is ignored because sources are configured" Silent >> - user doesn't know their config is ignored >> Clear feedback to user Confusion when src/custom/java isn't used >> 2. Analysis >> >> 2.1 Core Design Question >> >> How should the source reading and validation logic work? >> >> The fundamental question is about priority and conflict resolution when both >> new (<sources>) and legacy (<sourceDirectory>, <resources>) configuration >> elements are present. >> >> Key Design Principle: Modular Sources Have Priority >> >> When a project uses the new <sources> element with modular configuration >> (<module> element), the modular approach should take precedence: >> >> Modular sources (<sources> with <module>) demand proper modular layout and >> behavior >> Non-modular sources (<sources> without <module>) should not be used (error >> or warning) in a modular project >> Legacy configuration (<sourceDirectory>, <resources>) is used only as >> fallback when no <sources> are configured >> This principle ensures: >> >> Clear, predictable behavior >> No silent mixing of old and new approaches >> Users explicitly opt-in to the new model >> Path Resolution Logic >> >> From DefaultSourceRoot.fromModel() — abbreviated pseudo code of the current >> implementation<https://github.com/apache/maven/blob/25c80d8ece4252421c9dce5c34fb46a21c7b9f23/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java#L135-L141>: >> >> if (module specified) { >> // Modular mode >> path = directory specified ? baseDir.resolve(directory) >> : baseDir/src/<module>/<scope>/<lang> >> } else { >> // Classic mode >> path = directory specified ? baseDir.resolve(directory) >> : baseDir/src/<scope>/<lang> >> } >> Module Directory Mode Result >> No No Classic src/<scope>/<lang> (default) >> No Yes Classic <directory> (override) >> Yes No Modular src/<module>/<scope>/<lang> (default) >> Yes Yes Modular <directory> (override, module = metadata) >> 2.2 Strict vs Lenient Approach >> >> Two approaches are possible for handling configuration conflicts: >> >> Approach A: Strict (Fail Fast) >> >> Configuration conflicts result in errors that fail the build. >> >> Rule Condition Severity Message >> R1 <sources> present AND explicit <sourceDirectory> ERROR "Cannot >> combine <sources> with explicit <sourceDirectory>. Use one or the other." >> R2 <sources> with mixed module/no-module elements ERROR "Cannot mix >> modular and non-modular sources. All <source> elements must either have a >> module or none." >> R3 Duplicate <source> (same scope/lang/module) ERROR "Duplicate >> source definition for scope='X', lang='Y', module='Z'" >> R4 <sources> configured but classic directory exists on filesystem WARN >> "Directory 'src/main/java' exists but will be ignored because <sources> is >> configured" >> Approach B: Lenient (Warn and Continue) >> >> Configuration conflicts result in warnings but the build continues. >> >> Rule Condition Severity Message >> R1' <sources> present AND explicit <sourceDirectory> WARN >> "Explicit <sourceDirectory> will be ignored because <sources> is configured." >> R2' <sources> with mixed module/no-module elements WARN "Mixing modular >> and non-modular sources may lead to unexpected behavior." >> R3 Duplicate <source> (same scope/lang/module) ERROR "Duplicate >> source definition for scope='X', lang='Y', module='Z'" >> R4 <sources> configured but classic directory exists on filesystem WARN >> "Directory 'src/main/java' exists but will be ignored because <sources> is >> configured" >> Comparison >> >> Aspect Strict (A) Lenient (B) >> <sources> + explicit SD/TSD ERROR WARN >> Mixed module/no-module ERROR WARN >> Duplicate sources ERROR ERROR >> Filesystem mismatch WARN WARN >> Migration friendliness Lower Higher >> User confusion risk Lower Higher >> Fail-fast principle Yes No >> Recommendation >> >> Approach A (Strict) is recommended for new projects because: >> >> Configuration conflicts indicate user error/misunderstanding >> Silent ignoring of explicit configuration is confusing >> Better to fail early than have unexpected behavior at compile time >> Users can easily fix by removing the conflicting element >> Approach B (Lenient) could be considered if: >> >> Migration from Maven 3.x to 4.x needs to be smoother >> Tooling (IDEs) may generate both elements during transition >> A deprecation period is desired before enforcing strict rules >> 2.3 Unified Permutation Matrix >> >> This matrix shows all configuration combinations and their expected behavior >> under each approach. >> >> Legend >> >> Abbreviation Meaning >> SD <sourceDirectory> - classic Maven 3.x element for main Java sources >> TSD <testSourceDirectory> - classic Maven 3.x element for test Java sources >> S <source> element within <sources> - Maven 4.x way to define sources >> R <resources> / <resource> - classic Maven 3.x element for resources >> M <module> element within <source> - specifies JPMS module name >> Java Sources >> >> # Configuration Current Lenient Strict Compiler Plugin >> No <sources> (classic mode) >> 1 SD=implicit (Super POM) src/main/java OK OK - >> 2 SD=explicit user's path OK OK - >> <sources> present (new mode) >> 3 S(no M), SD=implicit src/main/java OK OK - >> 4 S(no M), SD=explicit Silent ignore WARN ERROR Not caught >> 5 S(M=X), SD=implicit src/X/main/java OK OK - >> 6 S(M=X), SD=explicit Silent ignore WARN ERROR Not caught >> 7 S(M=X) + S(M=Y) Both paths OK OK - >> 8 S(M=X) + S(no M) Silent accept WARN ERROR Caught ✓ >> 9 S(dir=custom, no M) custom path OK OK - >> 10 S(dir=custom, M=X) custom (M=meta) OK OK - >> 11 Duplicate S Silent accept ERROR ERROR Not caught >> 12 S(M=X) + src/main/java exists Silent ignore WARN WARN Not >> caught >> 13 S(scope=test, M=X) only Injects src/main/java No inject No >> inject Not caught ⚠️ >> Resources >> >> # S Config R Config Current Proposed (Phase 1) Lenient >> Strict >> 1 S(lang=resources) R=any Duplicates! Use S, WARN if R >> explicit Use S, WARN Use S, WARN >> 2 S(java, no M) R=implicit src/main/resources >> src/main/resources OK OK >> 3 S(java, no M) R=explicit User's path User's path OK >> OK >> 4 S(java, M=X) R=implicit src/main/resources Inject modular >> Inject modular Inject modular >> 5 S(java, M=X) R=explicit User's path Inject modular, WARN >> WARN ERROR >> 3. Current Implementation Status >> >> As of commit >> 25c80d8<https://github.com/apache/maven/blob/25c80d8ece4252421c9dce5c34fb46a21c7b9f23/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L645-L708> >> Implementation Location >> >> The source/resource handling logic is in DefaultProjectBuilder.initProject(): >> >> // Lines 669-686: Iterate over <sources> and track what's present >> boolean hasScript = false; >> boolean hasMain = false; // true if ANY <source> has lang=java, scope=main >> boolean hasTest = false; // true if ANY <source> has lang=java, scope=test >> for (var source : sources) { >> var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, >> source); >> project.addSourceRoot(src); >> // ... tracking logic >> } >> >> // Lines 694-701: Decide whether to use legacy >> sourceDirectory/testSourceDirectory >> // (Lines 687-692 contain a comment explaining this behavior) >> if (!hasScript) { >> project.addScriptSourceRoot(build.getScriptSourceDirectory()); >> } >> if (!hasMain) { >> project.addCompileSourceRoot(build.getSourceDirectory()); // Silent >> fallback >> } >> if (!hasTest) { >> project.addTestCompileSourceRoot(build.getTestSourceDirectory()); // >> Silent fallback >> } >> >> // Lines 703-708: Resources are ALWAYS added from legacy elements (no >> checks!) >> for (Resource resource : project.getBuild().getDelegate().getResources()) { >> project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, >> resource)); >> } >> for (Resource resource : >> project.getBuild().getDelegate().getTestResources()) { >> project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, >> resource)); >> } >> Gap Analysis >> >> Issue Description Core Status Compiler Plugin >> No warnings Explicit <sourceDirectory> silently ignored when <sources> >> present Not implemented Not caught >> No mixed validation Mixing S(M=X) with S(no M) silently accepted Not >> implemented Caught ✓ >> Test-only injection src/main/java injected for test-only modular projects >> Not implemented Not caught ⚠️ >> No duplicates check Same source can be defined multiple times Not >> implemented Not caught >> No modular resources Resources always from legacy <resources> element >> ✅ Implemented (Phase >> 1<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented>) >> N/A >> Key insight: Only the mixed modular/non-modular validation (#8) is currently >> caught by the compiler plugin. All other issues require core implementation. >> >> What Works >> >> Processing <source> elements and adding them as source roots >> Fallback to legacy <sourceDirectory> when no Java sources in <sources> >> Path resolution for both modular and classic layouts >> What's Missing >> >> Warnings when explicit configuration is ignored >> Validation for mixed modular/non-modular sources (currently deferred to >> compiler plugin) >> Correct handling of test-only modular projects (no main injection) >> Duplicate detection >> Modular resource handling ✅ Implemented in Phase >> 1<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented> >> 4. Implementation Approaches >> >> 4.1 Phase 1: Module-Aware Resource Handling (Implemented) >> >> Goal: Enable modular resource handling without requiring explicit >> maven-resources-plugin configuration. >> >> Status: ✅ Implemented in >> 3e2d01f<https://github.com/support-and-care/maven/commit/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3> >> Implementation: >> >> Resource tracking via <sources>: (Lines >> 672-673<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L672-L673>) >> boolean hasMainResources = false; >> boolean hasTestResources = false; >> for (var source : sources) { >> if (Language.RESOURCES.equals(language)) { >> if (ProjectScope.MAIN.equals(scope)) { >> hasMainResources = true; >> } else { >> hasTestResources |= ProjectScope.TEST.equals(scope); >> } >> } >> } >> Module extraction to detect modular projects: (Lines >> 713-714<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L713-L714>, >> extractModules() at >> L1256<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L1256-L1272>) >> Set<String> modules = extractModules(sources); >> boolean isModularProject = !modules.isEmpty(); >> Module-aware resource injection for modular projects: (Lines >> 729-770<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L729-L770>, >> createModularResourceRoot() at >> L1275<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L1275-L1300>) >> If resources configured via <sources>: use them (already added during >> iteration) >> If no resources in <sources>: inject module-aware defaults for each module >> Warn (as ModelProblem) if legacy <resources> is present but ignored >> if (isModularProject) { >> if (hasMainResources) { >> // Already added via <sources>, warn if legacy <resources> present >> } else { >> // Inject module-aware defaults: src/<module>/main/resources >> for (String module : modules) { >> project.addSourceRoot(createModularResourceRoot(baseDir, module, >> ProjectScope.MAIN, ...)); >> } >> } >> } >> Super POM default detection: (hasOnlySuperPomDefaults() at >> L1303<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L1303-L1331>) >> hasOnlySuperPomDefaults() checks if <resources> contains only inherited >> defaults >> Warnings are only issued for explicitly configured legacy resources, not >> Super POM defaults >> Priority Hierarchy (Proposed): >> >> Priority Condition Behavior >> 1 Modular project + resources in <sources> Use <sources> >> resources, warn if legacy present >> 2 Modular project + no resources in <sources> Inject >> src/<module>/<scope>/resources for each module >> 3 Classic project + resources in <sources> Use <sources> >> resources, warn if legacy present >> 4 Classic project + no resources in <sources> Use legacy <resources> >> element >> Example - Would Work Without Explicit Plugin Configuration: >> >> <build> >> <sources> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.moduleA</module> >> </source> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.moduleB</module> >> </source> >> </sources> >> </build> >> Resources would be automatically picked up from: >> >> src/org.foo.moduleA/main/resources >> src/org.foo.moduleB/main/resources >> What's NOT Addressed in This Proposal: >> >> Problem 1: Mixed modular/non-modular sources validation (still deferred to >> compiler plugin) >> Problem 2: Test-only modular project main injection >> Problem 4: Warning when explicit <sourceDirectory> is ignored >> Duplicate source detection >> Per-module tracking (project-level booleans still used) >> 4.2 Phase 2: Comprehensive Refactoring >> >> Goal: Full implementation of the design principles from Section 2 with >> proper validation. >> >> Scope: >> >> Per-module tracking instead of project-level booleans: >> record ModuleConfig(boolean hasMain, boolean hasTest, boolean >> hasMainResources, boolean hasTestResources) {} >> Map<String, ModuleConfig> moduleConfigs = new HashMap<>(); >> Validation in ModelValidator (not DefaultProjectBuilder): >> Move conflict detection to validateEffectiveModel() >> Report ModelProblem with line numbers and severity >> Early feedback during POM processing >> Strict validation rules (from Section 2.2): >> Rule Condition Severity Message >> R1 <sources> + explicit <sourceDirectory> ERROR "Cannot combine >> <sources> with explicit <sourceDirectory>" >> R2 Mixed modular/non-modular sources ERROR "Cannot mix modular and >> non-modular sources" >> R3 Duplicate <source> definition ERROR "Duplicate source for >> scope='X', lang='Y', module='Z'" >> R4 <sources> configured but classic dir exists WARN "Directory >> 'src/main/java' exists but will be ignored" >> Fix test-only modular project injection (Problem 2): >> // Don't inject legacy main sources for modular projects >> if (!hasMain && !isModularProject) { >> project.addCompileSourceRoot(build.getSourceDirectory()); >> } >> Warning when explicit configuration is ignored (Problem 4): >> if (hasMain && isExplicitSourceDirectory(build)) { >> // Report ModelProblem with line number >> } >> InputLocation-based detection for explicit vs inherited configuration: >> InputLocation location = build.getLocation("sourceDirectory"); >> boolean isExplicit = location != null && !isSuperPomLocation(location); >> Implementation Location: >> >> Component Responsibility >> DefaultModelValidator Validation rules R1-R4, early feedback with >> line numbers >> DefaultProjectBuilder Source root creation, fallback logic >> DefaultSourceRoot Path resolution (already implemented) >> Implementation Timeline >> >> Phase Scope Status >> Phase 1 Module-aware resource handling ✅ Implemented >> (3e2d01f<https://github.com/support-and-care/maven/commit/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3>) >> Phase 2 Full validation + per-module tracking + ModelValidator >> integration Pending >> 5. Reference Material >> >> Model Version vs Maven Version >> >> Model Version Maven Version Notes >> 4.0.0 Maven 3.x Classic POM model, no <sources> element >> 4.1.0 Maven 4.x Introduces <sources> element >> 4.2.0 Future Reserved for future use >> The <sources> element requires model version 4.1.0: >> >> <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.xsd"> >> <modelVersion>4.1.0</modelVersion> >> ... >> </project> >> The Maven 4.x Unified Source Model >> >> In Maven 4.x, all source types can be configured via <sources>: >> >> <sources> >> <!-- Java sources --> >> <source> >> <scope>main</scope> >> <lang>java</lang> >> <module>org.foo.bar</module> >> </source> >> <!-- Resources --> >> <source> >> <scope>main</scope> >> <lang>resources</lang> >> <module>org.foo.bar</module> >> </source> >> </sources> >> The <lang> element accepts: >> >> java - compiled by compiler plugin (default if omitted) >> resources - processed by resources plugin >> script - deprecated, use resources instead >> Extensible via LanguageProvider SPI for other languages (Kotlin, Groovy, >> etc.). >> >> Processing Flow >> >> ┌─────────────────────────────────────────────────────────────────┐ >> │ POM Model │ >> │ <sources> │ >> │ <source><lang>java</lang><module>X</module></source> │ >> │ <source><lang>resources</lang><module>X</module></source> │ >> │ </sources> │ >> └─────────────────────────────────────────────────────────────────┘ >> │ >> ▼ >> ┌─────────────────────────────────────────────────────────────────┐ >> │ DefaultProjectBuilder │ >> │ Creates SourceRoot objects from <source> elements │ >> │ Adds to project.addSourceRoot(...) │ >> └─────────────────────────────────────────────────────────────────┘ >> │ >> ▼ >> ┌─────────────────────────────────────────────────────────────────┐ >> │ MavenProject │ >> │ getEnabledSourceRoots(ProjectScope scope, Language language) │ >> │ - Plugins query this to find relevant sources │ >> └─────────────────────────────────────────────────────────────────┘ >> │ >> ┌───────────────┴───────────────┐ >> ▼ ▼ >> ┌─────────────────────────┐ ┌─────────────────────────┐ >> │ Compiler Plugin │ │ Resources Plugin │ >> │ Queries: lang=java │ │ Queries: lang=resources │ >> └─────────────────────────┘ └─────────────────────────┘ >> Detecting Explicit vs Implicit Configuration >> >> To detect whether <sourceDirectory> was explicitly configured (vs inherited >> from Super POM): >> >> // Option 1: Compare to known Super POM default >> String superPomDefault = "${project.basedir}/src/main/java"; >> String resolvedDefault = baseDir.resolve("src/main/java").toString(); >> boolean isExplicit = !build.getSourceDirectory().equals(resolvedDefault); >> >> // Option 2: Use InputLocation tracking >> InputLocation location = build.getLocation("sourceDirectory"); >> boolean isExplicit = location != null && !isSuperPomLocation(location); >> Where to Implement Validation >> >> Option Location Pros Cons >> ModelValidator (Recommended) validateEffectiveModel() Line numbers, >> early, standard Values not fully resolved >> DefaultProjectBuilder initProject() All values resolved Later, >> less standard >> Lifecycle phase validate - Too late >> Related Code Locations >> >> impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java >> impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java >> impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java >> impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.0.0.xml >> (Super POM 4.0) >> impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml >> (Super POM 4.1) >> Open Questions >> >> Should we validate at validateRawModel() or validateEffectiveModel()? >> How to reliably detect "explicit" configuration (InputLocation vs value >> comparison)? >> Should the filesystem warning (R4) be optional/configurable? -- Gerd Aschemann --- Veröffentlichen heißt Verändern (Carmen Thomas) +49/173/3264070 -- [email protected] -- http://www.aschemann.net
