Fail on legacy config in modular projects#11702
Fail on legacy config in modular projects#11702ascheman wants to merge 4 commits intoapache:masterfrom
Conversation
70a7358 to
c1b431b
Compare
|
Reviews are welcome, in particular from @elharo and @desruisseaux - thanks! |
elharo
left a comment
There was a problem hiding this comment.
not obvious to me how claude code is being used here. Generally I would not expect simply pointing it at this issue without human editing to produce correct results.
| * | ||
| * Additionally, for modular projects, legacy directories are unconditionally | ||
| * ignored because it is not clear how to dispatch their content between | ||
| * rejected because it is not clear how to dispatch their content between |
There was a problem hiding this comment.
I don't think this is what we were looking for. The build fails completely, not simply a warning
There was a problem hiding this comment.
Proposal: replace "A warning is emitted if …" by "The build fails if …".
| * </ul> | ||
| * Legacy directories are unconditionally ignored in modular projects because it is not clear | ||
| * how to dispatch their content between different modules. | ||
| * In both cases, the legacy directory conflicts with modular sources and must not be used. |
There was a problem hiding this comment.
not "and must not be used" but rather the complete build fails.
There was a problem hiding this comment.
not "and must not be used" but rather the complete build fails.
Isn't what the next line below is saying?
| scopeId, | ||
| sourcesConfig); | ||
| String message = String.format( | ||
| "Legacy %s element must not be used because %s resources are configured via %s in <sources>.", |
There was a problem hiding this comment.
must not be used --> cannot be used in modular projects
| if (hasExplicitLegacyResources(resources, scopeId)) { | ||
| String message = "Legacy " + legacyElement | ||
| + " element is ignored because modular sources are configured. " | ||
| + " element must not be used because modular sources are configured. " |
| scopeId, | ||
| sourcesConfig); | ||
| String message = String.format( | ||
| "Legacy %s element must not be used because %s resources are configured via %s in <sources>.", |
| * Acceptance Criterion: AC2 (unified source tracking for all lang/scope combinations) | ||
| * Acceptance Criteria: | ||
| * - AC2 (unified source tracking for all lang/scope combinations) | ||
| * - AC8 (legacy directories error - supersedes AC7 which originally used WARNING) |
There was a problem hiding this comment.
not sure how the AC are being used, but shouldn't AC7 be changed rather than superseded?
There was a problem hiding this comment.
As AC7 has already become part of the master branch implementation, I do not want to override it now. I consider it better to invalidate it and write a new one. As they all belong to proper legacy source/resource handling wrt to the new <sources> configuration element, I proceed with the numbering. I am also preparing some overall documentation, covering all aspects (an update/replacement for the Confluence page where it all began.
| * In modular projects, legacy directories are unconditionally ignored because it is not clear | ||
| * how to dispatch their content between different modules. A warning is emitted if these | ||
| * properties are explicitly set (differ from Super POM defaults). | ||
| * In modular projects, legacy directories must not occur because it is not clear |
There was a problem hiding this comment.
In modular projects, legacy directories must not occur --> Legacy directories cannot be used in modular projects
| /* | ||
| * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory` | ||
| * are ignored if the POM file contains at least one enabled <source> element | ||
| * are not used if the POM file contains at least one enabled <source> element |
There was a problem hiding this comment.
again here don't we need the build to fail in this case?
There was a problem hiding this comment.
Yes, I think that it is a change that was forgot in the documentation. The actual code raises an error as I read it.
There was a problem hiding this comment.
Looking at this more closely, I think we've uncovered an inconsistency:
Current behavior:
- Resources (
<resources>/<testResources>): ERROR for both modular and classic projects when<sources>conflicts with legacy config (handled inSourceHandlingContext) - Source directories (
<sourceDirectory>/<testSourceDirectory>): ERROR only for modular projects, silently skipped for classic projects
AC8 specifically scopes the error behavior to modular projects:
"Legacy directories trigger ERROR in modular projects"
This leaves classic projects with <sources> in an inconsistent state - their legacy resources cause an error, but legacy source directories are silently ignored.
Proposal: If @desruisseaux and @elharo agree, we could add:
AC9: For classic (non-modular) projects, legacy <sourceDirectory> and <testSourceDirectory> also trigger an ERROR when <sources> elements are configured for the same scope/language. This ensures consistency with resource handling and prevents silent configuration loss.
Should I create a follow-up issue for this, or would you prefer to extend the current PR?
There was a problem hiding this comment.
Thanks for spotting that. I suggest to extend the current pull request if this is fine for you.
There was a problem hiding this comment.
I'm not fully up to speed on all of this. I try to avoid modules. However, the general principle I would follow is that if the client project contains code or resources that the developer likely intended to be included and for some reason we are not including it, then Maven should hard fail the build.
There was a problem hiding this comment.
Seems very good to me.
There was a problem hiding this comment.
Seems very good to me.
OK, I will implement it this way then.
There was a problem hiding this comment.
And yet another use case: Assume we have the following structure:
<build>
<sources>
<source>
<scope>main</scope>
<lang>resources</lang>
<directory>src/main/custom-resources</directory>
</source>
<!-- No <source lang="java"> - expects implicit src/main/java? -->
</sources>
</build>I would think in a legacy directory structure but 4.1.0-model configuration (but no modules), the legacy directories should still be (implicitly) used, unless explicitly overridden. What do you think, @desruisseaux ?
Said that: the classic values were defined as default by the super POM. Do we expect similar defaults by the super POM for <sources> as well (currently not configured)?
There was a problem hiding this comment.
Yes, when the <module> element is not used I think that we can continue to use the legacy directories.
Regarding the default values specified in the super POM, I suggest to not do any change for now (we may revisit in some future version). The default values of <source> elements are implemented in Java code for now, and they are designed in such a way that, when no <module> element is used, they match the default values of the super POM.
There was a problem hiding this comment.
OK, I fully agree that we should not require something on the Super POM by now but just use the classic directories as fallback if nothing is explicitly configured. Nevertheless, I will create a new Acceptance Criterion to express that design choice - we are back at (another) AC9 then 😉:
AC9 Definition (Non-Modular Projects)
AC9 (Legacy directory handling for non-modular projects):
For non-modular projects using <sources>:
- When
<sources>has Java for a scope: Legacy directories are rejected if they differ from the default (explicit configuration detected) - When
<sources>has no Java for a scope: Legacy directories are used as implicit fallback only if they match the default (could be inherited)
I already made some progress implementing and testing this and will deliver test cases for both AC8 and AC9
AC8 and AC9: Legacy Directory Handling with
|
<sources> contains |
Legacy directory | Result |
|---|---|---|
<source><lang>java</lang><scope>main</scope></source> |
<sourceDirectory> differs |
ERROR |
<source><lang>java</lang><scope>main</scope></source> |
<sourceDirectory> default |
OK (ignored) |
<source><lang>resources</lang><scope>main</scope></source> only |
<sourceDirectory> differs |
ERROR |
<source><lang>resources</lang><scope>main</scope></source> only |
<sourceDirectory> default |
OK (used as fallback) |
Rationale
This allows incremental adoption of <sources>:
- Users can customize only specific source types (e.g., resources)
- Default Java directories continue to work without explicit configuration
- Explicit legacy overrides (differ from default) always trigger errors - this is detectable and indicates mixing of approaches
Example
<build>
<sources>
<!-- Only customize resources, keep default Java directories -->
<source>
<lang>resources</lang>
<scope>main</scope>
<directory>src/main/custom-resources</directory>
</source>
</sources>
<!-- src/main/java is used implicitly (AC9) -->
</build>Complete Test Matrix
| # | Project Type | <sources> has Java? |
Legacy differs? | Physical exists? | Result | AC | Test Project | Test Method |
|---|---|---|---|---|---|---|---|---|
| 1 | Classic | N/A | Any | Any | OK | - | - | (default behavior) |
| 2 | Modular | Yes | Yes | Any | ERROR | AC8 | modular-java-with-explicit-source-dir |
testModularWithJavaSourcesRejectsLegacySourceDirectory |
| 3 | Modular | Yes | No | Yes | ERROR | AC8 | modular-with-physical-legacy |
testModularWithPhysicalDefaultLegacyDirectory |
| 4 | Modular | Yes | No | No | OK | AC8 | - | (happy path) |
| 5 | Modular | No | Yes | Any | ERROR | AC8 | modular-no-test-java-with-explicit-test-source-dir |
testModularWithoutTestSourcesRejectsLegacyTestSourceDirectory |
| 6 | Modular | No | No | Yes | ERROR | AC8 | modular-resources-only-with-physical-legacy |
testModularResourcesOnlyWithPhysicalDefaultLegacyDirectory |
| 7 | Modular | No | No | No | OK | AC8 | - | (happy path) |
| 8 | Non-modular | Yes | Yes | Any | ERROR | AC9 | classic-sources-with-explicit-legacy |
testClassicSourcesWithExplicitLegacyDirectories |
| 9 | Non-modular | Yes | No | Any | OK | AC8 | - | (happy path) |
| 10 | Non-modular | No | Yes | Any | ERROR | AC9 | non-modular-resources-only-explicit-legacy |
testNonModularResourcesOnlyWithExplicitLegacyDirectoriesRejected |
| 11 | Non-modular | No | No | Any | OK (fallback) | AC9 | non-modular-resources-only |
testNonModularResourcesOnlyWithImplicitJavaFallback |
Legend
-
Project Type:
- Classic: No
<sources>element configured - Modular:
<sources>has<source>elements with<module>attribute - Non-modular:
<sources>has<source>elements without<module>attribute
- Classic: No
-
<sources>has Java?: Whether<sources>contains<source><lang>java</lang>...</source>for the scope being validated -
Legacy differs?: Whether
<sourceDirectory>/<testSourceDirectory>differs from Super POM default (src/main/java/src/test/java)Limitation: Maven works with the effective model after inheritance resolution. It cannot distinguish whether a value matching the default was inherited (from Super POM or any parent) or explicitly configured. For modular projects, the physical presence check provides an additional safeguard.
-
Physical exists?: Whether the default legacy directory physically exists on the filesystem (only checked for modular projects)
Test Coverage Summary
| Category | Tests |
|---|---|
| AC8 - Modular with explicit legacy | testModularWithJavaSourcesRejectsLegacySourceDirectory, testModularWithoutTestSourcesRejectsLegacyTestSourceDirectory |
| AC8 - Modular with physical presence | testModularWithPhysicalDefaultLegacyDirectory, testModularResourcesOnlyWithPhysicalDefaultLegacyDirectory |
| AC9 - Non-modular with Java in sources (legacy rejected) | testClassicSourcesWithExplicitLegacyDirectories |
| AC9 - Non-modular without Java in sources (implicit fallback) | testNonModularResourcesOnlyWithImplicitJavaFallback |
| AC9 - Non-modular without Java in sources (explicit rejected) | testNonModularResourcesOnlyWithExplicitLegacyDirectoriesRejected |
Implementation
The implementation uses:
-
failIfLegacyDirectoryPresentmethod withcheckPhysicalPresenceparameter:truefor modular projects (checks both config and filesystem)falsefor non-modular projects (checks only explicit config)
-
Conditional validation:
- Modular: Always validate both
<sourceDirectory>and<testSourceDirectory> - Non-modular: Validate each scope independently:
<sourceDirectory>is validated only ifhasSources(JAVA_FAMILY, MAIN)returns true<testSourceDirectory>is validated only ifhasSources(JAVA_FAMILY, TEST)returns true
- Modular: Always validate both
-
Implicit fallback:
- Non-modular without Java in
<sources>: calladdCompileSourceRoot()/addTestCompileSourceRoot()
- Non-modular without Java in
Code Structure
if (sources.isEmpty()) {
// Classic: use all legacy directories
project.addScriptSourceRoot(build.getScriptSourceDirectory());
project.addCompileSourceRoot(build.getSourceDirectory());
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
sourceContext.handleResourceConfiguration(MAIN);
sourceContext.handleResourceConfiguration(TEST);
} else {
// Source roots from <sources> are added by SourceHandlingContext.processSourceElements()
if (isModularProject) {
// AC8: reject ALL legacy directories (both scopes)
// (source roots come exclusively from <sources> elements)
failIfLegacyDirectoryPresent(sourceDirectory, ..., checkPhysicalPresence=true);
failIfLegacyDirectoryPresent(testSourceDirectory, ..., checkPhysicalPresence=true);
} else {
// Non-modular: always validate (error if differs from default)
failIfLegacyDirectoryPresent(sourceDirectory, ..., checkPhysicalPresence=false);
failIfLegacyDirectoryPresent(testSourceDirectory, ..., checkPhysicalPresence=false);
// AC9 fallback: only if no Java in <sources> AND legacy matches default
if (!hasSources(JAVA_FAMILY, MAIN) && matchesDefault(sourceDirectory)) {
project.addCompileSourceRoot(build.getSourceDirectory());
}
if (!hasSources(JAVA_FAMILY, TEST) && matchesDefault(testSourceDirectory)) {
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
}
}
}Related
- Issue Unified source handling for all lang/scope combinations with modular sources (Phase 2) #11612: Unified source handling for all lang/scope combinations
- Issue Legacy resources in modular build should fail #11701: Legacy resources in modular build should fail
- Confluence: Original Build Sources Validation - Discussion in Confluence (outdated as of 2026-02-13)
In modular projects, legacy directories and resources that would be silently ignored now trigger an ERROR instead of WARNING: - Explicit <sourceDirectory>/<testSourceDirectory> differing from defaults - Default src/main/java or src/test/java existing on filesystem - Explicit <resources>/<testResources> differing from Super POM defaults This prevents silent loss of user-configured sources/resources. AC8 supersedes AC7 which originally used WARNING. Fixes apache#11701 See apache#11701 (comment) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use "cannot" instead of "must not" in error messages - Update Javadoc: "The build fails" instead of "A warning is emitted"
AC8 (modular projects): - Reject all legacy directories when <sources> is configured - Check physical presence of default directories (src/main/java) AC9 (non-modular projects): - Reject legacy directories when <sources> has Java for that scope - Allow implicit fallback only when legacy matches default Split test projects for clearer scenario coverage. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4cfb6e9 to
befcf4a
Compare
impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
Outdated
Show resolved
Hide resolved
Use File.separator in test assertions for platform-independent path matching instead of normalizing paths in production code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
afe0e1e to
60faffc
Compare
NOTE: This summary is somewhat outdated, check for an update comment below, for clarification what this PR finally does.
Summary
Affected configurations in modular projects:
<sourceDirectory>/<testSourceDirectory>differing from defaultssrc/main/javaorsrc/test/javaexisting on filesystem<resources>/<testResources>differing from Super POM defaultsTest plan
ProjectBuilderTest#testModularSourcesWithExplicitResourcesIssuesErrorpassesProjectBuilderTest#testMixedSourcesModularMainClassicTestpassesFixes #11701
See #11701 (comment)
🤖 Generated with Claude Code