ascheman commented on PR #11702: URL: https://github.com/apache/maven/pull/11702#issuecomment-3898066448
# AC8 and AC9: Legacy Directory Handling with `<sources>` ## Summary This PR meanwhile covers two related acceptance criteria for handling legacy directories when `<sources>` is configured (cf. [#11701 / AC8+9](https://github.com/apache/maven/issues/11701#issuecomment-3897961755)): - **AC8** (Modular projects): ALL legacy directories are rejected - **AC9** (Non-modular projects): Implicit fallback to legacy directories when `<sources>` doesn't configure that scope/language ## Background Originally AC8 was proposed to reject legacy directories in modular projects because content cannot be dispatched between modules. However, for non-modular projects using `<sources>`, there's a valid use case for incremental adoption: customize only specific source types (e.g., resources) while keeping the default Java directory structure. ## AC8 Definition (Modular Projects) **AC8** (Legacy directory rejection for modular projects): For **modular projects** (projects with `<module>` attribute in `<source>` elements), the build fails with an **ERROR** if: 1. An explicit legacy directory configuration (`<sourceDirectory>` or `<testSourceDirectory>`) exists that **differs from the Super POM default**, OR 2. The default legacy directory (e.g., `src/main/java`) **exists physically** on the filesystem ### Rationale In modular projects using paths like `src/<module>/main/java`, legacy directories like `src/main/java` cannot be dispatched between modules. Any content there would be silently ignored, so the build must fail. ## AC9 Definition (Non-Modular Projects) **AC9** (Legacy directory handling for non-modular projects): For **non-modular projects** using `<sources>`: 1. **When `<sources>` has Java for a scope**: Legacy directories are rejected if they differ from the default (explicit configuration detected) 2. **When `<sources>` has no Java for a scope**: Legacy directories are used as **implicit fallback** only if they match the default (could be inherited) ### Behavior | `<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 ```xml <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 - **`<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: 1. **`failIfLegacyDirectoryPresent`** method with `checkPhysicalPresence` parameter: - `true` for modular projects (checks both config and filesystem) - `false` for non-modular projects (checks only explicit config) 2. **Conditional validation**: - Modular: Always validate both `<sourceDirectory>` and `<testSourceDirectory>` - Non-modular: Validate each scope independently: - `<sourceDirectory>` is validated only if `hasSources(JAVA_FAMILY, MAIN)` returns true - `<testSourceDirectory>` is validated only if `hasSources(JAVA_FAMILY, TEST)` returns true 3. **Implicit fallback**: - Non-modular without Java in `<sources>`: call `addCompileSourceRoot()`/`addTestCompileSourceRoot()` ### Code Structure ```java 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 #11612: Unified source handling for all lang/scope combinations - Issue #11701: Legacy resources in modular build should fail - Confluence: Original [Build Sources Validation - Discussion](https://cwiki.apache.org/confluence/x/84TMFw) in Confluence (outdated as of 2026-02-13) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
