@@ -692,49 +692,105 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
692692 }
693693
694694 /*
695- * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory`
696- * are ignored if the POM file contains at least one enabled <source> element
697- * for the corresponding scope and language. This rule exists because
698- * Maven provides default values for those elements which may conflict
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.
704- */
705- if (!sourceContext .hasSources (Language .SCRIPT , ProjectScope .MAIN )) {
695+ Source directory handling depends on project type and <sources> configuration:
696+
697+ 1. CLASSIC projects (no <sources>):
698+ - All legacy directories are used
699+
700+ 2. MODULAR projects (have <module> in <sources>):
701+ - ALL legacy directories cause the build to fail (cannot dispatch
702+ between modules)
703+ - The build also fails if default directories (src/main/java)
704+ physically exist on the filesystem
705+
706+ 3. NON-MODULAR projects with <sources>:
707+ - Explicit legacy directories (differ from default) always cause
708+ the build to fail
709+ - Legacy directories for scopes where <sources> defines Java are ignored
710+ - Legacy directories for scopes where <sources> has no Java serve as
711+ implicit fallback (only if they match the default, e.g., inherited)
712+ - This allows incremental adoption (e.g., custom resources + default Java)
713+ */
714+ if (sources .isEmpty ()) {
715+ // Classic fallback: no <sources> configured, use legacy directories
706716 project .addScriptSourceRoot (build .getScriptSourceDirectory ());
707- }
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 );
717+ project .addCompileSourceRoot (build .getSourceDirectory ());
718+ project .addTestCompileSourceRoot (build .getTestSourceDirectory ());
719+ // Handle resources using legacy configuration
720+ sourceContext .handleResourceConfiguration (ProjectScope .MAIN );
721+ sourceContext .handleResourceConfiguration (ProjectScope .TEST );
722722 } 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 ());
723+ // Add script source root if no <sources lang="script"> configured
724+ if (!sourceContext .hasSources (Language .SCRIPT , ProjectScope .MAIN )) {
725+ project .addScriptSourceRoot (build .getScriptSourceDirectory ());
726726 }
727- if (!sourceContext .hasSources (Language .JAVA_FAMILY , ProjectScope .TEST )) {
728- project .addTestCompileSourceRoot (build .getTestSourceDirectory ());
727+
728+ if (isModularProject ) {
729+ // Modular: reject ALL legacy directory configurations
730+ failIfLegacyDirectoryPresent (
731+ build .getSourceDirectory (),
732+ baseDir .resolve ("src/main/java" ),
733+ "<sourceDirectory>" ,
734+ project .getId (),
735+ result ,
736+ true ); // check physical presence
737+ failIfLegacyDirectoryPresent (
738+ build .getTestSourceDirectory (),
739+ baseDir .resolve ("src/test/java" ),
740+ "<testSourceDirectory>" ,
741+ project .getId (),
742+ result ,
743+ true ); // check physical presence
744+ } else {
745+ // Non-modular: always validate legacy directories (error if differs from default)
746+ Path mainDefault = baseDir .resolve ("src/main/java" );
747+ Path testDefault = baseDir .resolve ("src/test/java" );
748+
749+ failIfLegacyDirectoryPresent (
750+ build .getSourceDirectory (),
751+ mainDefault ,
752+ "<sourceDirectory>" ,
753+ project .getId (),
754+ result ,
755+ false ); // no physical presence check
756+ failIfLegacyDirectoryPresent (
757+ build .getTestSourceDirectory (),
758+ testDefault ,
759+ "<testSourceDirectory>" ,
760+ project .getId (),
761+ result ,
762+ false ); // no physical presence check
763+
764+ // Use legacy as fallback only if:
765+ // 1. <sources> doesn't have Java for this scope
766+ // 2. Legacy matches default (otherwise error was reported above)
767+ if (!sourceContext .hasSources (Language .JAVA_FAMILY , ProjectScope .MAIN )) {
768+ Path configuredMain = Path .of (build .getSourceDirectory ())
769+ .toAbsolutePath ()
770+ .normalize ();
771+ if (configuredMain .equals (
772+ mainDefault .toAbsolutePath ().normalize ())) {
773+ project .addCompileSourceRoot (build .getSourceDirectory ());
774+ }
775+ }
776+ if (!sourceContext .hasSources (Language .JAVA_FAMILY , ProjectScope .TEST )) {
777+ Path configuredTest = Path .of (build .getTestSourceDirectory ())
778+ .toAbsolutePath ()
779+ .normalize ();
780+ if (configuredTest .equals (
781+ testDefault .toAbsolutePath ().normalize ())) {
782+ project .addTestCompileSourceRoot (build .getTestSourceDirectory ());
783+ }
784+ }
729785 }
730- }
731786
732- // Validate that modular and classic sources are not mixed within <sources>
733- sourceContext .validateNoMixedModularAndClassicSources ();
787+ // Fail if modular and classic sources are mixed within <sources>
788+ sourceContext .failIfMixedModularAndClassicSources ();
734789
735- // Handle main and test resources using unified source handling
736- sourceContext .handleResourceConfiguration (ProjectScope .MAIN );
737- sourceContext .handleResourceConfiguration (ProjectScope .TEST );
790+ // Handle main and test resources using unified source handling
791+ sourceContext .handleResourceConfiguration (ProjectScope .MAIN );
792+ sourceContext .handleResourceConfiguration (ProjectScope .TEST );
793+ }
738794 }
739795
740796 project .setActiveProfiles (
@@ -906,44 +962,60 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
906962 }
907963
908964 /**
909- * Warns about legacy directory usage in a modular project. Two cases are handled:
965+ * Validates that legacy directory configuration does not conflict with {@code <sources>}.
966+ * <p>
967+ * When {@code <sources>} is configured, the build fails if:
910968 * <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>
969+ * <li><strong>Configuration presence</strong>: an explicit legacy configuration differs from the default</li>
970+ * <li><strong>Physical presence</strong>: the default directory exists on the filesystem (only checked
971+ * when {@code checkPhysicalPresence} is true, typically for modular projects where
972+ * {@code <source>} elements use different paths like {@code src/<module>/main/java})</li>
913973 * </ul>
914- * Legacy directories are unconditionally ignored in modular projects because it is not clear
915- * how to dispatch their content between different modules.
974+ * <p>
975+ * The presence of {@code <sources>} is the trigger for this validation, not whether the
976+ * project is modular or non-modular.
977+ * <p>
978+ * This ensures consistency with resource handling.
979+ *
980+ * @param configuredDir the configured legacy directory value
981+ * @param defaultDir the default legacy directory path
982+ * @param elementName the XML element name for error messages
983+ * @param projectId the project ID for error messages
984+ * @param result the model builder result for reporting problems
985+ * @param checkPhysicalPresence whether to check for physical presence of the default directory
986+ * @see SourceHandlingContext#handleResourceConfiguration(ProjectScope)
916987 */
917- private void warnIfExplicitLegacyDirectory (
988+ private void failIfLegacyDirectoryPresent (
918989 String configuredDir ,
919990 Path defaultDir ,
920991 String elementName ,
921992 String projectId ,
922- ModelBuilderResult result ) {
993+ ModelBuilderResult result ,
994+ boolean checkPhysicalPresence ) {
923995 if (configuredDir != null ) {
924996 Path configuredPath = Path .of (configuredDir ).toAbsolutePath ().normalize ();
925997 Path defaultPath = defaultDir .toAbsolutePath ().normalize ();
926998 if (!configuredPath .equals (defaultPath )) {
927- // Case 2: Explicit configuration differs from default - always warn
999+ // Configuration presence: explicit config differs from default
9281000 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 );
1001+ "Legacy %s cannot be used in project %s because sources are configured via <sources>. "
1002+ + "Remove the %s configuration." ,
1003+ elementName , projectId , elementName );
1004+ logger .error (message );
9341005 result .getProblemCollector ()
9351006 .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
1007+ message , Severity .ERROR , Version .V41 , null , -1 , -1 , null ));
1008+ } else if (checkPhysicalPresence && Files .isDirectory (defaultPath )) {
1009+ // Physical presence: default directory exists but would be ignored
9391010 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 );
1011+ "Legacy directory '%s' exists but cannot be used in project %s "
1012+ + "because sources are configured via <sources>. "
1013+ + "Remove or rename the directory." ,
1014+ defaultPath , projectId );
1015+ logger .error (message );
9441016 result .getProblemCollector ()
9451017 .reportProblem (new org .apache .maven .impl .model .DefaultModelProblem (
946- message , Severity .WARNING , Version .V41 , null , -1 , -1 , null ));
1018+ message , Severity .ERROR , Version .V41 , null , -1 , -1 , null ));
9471019 }
9481020 }
9491021 }
0 commit comments