Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -692,49 +692,102 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
}

/*
* `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory`
* are ignored if the POM file contains at least one enabled <source> 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.
* Source directory handling depends on project type and <sources> 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.
* 1. CLASSIC projects (no <sources>):
* - All legacy directories are used
*
* 2. MODULAR projects (have <module> in <sources>):
* - ALL legacy directories are rejected (can't dispatch between modules)
* - Physical presence of default directories (src/main/java) also triggers ERROR
*
* 3. NON-MODULAR projects with <sources>:
* - Explicit legacy directories (differ from default) are always rejected
* - If <sources> has Java for a scope: legacy is not used (even if matching default)
* - If <sources> has no Java for a scope: legacy is used as implicit fallback
* only if it matches the default (could be inherited)
* - This allows incremental adoption (e.g., custom resources + default Java)
*/
if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) {
if (sources.isEmpty()) {
// Classic fallback: no <sources> configured, use legacy directories
project.addScriptSourceRoot(build.getScriptSourceDirectory());
}
if (isModularProject) {
// Modular projects: unconditionally ignore legacy directories, warn if explicitly set
warnIfExplicitLegacyDirectory(
build.getSourceDirectory(),
baseDir.resolve("src/main/java"),
"<sourceDirectory>",
project.getId(),
result);
warnIfExplicitLegacyDirectory(
build.getTestSourceDirectory(),
baseDir.resolve("src/test/java"),
"<testSourceDirectory>",
project.getId(),
result);
project.addCompileSourceRoot(build.getSourceDirectory());
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
// Handle resources using legacy configuration
sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
sourceContext.handleResourceConfiguration(ProjectScope.TEST);
} else {
// Classic projects: use legacy directories if no sources defined in <sources>
if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.MAIN)) {
project.addCompileSourceRoot(build.getSourceDirectory());
// Add script source root if no <sources lang="script"> configured
if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) {
project.addScriptSourceRoot(build.getScriptSourceDirectory());
}
if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.TEST)) {
project.addTestCompileSourceRoot(build.getTestSourceDirectory());

if (isModularProject) {
// Modular: reject ALL legacy directory configurations
failIfLegacyDirectoryPresent(
build.getSourceDirectory(),
baseDir.resolve("src/main/java"),
"<sourceDirectory>",
project.getId(),
result,
true); // check physical presence
failIfLegacyDirectoryPresent(
build.getTestSourceDirectory(),
baseDir.resolve("src/test/java"),
"<testSourceDirectory>",
project.getId(),
result,
true); // check physical presence
} else {
// Non-modular: always validate legacy directories (error if differs from default)
Path mainDefault = baseDir.resolve("src/main/java");
Path testDefault = baseDir.resolve("src/test/java");

failIfLegacyDirectoryPresent(
build.getSourceDirectory(),
mainDefault,
"<sourceDirectory>",
project.getId(),
result,
false); // no physical presence check
failIfLegacyDirectoryPresent(
build.getTestSourceDirectory(),
testDefault,
"<testSourceDirectory>",
project.getId(),
result,
false); // no physical presence check

// Use legacy as fallback only if:
// 1. <sources> doesn't have Java for this scope
// 2. Legacy matches default (otherwise error was reported above)
if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.MAIN)) {
Path configuredMain = Path.of(build.getSourceDirectory())
.toAbsolutePath()
.normalize();
if (configuredMain.equals(
mainDefault.toAbsolutePath().normalize())) {
project.addCompileSourceRoot(build.getSourceDirectory());
}
}
if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.TEST)) {
Path configuredTest = Path.of(build.getTestSourceDirectory())
.toAbsolutePath()
.normalize();
if (configuredTest.equals(
testDefault.toAbsolutePath().normalize())) {
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
}
}
}
}

// Validate that modular and classic sources are not mixed within <sources>
sourceContext.validateNoMixedModularAndClassicSources();
// Fail if modular and classic sources are mixed within <sources>
sourceContext.failIfMixedModularAndClassicSources();

// Handle main and test resources using unified source handling
sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
sourceContext.handleResourceConfiguration(ProjectScope.TEST);
// Handle main and test resources using unified source handling
sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
sourceContext.handleResourceConfiguration(ProjectScope.TEST);
}
}

project.setActiveProfiles(
Expand Down Expand Up @@ -906,44 +959,60 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
}

