|
27 | 27 | import java.io.IOException; |
28 | 28 | import java.io.InputStream; |
29 | 29 | import java.nio.charset.StandardCharsets; |
| 30 | +import java.nio.file.Files; |
30 | 31 | import java.nio.file.Path; |
31 | 32 | import java.util.AbstractMap; |
32 | 33 | import java.util.ArrayList; |
@@ -525,7 +526,7 @@ List<ProjectBuildingResult> doBuild(List<File> pomFiles, boolean recursive) { |
525 | 526 | return pomFiles.stream() |
526 | 527 | .map(pomFile -> build(pomFile, recursive)) |
527 | 528 | .flatMap(List::stream) |
528 | | - .collect(Collectors.toList()); |
| 529 | + .toList(); |
529 | 530 | } finally { |
530 | 531 | Thread.currentThread().setContextClassLoader(oldContextClassLoader); |
531 | 532 | } |
@@ -571,7 +572,7 @@ private List<ProjectBuildingResult> build(File pomFile, boolean recursive) { |
571 | 572 | project.setCollectedProjects(results(r) |
572 | 573 | .filter(cr -> cr != r && cr.getEffectiveModel() != null) |
573 | 574 | .map(cr -> projectIndex.get(cr.getEffectiveModel().getId())) |
574 | | - .collect(Collectors.toList())); |
| 575 | + .toList()); |
575 | 576 |
|
576 | 577 | DependencyResolutionResult resolutionResult = null; |
577 | 578 | if (request.isResolveDependencies()) { |
@@ -665,65 +666,75 @@ private void initProject(MavenProject project, ModelBuilderResult result) { |
665 | 666 | return build.getDirectory(); |
666 | 667 | } |
667 | 668 | }; |
668 | | - boolean hasScript = false; |
669 | | - boolean hasMain = false; |
670 | | - boolean hasTest = false; |
671 | | - boolean hasMainResources = false; |
672 | | - boolean hasTestResources = false; |
| 669 | + // Extract modules from sources to detect modular projects |
| 670 | + Set<String> modules = extractModules(sources); |
| 671 | + boolean isModularProject = !modules.isEmpty(); |
| 672 | + |
| 673 | + logger.trace( |
| 674 | + "Module detection for project {}: found {} module(s) {} - modular project: {}.", |
| 675 | + project.getId(), |
| 676 | + modules.size(), |
| 677 | + modules, |
| 678 | + isModularProject); |
| 679 | + |
| 680 | + // Create source handling context for unified tracking of all lang/scope combinations |
| 681 | + SourceHandlingContext sourceContext = |
| 682 | + new SourceHandlingContext(project, baseDir, modules, isModularProject, result); |
| 683 | + |
| 684 | + // Process all sources, tracking enabled ones and detecting duplicates |
673 | 685 | for (var source : sources) { |
674 | | - var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source); |
675 | | - project.addSourceRoot(src); |
676 | | - Language language = src.language(); |
677 | | - if (Language.JAVA_FAMILY.equals(language)) { |
678 | | - ProjectScope scope = src.scope(); |
679 | | - if (ProjectScope.MAIN.equals(scope)) { |
680 | | - hasMain = true; |
681 | | - } else { |
682 | | - hasTest |= ProjectScope.TEST.equals(scope); |
683 | | - } |
684 | | - } else if (Language.RESOURCES.equals(language)) { |
685 | | - ProjectScope scope = src.scope(); |
686 | | - if (ProjectScope.MAIN.equals(scope)) { |
687 | | - hasMainResources = true; |
688 | | - } else if (ProjectScope.TEST.equals(scope)) { |
689 | | - hasTestResources = true; |
690 | | - } |
691 | | - } else { |
692 | | - hasScript |= Language.SCRIPT.equals(language); |
| 686 | + var sourceRoot = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source); |
| 687 | + // Track enabled sources for duplicate detection and hasSources() queries |
| 688 | + // Only add source if it's not a duplicate enabled source (first enabled wins) |
| 689 | + if (sourceContext.shouldAddSource(sourceRoot)) { |
| 690 | + project.addSourceRoot(sourceRoot); |
693 | 691 | } |
694 | 692 | } |
| 693 | + |
695 | 694 | /* |
696 | 695 | * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory` |
697 | | - * are ignored if the POM file contains at least one <source> element |
| 696 | + * are ignored if the POM file contains at least one enabled <source> element |
698 | 697 | * for the corresponding scope and language. This rule exists because |
699 | 698 | * Maven provides default values for those elements which may conflict |
700 | 699 | * with user's configuration. |
| 700 | + * |
| 701 | + * Additionally, for modular projects, legacy directories are unconditionally |
| 702 | + * ignored because it is not clear how to dispatch their content between |
| 703 | + * different modules. A warning is emitted if these properties are explicitly set. |
701 | 704 | */ |
702 | | - if (!hasScript) { |
| 705 | + if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) { |
703 | 706 | project.addScriptSourceRoot(build.getScriptSourceDirectory()); |
704 | 707 | } |
705 | | - if (!hasMain) { |
706 | | - project.addCompileSourceRoot(build.getSourceDirectory()); |
707 | | - } |
708 | | - if (!hasTest) { |
709 | | - project.addTestCompileSourceRoot(build.getTestSourceDirectory()); |
| 708 | + if (isModularProject) { |
| 709 | + // Modular projects: unconditionally ignore legacy directories, warn if explicitly set |
| 710 | + warnIfExplicitLegacyDirectory( |
| 711 | + build.getSourceDirectory(), |
| 712 | + baseDir.resolve("src/main/java"), |
| 713 | + "<sourceDirectory>", |
| 714 | + project.getId(), |
| 715 | + result); |
| 716 | + warnIfExplicitLegacyDirectory( |
| 717 | + build.getTestSourceDirectory(), |
| 718 | + baseDir.resolve("src/test/java"), |
| 719 | + "<testSourceDirectory>", |
| 720 | + project.getId(), |
| 721 | + result); |
| 722 | + } else { |
| 723 | + // Classic projects: use legacy directories if no sources defined in <sources> |
| 724 | + if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.MAIN)) { |
| 725 | + project.addCompileSourceRoot(build.getSourceDirectory()); |
| 726 | + } |
| 727 | + if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.TEST)) { |
| 728 | + project.addTestCompileSourceRoot(build.getTestSourceDirectory()); |
| 729 | + } |
710 | 730 | } |
711 | | - // Extract modules from sources to detect modular projects |
712 | | - Set<String> modules = extractModules(sources); |
713 | | - boolean isModularProject = !modules.isEmpty(); |
714 | 731 |
|
715 | | - logger.trace( |
716 | | - "Module detection for project {}: found {} module(s) {} - modular project: {}.", |
717 | | - project.getId(), |
718 | | - modules.size(), |
719 | | - modules, |
720 | | - isModularProject); |
| 732 | + // Validate that modular and classic sources are not mixed within <sources> |
| 733 | + sourceContext.validateNoMixedModularAndClassicSources(); |
721 | 734 |
|
722 | | - // Handle main and test resources |
723 | | - ResourceHandlingContext resourceContext = |
724 | | - new ResourceHandlingContext(project, baseDir, modules, isModularProject, result); |
725 | | - resourceContext.handleResourceConfiguration(ProjectScope.MAIN, hasMainResources); |
726 | | - resourceContext.handleResourceConfiguration(ProjectScope.TEST, hasTestResources); |
| 735 | + // Handle main and test resources using unified source handling |
| 736 | + sourceContext.handleResourceConfiguration(ProjectScope.MAIN); |
| 737 | + sourceContext.handleResourceConfiguration(ProjectScope.TEST); |
727 | 738 | } |
728 | 739 |
|
729 | 740 | project.setActiveProfiles( |
@@ -894,6 +905,49 @@ private void initProject(MavenProject project, ModelBuilderResult result) { |
894 | 905 | project.setRemoteArtifactRepositories(remoteRepositories); |
895 | 906 | } |
896 | 907 |
|
| 908 | + /** |
| 909 | + * Warns about legacy directory usage in a modular project. Two cases are handled: |
| 910 | + * <ul> |
| 911 | + * <li>Case 1: The default legacy directory exists on the filesystem (e.g., src/main/java exists)</li> |
| 912 | + * <li>Case 2: An explicit legacy directory is configured that differs from the default</li> |
| 913 | + * </ul> |
| 914 | + * Legacy directories are unconditionally ignored in modular projects because it is not clear |
| 915 | + * how to dispatch their content between different modules. |
| 916 | + */ |
| 917 | + private void warnIfExplicitLegacyDirectory( |
| 918 | + String configuredDir, |
| 919 | + Path defaultDir, |
| 920 | + String elementName, |
| 921 | + String projectId, |
| 922 | + ModelBuilderResult result) { |
| 923 | + if (configuredDir != null) { |
| 924 | + Path configuredPath = Path.of(configuredDir).toAbsolutePath().normalize(); |
| 925 | + Path defaultPath = defaultDir.toAbsolutePath().normalize(); |
| 926 | + if (!configuredPath.equals(defaultPath)) { |
| 927 | + // Case 2: Explicit configuration differs from default - always warn |
| 928 | + String message = String.format( |
| 929 | + "Legacy %s is ignored in modular project %s. " |
| 930 | + + "In modular projects, source directories must be defined via <sources> " |
| 931 | + + "with a module element for each module.", |
| 932 | + elementName, projectId); |
| 933 | + logger.warn(message); |
| 934 | + result.getProblemCollector() |
| 935 | + .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( |
| 936 | + message, Severity.WARNING, Version.V41, null, -1, -1, null)); |
| 937 | + } else if (Files.isDirectory(defaultPath)) { |
| 938 | + // Case 1: Default configuration, but the default directory exists on filesystem |
| 939 | + String message = String.format( |
| 940 | + "Legacy %s '%s' exists but is ignored in modular project %s. " |
| 941 | + + "In modular projects, source directories must be defined via <sources>.", |
| 942 | + elementName, defaultPath, projectId); |
| 943 | + logger.warn(message); |
| 944 | + result.getProblemCollector() |
| 945 | + .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem( |
| 946 | + message, Severity.WARNING, Version.V41, null, -1, -1, null)); |
| 947 | + } |
| 948 | + } |
| 949 | + } |
| 950 | + |
897 | 951 | private void initParent(MavenProject project, ModelBuilderResult result) { |
898 | 952 | Model parentModel = result.getParentModel(); |
899 | 953 |
|
@@ -1035,8 +1089,8 @@ private DependencyResolutionResult resolveDependencies(MavenProject project) { |
1035 | 1089 | } |
1036 | 1090 | } |
1037 | 1091 |
|
1038 | | - private List<String> getProfileIds(List<Profile> profiles) { |
1039 | | - return profiles.stream().map(Profile::getId).collect(Collectors.toList()); |
| 1092 | + private static List<String> getProfileIds(List<Profile> profiles) { |
| 1093 | + return profiles.stream().map(Profile::getId).toList(); |
1040 | 1094 | } |
1041 | 1095 |
|
1042 | 1096 | private static ModelSource createStubModelSource(Artifact artifact) { |
|
0 commit comments