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 @@ -96,7 +96,7 @@ public void injectTransformedArtifacts(RepositorySystemSession session, MavenPro
}
}

TransformedArtifact createConsumerPomArtifact(
private TransformedArtifact createConsumerPomArtifact(
MavenProject project, Path consumer, RepositorySystemSession session) {
Path actual = project.getFile().toPath();
Path parent = project.getBaseDirectory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
import org.apache.maven.api.model.Parent;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.model.Repository;
import org.apache.maven.api.model.Scm;
Expand All @@ -50,6 +51,7 @@
import org.apache.maven.impl.InternalSession;
import org.apache.maven.model.v4.MavenModelVersion;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectSourcesHelper;
import org.eclipse.aether.RepositorySystemSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -342,7 +344,7 @@ static Model transformNonPom(Model model, MavenProject project) {
return model;
}

static Model transformBom(Model model, MavenProject project) {
private static Model transformBom(Model model, MavenProject project) {
boolean preserveModelVersion = model.isPreserveModelVersion();

Model.Builder builder = prune(
Expand All @@ -369,19 +371,33 @@ static Model transformPom(Model model, MavenProject project) {

// raw to consumer transform
model = model.withRoot(false).withModules(null).withSubprojects(null);
if (model.getParent() != null) {
model = model.withParent(model.getParent().withRelativePath(null));
Parent parent = model.getParent();
if (parent != null) {
model = model.withParent(parent.withRelativePath(null));
}
var sources = new ProjectSourcesHelper(project);
if (sources.useModuleSourceHierarchy()) {
// Dependencies are dispatched by maven-jar-plugin in the POM generated for each module.
model = model.withDependencies(null);
}

if (!preserveModelVersion) {
/*
* If tne <build> contains <source> elements, it is not compatible with the Maven 4.0.0 model.
* Remove the full <build> element instead of removing only the <sources> element, because the
* build without sources does not mean much. Reminder: this removal can be disabled by setting
* the `preserveModelVersion` XML attribute or `preserve.model.version` property to true.
*/
if (sources.hasEnabledSources()) {
model = model.withBuild(null);
}
model = model.withPreserveModelVersion(false);
String modelVersion = new MavenModelVersion().getModelVersion(model);
model = model.withModelVersion(modelVersion);
}
return model;
}

static void warnNotDowngraded(MavenProject project) {
private static void warnNotDowngraded(MavenProject project) {
LOGGER.warn("The consumer POM for " + project.getId() + " cannot be downgraded to 4.0.0. "
+ "If you intent your build to be consumed with Maven 3 projects, you need to remove "
+ "the features that request a newer model version. If you're fine with having the "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,6 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
// only set those on 2nd phase, ignore on 1st pass
if (project.getFile() != null) {
Build build = project.getBuild().getDelegate();
List<org.apache.maven.api.model.Source> sources = build.getSources();
Path baseDir = project.getBaseDirectory();
Function<ProjectScope, String> outputDirectory = (scope) -> {
if (scope == ProjectScope.MAIN) {
Expand All @@ -666,23 +665,11 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
return build.getDirectory();
}
};
// Extract modules from sources to detect modular projects
Set<String> modules = extractModules(sources);
boolean isModularProject = !modules.isEmpty();

logger.trace(
"Module detection for project {}: found {} module(s) {} - modular project: {}.",
project.getId(),
modules.size(),
modules,
isModularProject);

// Create source handling context for unified tracking of all lang/scope combinations
SourceHandlingContext sourceContext =
new SourceHandlingContext(project, baseDir, modules, isModularProject, result);
final SourceHandlingContext sourceContext = new SourceHandlingContext(project, baseDir, result);

// Process all sources, tracking enabled ones and detecting duplicates
for (var source : sources) {
for (org.apache.maven.api.model.Source source : sourceContext.sources) {
var sourceRoot = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source);
// Track enabled sources for duplicate detection and hasSources() queries
// Only add source if it's not a duplicate enabled source (first enabled wins)
Expand All @@ -705,7 +692,7 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) {
project.addScriptSourceRoot(build.getScriptSourceDirectory());
}
if (isModularProject) {
if (sourceContext.useModuleSourceHierarchy()) {
// Modular projects: unconditionally ignore legacy directories, warn if explicitly set
warnIfExplicitLegacyDirectory(
build.getSourceDirectory(),
Expand Down Expand Up @@ -1171,22 +1158,6 @@ public Set<Entry<K, V>> 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 static Set<String> extractModules(List<org.apache.maven.api.model.Source> 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());
}

private Model injectLifecycleBindings(
Model model,
ModelBuilderRequest request,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

import org.apache.maven.api.model.Source;

/**
* Utility methods for analyzing the {@code <source>} elements of a project.
* <strong>Warning:</strong> This is an internal utility class that is only public for technical reasons.
* It is not part of the public <abbr>API</abbr>. In particular, this class can be changed or deleted without
* prior notice.
*/
public class ProjectSourcesHelper {
/**
* All sources of the project.
*/
protected final Collection<Source> sources;

/**
* Creates a new helper for the given project.
*
* @param project the Maven project from which to get the sources
*/
public ProjectSourcesHelper(final MavenProject project) {
sources = project.getBuild().getDelegate().getSources();
}

/**
* Returns whether the project declares at least one {@code <source>} element which is enabled.
* This is regardless if the source declares a module or not.
*
* @return whether the project declares at least one {@code <source>} element which is enabled
*/
public boolean hasEnabledSources() {
for (Source source : sources) {
if (source.isEnabled()) {
return true;
}
}
return false;
}

/**
* Returns a stream of non-blank module names.
* The stream may contain duplicated values.
* This method does not filter disabled sources.
*
* @return a stream of non-blank module names
*/
private Stream<String> streamOfModuleNames() {
return sources.stream()
.map(org.apache.maven.api.model.Source::getModule)
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isBlank());
}

/**
* Extracts unique module names from the list of source elements.
* A project uses module source hierarchy if it has at least one module name.
*
* @return set of non-blank module names in declaration order
*/
public Set<String> getModuleNames() {
var modules = new LinkedHashSet<String>(); // Preferred to `Collectors.toSet()` for preserving order.
streamOfModuleNames().forEach(modules::add);
return modules;
}

/**
* Whether the project uses module source hierarchy. This method returns {@code true} it at least one
* {@code <source>} element declares a Java modules. While modular and non-modular sources should not be mixed,
* this code is tolerant to such mixes because non-modular source elements may have been incorrectly generated
* by non module-aware codes.
*
* @return whether the project uses module source hierarchy
*/
public boolean useModuleSourceHierarchy() {
return streamOfModuleNames().findAny().isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@

/**
* Handles source configuration for Maven projects with unified tracking for all language/scope combinations.
* <p>
* This class replaces the previous approach of hardcoded boolean flags (hasMain, hasTest, etc.)
* with a flexible set-based tracking mechanism that works for any language and scope combination.
* This class uses a flexible set-based tracking mechanism that works for any language and scope combination.
* <p>
* Key features:
* <ul>
Expand All @@ -51,7 +49,7 @@
*
* @since 4.0.0
*/
class SourceHandlingContext {
final class SourceHandlingContext extends ProjectSourcesHelper {

private static final Logger LOGGER = LoggerFactory.getLogger(SourceHandlingContext.class);

Expand All @@ -67,19 +65,30 @@ record SourceKey(Language language, ProjectScope scope, String module, Path dire
private final ModelBuilderResult result;
private final Set<SourceKey> declaredSources;

SourceHandlingContext(
MavenProject project,
Path baseDir,
Set<String> modules,
boolean modularProject,
ModelBuilderResult result) {
SourceHandlingContext(MavenProject project, Path baseDir, ModelBuilderResult result) {
super(project);
this.project = project;
this.baseDir = baseDir;
this.modules = modules;
this.modularProject = modularProject;
this.modules = getModuleNames();
this.modularProject = !modules.isEmpty();
this.result = result;
// Each module typically has main, test, main resources, test resources = 4 sources
this.declaredSources = new HashSet<>(4 * modules.size());
LOGGER.trace(
"Module detection for project {}: found {} module(s) {} - modular project: {}.",
project.getId(),
modules.size(),
modules,
modularProject);
}

/**
* Whether the project uses module source hierarchy.
* Overridden for returning the cached value.
*/
@Override
public boolean useModuleSourceHierarchy() {
return modularProject;
}

/**
Expand Down
Loading