/**
* Warns about legacy directory usage in a modular project. Two cases are handled:
* Validates that legacy directory configuration does not conflict with {@code <sources>}.
* <p>
* When {@code <sources>} is configured, the build fails if:
* <ul>
* <li>Case 1: The default legacy directory exists on the filesystem (e.g., src/main/java exists)</li>
* <li>Case 2: An explicit legacy directory is configured that differs from the default</li>
* <li><b>Configuration presence</b>: an explicit legacy configuration differs from the default</li>
* <li><b>Physical presence</b>: the default directory exists on the filesystem (only checked
* when {@code checkPhysicalPresence} is true, typically for modular projects where
* {@code <source>} elements use different paths like {@code src/<module>/main/java})</li>
* </ul>
* Legacy directories are unconditionally ignored in modular projects because it is not clear
* how to dispatch their content between different modules.
* <p>
* The presence of {@code <sources>} is the trigger for this validation, not whether the
* project is modular or non-modular.
* <p>
* This ensures consistency with resource handling.
*
* @param configuredDir the configured legacy directory value
* @param defaultDir the default legacy directory path
* @param elementName the XML element name for error messages
* @param projectId the project ID for error messages
* @param result the model builder result for reporting problems
* @param checkPhysicalPresence whether to check for physical presence of the default directory
* @see SourceHandlingContext#handleResourceConfiguration(ProjectScope)
*/
private void warnIfExplicitLegacyDirectory(
private void failIfLegacyDirectoryPresent(
String configuredDir,
Path defaultDir,
String elementName,
String projectId,
ModelBuilderResult result) {
ModelBuilderResult result,
boolean checkPhysicalPresence) {
if (configuredDir != null) {
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. "
+ "In modular projects, source directories must be defined via <sources> "
+ "with a module element for each module.",
elementName, projectId);
logger.warn(message);
"Legacy %s cannot be used in project %s because sources are configured via <sources>. "
+ "Remove the %s configuration.",
elementName, projectId, elementName);
logger.error(message);
result.getProblemCollector()
.reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
message, Severity.WARNING, Version.V41, null, -1, -1, null));
} else if (Files.isDirectory(defaultPath)) {
// Case 1: Default configuration, but the default directory exists on filesystem
message, Severity.ERROR, Version.V41, null, -1, -1, null));
} else if (checkPhysicalPresence && Files.isDirectory(defaultPath)) {
// Physical presence: default directory exists but would be ignored
String message = String.format(
"Legacy %s '%s' exists but is ignored in modular project %s. "
+ "In modular projects, source directories must be defined via <sources>.",
elementName, defaultPath, projectId);
logger.warn(message);
"Legacy directory '%s' exists but cannot be used in project %s "
+ "because sources are configured via <sources>. "
+ "Remove or rename the directory.",
defaultPath, projectId);
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));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ boolean hasSources(Language language, ProjectScope scope) {
}

/**
* Validates that a project does not mix modular and classic (non-modular) sources.
* Fails the build if modular and classic (non-modular) sources are mixed within {@code <sources>}.
* <p>
* A project must be either fully modular (all sources have a module) or fully classic
* (no sources have a module). Mixing modular and non-modular sources within the same
Expand All @@ -161,7 +161,7 @@ boolean hasSources(Language language, ProjectScope scope) {
* This validation checks each (language, scope) combination and reports an ERROR if
* both modular and non-modular sources are found.
*/
void validateNoMixedModularAndClassicSources() {
void failIfMixedModularAndClassicSources() {
for (ProjectScope scope : List.of(ProjectScope.MAIN, ProjectScope.TEST)) {
for (Language language : List.of(Language.JAVA_FAMILY, Language.RESOURCES)) {
boolean hasModular = declaredSources.stream()
Expand Down Expand Up @@ -200,6 +200,8 @@ void validateNoMixedModularAndClassicSources() {
* <li>Modular project: use resources from {@code <sources>} if present, otherwise inject defaults</li>
* <li>Classic project: use resources from {@code <sources>} if present, otherwise use legacy resources</li>
* </ol>
* <p>
* The error behavior for conflicting legacy configuration is consistent with source directory handling.
*
* @param scope the project scope (MAIN or TEST)
*/
Expand All @@ -221,11 +223,19 @@ void handleResourceConfiguration(ProjectScope scope) {
if (hasResourcesInSources) {
// Modular project with resources configured via <sources> - already added above
if (hasExplicitLegacyResources(resources, scopeId)) {
LOGGER.warn(
"Legacy {} element is ignored because {} resources are configured via {} in <sources>.",
legacyElement,
scopeId,
sourcesConfig);
String message = String.format(
"Legacy %s element cannot be used because %s resources are configured via %s in <sources>.",
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 <sources> element, ignoring legacy {} element.",
Expand All @@ -236,13 +246,13 @@ void handleResourceConfiguration(ProjectScope scope) {
// Modular project without resources in <sources> - 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 <sources> 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,
Expand All @@ -265,11 +275,19 @@ void handleResourceConfiguration(ProjectScope scope) {
if (hasResourcesInSources) {
// Resources configured via <sources> - already added above
if (hasExplicitLegacyResources(resources, scopeId)) {
LOGGER.warn(
"Legacy {} element is ignored because {} resources are configured via {} in <sources>.",
legacyElement,
scopeId,
sourcesConfig);
String message = String.format(
"Legacy %s element cannot be used because %s resources are configured via %s in <sources>.",
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 <sources> element, ignoring legacy {} element.",
Expand Down Expand Up @@ -319,7 +337,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<Resource> resources, String scope) {
if (resources.isEmpty()) {
Expand Down
Loading