diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java index 9de2ca1348ff..deb59f24df01 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java @@ -693,27 +693,27 @@ private void initProject(MavenProject project, ModelBuilderResult result) { /* * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory` - * are ignored if the POM file contains at least one enabled element + * are not used if the POM file contains at least one enabled element * for the corresponding scope and language. This rule exists because * Maven provides default values for those elements which may conflict * with user's configuration. * * Additionally, for 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. + * rejected because it is not clear how to dispatch their content between + * different modules. The build fails if these properties are explicitly set. */ if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) { project.addScriptSourceRoot(build.getScriptSourceDirectory()); } if (isModularProject) { - // Modular projects: unconditionally ignore legacy directories, warn if explicitly set - warnIfExplicitLegacyDirectory( + // Modular projects: legacy directories conflict with modular sources + failIfLegacyDirectoryPresent( build.getSourceDirectory(), baseDir.resolve("src/main/java"), "", project.getId(), result); - warnIfExplicitLegacyDirectory( + failIfLegacyDirectoryPresent( build.getTestSourceDirectory(), baseDir.resolve("src/test/java"), "", @@ -906,15 +906,17 @@ private void initProject(MavenProject project, ModelBuilderResult result) { } /** - * Warns about legacy directory usage in a modular project. Two cases are handled: + * Fails the build if a legacy directory is present in a modular project. + *

+ * "Present" means either: *

- * 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 cannot be used. + * Failing the build forces the user to resolve the conflict explicitly. */ - private void warnIfExplicitLegacyDirectory( + private void failIfLegacyDirectoryPresent( String configuredDir, Path defaultDir, String elementName, @@ -924,26 +926,26 @@ private void warnIfExplicitLegacyDirectory( Path configuredPath = Path.of(configuredDir).toAbsolutePath().normalize(); Path defaultPath = defaultDir.toAbsolutePath().normalize(); if (!configuredPath.equals(defaultPath)) { - // Case 2: Explicit configuration differs from default - always warn + // Configuration presence: explicit config differs from default String message = String.format( - "Legacy %s is ignored in modular project %s. " + "Legacy %s cannot be used in modular project %s." + "In modular projects, source directories must be defined via " + "with a module element for each module.", elementName, projectId); - logger.warn(message); + logger.error(message); result.getProblemCollector() .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( - message, Severity.WARNING, Version.V41, null, -1, -1, null)); + message, Severity.ERROR, Version.V41, null, -1, -1, null)); } else if (Files.isDirectory(defaultPath)) { - // Case 1: Default configuration, but the default directory exists on filesystem + // Physical presence: default directory exists on filesystem String message = String.format( - "Legacy %s '%s' exists but is ignored in modular project %s. " + "Legacy %s '%s' exists but cannot be used in modular project %s." + "In modular projects, source directories must be defined via .", elementName, defaultPath, projectId); - logger.warn(message); + logger.error(message); result.getProblemCollector() .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( - message, Severity.WARNING, Version.V41, null, -1, -1, null)); + message, Severity.ERROR, Version.V41, null, -1, -1, null)); } } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java b/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java index e7691eb86bcf..d704ace5f961 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java @@ -221,11 +221,19 @@ void handleResourceConfiguration(ProjectScope scope) { if (hasResourcesInSources) { // Modular project with resources configured via - already added above if (hasExplicitLegacyResources(resources, scopeId)) { - LOGGER.warn( - "Legacy {} element is ignored because {} resources are configured via {} in .", - legacyElement, - scopeId, - sourcesConfig); + String message = String.format( + "Legacy %s element cannot be used because %s resources are configured via %s in .", + legacyElement, scopeId, sourcesConfig); + LOGGER.error(message); + result.getProblemCollector() + .reportProblem(new DefaultModelProblem( + message, + Severity.ERROR, + Version.V41, + project.getModel().getDelegate(), + -1, + -1, + null)); } else { LOGGER.debug( "{} resources configured via element, ignoring legacy {} element.", @@ -236,13 +244,13 @@ void handleResourceConfiguration(ProjectScope scope) { // Modular project without resources in - inject module-aware defaults if (hasExplicitLegacyResources(resources, scopeId)) { String message = "Legacy " + legacyElement - + " element is ignored because modular sources are configured. " + + " element cannot be used because modular sources are configured. " + "Use " + sourcesConfig + " in for custom resource paths."; - LOGGER.warn(message); + LOGGER.error(message); result.getProblemCollector() .reportProblem(new DefaultModelProblem( message, - Severity.WARNING, + Severity.ERROR, Version.V41, project.getModel().getDelegate(), -1, @@ -265,11 +273,19 @@ void handleResourceConfiguration(ProjectScope scope) { if (hasResourcesInSources) { // Resources configured via - already added above if (hasExplicitLegacyResources(resources, scopeId)) { - LOGGER.warn( - "Legacy {} element is ignored because {} resources are configured via {} in .", - legacyElement, - scopeId, - sourcesConfig); + String message = String.format( + "Legacy %s element cannot be used because %s resources are configured via %s in .", + legacyElement, scopeId, sourcesConfig); + LOGGER.error(message); + result.getProblemCollector() + .reportProblem(new DefaultModelProblem( + message, + Severity.ERROR, + Version.V41, + project.getModel().getDelegate(), + -1, + -1, + null)); } else { LOGGER.debug( "{} resources configured via element, ignoring legacy {} element.", @@ -319,7 +335,7 @@ private DefaultSourceRoot createModularResourceRoot(String module, ProjectScope * * @param resources list of resources to check * @param scope scope (main or test) - * @return true if explicit legacy resources are present that would be ignored + * @return true if explicit legacy resources are present that conflict with modular sources */ private boolean hasExplicitLegacyResources(List resources, String scope) { if (resources.isEmpty()) { diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java index 5da89a0f58b9..ee6f5200f662 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java @@ -422,19 +422,21 @@ void testModularSourcesInjectResourceRoots() throws Exception { } /** - * Tests that when modular sources are configured alongside explicit legacy resources, - * the legacy resources are ignored and a warning is issued. + * Tests that when modular sources are configured alongside explicit legacy resources, an error is raised. *

* This verifies the behavior described in the design: - * - Modular projects with explicit legacy {@code } configuration should issue a warning + * - Modular projects with explicit legacy {@code } configuration should raise an error * - The modular resource roots are injected instead of using the legacy configuration *

- * 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) * * @see Issue #11612 + * @see AC8 definition */ @Test - void testModularSourcesWithExplicitResourcesIssuesWarning() throws Exception { + void testModularSourcesWithExplicitResourcesIssuesError() throws Exception { File pom = getProject("modular-sources-with-explicit-resources"); MavenSession mavenSession = createMavenSession(null); @@ -447,19 +449,19 @@ void testModularSourcesWithExplicitResourcesIssuesWarning() throws Exception { MavenProject project = result.getProject(); - // Verify warnings are issued for ignored legacy resources - List warnings = result.getProblems().stream() - .filter(p -> p.getSeverity() == ModelProblem.Severity.WARNING) - .filter(p -> p.getMessage().contains("Legacy") && p.getMessage().contains("ignored")) + // Verify errors are raised for conflicting legacy resources (AC8) + List errors = result.getProblems().stream() + .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR) + .filter(p -> p.getMessage().contains("Legacy") && p.getMessage().contains("cannot be used")) .toList(); - assertEquals(2, warnings.size(), "Should have 2 warnings (one for resources, one for testResources)"); + assertEquals(2, errors.size(), "Should have 2 errors (one for resources, one for testResources)"); assertTrue( - warnings.stream().anyMatch(w -> w.getMessage().contains("")), - "Should warn about ignored "); + errors.stream().anyMatch(e -> e.getMessage().contains("")), + "Should error about conflicting "); assertTrue( - warnings.stream().anyMatch(w -> w.getMessage().contains("")), - "Should warn about ignored "); + errors.stream().anyMatch(e -> e.getMessage().contains("")), + "Should error about conflicting "); // Verify modular resources are still injected correctly List mainResourceRoots = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) @@ -478,23 +480,23 @@ void testModularSourcesWithExplicitResourcesIssuesWarning() throws Exception { } /** - * Tests that legacy sourceDirectory and testSourceDirectory are ignored in modular projects. + * Tests that legacy sourceDirectory and testSourceDirectory raise an error in modular projects. *

- * 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). + * Legacy directories cannot be used in modular projects because it is not clear + * how to dispatch their content between different modules. An error is raised if these + * properties are explicitly set (and differ from Super POM defaults). *

* This verifies: - * - WARNINGs are emitted for explicitly set legacy directories in modular projects - * - sourceDirectory and testSourceDirectory are both ignored + * - ERRORs are raised for explicitly set legacy directories in modular projects * - Only modular sources from {@code } are used *

* Acceptance Criteria: * - AC1 (boolean flags eliminated - uses hasSources() for main/test detection) - * - AC7 (legacy directories warning - {@code } and {@code } - * are unconditionally ignored with a WARNING in modular projects) + * - AC8 (legacy directories error - supersedes AC7 which originally used WARNING; + * {@code } and {@code } are raising an ERROR in modular projects) * * @see Issue #11612 + * @see AC8 definition */ @Test void testMixedSourcesModularMainClassicTest() throws Exception { @@ -510,20 +512,21 @@ void testMixedSourcesModularMainClassicTest() throws Exception { MavenProject project = result.getProject(); - // Verify WARNINGs are emitted for explicitly set legacy directories - List warnings = result.getProblems().stream() - .filter(p -> p.getSeverity() == ModelProblem.Severity.WARNING) - .filter(p -> p.getMessage().contains("Legacy") && p.getMessage().contains("ignored in modular project")) + // Verify ERRORs are raised for explicitly set legacy directories (AC8) + List errors = result.getProblems().stream() + .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR) + .filter(p -> p.getMessage().contains("Legacy") + && p.getMessage().contains("cannot be used in modular project")) .toList(); - // Should have 2 warnings: one for sourceDirectory, one for testSourceDirectory - assertEquals(2, warnings.size(), "Should have 2 warnings for ignored legacy directories"); + // Should have 2 errors: one for sourceDirectory, one for testSourceDirectory + assertEquals(2, errors.size(), "Should have 2 errors for conflicting legacy directories"); assertTrue( - warnings.stream().anyMatch(w -> w.getMessage().contains("")), - "Should warn about ignored "); + errors.stream().anyMatch(e -> e.getMessage().contains("")), + "Should error about conflicting "); assertTrue( - warnings.stream().anyMatch(w -> w.getMessage().contains("")), - "Should warn about ignored "); + errors.stream().anyMatch(e -> e.getMessage().contains("")), + "Should error about conflicting "); // Get main Java source roots - should have modular sources, not classic sourceDirectory List mainJavaRoots = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY) @@ -541,17 +544,17 @@ void testMixedSourcesModularMainClassicTest() throws Exception { assertTrue(mainModules.contains("org.foo.moduleA"), "Should have main source for moduleA"); assertTrue(mainModules.contains("org.foo.moduleB"), "Should have main source for moduleB"); - // Verify the classic sourceDirectory is NOT used (should be ignored) + // Verify the classic sourceDirectory is NOT used (rejected in modular projects) boolean hasClassicMainSource = mainJavaRoots.stream().anyMatch(sr -> sr.directory() .toString() .replace(File.separatorChar, '/') .contains("src/classic/main/java")); - assertTrue(!hasClassicMainSource, "Classic sourceDirectory should be ignored"); + assertTrue(!hasClassicMainSource, "Classic sourceDirectory cannot be used"); - // Test sources should NOT be added (legacy testSourceDirectory is ignored in modular projects) + // Test sources should NOT be added (legacy testSourceDirectory is rejected in modular projects) List testJavaRoots = project.getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY) .toList(); - assertEquals(0, testJavaRoots.size(), "Should have no test Java sources (legacy is ignored)"); + assertEquals(0, testJavaRoots.size(), "Should have no test Java sources (legacy is rejected)"); } /** @@ -563,7 +566,7 @@ void testMixedSourcesModularMainClassicTest() throws Exception { *

* This verifies: * - An ERROR is reported when both modular and non-modular sources exist in {@code } - * - sourceDirectory is ignored because {@code } exists + * - sourceDirectory is not used because {@code } exists *

* Acceptance Criteria: * - AC1 (boolean flags eliminated - uses hasSources() for source detection)