blob: 02d935df4fee9d8f9dac1bbf383729723749af89 [file] [log] [blame]
/*
* 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.graph;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.ProjectCycleException;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.BuildResumptionDataRepository;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectActivation;
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.building.DefaultModelProblem;
import org.apache.maven.model.building.Result;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.collector.MultiModuleCollectionStrategy;
import org.apache.maven.project.collector.PomlessCollectionStrategy;
import org.apache.maven.project.collector.RequestPomCollectionStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Comparator.comparing;
/**
* Builds the {@link ProjectDependencyGraph inter-dependencies graph} between projects in the reactor.
*/
@Named(GraphBuilder.HINT)
@Singleton
public class DefaultGraphBuilder implements GraphBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGraphBuilder.class);
private final BuildResumptionDataRepository buildResumptionDataRepository;
private final PomlessCollectionStrategy pomlessCollectionStrategy;
private final MultiModuleCollectionStrategy multiModuleCollectionStrategy;
private final RequestPomCollectionStrategy requestPomCollectionStrategy;
private final ProjectSelector projectSelector;
/**
* @deprecated Use {@link #DefaultGraphBuilder(BuildResumptionDataRepository, PomlessCollectionStrategy,
* MultiModuleCollectionStrategy, RequestPomCollectionStrategy)} instead or rely on JSR 330
*/
@Deprecated
public DefaultGraphBuilder() {
this(null, null, null, null);
}
@Inject
public DefaultGraphBuilder(
BuildResumptionDataRepository buildResumptionDataRepository,
PomlessCollectionStrategy pomlessCollectionStrategy,
MultiModuleCollectionStrategy multiModuleCollectionStrategy,
RequestPomCollectionStrategy requestPomCollectionStrategy) {
this.buildResumptionDataRepository = buildResumptionDataRepository;
this.pomlessCollectionStrategy = pomlessCollectionStrategy;
this.multiModuleCollectionStrategy = multiModuleCollectionStrategy;
this.requestPomCollectionStrategy = requestPomCollectionStrategy;
this.projectSelector = new ProjectSelector(); // if necessary switch to DI
}
@Override
public Result<ProjectDependencyGraph> build(MavenSession session) {
try {
Result<ProjectDependencyGraph> result = sessionDependencyGraph(session);
if (result == null) {
final List<MavenProject> projects = getProjectsForMavenReactor(session);
validateProjects(projects, session.getRequest());
processPackagingAttribute(projects, session.getRequest());
enrichRequestFromResumptionData(projects, session.getRequest());
result = reactorDependencyGraph(session, projects);
}
return result;
} catch (final ProjectBuildingException | DuplicateProjectException | MavenExecutionException e) {
return Result.error(Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, e)));
} catch (final CycleDetectedException e) {
String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
ProjectCycleException error = new ProjectCycleException(message, e);
return Result.error(
Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, error)));
}
}
private Result<ProjectDependencyGraph> sessionDependencyGraph(final MavenSession session)
throws CycleDetectedException, DuplicateProjectException {
Result<ProjectDependencyGraph> result = null;
if (session.getProjectDependencyGraph() != null || session.getProjects() != null) {
final ProjectDependencyGraph graph =
new DefaultProjectDependencyGraph(session.getAllProjects(), session.getProjects());
result = Result.success(graph);
}
return result;
}
private Result<ProjectDependencyGraph> reactorDependencyGraph(MavenSession session, List<MavenProject> projects)
throws CycleDetectedException, DuplicateProjectException, MavenExecutionException {
ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph(projects);
List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
List<MavenProject> allSortedProjects = projectDependencyGraph.getSortedProjects();
activeProjects = trimProjectsToRequest(activeProjects, projectDependencyGraph, session.getRequest());
activeProjects =
trimSelectedProjects(activeProjects, allSortedProjects, projectDependencyGraph, session.getRequest());
activeProjects = trimResumedProjects(activeProjects, projectDependencyGraph, session.getRequest());
activeProjects = trimExcludedProjects(activeProjects, projectDependencyGraph, session.getRequest());
if (activeProjects.size() != projectDependencyGraph.getSortedProjects().size()) {
projectDependencyGraph = new FilteredProjectDependencyGraph(projectDependencyGraph, activeProjects);
}
return Result.success(projectDependencyGraph);
}
private List<MavenProject> trimProjectsToRequest(
List<MavenProject> activeProjects, ProjectDependencyGraph graph, MavenExecutionRequest request)
throws MavenExecutionException {
List<MavenProject> result = activeProjects;
if (request.getPom() != null) {
result = getProjectsInRequestScope(request, activeProjects);
List<MavenProject> sortedProjects = graph.getSortedProjects();
result.sort(comparing(sortedProjects::indexOf));
result = includeAlsoMakeTransitively(result, request, graph);
}
return result;
}
private List<MavenProject> trimSelectedProjects(
List<MavenProject> projects,
List<MavenProject> allSortedProjects,
ProjectDependencyGraph graph,
MavenExecutionRequest request)
throws MavenExecutionException {
List<MavenProject> result = projects;
ProjectActivation projectActivation = request.getProjectActivation();
Set<String> requiredSelectors = projectActivation.getRequiredActiveProjectSelectors();
Set<String> optionalSelectors = projectActivation.getOptionalActiveProjectSelectors();
if (!requiredSelectors.isEmpty() || !optionalSelectors.isEmpty()) {
Set<MavenProject> selectedProjects = new HashSet<>(requiredSelectors.size() + optionalSelectors.size());
selectedProjects.addAll(
projectSelector.getRequiredProjectsBySelectors(request, allSortedProjects, requiredSelectors));
selectedProjects.addAll(
projectSelector.getOptionalProjectsBySelectors(request, allSortedProjects, optionalSelectors));
// it can be empty when an optional project is missing from the reactor, fallback to returning all projects
if (!selectedProjects.isEmpty()) {
result = new ArrayList<>(selectedProjects);
result = includeAlsoMakeTransitively(result, request, graph);
// Order the new list in the original order
List<MavenProject> sortedProjects = graph.getSortedProjects();
result.sort(comparing(sortedProjects::indexOf));
}
}
return result;
}
private List<MavenProject> trimResumedProjects(
List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
throws MavenExecutionException {
List<MavenProject> result = projects;
if (request.getResumeFrom() != null && !request.getResumeFrom().isEmpty()) {
File reactorDirectory = projectSelector.getBaseDirectoryFromRequest(request);
String selector = request.getResumeFrom();
MavenProject resumingFromProject = projects.stream()
.filter(project -> projectSelector.isMatchingProject(project, selector, reactorDirectory))
.findFirst()
.orElseThrow(() -> new MavenExecutionException(
"Could not find project to resume reactor build from: " + selector + " vs "
+ formatProjects(projects),
request.getPom()));
int resumeFromProjectIndex = projects.indexOf(resumingFromProject);
List<MavenProject> retainingProjects = result.subList(resumeFromProjectIndex, projects.size());
result = includeAlsoMakeTransitively(retainingProjects, request, graph);
}
return result;
}
private List<MavenProject> trimExcludedProjects(
List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
throws MavenExecutionException {
List<MavenProject> result = projects;
ProjectActivation projectActivation = request.getProjectActivation();
Set<String> requiredSelectors = projectActivation.getRequiredInactiveProjectSelectors();
Set<String> optionalSelectors = projectActivation.getOptionalInactiveProjectSelectors();
if (!requiredSelectors.isEmpty() || !optionalSelectors.isEmpty()) {
Set<MavenProject> excludedProjects = new HashSet<>(requiredSelectors.size() + optionalSelectors.size());
List<MavenProject> allProjects = graph.getAllProjects();
excludedProjects.addAll(
projectSelector.getRequiredProjectsBySelectors(request, allProjects, requiredSelectors));
excludedProjects.addAll(
projectSelector.getOptionalProjectsBySelectors(request, allProjects, optionalSelectors));
result = new ArrayList<>(projects);
result.removeAll(excludedProjects);
if (result.isEmpty()) {
boolean isPlural = excludedProjects.size() > 1;
String message = String.format(
"The project exclusion%s in --projects/-pl resulted in an "
+ "empty reactor, please correct %s.",
isPlural ? "s" : "", isPlural ? "them" : "it");
throw new MavenExecutionException(message, request.getPom());
}
}
return result;
}
private List<MavenProject> includeAlsoMakeTransitively(
List<MavenProject> projects, MavenExecutionRequest request, ProjectDependencyGraph graph)
throws MavenExecutionException {
List<MavenProject> result = projects;
String makeBehavior = request.getMakeBehavior();
boolean makeBoth = MavenExecutionRequest.REACTOR_MAKE_BOTH.equals(makeBehavior);
boolean makeUpstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals(makeBehavior);
boolean makeDownstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals(makeBehavior);
if ((makeBehavior != null && !makeBehavior.isEmpty()) && !makeUpstream && !makeDownstream) {
throw new MavenExecutionException("Invalid reactor make behavior: " + makeBehavior, request.getPom());
}
if (makeUpstream || makeDownstream) {
Set<MavenProject> projectsSet = new HashSet<>(projects);
for (MavenProject project : projects) {
if (makeUpstream) {
projectsSet.addAll(graph.getUpstreamProjects(project, true));
}
if (makeDownstream) {
projectsSet.addAll(graph.getDownstreamProjects(project, true));
}
}
result = new ArrayList<>(projectsSet);
// Order the new list in the original order
List<MavenProject> sortedProjects = graph.getSortedProjects();
result.sort(comparing(sortedProjects::indexOf));
}
return result;
}
private void enrichRequestFromResumptionData(List<MavenProject> projects, MavenExecutionRequest request) {
if (request.isResume()) {
projects.stream()
.filter(MavenProject::isExecutionRoot)
.findFirst()
.ifPresent(rootProject -> buildResumptionDataRepository.applyResumptionData(request, rootProject));
}
}
private List<MavenProject> getProjectsInRequestScope(MavenExecutionRequest request, List<MavenProject> projects)
throws MavenExecutionException {
if (request.getPom() == null) {
return projects;
}
MavenProject requestPomProject = projects.stream()
.filter(project -> request.getPom().equals(project.getFile()))
.findFirst()
.orElseThrow(() -> new MavenExecutionException(
"Could not find a project in reactor matching the request POM", request.getPom()));
List<MavenProject> modules = requestPomProject.getCollectedProjects() != null
? requestPomProject.getCollectedProjects()
: Collections.emptyList();
List<MavenProject> result = new ArrayList<>(modules);
result.add(requestPomProject);
return result;
}
private String formatProjects(List<MavenProject> projects) {
StringBuilder projectNames = new StringBuilder();
Iterator<MavenProject> iterator = projects.iterator();
while (iterator.hasNext()) {
MavenProject project = iterator.next();
projectNames.append(project.getGroupId()).append(":").append(project.getArtifactId());
if (iterator.hasNext()) {
projectNames.append(", ");
}
}
return projectNames.toString();
}
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Project collection
//
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private List<MavenProject> getProjectsForMavenReactor(MavenSession session) throws ProjectBuildingException {
MavenExecutionRequest request = session.getRequest();
request.getProjectBuildingRequest().setRepositorySession(session.getRepositorySession());
// 1. Collect project for invocation without a POM.
if (request.getPom() == null) {
return pomlessCollectionStrategy.collectProjects(request);
}
// 2. Collect projects for all modules in the multi-module project.
if (request.getMakeBehavior() != null || !request.getProjectActivation().isEmpty()) {
List<MavenProject> projects = multiModuleCollectionStrategy.collectProjects(request);
if (!projects.isEmpty()) {
return projects;
}
}
// 3. Collect projects for explicitly requested POM.
return requestPomCollectionStrategy.collectProjects(request);
}
private void validateProjects(List<MavenProject> projects, MavenExecutionRequest request)
throws MavenExecutionException {
Map<String, MavenProject> projectsMap = new HashMap<>();
List<MavenProject> projectsInRequestScope = getProjectsInRequestScope(request, projects);
for (MavenProject p : projectsInRequestScope) {
String projectKey = ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion());
projectsMap.put(projectKey, p);
}
for (MavenProject project : projects) {
// MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor
for (Plugin plugin : project.getBuildPlugins()) {
if (plugin.isExtensions()) {
String pluginKey =
ArtifactUtils.key(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
if (projectsMap.containsKey(pluginKey)) {
LOGGER.warn(
"'{}' uses '{}' as extension which is not possible within the same reactor build. "
+ "This plugin was pulled from the local repository!",
project.getName(),
plugin.getKey());
}
}
}
}
}
private void processPackagingAttribute(List<MavenProject> projects, MavenExecutionRequest request)
throws MavenExecutionException {
List<MavenProject> projectsInRequestScope = getProjectsInRequestScope(request, projects);
for (MavenProject p : projectsInRequestScope) {
if ("bom".equals(p.getPackaging())) {
LOGGER.info(
"The packaging attribute of the '{}' project is configured as 'bom' and changed to 'pom'",
p.getName());
p.setPackaging("pom");
}
}
}
}