From a3c7ba1d937c3f7fad936a5870a836b13e5e223f Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Sun, 30 Nov 2025 14:17:42 +0100 Subject: [PATCH 01/10] Add module-aware resource handling for modular sources Maven 4.x introduces a unified element that supports modular project layouts (src///). However, resource handling did not follow the modular layout - resources were always loaded from the legacy element which defaults to src/main/resources. This change implements automatic module-aware resource injection: - Detect modular projects (projects with at least one module in sources) - For modular projects without resource configuration in , automatically inject resource roots following the modular layout: src//main/resources and src//test/resources - Resources configured via take priority over legacy - Issue warnings (as ModelProblem) when explicit legacy resources are ignored Example: A project with modular sources for org.foo.moduleA will now automatically pick up resources from: - src/org.foo.moduleA/main/resources - src/org.foo.moduleA/test/resources This eliminates the need for explicit maven-resources-plugin executions when using modular project layouts. --- .../maven/project/DefaultProjectBuilder.java | 235 +++++++++++++++++- .../maven/project/ProjectBuilderTest.java | 99 ++++++++ .../pom.xml | 39 +++ .../project-builder/modular-sources/pom.xml | 40 +++ 4 files changed, 409 insertions(+), 4 deletions(-) create mode 100644 impl/maven-core/src/test/projects/project-builder/modular-sources-with-explicit-resources/pom.xml create mode 100644 impl/maven-core/src/test/projects/project-builder/modular-sources/pom.xml 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 a1a331ab6940..83411a99607b 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 @@ -669,6 +669,8 @@ private void initProject(MavenProject project, ModelBuilderResult result) { boolean hasScript = false; boolean hasMain = false; boolean hasTest = false; + boolean hasMainResources = false; + boolean hasTestResources = false; for (var source : sources) { var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source); project.addSourceRoot(src); @@ -680,6 +682,13 @@ private void initProject(MavenProject project, ModelBuilderResult result) { } else { hasTest |= ProjectScope.TEST.equals(scope); } + } else if (Language.RESOURCES.equals(language)) { + ProjectScope scope = src.scope(); + if (ProjectScope.MAIN.equals(scope)) { + hasMainResources = true; + } else { + hasTestResources |= ProjectScope.TEST.equals(scope); + } } else { hasScript |= Language.SCRIPT.equals(language); } @@ -700,11 +709,149 @@ private void initProject(MavenProject project, ModelBuilderResult result) { if (!hasTest) { project.addTestCompileSourceRoot(build.getTestSourceDirectory()); } - for (Resource resource : project.getBuild().getDelegate().getResources()) { - project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, resource)); + // Extract modules from sources to detect modular projects + Set modules = extractModules(sources); + boolean isModularProject = !modules.isEmpty(); + + logger.debug( + "Module detection for project {}: found {} module(s) {} - modular project: {}", + project.getId(), + modules.size(), + modules, + isModularProject); + + /* + * Handle main resources - modular project has highest priority: + * 1. Modular project: use resources from if present, otherwise inject defaults + * 2. Classic project: use resources from if present, otherwise use legacy + */ + List resources = project.getBuild().getDelegate().getResources(); + if (isModularProject) { + if (hasMainResources) { + // Modular project with resources configured via - already added above + if (!resources.isEmpty() + && !hasOnlySuperPomDefaults(resources, baseDir, ProjectScope.MAIN.id())) { + logger.warn("Legacy element is ignored because main resources are " + + "configured via resources in "); + } + logger.debug( + "Main resources configured via element, ignoring legacy element"); + } else { + // Modular project without resources in - inject module-aware defaults + if (!resources.isEmpty() + && !hasOnlySuperPomDefaults(resources, baseDir, ProjectScope.MAIN.id())) { + String message = + "Legacy element is ignored because modular sources are configured. " + + "Use resources in for custom resource paths."; + logger.warn(message); + result.getProblemCollector() + .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( + message, + Severity.WARNING, + Version.V41, + project.getModel().getDelegate(), + -1, + -1, + null)); + } + logger.debug("Injecting module-aware main resource roots for {} modules", modules.size()); + for (String module : modules) { + Path resourcePath = baseDir.resolve("src") + .resolve(module) + .resolve(ProjectScope.MAIN.id()) + .resolve("resources"); + logger.debug(" - Adding main resource root: {} (module: {})", resourcePath, module); + project.addSourceRoot( + createModularResourceRoot(baseDir, module, ProjectScope.MAIN, outputDirectory)); + } + } + } else { + // Classic (non-modular) project + if (hasMainResources) { + // Resources configured via - already added above + if (!resources.isEmpty() + && !hasOnlySuperPomDefaults(resources, baseDir, ProjectScope.MAIN.id())) { + logger.warn("Legacy element is ignored because main resources are " + + "configured via resources in "); + } + logger.debug( + "Main resources configured via element, ignoring legacy element"); + } else { + // Use legacy element + logger.debug("Using explicit or default resources ({} resources configured)", resources.size()); + for (Resource resource : resources) { + project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, resource)); + } + } } - for (Resource resource : project.getBuild().getDelegate().getTestResources()) { - project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, resource)); + + /* + * Handle test resources - same priority as main resources: + * 1. Modular project: use test resources from if present, otherwise inject defaults + * 2. Classic project: use test resources from if present, otherwise use legacy + */ + List testResources = project.getBuild().getDelegate().getTestResources(); + if (isModularProject) { + if (hasTestResources) { + // Modular project with test resources configured via - already added above + if (!testResources.isEmpty() + && !hasOnlySuperPomDefaults(testResources, baseDir, ProjectScope.TEST.id())) { + logger.warn( + "Legacy element is ignored because test resources are " + + "configured via resourcestest in "); + } + logger.debug( + "Test resources configured via element, ignoring legacy element"); + } else { + // Modular project without test resources in - inject module-aware defaults + if (!testResources.isEmpty() + && !hasOnlySuperPomDefaults(testResources, baseDir, ProjectScope.TEST.id())) { + String message = + "Legacy element is ignored because modular sources are configured. " + + "Use resourcestest in for custom resource paths."; + logger.warn(message); + result.getProblemCollector() + .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( + message, + Severity.WARNING, + Version.V41, + project.getModel().getDelegate(), + -1, + -1, + null)); + } + logger.debug("Injecting module-aware test resource roots for {} modules", modules.size()); + for (String module : modules) { + Path resourcePath = baseDir.resolve("src") + .resolve(module) + .resolve(ProjectScope.TEST.id()) + .resolve("resources"); + logger.debug(" - Adding test resource root: {} (module: {})", resourcePath, module); + project.addSourceRoot( + createModularResourceRoot(baseDir, module, ProjectScope.TEST, outputDirectory)); + } + } + } else { + // Classic (non-modular) project + if (hasTestResources) { + // Test resources configured via - already added above + if (!testResources.isEmpty() + && !hasOnlySuperPomDefaults(testResources, baseDir, ProjectScope.TEST.id())) { + logger.warn( + "Legacy element is ignored because test resources are " + + "configured via resourcestest in "); + } + logger.debug( + "Test resources configured via element, ignoring legacy element"); + } else { + // Use legacy element + logger.debug( + "Using explicit or default test resources ({} resources configured)", + testResources.size()); + for (Resource resource : testResources) { + project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, resource)); + } + } } } @@ -1099,6 +1246,86 @@ public Set> entrySet() { } } + /** + * Extracts unique module names from the given list of source elements. + * A project is considered modular if it has at least one module name. + * + * @param sources list of source elements from the build + * @return set of non-blank module names + */ + private Set extractModules(List sources) { + return sources.stream() + .map(org.apache.maven.api.model.Source::getModule) + .filter(Objects::nonNull) + .map(String::trim) + .filter(s -> !s.isBlank()) + .collect(Collectors.toSet()); + } + + /** + * Creates a DefaultSourceRoot for module-aware resource directories. + * Generates paths following the pattern: src/<module>/<scope>/resources + * + * @param baseDir base directory of the project + * @param module module name + * @param scope project scope (main or test) + * @param outputDirectory function providing output directory for the scope + * @return configured DefaultSourceRoot for the module's resources + */ + private DefaultSourceRoot createModularResourceRoot( + Path baseDir, String module, ProjectScope scope, Function outputDirectory) { + Path resourceDir = + baseDir.resolve("src").resolve(module).resolve(scope.id()).resolve("resources"); + + return new DefaultSourceRoot( + scope, + Language.RESOURCES, + module, + null, // targetVersion + resourceDir, + null, // includes + null, // excludes + false, // stringFiltering + Path.of(module), // targetPath - resources go to target/classes/ + true // enabled + ); + } + + /** + * Checks if the given resource list contains only Super POM default resources. + * Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered + * + * @param resources list of resources to check + * @param baseDir project base directory + * @param scope scope (main or test) + * @return true if only Super POM defaults are present + */ + private boolean hasOnlySuperPomDefaults(List resources, Path baseDir, String scope) { + if (resources.isEmpty()) { + return false; + } + + // Super POM default paths + String defaultPath = + baseDir.resolve("src").resolve(scope).resolve("resources").toString(); + String defaultFilteredPath = baseDir.resolve("src") + .resolve(scope) + .resolve("resources-filtered") + .toString(); + + // Check if all resources are Super POM defaults + for (Resource resource : resources) { + String resourceDir = resource.getDirectory(); + if (resourceDir != null && !resourceDir.equals(defaultPath) && !resourceDir.equals(defaultFilteredPath)) { + // Found a non-default resource + return false; + } + } + + logger.debug("Detected only Super POM default resources for scope: {}", scope); + return true; + } + private Model injectLifecycleBindings( Model model, ModelBuilderRequest request, 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 69b1aef2270e..1e4d18656a98 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 @@ -26,9 +26,14 @@ import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.apache.maven.AbstractCoreMavenComponentTestCase; +import org.apache.maven.api.Language; +import org.apache.maven.api.ProjectScope; +import org.apache.maven.api.SourceRoot; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.model.InputLocation; @@ -371,4 +376,98 @@ void testLocationTrackingResolution() throws Exception { assertEquals( "org.apache.maven.its:parent:0.1", pluginLocation.getSource().getModelId()); } + /** + * Tests that a project with multiple modules defined in sources is detected as modular, + * and module-aware resource roots are injected for each module. + */ + @Test + void testModularSourcesInjectResourceRoots() throws Exception { + File pom = getProject("modular-sources"); + + MavenSession session = createMavenSession(pom); + MavenProject project = session.getCurrentProject(); + + // Get all resource source roots for main scope + List mainResourceRoots = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) + .collect(Collectors.toList()); + + // Should have resource roots for both modules + Set modules = mainResourceRoots.stream() + .map(SourceRoot::module) + .filter(opt -> opt.isPresent()) + .map(opt -> opt.get()) + .collect(Collectors.toSet()); + + assertEquals(2, modules.size(), "Should have resource roots for 2 modules"); + assertTrue(modules.contains("org.foo.moduleA"), "Should have resource root for moduleA"); + assertTrue(modules.contains("org.foo.moduleB"), "Should have resource root for moduleB"); + + // Get all resource source roots for test scope + List testResourceRoots = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES) + .collect(Collectors.toList()); + + // Should have test resource roots for both modules + Set testModules = testResourceRoots.stream() + .map(SourceRoot::module) + .filter(opt -> opt.isPresent()) + .map(opt -> opt.get()) + .collect(Collectors.toSet()); + + assertEquals(2, testModules.size(), "Should have test resource roots for 2 modules"); + assertTrue(testModules.contains("org.foo.moduleA"), "Should have test resource root for moduleA"); + assertTrue(testModules.contains("org.foo.moduleB"), "Should have test resource root for moduleB"); + } + + /** + * Tests that when modular sources are configured alongside explicit legacy resources, + * the legacy resources are ignored and a warning is issued. + * + * This verifies the behavior described in the design: + * - Modular projects with explicit legacy {@code } configuration should issue a warning + * - The modular resource roots are injected instead of using the legacy configuration + */ + @Test + void testModularSourcesWithExplicitResourcesIssuesWarning() throws Exception { + File pom = getProject("modular-sources-with-explicit-resources"); + + MavenSession mavenSession = createMavenSession(null); + ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest(); + configuration.setRepositorySession(mavenSession.getRepositorySession()); + + ProjectBuildingResult result = getContainer() + .lookup(org.apache.maven.project.ProjectBuilder.class) + .build(pom, configuration); + + MavenProject project = result.getProject(); + + // Verify warnings are issued for ignored legacy resources + List warnings = result.getProblems().stream() + .filter(p -> p.getSeverity() == org.apache.maven.model.building.ModelProblem.Severity.WARNING) + .filter(p -> p.getMessage().contains("Legacy") && p.getMessage().contains("ignored")) + .collect(Collectors.toList()); + + assertEquals(2, warnings.size(), "Should have 2 warnings (one for resources, one for testResources)"); + assertTrue( + warnings.stream().anyMatch(w -> w.getMessage().contains("")), + "Should warn about ignored "); + assertTrue( + warnings.stream().anyMatch(w -> w.getMessage().contains("")), + "Should warn about ignored "); + + // Verify modular resources are still injected correctly + List mainResourceRoots = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) + .collect(Collectors.toList()); + + assertEquals(2, mainResourceRoots.size(), "Should have 2 modular resource roots (one per module)"); + + Set mainModules = mainResourceRoots.stream() + .map(SourceRoot::module) + .filter(opt -> opt.isPresent()) + .map(opt -> opt.get()) + .collect(Collectors.toSet()); + + assertEquals(2, mainModules.size(), "Should have resource roots for 2 modules"); + assertTrue(mainModules.contains("org.foo.moduleA"), "Should have resource root for moduleA"); + assertTrue(mainModules.contains("org.foo.moduleB"), "Should have resource root for moduleB"); + } } diff --git a/impl/maven-core/src/test/projects/project-builder/modular-sources-with-explicit-resources/pom.xml b/impl/maven-core/src/test/projects/project-builder/modular-sources-with-explicit-resources/pom.xml new file mode 100644 index 000000000000..d2bd1a614b3f --- /dev/null +++ b/impl/maven-core/src/test/projects/project-builder/modular-sources-with-explicit-resources/pom.xml @@ -0,0 +1,39 @@ + + + 4.1.0 + + org.apache.maven.tests + modular-sources-explicit-resources-test + 1.0-SNAPSHOT + jar + + + + + + main + java + org.foo.moduleA + + + + main + java + org.foo.moduleB + + + + + + src/custom/resources + + + + + src/custom/test-resources + + + + \ No newline at end of file diff --git a/impl/maven-core/src/test/projects/project-builder/modular-sources/pom.xml b/impl/maven-core/src/test/projects/project-builder/modular-sources/pom.xml new file mode 100644 index 000000000000..2f9b1e7b0371 --- /dev/null +++ b/impl/maven-core/src/test/projects/project-builder/modular-sources/pom.xml @@ -0,0 +1,40 @@ + + + 4.1.0 + + org.apache.maven.tests + modular-sources-test + 1.0-SNAPSHOT + jar + + + + + + main + java + org.foo.moduleA + + + + test + java + org.foo.moduleA + + + + main + java + org.foo.moduleB + + + + test + java + org.foo.moduleB + + + + \ No newline at end of file From a6e2733aa6c4e71ec53c76bc0b93a4834cac4fb8 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Wed, 3 Dec 2025 18:18:18 +0100 Subject: [PATCH 02/10] Address PR review comments for resource handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename hasOnlySuperPomDefaults to hasExplicitLegacyResources with inverted logic to avoid double negations in predicates - Remove redundant isEmpty() checks now handled by the method - Make extractModules method static Note: Extracting a shared method for main/test resource handling was not possible due to checkstyle ParameterNumber limit (max 7). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../maven/project/DefaultProjectBuilder.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) 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 83411a99607b..1c1c32f5ef39 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 @@ -729,8 +729,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) { if (isModularProject) { if (hasMainResources) { // Modular project with resources configured via - already added above - if (!resources.isEmpty() - && !hasOnlySuperPomDefaults(resources, baseDir, ProjectScope.MAIN.id())) { + if (hasExplicitLegacyResources(resources, baseDir, ProjectScope.MAIN.id())) { logger.warn("Legacy element is ignored because main resources are " + "configured via resources in "); } @@ -738,8 +737,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) { "Main resources configured via element, ignoring legacy element"); } else { // Modular project without resources in - inject module-aware defaults - if (!resources.isEmpty() - && !hasOnlySuperPomDefaults(resources, baseDir, ProjectScope.MAIN.id())) { + if (hasExplicitLegacyResources(resources, baseDir, ProjectScope.MAIN.id())) { String message = "Legacy element is ignored because modular sources are configured. " + "Use resources in for custom resource paths."; @@ -769,8 +767,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) { // Classic (non-modular) project if (hasMainResources) { // Resources configured via - already added above - if (!resources.isEmpty() - && !hasOnlySuperPomDefaults(resources, baseDir, ProjectScope.MAIN.id())) { + if (hasExplicitLegacyResources(resources, baseDir, ProjectScope.MAIN.id())) { logger.warn("Legacy element is ignored because main resources are " + "configured via resources in "); } @@ -794,8 +791,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) { if (isModularProject) { if (hasTestResources) { // Modular project with test resources configured via - already added above - if (!testResources.isEmpty() - && !hasOnlySuperPomDefaults(testResources, baseDir, ProjectScope.TEST.id())) { + if (hasExplicitLegacyResources(testResources, baseDir, ProjectScope.TEST.id())) { logger.warn( "Legacy element is ignored because test resources are " + "configured via resourcestest in "); @@ -804,8 +800,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) { "Test resources configured via element, ignoring legacy element"); } else { // Modular project without test resources in - inject module-aware defaults - if (!testResources.isEmpty() - && !hasOnlySuperPomDefaults(testResources, baseDir, ProjectScope.TEST.id())) { + if (hasExplicitLegacyResources(testResources, baseDir, ProjectScope.TEST.id())) { String message = "Legacy element is ignored because modular sources are configured. " + "Use resourcestest in for custom resource paths."; @@ -835,8 +830,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) { // Classic (non-modular) project if (hasTestResources) { // Test resources configured via - already added above - if (!testResources.isEmpty() - && !hasOnlySuperPomDefaults(testResources, baseDir, ProjectScope.TEST.id())) { + if (hasExplicitLegacyResources(testResources, baseDir, ProjectScope.TEST.id())) { logger.warn( "Legacy element is ignored because test resources are " + "configured via resourcestest in "); @@ -1253,7 +1247,7 @@ public Set> entrySet() { * @param sources list of source elements from the build * @return set of non-blank module names */ - private Set extractModules(List sources) { + private static Set extractModules(List sources) { return sources.stream() .map(org.apache.maven.api.model.Source::getModule) .filter(Objects::nonNull) @@ -1292,17 +1286,17 @@ private DefaultSourceRoot createModularResourceRoot( } /** - * Checks if the given resource list contains only Super POM default resources. - * Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered + * Checks if the given resource list contains explicit legacy resources that differ + * from Super POM defaults. Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered * * @param resources list of resources to check * @param baseDir project base directory * @param scope scope (main or test) - * @return true if only Super POM defaults are present + * @return true if explicit legacy resources are present that would be ignored */ - private boolean hasOnlySuperPomDefaults(List resources, Path baseDir, String scope) { + private boolean hasExplicitLegacyResources(List resources, Path baseDir, String scope) { if (resources.isEmpty()) { - return false; + return false; // No resources means no explicit legacy resources to warn about } // Super POM default paths @@ -1313,17 +1307,17 @@ private boolean hasOnlySuperPomDefaults(List resources, Path baseDir, .resolve("resources-filtered") .toString(); - // Check if all resources are Super POM defaults + // Check if any resource differs from Super POM defaults for (Resource resource : resources) { String resourceDir = resource.getDirectory(); if (resourceDir != null && !resourceDir.equals(defaultPath) && !resourceDir.equals(defaultFilteredPath)) { - // Found a non-default resource - return false; + // Found an explicit legacy resource + return true; } } - logger.debug("Detected only Super POM default resources for scope: {}", scope); - return true; + logger.debug("Only Super POM default resources found for scope: {}", scope); + return false; } private Model injectLifecycleBindings( From 234889b28a23f8620e59bd1050016114c72c0d61 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Wed, 3 Dec 2025 18:50:52 +0100 Subject: [PATCH 03/10] Extract shared method for main/test resource handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor duplicate resource handling code into a shared method: - Add ResourceHandlingContext record to group shared parameters - Add handleResourceConfiguration method (4 parameters vs 9) - Remove unused outputDirectory param from createModularResourceRoot - Replace ~120 lines of duplicate code with 7 lines This addresses PR review comment #1 about code duplication between main and test resource handling blocks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../maven/project/DefaultProjectBuilder.java | 244 ++++++++---------- 1 file changed, 114 insertions(+), 130 deletions(-) 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 1c1c32f5ef39..4330c76a5215 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 @@ -720,133 +720,13 @@ private void initProject(MavenProject project, ModelBuilderResult result) { modules, isModularProject); - /* - * Handle main resources - modular project has highest priority: - * 1. Modular project: use resources from if present, otherwise inject defaults - * 2. Classic project: use resources from if present, otherwise use legacy - */ - List resources = project.getBuild().getDelegate().getResources(); - if (isModularProject) { - if (hasMainResources) { - // Modular project with resources configured via - already added above - if (hasExplicitLegacyResources(resources, baseDir, ProjectScope.MAIN.id())) { - logger.warn("Legacy element is ignored because main resources are " - + "configured via resources in "); - } - logger.debug( - "Main resources configured via element, ignoring legacy element"); - } else { - // Modular project without resources in - inject module-aware defaults - if (hasExplicitLegacyResources(resources, baseDir, ProjectScope.MAIN.id())) { - String message = - "Legacy element is ignored because modular sources are configured. " - + "Use resources in for custom resource paths."; - logger.warn(message); - result.getProblemCollector() - .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( - message, - Severity.WARNING, - Version.V41, - project.getModel().getDelegate(), - -1, - -1, - null)); - } - logger.debug("Injecting module-aware main resource roots for {} modules", modules.size()); - for (String module : modules) { - Path resourcePath = baseDir.resolve("src") - .resolve(module) - .resolve(ProjectScope.MAIN.id()) - .resolve("resources"); - logger.debug(" - Adding main resource root: {} (module: {})", resourcePath, module); - project.addSourceRoot( - createModularResourceRoot(baseDir, module, ProjectScope.MAIN, outputDirectory)); - } - } - } else { - // Classic (non-modular) project - if (hasMainResources) { - // Resources configured via - already added above - if (hasExplicitLegacyResources(resources, baseDir, ProjectScope.MAIN.id())) { - logger.warn("Legacy element is ignored because main resources are " - + "configured via resources in "); - } - logger.debug( - "Main resources configured via element, ignoring legacy element"); - } else { - // Use legacy element - logger.debug("Using explicit or default resources ({} resources configured)", resources.size()); - for (Resource resource : resources) { - project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, resource)); - } - } - } - - /* - * Handle test resources - same priority as main resources: - * 1. Modular project: use test resources from if present, otherwise inject defaults - * 2. Classic project: use test resources from if present, otherwise use legacy - */ - List testResources = project.getBuild().getDelegate().getTestResources(); - if (isModularProject) { - if (hasTestResources) { - // Modular project with test resources configured via - already added above - if (hasExplicitLegacyResources(testResources, baseDir, ProjectScope.TEST.id())) { - logger.warn( - "Legacy element is ignored because test resources are " - + "configured via resourcestest in "); - } - logger.debug( - "Test resources configured via element, ignoring legacy element"); - } else { - // Modular project without test resources in - inject module-aware defaults - if (hasExplicitLegacyResources(testResources, baseDir, ProjectScope.TEST.id())) { - String message = - "Legacy element is ignored because modular sources are configured. " - + "Use resourcestest in for custom resource paths."; - logger.warn(message); - result.getProblemCollector() - .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( - message, - Severity.WARNING, - Version.V41, - project.getModel().getDelegate(), - -1, - -1, - null)); - } - logger.debug("Injecting module-aware test resource roots for {} modules", modules.size()); - for (String module : modules) { - Path resourcePath = baseDir.resolve("src") - .resolve(module) - .resolve(ProjectScope.TEST.id()) - .resolve("resources"); - logger.debug(" - Adding test resource root: {} (module: {})", resourcePath, module); - project.addSourceRoot( - createModularResourceRoot(baseDir, module, ProjectScope.TEST, outputDirectory)); - } - } - } else { - // Classic (non-modular) project - if (hasTestResources) { - // Test resources configured via - already added above - if (hasExplicitLegacyResources(testResources, baseDir, ProjectScope.TEST.id())) { - logger.warn( - "Legacy element is ignored because test resources are " - + "configured via resourcestest in "); - } - logger.debug( - "Test resources configured via element, ignoring legacy element"); - } else { - // Use legacy element - logger.debug( - "Using explicit or default test resources ({} resources configured)", - testResources.size()); - for (Resource resource : testResources) { - project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, resource)); - } - } - } + // Handle main and test resources using shared method + ResourceHandlingContext ctx = + new ResourceHandlingContext(project, baseDir, modules, isModularProject, result); + handleResourceConfiguration( + ctx, project.getBuild().getDelegate().getResources(), hasMainResources, ProjectScope.MAIN); + handleResourceConfiguration( + ctx, project.getBuild().getDelegate().getTestResources(), hasTestResources, ProjectScope.TEST); } project.setActiveProfiles( @@ -1240,6 +1120,112 @@ public Set> entrySet() { } } + /** + * Context object for resource handling configuration. + * Groups parameters shared between main and test resource handling to reduce method parameter count. + */ + private record ResourceHandlingContext( + MavenProject project, + Path baseDir, + Set modules, + boolean modularProject, + ModelBuilderResult result) {} + + /** + * Handles resource configuration for a given scope (main or test). + * This method applies the resource priority rules: + *
    + *
  1. Modular project: use resources from <sources> if present, otherwise inject defaults
  2. + *
  3. Classic project: use resources from <sources> if present, otherwise use legacy resources
  4. + *
+ * + * @param ctx the resource handling context containing project info + * @param resources the legacy resource list (from <resources> or <testResources>) + * @param hasResourcesInSources whether resources are configured via <sources> + * @param scope the project scope (MAIN or TEST) + */ + private void handleResourceConfiguration( + ResourceHandlingContext ctx, List resources, boolean hasResourcesInSources, ProjectScope scope) { + + String scopeId = scope.id(); + String scopeName = scope == ProjectScope.MAIN ? "Main" : "Test"; + String legacyElement = scope == ProjectScope.MAIN ? "" : ""; + String sourcesConfig = scope == ProjectScope.MAIN + ? "resources" + : "resourcestest"; + + if (ctx.modularProject()) { + if (hasResourcesInSources) { + // Modular project with resources configured via - already added above + if (hasExplicitLegacyResources(resources, ctx.baseDir(), scopeId)) { + logger.warn( + "Legacy {} element is ignored because {} resources are configured via {} in ", + legacyElement, + scopeId, + sourcesConfig); + } + logger.debug( + "{} resources configured via element, ignoring legacy {} element", + scopeName, + legacyElement); + } else { + // Modular project without resources in - inject module-aware defaults + if (hasExplicitLegacyResources(resources, ctx.baseDir(), scopeId)) { + String message = "Legacy " + legacyElement + + " element is ignored because modular sources are configured. " + + "Use " + sourcesConfig + " in for custom resource paths."; + logger.warn(message); + ctx.result() + .getProblemCollector() + .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( + message, + Severity.WARNING, + Version.V41, + ctx.project().getModel().getDelegate(), + -1, + -1, + null)); + } + logger.debug( + "Injecting module-aware {} resource roots for {} modules", + scopeId, + ctx.modules().size()); + for (String module : ctx.modules()) { + Path resourcePath = ctx.baseDir() + .resolve("src") + .resolve(module) + .resolve(scopeId) + .resolve("resources"); + logger.debug(" - Adding {} resource root: {} (module: {})", scopeId, resourcePath, module); + ctx.project().addSourceRoot(createModularResourceRoot(ctx.baseDir(), module, scope)); + } + } + } else { + // Classic (non-modular) project + if (hasResourcesInSources) { + // Resources configured via - already added above + if (hasExplicitLegacyResources(resources, ctx.baseDir(), scopeId)) { + logger.warn( + "Legacy {} element is ignored because {} resources are configured via {} in ", + legacyElement, + scopeId, + sourcesConfig); + } + logger.debug( + "{} resources configured via element, ignoring legacy {} element", + scopeName, + legacyElement); + } else { + // Use legacy resources element + logger.debug( + "Using explicit or default {} resources ({} resources configured)", scopeId, resources.size()); + for (Resource resource : resources) { + ctx.project().addSourceRoot(new DefaultSourceRoot(ctx.baseDir(), scope, resource)); + } + } + } + } + /** * Extracts unique module names from the given list of source elements. * A project is considered modular if it has at least one module name. @@ -1263,11 +1249,9 @@ private static Set extractModules(List outputDirectory) { + private DefaultSourceRoot createModularResourceRoot(Path baseDir, String module, ProjectScope scope) { Path resourceDir = baseDir.resolve("src").resolve(module).resolve(scope.id()).resolve("resources"); From 9276eecfcb697ecda491e0f9134ed5ede962201f Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Sat, 13 Dec 2025 10:15:19 +0100 Subject: [PATCH 04/10] Address PR review: avoid abbreviations, use imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename 'ctx' to 'context' in DefaultProjectBuilder - Use short form 'ModelProblem' instead of FQCN in test Addresses review comments from elharo on PR #11505. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../org/apache/maven/project/DefaultProjectBuilder.java | 9 ++++++--- .../org/apache/maven/project/ProjectBuilderTest.java | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) 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 4330c76a5215..6491bc1b1f61 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 @@ -721,12 +721,15 @@ private void initProject(MavenProject project, ModelBuilderResult result) { isModularProject); // Handle main and test resources using shared method - ResourceHandlingContext ctx = + ResourceHandlingContext context = new ResourceHandlingContext(project, baseDir, modules, isModularProject, result); handleResourceConfiguration( - ctx, project.getBuild().getDelegate().getResources(), hasMainResources, ProjectScope.MAIN); + context, project.getBuild().getDelegate().getResources(), hasMainResources, ProjectScope.MAIN); handleResourceConfiguration( - ctx, project.getBuild().getDelegate().getTestResources(), hasTestResources, ProjectScope.TEST); + context, + project.getBuild().getDelegate().getTestResources(), + hasTestResources, + ProjectScope.TEST); } project.setActiveProfiles( 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 1e4d18656a98..a8825bc5245b 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 @@ -441,8 +441,8 @@ 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() == org.apache.maven.model.building.ModelProblem.Severity.WARNING) + List warnings = result.getProblems().stream() + .filter(p -> p.getSeverity() == ModelProblem.Severity.WARNING) .filter(p -> p.getMessage().contains("Legacy") && p.getMessage().contains("ignored")) .collect(Collectors.toList()); From 9cda55312139a718bc37b7cb9f19e1de74d8d491 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Sat, 13 Dec 2025 15:48:15 +0100 Subject: [PATCH 05/10] Use {@code} for XML elements in private method javadoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use {@code } instead of <element> escapes in javadoc for better readability in source code (per Martin's suggestion). Addresses review comment from desruisseaux on PR #11505. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../apache/maven/project/DefaultProjectBuilder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 6491bc1b1f61..3741a4da9248 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 @@ -1138,13 +1138,13 @@ private record ResourceHandlingContext( * Handles resource configuration for a given scope (main or test). * This method applies the resource priority rules: *
    - *
  1. Modular project: use resources from <sources> if present, otherwise inject defaults
  2. - *
  3. Classic project: use resources from <sources> if present, otherwise use legacy resources
  4. + *
  5. Modular project: use resources from {@code } if present, otherwise inject defaults
  6. + *
  7. Classic project: use resources from {@code } if present, otherwise use legacy resources
  8. *
* * @param ctx the resource handling context containing project info - * @param resources the legacy resource list (from <resources> or <testResources>) - * @param hasResourcesInSources whether resources are configured via <sources> + * @param resources the legacy resource list (from {@code } or {@code }) + * @param hasResourcesInSources whether resources are configured via {@code } * @param scope the project scope (MAIN or TEST) */ private void handleResourceConfiguration( @@ -1247,7 +1247,7 @@ private static Set extractModules(List//resources} * * @param baseDir base directory of the project * @param module module name From e99a3213932ab327e2c765861157d3083a3b1675 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Sun, 14 Dec 2025 10:37:28 +0100 Subject: [PATCH 06/10] Rename ctx to context in handleResourceConfiguration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid abbreviations in new code per project conventions. Addresses review comment from elharo on PR #11505. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../maven/project/DefaultProjectBuilder.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) 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 3741a4da9248..4eca7e39db4d 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 @@ -1142,13 +1142,16 @@ private record ResourceHandlingContext( *
  • Classic project: use resources from {@code } if present, otherwise use legacy resources
  • * * - * @param ctx the resource handling context containing project info + * @param context the resource handling context containing project info * @param resources the legacy resource list (from {@code } or {@code }) * @param hasResourcesInSources whether resources are configured via {@code } * @param scope the project scope (MAIN or TEST) */ private void handleResourceConfiguration( - ResourceHandlingContext ctx, List resources, boolean hasResourcesInSources, ProjectScope scope) { + ResourceHandlingContext context, + List resources, + boolean hasResourcesInSources, + ProjectScope scope) { String scopeId = scope.id(); String scopeName = scope == ProjectScope.MAIN ? "Main" : "Test"; @@ -1157,10 +1160,10 @@ private void handleResourceConfiguration( ? "resources" : "resourcestest"; - if (ctx.modularProject()) { + if (context.modularProject()) { if (hasResourcesInSources) { // Modular project with resources configured via - already added above - if (hasExplicitLegacyResources(resources, ctx.baseDir(), scopeId)) { + if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { logger.warn( "Legacy {} element is ignored because {} resources are configured via {} in ", legacyElement, @@ -1173,18 +1176,18 @@ private void handleResourceConfiguration( legacyElement); } else { // Modular project without resources in - inject module-aware defaults - if (hasExplicitLegacyResources(resources, ctx.baseDir(), scopeId)) { + if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { String message = "Legacy " + legacyElement + " element is ignored because modular sources are configured. " + "Use " + sourcesConfig + " in for custom resource paths."; logger.warn(message); - ctx.result() + context.result() .getProblemCollector() .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( message, Severity.WARNING, Version.V41, - ctx.project().getModel().getDelegate(), + context.project().getModel().getDelegate(), -1, -1, null)); @@ -1192,22 +1195,22 @@ private void handleResourceConfiguration( logger.debug( "Injecting module-aware {} resource roots for {} modules", scopeId, - ctx.modules().size()); - for (String module : ctx.modules()) { - Path resourcePath = ctx.baseDir() + context.modules().size()); + for (String module : context.modules()) { + Path resourcePath = context.baseDir() .resolve("src") .resolve(module) .resolve(scopeId) .resolve("resources"); logger.debug(" - Adding {} resource root: {} (module: {})", scopeId, resourcePath, module); - ctx.project().addSourceRoot(createModularResourceRoot(ctx.baseDir(), module, scope)); + context.project().addSourceRoot(createModularResourceRoot(context.baseDir(), module, scope)); } } } else { // Classic (non-modular) project if (hasResourcesInSources) { // Resources configured via - already added above - if (hasExplicitLegacyResources(resources, ctx.baseDir(), scopeId)) { + if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { logger.warn( "Legacy {} element is ignored because {} resources are configured via {} in ", legacyElement, @@ -1223,7 +1226,7 @@ private void handleResourceConfiguration( logger.debug( "Using explicit or default {} resources ({} resources configured)", scopeId, resources.size()); for (Resource resource : resources) { - ctx.project().addSourceRoot(new DefaultSourceRoot(ctx.baseDir(), scope, resource)); + context.project().addSourceRoot(new DefaultSourceRoot(context.baseDir(), scope, resource)); } } } From 74b7c769687468e8e07c7951612502f0a602599e Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Tue, 16 Dec 2025 11:10:55 +0100 Subject: [PATCH 07/10] Resolve proposal from #11505 PR discussion Cf. https://github.com/apache/maven/pull/11505/files#r2622241004 --- .../java/org/apache/maven/project/DefaultProjectBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4eca7e39db4d..858deca34b4e 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 @@ -686,8 +686,8 @@ private void initProject(MavenProject project, ModelBuilderResult result) { ProjectScope scope = src.scope(); if (ProjectScope.MAIN.equals(scope)) { hasMainResources = true; - } else { - hasTestResources |= ProjectScope.TEST.equals(scope); + } else if (ProjectScope.TEST.equals(scope)) { + hasTestResources = true; } } else { hasScript |= Language.SCRIPT.equals(language); From 4c3252b4f6b47abcf482fe0c454948050e222349 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Mon, 22 Dec 2025 10:26:53 +0100 Subject: [PATCH 08/10] Address PR #11505 review: improve logging and static methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change debug logging to trace level for reduced verbosity - Add periods to end of all log/warning messages - Use else blocks to avoid redundant logging after warnings - Make hasExplicitLegacyResources and createModularResourceRoot static - Remove low-level debug log from hasExplicitLegacyResources - Simplify loop logging by using context.modules() directly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../maven/project/DefaultProjectBuilder.java | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) 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 858deca34b4e..310feb0c2a33 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 @@ -713,8 +713,8 @@ private void initProject(MavenProject project, ModelBuilderResult result) { Set modules = extractModules(sources); boolean isModularProject = !modules.isEmpty(); - logger.debug( - "Module detection for project {}: found {} module(s) {} - modular project: {}", + logger.trace( + "Module detection for project {}: found {} module(s) {} - modular project: {}.", project.getId(), modules.size(), modules, @@ -1165,15 +1165,16 @@ private void handleResourceConfiguration( // Modular project with resources configured via - already added above if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { logger.warn( - "Legacy {} element is ignored because {} resources are configured via {} in ", + "Legacy {} element is ignored because {} resources are configured via {} in .", legacyElement, scopeId, sourcesConfig); + } else { + logger.trace( + "{} resources configured via element, ignoring legacy {} element.", + scopeName, + legacyElement); } - logger.debug( - "{} resources configured via element, ignoring legacy {} element", - scopeName, - legacyElement); } else { // Modular project without resources in - inject module-aware defaults if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { @@ -1192,19 +1193,16 @@ private void handleResourceConfiguration( -1, null)); } - logger.debug( - "Injecting module-aware {} resource roots for {} modules", - scopeId, - context.modules().size()); for (String module : context.modules()) { - Path resourcePath = context.baseDir() - .resolve("src") - .resolve(module) - .resolve(scopeId) - .resolve("resources"); - logger.debug(" - Adding {} resource root: {} (module: {})", scopeId, resourcePath, module); context.project().addSourceRoot(createModularResourceRoot(context.baseDir(), module, scope)); } + if (!context.modules().isEmpty()) { + logger.trace( + "Injected {} module-aware {} resource root(s) for modules: {}.", + context.modules().size(), + scopeId, + context.modules()); + } } } else { // Classic (non-modular) project @@ -1212,19 +1210,20 @@ private void handleResourceConfiguration( // Resources configured via - already added above if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { logger.warn( - "Legacy {} element is ignored because {} resources are configured via {} in ", + "Legacy {} element is ignored because {} resources are configured via {} in .", legacyElement, scopeId, sourcesConfig); + } else { + logger.trace( + "{} resources configured via element, ignoring legacy {} element.", + scopeName, + legacyElement); } - logger.debug( - "{} resources configured via element, ignoring legacy {} element", - scopeName, - legacyElement); } else { // Use legacy resources element - logger.debug( - "Using explicit or default {} resources ({} resources configured)", scopeId, resources.size()); + logger.trace( + "Using explicit or default {} resources ({} resources configured).", scopeId, resources.size()); for (Resource resource : resources) { context.project().addSourceRoot(new DefaultSourceRoot(context.baseDir(), scope, resource)); } @@ -1257,7 +1256,7 @@ private static Set extractModules(List resources, Path baseDir, String scope) { + private static boolean hasExplicitLegacyResources(List resources, Path baseDir, String scope) { if (resources.isEmpty()) { return false; // No resources means no explicit legacy resources to warn about } @@ -1306,7 +1305,6 @@ private boolean hasExplicitLegacyResources(List resources, Path baseDi } } - logger.debug("Only Super POM default resources found for scope: {}", scope); return false; } From b00d3268c336d0d66b9037e0f46799375bca43f3 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Tue, 23 Dec 2025 07:19:23 +0100 Subject: [PATCH 09/10] Extract ResourceHandlingContext class from DefaultProjectBuilder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move resource handling logic to dedicated package-private class: - Create ResourceHandlingContext with project-level state - Method handleResourceConfiguration(scope, hasResourcesInSources) derives resources from scope, takes flag as parameter - Reduces DefaultProjectBuilder by 179 lines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../maven/project/DefaultProjectBuilder.java | 183 +-------------- .../project/ResourceHandlingContext.java | 213 ++++++++++++++++++ 2 files changed, 217 insertions(+), 179 deletions(-) create mode 100644 impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java 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 310feb0c2a33..99289fc7aff6 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 @@ -62,7 +62,6 @@ import org.apache.maven.api.model.Plugin; import org.apache.maven.api.model.Profile; import org.apache.maven.api.model.ReportPlugin; -import org.apache.maven.api.model.Resource; import org.apache.maven.api.services.ArtifactResolver; import org.apache.maven.api.services.ArtifactResolverException; import org.apache.maven.api.services.ArtifactResolverRequest; @@ -720,16 +719,11 @@ private void initProject(MavenProject project, ModelBuilderResult result) { modules, isModularProject); - // Handle main and test resources using shared method - ResourceHandlingContext context = + // Handle main and test resources + ResourceHandlingContext resourceContext = new ResourceHandlingContext(project, baseDir, modules, isModularProject, result); - handleResourceConfiguration( - context, project.getBuild().getDelegate().getResources(), hasMainResources, ProjectScope.MAIN); - handleResourceConfiguration( - context, - project.getBuild().getDelegate().getTestResources(), - hasTestResources, - ProjectScope.TEST); + resourceContext.handleResourceConfiguration(ProjectScope.MAIN, hasMainResources); + resourceContext.handleResourceConfiguration(ProjectScope.TEST, hasTestResources); } project.setActiveProfiles( @@ -1123,114 +1117,6 @@ public Set> entrySet() { } } - /** - * Context object for resource handling configuration. - * Groups parameters shared between main and test resource handling to reduce method parameter count. - */ - private record ResourceHandlingContext( - MavenProject project, - Path baseDir, - Set modules, - boolean modularProject, - ModelBuilderResult result) {} - - /** - * Handles resource configuration for a given scope (main or test). - * This method applies the resource priority rules: - *
      - *
    1. Modular project: use resources from {@code } if present, otherwise inject defaults
    2. - *
    3. Classic project: use resources from {@code } if present, otherwise use legacy resources
    4. - *
    - * - * @param context the resource handling context containing project info - * @param resources the legacy resource list (from {@code } or {@code }) - * @param hasResourcesInSources whether resources are configured via {@code } - * @param scope the project scope (MAIN or TEST) - */ - private void handleResourceConfiguration( - ResourceHandlingContext context, - List resources, - boolean hasResourcesInSources, - ProjectScope scope) { - - String scopeId = scope.id(); - String scopeName = scope == ProjectScope.MAIN ? "Main" : "Test"; - String legacyElement = scope == ProjectScope.MAIN ? "" : ""; - String sourcesConfig = scope == ProjectScope.MAIN - ? "resources" - : "resourcestest"; - - if (context.modularProject()) { - if (hasResourcesInSources) { - // Modular project with resources configured via - already added above - if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { - logger.warn( - "Legacy {} element is ignored because {} resources are configured via {} in .", - legacyElement, - scopeId, - sourcesConfig); - } else { - logger.trace( - "{} resources configured via element, ignoring legacy {} element.", - scopeName, - legacyElement); - } - } else { - // Modular project without resources in - inject module-aware defaults - if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { - String message = "Legacy " + legacyElement - + " element is ignored because modular sources are configured. " - + "Use " + sourcesConfig + " in for custom resource paths."; - logger.warn(message); - context.result() - .getProblemCollector() - .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( - message, - Severity.WARNING, - Version.V41, - context.project().getModel().getDelegate(), - -1, - -1, - null)); - } - for (String module : context.modules()) { - context.project().addSourceRoot(createModularResourceRoot(context.baseDir(), module, scope)); - } - if (!context.modules().isEmpty()) { - logger.trace( - "Injected {} module-aware {} resource root(s) for modules: {}.", - context.modules().size(), - scopeId, - context.modules()); - } - } - } else { - // Classic (non-modular) project - if (hasResourcesInSources) { - // Resources configured via - already added above - if (hasExplicitLegacyResources(resources, context.baseDir(), scopeId)) { - logger.warn( - "Legacy {} element is ignored because {} resources are configured via {} in .", - legacyElement, - scopeId, - sourcesConfig); - } else { - logger.trace( - "{} resources configured via element, ignoring legacy {} element.", - scopeName, - legacyElement); - } - } else { - // Use legacy resources element - logger.trace( - "Using explicit or default {} resources ({} resources configured).", scopeId, resources.size()); - for (Resource resource : resources) { - context.project().addSourceRoot(new DefaultSourceRoot(context.baseDir(), scope, resource)); - } - } - } - } - /** * Extracts unique module names from the given list of source elements. * A project is considered modular if it has at least one module name. @@ -1247,67 +1133,6 @@ private static Set extractModules(List//resources} - * - * @param baseDir base directory of the project - * @param module module name - * @param scope project scope (main or test) - * @return configured DefaultSourceRoot for the module's resources - */ - private static DefaultSourceRoot createModularResourceRoot(Path baseDir, String module, ProjectScope scope) { - Path resourceDir = - baseDir.resolve("src").resolve(module).resolve(scope.id()).resolve("resources"); - - return new DefaultSourceRoot( - scope, - Language.RESOURCES, - module, - null, // targetVersion - resourceDir, - null, // includes - null, // excludes - false, // stringFiltering - Path.of(module), // targetPath - resources go to target/classes/ - true // enabled - ); - } - - /** - * Checks if the given resource list contains explicit legacy resources that differ - * from Super POM defaults. Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered - * - * @param resources list of resources to check - * @param baseDir project base directory - * @param scope scope (main or test) - * @return true if explicit legacy resources are present that would be ignored - */ - private static boolean hasExplicitLegacyResources(List resources, Path baseDir, String scope) { - if (resources.isEmpty()) { - return false; // No resources means no explicit legacy resources to warn about - } - - // Super POM default paths - String defaultPath = - baseDir.resolve("src").resolve(scope).resolve("resources").toString(); - String defaultFilteredPath = baseDir.resolve("src") - .resolve(scope) - .resolve("resources-filtered") - .toString(); - - // Check if any resource differs from Super POM defaults - for (Resource resource : resources) { - String resourceDir = resource.getDirectory(); - if (resourceDir != null && !resourceDir.equals(defaultPath) && !resourceDir.equals(defaultFilteredPath)) { - // Found an explicit legacy resource - return true; - } - } - - return false; - } - private Model injectLifecycleBindings( Model model, ModelBuilderRequest request, diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java b/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java new file mode 100644 index 000000000000..79be248e2cb2 --- /dev/null +++ b/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.project; + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.apache.maven.api.Language; +import org.apache.maven.api.ProjectScope; +import org.apache.maven.api.model.Resource; +import org.apache.maven.api.services.BuilderProblem.Severity; +import org.apache.maven.api.services.ModelBuilderResult; +import org.apache.maven.api.services.ModelProblem.Version; +import org.apache.maven.impl.DefaultSourceRoot; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles resource configuration for Maven projects. + * Groups parameters shared between main and test resource handling. + */ +class ResourceHandlingContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceHandlingContext.class); + + private final MavenProject project; + private final Path baseDir; + private final Set modules; + private final boolean modularProject; + private final ModelBuilderResult result; + + ResourceHandlingContext( + MavenProject project, + Path baseDir, + Set modules, + boolean modularProject, + ModelBuilderResult result) { + this.project = project; + this.baseDir = baseDir; + this.modules = modules; + this.modularProject = modularProject; + this.result = result; + } + + /** + * Handles resource configuration for a given scope (main or test). + * This method applies the resource priority rules: + *
      + *
    1. Modular project: use resources from {@code } if present, otherwise inject defaults
    2. + *
    3. Classic project: use resources from {@code } if present, otherwise use legacy resources
    4. + *
    + * + * @param scope the project scope (MAIN or TEST) + * @param hasResourcesInSources whether resources are configured via {@code } + */ + void handleResourceConfiguration(ProjectScope scope, boolean hasResourcesInSources) { + List resources = scope == ProjectScope.MAIN + ? project.getBuild().getDelegate().getResources() + : project.getBuild().getDelegate().getTestResources(); + + String scopeId = scope.id(); + String scopeName = scope == ProjectScope.MAIN ? "Main" : "Test"; + String legacyElement = scope == ProjectScope.MAIN ? "" : ""; + String sourcesConfig = scope == ProjectScope.MAIN + ? "resources" + : "resourcestest"; + + if (modularProject) { + 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); + } else { + LOGGER.trace( + "{} resources configured via element, ignoring legacy {} element.", + scopeName, + legacyElement); + } + } else { + // 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. " + + "Use " + sourcesConfig + " in for custom resource paths."; + LOGGER.warn(message); + result.getProblemCollector() + .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( + message, + Severity.WARNING, + Version.V41, + project.getModel().getDelegate(), + -1, + -1, + null)); + } + for (String module : modules) { + project.addSourceRoot(createModularResourceRoot(module, scope)); + } + if (!modules.isEmpty()) { + LOGGER.trace( + "Injected {} module-aware {} resource root(s) for modules: {}.", + modules.size(), + scopeId, + modules); + } + } + } else { + // Classic (non-modular) project + 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); + } else { + LOGGER.trace( + "{} resources configured via element, ignoring legacy {} element.", + scopeName, + legacyElement); + } + } else { + // Use legacy resources element + LOGGER.trace( + "Using explicit or default {} resources ({} resources configured).", scopeId, resources.size()); + for (Resource resource : resources) { + project.addSourceRoot(new DefaultSourceRoot(baseDir, scope, resource)); + } + } + } + } + + /** + * Creates a DefaultSourceRoot for module-aware resource directories. + * Generates paths following the pattern: {@code src///resources} + * + * @param module module name + * @param scope project scope (main or test) + * @return configured DefaultSourceRoot for the module's resources + */ + private DefaultSourceRoot createModularResourceRoot(String module, ProjectScope scope) { + Path resourceDir = + baseDir.resolve("src").resolve(module).resolve(scope.id()).resolve("resources"); + + return new DefaultSourceRoot( + scope, + Language.RESOURCES, + module, + null, // targetVersion + resourceDir, + null, // includes + null, // excludes + false, // stringFiltering + Path.of(module), // targetPath - resources go to target/classes/ + true // enabled + ); + } + + /** + * Checks if the given resource list contains explicit legacy resources that differ + * from Super POM defaults. Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered + * + * @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 + */ + private boolean hasExplicitLegacyResources(List resources, String scope) { + if (resources.isEmpty()) { + return false; // No resources means no explicit legacy resources to warn about + } + + // Super POM default paths + String defaultPath = + baseDir.resolve("src").resolve(scope).resolve("resources").toString(); + String defaultFilteredPath = baseDir.resolve("src") + .resolve(scope) + .resolve("resources-filtered") + .toString(); + + // Check if any resource differs from Super POM defaults + for (Resource resource : resources) { + String resourceDir = resource.getDirectory(); + if (resourceDir != null && !resourceDir.equals(defaultPath) && !resourceDir.equals(defaultFilteredPath)) { + // Found an explicit legacy resource + return true; + } + } + + return false; + } +} From dc90ada936d5efa976333d01750a88279485fb7f Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Wed, 24 Dec 2025 15:08:49 +0100 Subject: [PATCH 10/10] Restore debug level for handleResourceConfiguration logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per PR #11505 review feedback from desruisseaux: the logs in handleResourceConfiguration are the result of more elaborated analysis and should remain at debug level, not trace. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../org/apache/maven/project/ResourceHandlingContext.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java b/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java index 79be248e2cb2..48fc9e7e03c9 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/ResourceHandlingContext.java @@ -92,7 +92,7 @@ void handleResourceConfiguration(ProjectScope scope, boolean hasResourcesInSourc scopeId, sourcesConfig); } else { - LOGGER.trace( + LOGGER.debug( "{} resources configured via element, ignoring legacy {} element.", scopeName, legacyElement); @@ -118,7 +118,7 @@ void handleResourceConfiguration(ProjectScope scope, boolean hasResourcesInSourc project.addSourceRoot(createModularResourceRoot(module, scope)); } if (!modules.isEmpty()) { - LOGGER.trace( + LOGGER.debug( "Injected {} module-aware {} resource root(s) for modules: {}.", modules.size(), scopeId, @@ -136,14 +136,14 @@ void handleResourceConfiguration(ProjectScope scope, boolean hasResourcesInSourc scopeId, sourcesConfig); } else { - LOGGER.trace( + LOGGER.debug( "{} resources configured via element, ignoring legacy {} element.", scopeName, legacyElement); } } else { // Use legacy resources element - LOGGER.trace( + LOGGER.debug( "Using explicit or default {} resources ({} resources configured).", scopeId, resources.size()); for (Resource resource : resources) { project.addSourceRoot(new DefaultSourceRoot(baseDir, scope, resource));