diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java index 164c3dcd0e..31cd25815d 100644 --- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java @@ -21,7 +21,6 @@ package org.apache.maven.graph; import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -36,7 +35,6 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.apache.maven.DefaultMaven; import org.apache.maven.MavenExecutionException; import org.apache.maven.ProjectCycleException; import org.apache.maven.artifact.ArtifactUtils; @@ -46,20 +44,17 @@ import org.apache.maven.execution.MavenSession; 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.ModelProblem; -import org.apache.maven.model.building.ModelProblemUtils; -import org.apache.maven.model.building.ModelSource; import org.apache.maven.model.building.Result; -import org.apache.maven.model.building.UrlModelSource; import org.apache.maven.project.DuplicateProjectException; import org.apache.maven.project.MavenProject; -import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingException; -import org.apache.maven.project.ProjectBuildingRequest; -import org.apache.maven.project.ProjectBuildingResult; -import org.codehaus.plexus.logging.Logger; +import org.apache.maven.project.collector.MultiModuleCollectionStrategy; +import org.apache.maven.project.collector.PomlessCollectionStrategy; +import org.apache.maven.project.collector.RequestPomCollectionStrategy; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.dag.CycleDetectedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static java.util.Comparator.comparing; @@ -71,15 +66,24 @@ import static java.util.Comparator.comparing; 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; @Inject - private Logger logger; - - @Inject - protected ProjectBuilder projectBuilder; - - @Inject - private BuildResumptionDataRepository buildResumptionDataRepository; + public DefaultGraphBuilder( BuildResumptionDataRepository buildResumptionDataRepository, + PomlessCollectionStrategy pomlessCollectionStrategy, + MultiModuleCollectionStrategy multiModuleCollectionStrategy, + RequestPomCollectionStrategy requestPomCollectionStrategy ) + { + this.buildResumptionDataRepository = buildResumptionDataRepository; + this.pomlessCollectionStrategy = pomlessCollectionStrategy; + this.multiModuleCollectionStrategy = multiModuleCollectionStrategy; + this.requestPomCollectionStrategy = requestPomCollectionStrategy; + } @Override public Result build( MavenSession session ) @@ -91,7 +95,7 @@ public class DefaultGraphBuilder if ( result == null ) { final List projects = getProjectsForMavenReactor( session ); - validateProjects( projects ); + validateProjects( projects, session.getRequest() ); enrichRequestFromResumptionData( projects, session.getRequest() ); result = reactorDependencyGraph( session, projects ); } @@ -133,6 +137,7 @@ public class DefaultGraphBuilder { ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph( projects ); List activeProjects = projectDependencyGraph.getSortedProjects(); + activeProjects = trimProjectsToRequest( activeProjects, projectDependencyGraph, session.getRequest() ); activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, session.getRequest() ); activeProjects = trimResumedProjects( activeProjects, projectDependencyGraph, session.getRequest() ); activeProjects = trimExcludedProjects( activeProjects, session.getRequest() ); @@ -145,6 +150,26 @@ public class DefaultGraphBuilder return Result.success( projectDependencyGraph ); } + private List trimProjectsToRequest( List activeProjects, + ProjectDependencyGraph graph, + MavenExecutionRequest request ) + throws MavenExecutionException + { + List result = activeProjects; + + if ( request.getPom() != null ) + { + result = getProjectsInRequestScope( request, activeProjects ); + + List sortedProjects = graph.getSortedProjects(); + result.sort( comparing( sortedProjects::indexOf ) ); + + result = includeAlsoMakeTransitively( result, request, graph ); + } + + return result; + } + private List trimSelectedProjects( List projects, ProjectDependencyGraph graph, MavenExecutionRequest request ) throws MavenExecutionException @@ -287,6 +312,28 @@ public class DefaultGraphBuilder } } + private List getProjectsInRequestScope( MavenExecutionRequest request, List 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 modules = requestPomProject.getCollectedProjects() != null + ? requestPomProject.getCollectedProjects() : Collections.emptyList(); + + List result = new ArrayList<>( modules ); + result.add( requestPomProject ); + return result; + } + private String formatProjects( List projects ) { StringBuilder projectNames = new StringBuilder(); @@ -361,76 +408,32 @@ public class DefaultGraphBuilder throws ProjectBuildingException { MavenExecutionRequest request = session.getRequest(); - request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() ); - List projects = new ArrayList<>(); - - // We have no POM file. - // + // 1. Collect project for invocation without a POM. if ( request.getPom() == null ) { - ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) ); - MavenProject project = projectBuilder.build( modelSource, request.getProjectBuildingRequest() ) - .getProject(); - project.setExecutionRoot( true ); - projects.add( project ); - request.setProjectPresent( false ); + return pomlessCollectionStrategy.collectProjects( request ); + } + + // 2. Collect projects for all modules in the multi-module project. + List projects = multiModuleCollectionStrategy.collectProjects( request ); + if ( !projects.isEmpty() ) + { return projects; } - List files = Arrays.asList( request.getPom().getAbsoluteFile() ); - collectProjects( projects, files, request ); - return projects; + // 3. Collect projects for explicitly requested POM. + return requestPomCollectionStrategy.collectProjects( request ); } - private void collectProjects( List projects, List files, MavenExecutionRequest request ) - throws ProjectBuildingException - { - ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest(); - - List results = projectBuilder.build( files, request.isRecursive(), - projectBuildingRequest ); - - boolean problems = false; - - for ( ProjectBuildingResult result : results ) - { - projects.add( result.getProject() ); - - if ( !result.getProblems().isEmpty() && logger.isWarnEnabled() ) - { - logger.warn( "" ); - logger.warn( "Some problems were encountered while building the effective model for " - + result.getProject().getId() ); - - for ( ModelProblem problem : result.getProblems() ) - { - String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() ); - logger.warn( problem.getMessage() + ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) ); - } - - problems = true; - } - } - - if ( problems ) - { - logger.warn( "" ); - logger.warn( "It is highly recommended to fix these problems" - + " because they threaten the stability of your build." ); - logger.warn( "" ); - logger.warn( "For this reason, future Maven versions might no" - + " longer support building such malformed projects." ); - logger.warn( "" ); - } - } - - private void validateProjects( List projects ) + private void validateProjects( List projects, MavenExecutionRequest request ) + throws MavenExecutionException { Map projectsMap = new HashMap<>(); - for ( MavenProject p : projects ) + List projectsInRequestScope = getProjectsInRequestScope( request, projects ); + for ( MavenProject p : projectsInRequestScope ) { String projectKey = ArtifactUtils.key( p.getGroupId(), p.getArtifactId(), p.getVersion() ); @@ -449,9 +452,8 @@ public class DefaultGraphBuilder if ( projectsMap.containsKey( pluginKey ) ) { - logger.warn( project.getName() + " uses " + plugin.getKey() - + " as extensions, which is not possible within the same reactor build. " - + "This plugin was pulled from the local repository!" ); + LOGGER.warn( "{} uses {} as extensions, which is not possible within the same reactor build. " + + "This plugin was pulled from the local repository!", project.getName(), plugin.getKey() ); } } } diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultModelBuildingListener.java b/maven-core/src/main/java/org/apache/maven/project/DefaultModelBuildingListener.java index 95317b2f79..ca6079426d 100644 --- a/maven-core/src/main/java/org/apache/maven/project/DefaultModelBuildingListener.java +++ b/maven-core/src/main/java/org/apache/maven/project/DefaultModelBuildingListener.java @@ -106,7 +106,7 @@ public class DefaultModelBuildingListener catch ( PluginResolutionException | PluginManagerException | PluginVersionResolutionException e ) { event.getProblems().add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) - .setMessage( "Unresolveable build extension: " + e.getMessage() ) + .setMessage( "Unresolvable build extension: " + e.getMessage() ) .setException( e ) ); } diff --git a/maven-core/src/main/java/org/apache/maven/project/collector/DefaultProjectsCollector.java b/maven-core/src/main/java/org/apache/maven/project/collector/DefaultProjectsCollector.java new file mode 100644 index 0000000000..297b4a0098 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/project/collector/DefaultProjectsCollector.java @@ -0,0 +1,98 @@ +package org.apache.maven.project.collector; + +/* + * 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. + */ + +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.model.building.ModelProblem; +import org.apache.maven.model.building.ModelProblemUtils; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.project.ProjectBuildingResult; +import org.codehaus.plexus.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.File; +import java.util.List; + +/** + * Utility to collect projects for a given set of pom.xml files. + */ +@Named +@Singleton +public class DefaultProjectsCollector implements ProjectsCollector +{ + private static final Logger LOGGER = LoggerFactory.getLogger( DefaultProjectsCollector.class ); + private final ProjectBuilder projectBuilder; + + @Inject + public DefaultProjectsCollector( ProjectBuilder projectBuilder ) + { + this.projectBuilder = projectBuilder; + } + + @Override + public void collectProjects( List projects, List files, MavenExecutionRequest request ) + throws ProjectBuildingException + { + ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest(); + + List results = projectBuilder.build( files, request.isRecursive(), + projectBuildingRequest ); + + boolean problems = false; + + for ( ProjectBuildingResult result : results ) + { + projects.add( result.getProject() ); + + if ( !result.getProblems().isEmpty() && LOGGER.isWarnEnabled() ) + { + LOGGER.warn( "" ); + LOGGER.warn( "Some problems were encountered while building the effective model for {}", + result.getProject().getId() ); + + for ( ModelProblem problem : result.getProblems() ) + { + String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() ); + LOGGER.warn( "{}{}", problem.getMessage(), ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) ); + } + + problems = true; + } + } + + if ( problems ) + { + LOGGER.warn( "" ); + LOGGER.warn( "It is highly recommended to fix these problems" + + " because they threaten the stability of your build." ); + LOGGER.warn( "" ); + LOGGER.warn( "For this reason, future Maven versions might no" + + " longer support building such malformed projects." ); + LOGGER.warn( "" ); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/project/collector/MultiModuleCollectionStrategy.java b/maven-core/src/main/java/org/apache/maven/project/collector/MultiModuleCollectionStrategy.java new file mode 100644 index 0000000000..d05c02a7fc --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/project/collector/MultiModuleCollectionStrategy.java @@ -0,0 +1,195 @@ +package org.apache.maven.project.collector; + +/* + * 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. + */ + +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.building.ModelProblem; +import org.apache.maven.model.locator.ModelLocator; +import org.apache.maven.plugin.PluginManagerException; +import org.apache.maven.plugin.PluginResolutionException; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.ProjectBuildingResult; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.transfer.ArtifactNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.List; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Strategy for collecting Maven projects from the multi-module project root, even when executed in a submodule. + */ +@Named( "MultiModuleCollectionStrategy" ) +@Singleton +public class MultiModuleCollectionStrategy implements ProjectCollectionStrategy +{ + private final Logger logger = LoggerFactory.getLogger( getClass() ); + private final ModelLocator modelLocator; + private final ProjectsCollector projectsCollector; + + @Inject + public MultiModuleCollectionStrategy( ModelLocator modelLocator, ProjectsCollector projectsCollector ) + { + this.modelLocator = modelLocator; + this.projectsCollector = projectsCollector; + } + + @Override + public List collectProjects( MavenExecutionRequest request ) throws ProjectBuildingException + { + File moduleProjectPomFile = getMultiModuleProjectPomFile( request ); + List files = Collections.singletonList( moduleProjectPomFile.getAbsoluteFile() ); + try + { + List projects = new ArrayList<>(); + projectsCollector.collectProjects( projects, files, request ); + boolean isRequestedProjectCollected = isRequestedProjectCollected( request, projects ); + if ( isRequestedProjectCollected ) + { + return projects; + } + else + { + logger.debug( "Multi module project collection failed: {}" + + "Detected a POM file next to a .mvn folder in a parent directory ({}). " + + "Maven assumed that POM file to be the parent of the requested project ({}), but it turned " + + "out that it was not. Another project collection strategy will be executed as result.", + System.lineSeparator(), moduleProjectPomFile.getAbsolutePath(), + request.getPom().getAbsolutePath() ); + return Collections.emptyList(); + } + } + catch ( ProjectBuildingException e ) + { + boolean fallThrough = isModuleOutsideRequestScopeDependingOnPluginModule( request, e ); + + if ( fallThrough ) + { + logger.debug( "Multi module project collection failed: {}" + + "Detected that one of the modules of this multi module project uses another module as " + + "plugin extension which still needed to be built. This is not possible within the same " + + "reactor build. Another project collection strategy will be executed as result.", + System.lineSeparator() ); + return Collections.emptyList(); + } + + throw e; + } + } + + private File getMultiModuleProjectPomFile( MavenExecutionRequest request ) + { + if ( request.getPom().getParentFile().equals( request.getMultiModuleProjectDirectory() ) ) + { + return request.getPom(); + } + else + { + File multiModuleProjectPom = modelLocator.locatePom( request.getMultiModuleProjectDirectory() ); + if ( !multiModuleProjectPom.exists() ) + { + logger.info( "Maven detected that the requested POM file is part of a multi module project, " + + "but could not find a pom.xml file in the multi module root directory: '" + + request.getMultiModuleProjectDirectory() + "'. " ); + logger.info( "The reactor is limited to all projects under: " + request.getPom().getParent() ); + return request.getPom(); + } + + return multiModuleProjectPom; + } + } + + /** + * multiModuleProjectDirectory in MavenExecutionRequest is not always the parent of the request pom. + * We should always check whether the request pom project is collected. + * The integration tests for MNG-6223 are examples for this scenario. + * + * @return true if the collected projects contain the requested project (for example with -f) + */ + private boolean isRequestedProjectCollected( MavenExecutionRequest request, List projects ) + { + return projects.stream() + .map( MavenProject::getFile ) + .anyMatch( request.getPom()::equals ); + } + + /** + * This method finds out whether collecting projects failed because of the following scenario: + * - A multi module project containing a module which is a plugin and another module which depends on it. + * - Just the plugin is being built with the -f flag. + * - Because of inter-module dependency collection, all projects in the multi-module project are collected. + * - The plugin is not yet installed in a repository. + * + * Therefore the build fails because the plugin is not found and plugins cannot be built in the same session. + * + * The integration test for MNG-5572 is an + * example of this scenario. + * + * @return true if the module which fails to collect the inter-module plugin is not part of the build. + */ + private boolean isModuleOutsideRequestScopeDependingOnPluginModule( MavenExecutionRequest request, + ProjectBuildingException exception ) + { + return exception.getResults().stream() + .map( ProjectBuildingResult::getProject ) + .filter( Objects::nonNull ) + .filter( project -> request.getPom().equals( project.getFile() ) ) + .findFirst() + .map( requestPomProject -> + { + List modules = requestPomProject.getCollectedProjects() != null + ? requestPomProject.getCollectedProjects() : Collections.emptyList(); + List projectsInRequestScope = new ArrayList<>( modules ); + projectsInRequestScope.add( requestPomProject ); + + Predicate projectsOutsideOfRequestScope = + pr -> !projectsInRequestScope.contains( pr.getProject() ); + + Predicate pluginArtifactNotFoundException = + exc -> exc instanceof PluginManagerException + && exc.getCause() instanceof PluginResolutionException + && exc.getCause().getCause() instanceof ArtifactResolutionException + && exc.getCause().getCause().getCause() instanceof ArtifactNotFoundException; + + Predicate isPluginPartOfRequestScope = plugin -> projectsInRequestScope.stream() + .anyMatch( project -> project.getGroupId().equals( plugin.getGroupId() ) + && project.getArtifactId().equals( plugin.getArtifactId() ) + && project.getVersion().equals( plugin.getVersion() ) ); + + return exception.getResults().stream() + .filter( projectsOutsideOfRequestScope ) + .flatMap( projectBuildingResult -> projectBuildingResult.getProblems().stream() ) + .map( ModelProblem::getException ) + .filter( pluginArtifactNotFoundException ) + .map( exc -> ( ( PluginResolutionException ) exc.getCause() ).getPlugin() ) + .anyMatch( isPluginPartOfRequestScope ); + } ).orElse( false ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/project/collector/PomlessCollectionStrategy.java b/maven-core/src/main/java/org/apache/maven/project/collector/PomlessCollectionStrategy.java new file mode 100644 index 0000000000..d86de744ac --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/project/collector/PomlessCollectionStrategy.java @@ -0,0 +1,67 @@ +package org.apache.maven.project.collector; + +/* + * 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. + */ + +import org.apache.maven.DefaultMaven; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.model.building.ModelSource; +import org.apache.maven.model.building.UrlModelSource; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.ProjectBuildingRequest; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.List; + +/** + * Strategy to collect projects for building when the Maven invocation is not in a folder that contains a pom.xml. + */ +@Named( "PomlessCollectionStrategy" ) +@Singleton +public class PomlessCollectionStrategy + implements ProjectCollectionStrategy +{ + private final ProjectBuilder projectBuilder; + + @Inject + public PomlessCollectionStrategy( ProjectBuilder projectBuilder ) + { + this.projectBuilder = projectBuilder; + } + + @Override + public List collectProjects( final MavenExecutionRequest request ) + throws ProjectBuildingException + { + ProjectBuildingRequest buildingRequest = request.getProjectBuildingRequest(); + ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) ); + MavenProject project = projectBuilder.build( modelSource, buildingRequest ).getProject(); + project.setExecutionRoot( true ); + request.setProjectPresent( false ); + + final List result = new ArrayList<>(); + result.add( project ); + return result; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/project/collector/ProjectCollectionStrategy.java b/maven-core/src/main/java/org/apache/maven/project/collector/ProjectCollectionStrategy.java new file mode 100644 index 0000000000..e1931fe43a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/project/collector/ProjectCollectionStrategy.java @@ -0,0 +1,41 @@ +package org.apache.maven.project.collector; + +/* + * 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. + */ + +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuildingException; + +import java.util.List; + +/** + * Describes strategies for finding projects that Maven could build. + */ +public interface ProjectCollectionStrategy +{ + /** + * + * @param request + * @return + * @throws ProjectBuildingException + */ + List collectProjects( final MavenExecutionRequest request ) + throws ProjectBuildingException; +} diff --git a/maven-core/src/main/java/org/apache/maven/project/collector/ProjectsCollector.java b/maven-core/src/main/java/org/apache/maven/project/collector/ProjectsCollector.java new file mode 100644 index 0000000000..10b139a79d --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/project/collector/ProjectsCollector.java @@ -0,0 +1,43 @@ +package org.apache.maven.project.collector; + +/* + * 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. + */ + +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuildingException; + +import java.io.File; +import java.util.List; + +/** + * Facade to collect projects for a given set of pom.xml files. + */ +public interface ProjectsCollector +{ + /** + * Collect Maven projects from a list of POM files. + * @param projects List that will be filled with the found projects. + * @param files List of POM files. + * @param request The {@link MavenExecutionRequest} + * @throws ProjectBuildingException In case the POMs are not used. + */ + void collectProjects( List projects, List files, MavenExecutionRequest request ) + throws ProjectBuildingException; +} diff --git a/maven-core/src/main/java/org/apache/maven/project/collector/RequestPomCollectionStrategy.java b/maven-core/src/main/java/org/apache/maven/project/collector/RequestPomCollectionStrategy.java new file mode 100644 index 0000000000..030b812c5a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/project/collector/RequestPomCollectionStrategy.java @@ -0,0 +1,57 @@ +package org.apache.maven.project.collector; + +/* + * 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. + */ + +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuildingException; + +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.List; + +/** + * Strategy to collect projects based on the -f CLI parameter or the pom.xml in the working directory. + */ +@Named( "RequestPomCollectionStrategy" ) +@Singleton +public class RequestPomCollectionStrategy implements ProjectCollectionStrategy +{ + private final ProjectsCollector projectsCollector; + + @Inject + public RequestPomCollectionStrategy( ProjectsCollector projectsCollector ) + { + this.projectsCollector = projectsCollector; + } + + @Override + public List collectProjects( MavenExecutionRequest request ) throws ProjectBuildingException + { + List files = Collections.singletonList( request.getPom().getAbsoluteFile() ); + List projects = new ArrayList<>(); + projectsCollector.collectProjects( projects, files, request ); + return projects; + } +} diff --git a/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java b/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java index 179b8f9741..9f41b3c5f1 100644 --- a/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java +++ b/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java @@ -116,6 +116,11 @@ public abstract class AbstractCoreMavenComponentTestCase .setPluginArtifactRepositories( getPluginArtifactRepositories() ) .setGoals( Arrays.asList( "package" ) ); + if ( pom != null ) + { + request.setMultiModuleProjectDirectory( pom.getParentFile() ); + } + return request; } diff --git a/maven-core/src/test/java/org/apache/maven/graph/DefaultGraphBuilderTest.java b/maven-core/src/test/java/org/apache/maven/graph/DefaultGraphBuilderTest.java index 0c545a8312..4036c18ccd 100644 --- a/maven-core/src/test/java/org/apache/maven/graph/DefaultGraphBuilderTest.java +++ b/maven-core/src/test/java/org/apache/maven/graph/DefaultGraphBuilderTest.java @@ -19,37 +19,45 @@ package org.apache.maven.graph; * under the License. */ -import com.google.common.collect.ImmutableMap; +import org.apache.maven.execution.BuildResumptionDataRepository; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.model.Dependency; +import org.apache.maven.model.Parent; import org.apache.maven.model.building.Result; +import org.apache.maven.model.locator.DefaultModelLocator; +import org.apache.maven.model.locator.ModelLocator; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.project.ProjectBuildingResult; +import org.apache.maven.project.collector.DefaultProjectsCollector; +import org.apache.maven.project.collector.MultiModuleCollectionStrategy; +import org.apache.maven.project.collector.PomlessCollectionStrategy; +import org.apache.maven.project.collector.ProjectsCollector; +import org.apache.maven.project.collector.RequestPomCollectionStrategy; import org.codehaus.plexus.util.StringUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import java.io.File; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.function.Function.identity; import static junit.framework.TestCase.assertEquals; -import static org.apache.maven.execution.MavenExecutionRequest.*; +import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM; import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_UPSTREAM; import static org.apache.maven.graph.DefaultGraphBuilderTest.ScenarioBuilder.scenario; import static org.mockito.ArgumentMatchers.*; @@ -59,22 +67,39 @@ import static org.mockito.Mockito.when; @RunWith( Parameterized.class ) public class DefaultGraphBuilderTest { + /* + The multi-module structure in this project is displayed as follows: + + module-parent + └─── module-independent (without parent declaration) + module-a + module-b (depends on module-a) + module-c + └─── module-c-1 + module-c-2 (depends on module-b) + */ + private static final String PARENT_MODULE = "module-parent"; private static final String INDEPENDENT_MODULE = "module-independent"; private static final String MODULE_A = "module-a"; - private static final String MODULE_B = "module-b"; // depends on module-a - private static final String MODULE_C = "module-c"; // depends on module-b + private static final String MODULE_B = "module-b"; + private static final String MODULE_C = "module-c"; + private static final String MODULE_C_1 = "module-c-1"; + private static final String MODULE_C_2 = "module-c-2"; - @InjectMocks private DefaultGraphBuilder graphBuilder; - @Mock - private ProjectBuilder projectBuilder; + private final ProjectBuilder projectBuilder = mock( ProjectBuilder.class ); + private final MavenSession session = mock( MavenSession.class ); + private final MavenExecutionRequest mavenExecutionRequest = mock( MavenExecutionRequest.class ); - @Mock - private MavenSession session; + private final ProjectsCollector projectsCollector = new DefaultProjectsCollector( projectBuilder ); - @Mock - private MavenExecutionRequest mavenExecutionRequest; + // Not using mocks for these strategies - a mock would just copy the actual implementation. + + private final ModelLocator modelLocator = new DefaultModelLocator(); + private final PomlessCollectionStrategy pomlessCollectionStrategy = new PomlessCollectionStrategy( projectBuilder ); + private final MultiModuleCollectionStrategy multiModuleCollectionStrategy = new MultiModuleCollectionStrategy( modelLocator, projectsCollector ); + private final RequestPomCollectionStrategy requestPomCollectionStrategy = new RequestPomCollectionStrategy( projectsCollector ); private Map artifactIdProjectMap; @@ -85,73 +110,85 @@ public class DefaultGraphBuilderTest private final String parameterResumeFrom; private final String parameterMakeBehavior; private final List parameterExpectedResult; + private final File parameterRequestedPom; @Parameters(name = "{index}. {0}") public static Collection parameters() { return asList( - scenario( "Full reactor" ) - .expectResult( asList( INDEPENDENT_MODULE, MODULE_A, MODULE_B, MODULE_C ) ), + scenario( "Full reactor in order" ) + .expectResult( PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_B, MODULE_C_2, INDEPENDENT_MODULE ), scenario( "Selected project" ) - .selectedProjects( singletonList( MODULE_B ) ) - .expectResult( singletonList( MODULE_B ) ), + .selectedProjects( MODULE_B ) + .expectResult( MODULE_B ), scenario( "Excluded project" ) - .excludedProjects( singletonList( MODULE_B ) ) - .expectResult( asList( INDEPENDENT_MODULE, MODULE_A, MODULE_C ) ), + .excludedProjects( MODULE_B ) + .expectResult( PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE ), scenario( "Resuming from project" ) .resumeFrom( MODULE_B ) - .expectResult( asList( MODULE_B, MODULE_C ) ), + .expectResult( MODULE_B, MODULE_C_2, INDEPENDENT_MODULE ), scenario( "Selected project with also make dependencies" ) - .selectedProjects( singletonList( MODULE_C ) ) + .selectedProjects( MODULE_C_2 ) .makeBehavior( REACTOR_MAKE_UPSTREAM ) - .expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ), + .expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2 ), scenario( "Selected project with also make dependents" ) - .selectedProjects( singletonList( MODULE_B ) ) + .selectedProjects( MODULE_B ) .makeBehavior( REACTOR_MAKE_DOWNSTREAM ) - .expectResult( asList( MODULE_B, MODULE_C ) ), + .expectResult( MODULE_B, MODULE_C_2 ), scenario( "Resuming from project with also make dependencies" ) .makeBehavior( REACTOR_MAKE_UPSTREAM ) - .resumeFrom( MODULE_C ) - .expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ), - scenario( "Selected project with resume from an also make dependency (MNG-4960 IT#1)" ) - .selectedProjects( singletonList( MODULE_C ) ) + .resumeFrom( MODULE_C_2 ) + .expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2, INDEPENDENT_MODULE ), + scenario( "Selected project with resume from and also make dependency (MNG-4960 IT#1)" ) + .selectedProjects( MODULE_C_2 ) .resumeFrom( MODULE_B ) .makeBehavior( REACTOR_MAKE_UPSTREAM ) - .expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ), - scenario( "Selected project with resume from an also make dependent (MNG-4960 IT#2)" ) - .selectedProjects( singletonList( MODULE_B ) ) - .resumeFrom( MODULE_C ) + .expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2 ), + scenario( "Selected project with resume from and also make dependent (MNG-4960 IT#2)" ) + .selectedProjects( MODULE_B ) + .resumeFrom( MODULE_C_2 ) .makeBehavior( REACTOR_MAKE_DOWNSTREAM ) - .expectResult( singletonList( MODULE_C ) ), + .expectResult( MODULE_C_2 ), scenario( "Excluding an also make dependency from selectedProject does take its transitive dependency" ) - .selectedProjects( singletonList( MODULE_C ) ) - .excludedProjects( singletonList( MODULE_B ) ) + .selectedProjects( MODULE_C_2 ) + .excludedProjects( MODULE_B ) .makeBehavior( REACTOR_MAKE_UPSTREAM ) - .expectResult( asList( MODULE_A, MODULE_C ) ), + .expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_C_2 ), scenario( "Excluding an also make dependency from resumeFrom does take its transitive dependency" ) - .resumeFrom( MODULE_C ) - .excludedProjects( singletonList( MODULE_B ) ) + .resumeFrom( MODULE_C_2 ) + .excludedProjects( MODULE_B ) .makeBehavior( REACTOR_MAKE_UPSTREAM ) - .expectResult( asList( MODULE_A, MODULE_C ) ), + .expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE ), scenario( "Resume from exclude project downstream" ) .resumeFrom( MODULE_A ) - .excludedProjects( singletonList( MODULE_B ) ) - .expectResult( asList( MODULE_A, MODULE_C ) ), + .excludedProjects( MODULE_B ) + .expectResult( MODULE_A, MODULE_C_2, INDEPENDENT_MODULE ), scenario( "Exclude the project we are resuming from (as proposed in MNG-6676)" ) .resumeFrom( MODULE_B ) - .excludedProjects( singletonList( MODULE_B ) ) - .expectResult( singletonList( MODULE_C ) ), + .excludedProjects( MODULE_B ) + .expectResult( MODULE_C_2, INDEPENDENT_MODULE ), scenario( "Selected projects in wrong order are resumed correctly in order" ) - .selectedProjects( asList( MODULE_C, MODULE_B, MODULE_A ) ) + .selectedProjects( MODULE_C_2, MODULE_B, MODULE_A ) .resumeFrom( MODULE_B ) - .expectResult( asList( MODULE_B, MODULE_C ) ), + .expectResult( MODULE_B, MODULE_C_2 ), scenario( "Duplicate projects are filtered out" ) - .selectedProjects( asList( MODULE_A, MODULE_A ) ) - .expectResult( singletonList( MODULE_A ) ) + .selectedProjects( MODULE_A, MODULE_A ) + .expectResult( MODULE_A ), + scenario( "Select reactor by specific pom" ) + .requestedPom( MODULE_C ) + .expectResult( MODULE_C, MODULE_C_1, MODULE_C_2 ), + scenario( "Select reactor by specific pom with also make dependencies" ) + .requestedPom( MODULE_C ) + .makeBehavior( REACTOR_MAKE_UPSTREAM ) + .expectResult( PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_B, MODULE_C_2 ), + scenario( "Select reactor by specific pom with also make dependents" ) + .requestedPom( MODULE_B ) + .makeBehavior( REACTOR_MAKE_DOWNSTREAM ) + .expectResult( MODULE_B, MODULE_C_2 ) ); } - public DefaultGraphBuilderTest( String description, List selectedProjects, List excludedProjects, String resumedFrom, String makeBehavior, List expectedReactorProjects ) + public DefaultGraphBuilderTest( String description, List selectedProjects, List excludedProjects, String resumedFrom, String makeBehavior, List expectedReactorProjects, File requestedPom ) { this.parameterDescription = description; this.parameterSelectedProjects = selectedProjects; @@ -159,6 +196,7 @@ public class DefaultGraphBuilderTest this.parameterResumeFrom = resumedFrom; this.parameterMakeBehavior = makeBehavior; this.parameterExpectedResult = expectedReactorProjects; + this.parameterRequestedPom = requestedPom; } @Test @@ -171,6 +209,7 @@ public class DefaultGraphBuilderTest when( mavenExecutionRequest.getSelectedProjects() ).thenReturn( selectedProjects ); when( mavenExecutionRequest.getExcludedProjects() ).thenReturn( excludedProjects ); when( mavenExecutionRequest.getMakeBehavior() ).thenReturn( parameterMakeBehavior ); + when( mavenExecutionRequest.getPom() ).thenReturn( parameterRequestedPom ); if ( StringUtils.isNotEmpty( parameterResumeFrom ) ) { when( mavenExecutionRequest.getResumeFrom() ).thenReturn( ":" + parameterResumeFrom ); @@ -190,40 +229,47 @@ public class DefaultGraphBuilderTest @Before public void before() throws Exception { - MockitoAnnotations.initMocks( this ); + graphBuilder = new DefaultGraphBuilder( + mock( BuildResumptionDataRepository.class ), + pomlessCollectionStrategy, + multiModuleCollectionStrategy, + requestPomCollectionStrategy + ); - ProjectBuildingRequest projectBuildingRequest = mock( ProjectBuildingRequest.class ); - ProjectBuildingResult projectBuildingResult1 = mock( ProjectBuildingResult.class ); - ProjectBuildingResult projectBuildingResult2 = mock( ProjectBuildingResult.class ); - ProjectBuildingResult projectBuildingResult3 = mock( ProjectBuildingResult.class ); - ProjectBuildingResult projectBuildingResult4 = mock( ProjectBuildingResult.class ); - MavenProject projectIndependentModule = getMavenProject( "independent-module" ); - MavenProject projectModuleA = getMavenProject( "module-a" ); - MavenProject projectModuleB = getMavenProject( "module-b" ); - MavenProject projectModuleC = getMavenProject( "module-c" ); - projectModuleB.setDependencies( singletonList( toDependency( projectModuleA) ) ); - projectModuleC.setDependencies( singletonList( toDependency( projectModuleB) ) ); + // Create projects + MavenProject projectParent = getMavenProject( PARENT_MODULE ); + MavenProject projectIndependentModule = getMavenProject( INDEPENDENT_MODULE ); + MavenProject projectModuleA = getMavenProject( MODULE_A, projectParent ); + MavenProject projectModuleB = getMavenProject( MODULE_B, projectParent ); + MavenProject projectModuleC = getMavenProject( MODULE_C, projectParent ); + MavenProject projectModuleC1 = getMavenProject( MODULE_C_1, projectModuleC ); + MavenProject projectModuleC2 = getMavenProject( MODULE_C_2, projectModuleC ); + artifactIdProjectMap = Stream.of( projectParent, projectIndependentModule, projectModuleA, projectModuleB, projectModuleC, projectModuleC1, projectModuleC2 ) + .collect( Collectors.toMap( MavenProject::getArtifactId, identity() ) ); + + // Set dependencies and modules + projectModuleB.setDependencies( singletonList( toDependency( projectModuleA ) ) ); + projectModuleC2.setDependencies( singletonList( toDependency( projectModuleB ) ) ); + projectParent.setCollectedProjects( asList( projectIndependentModule, projectModuleA, projectModuleB, projectModuleC, projectModuleC1, projectModuleC2 ) ); + projectModuleC.setCollectedProjects( asList( projectModuleC1, projectModuleC2 ) ); + + // Set up needed mocks when( session.getRequest() ).thenReturn( mavenExecutionRequest ); when( session.getProjects() ).thenReturn( null ); // needed, otherwise it will be an empty list by default + when( mavenExecutionRequest.getProjectBuildingRequest() ).thenReturn( mock( ProjectBuildingRequest.class ) ); + List projectBuildingResults = createProjectBuildingResultMocks( artifactIdProjectMap.values() ); + when( projectBuilder.build( anyList(), anyBoolean(), any( ProjectBuildingRequest.class ) ) ).thenReturn( projectBuildingResults ); + } - when( mavenExecutionRequest.getProjectBuildingRequest() ).thenReturn( projectBuildingRequest ); - when( mavenExecutionRequest.getPom() ).thenReturn( new File( "/tmp/unit-test" ) ); - - when( projectBuildingResult1.getProject() ).thenReturn( projectIndependentModule ); - when( projectBuildingResult2.getProject() ).thenReturn( projectModuleA ); - when( projectBuildingResult3.getProject() ).thenReturn( projectModuleB ); - when( projectBuildingResult4.getProject() ).thenReturn( projectModuleC ); - - when( projectBuilder.build( anyList(), anyBoolean(), any( ProjectBuildingRequest.class ) ) ) - .thenReturn( asList( projectBuildingResult1, projectBuildingResult2, projectBuildingResult3, projectBuildingResult4 ) ); - - artifactIdProjectMap = ImmutableMap.of( - INDEPENDENT_MODULE, projectIndependentModule, - MODULE_A, projectModuleA, - MODULE_B, projectModuleB, - MODULE_C, projectModuleC - ); + private MavenProject getMavenProject( String artifactId, MavenProject parentProject ) + { + MavenProject project = getMavenProject( artifactId ); + Parent parent = new Parent(); + parent.setGroupId( parentProject.getGroupId() ); + parent.setArtifactId( parentProject.getArtifactId() ); + project.getModel().setParent( parent ); + return project; } private MavenProject getMavenProject( String artifactId ) @@ -232,6 +278,8 @@ public class DefaultGraphBuilderTest mavenProject.setGroupId( "unittest" ); mavenProject.setArtifactId( artifactId ); mavenProject.setVersion( "1.0" ); + mavenProject.setPomFile( new File ( artifactId, "pom.xml" ) ); + mavenProject.setCollectedProjects( new ArrayList<>() ); return mavenProject; } @@ -244,6 +292,17 @@ public class DefaultGraphBuilderTest return dependency; } + private List createProjectBuildingResultMocks( Collection projects ) + { + return projects.stream() + .map( project -> { + ProjectBuildingResult result = mock( ProjectBuildingResult.class ); + when( result.getProject() ).thenReturn( project ); + return result; + } ) + .collect( Collectors.toList() ); + } + static class ScenarioBuilder { private String description; @@ -251,6 +310,7 @@ public class DefaultGraphBuilderTest private List excludedProjects = emptyList(); private String resumeFrom = ""; private String makeBehavior = ""; + private File requestedPom = new File( PARENT_MODULE, "pom.xml" ); private ScenarioBuilder() { } @@ -261,15 +321,15 @@ public class DefaultGraphBuilderTest return scenarioBuilder; } - public ScenarioBuilder selectedProjects( List selectedProjects ) + public ScenarioBuilder selectedProjects( String... selectedProjects ) { - this.selectedProjects = selectedProjects; + this.selectedProjects = asList( selectedProjects ); return this; } - public ScenarioBuilder excludedProjects( List excludedProjects ) + public ScenarioBuilder excludedProjects( String... excludedProjects ) { - this.excludedProjects = excludedProjects; + this.excludedProjects = asList( excludedProjects ); return this; } @@ -285,10 +345,16 @@ public class DefaultGraphBuilderTest return this; } - public Object[] expectResult( List expectedReactorProjects ) + public ScenarioBuilder requestedPom( String requestedPom ) + { + this.requestedPom = new File( requestedPom, "pom.xml" ); + return this; + } + + public Object[] expectResult( String... expectedReactorProjects ) { return new Object[] { - description, selectedProjects, excludedProjects, resumeFrom, makeBehavior, expectedReactorProjects + description, selectedProjects, excludedProjects, resumeFrom, makeBehavior, asList( expectedReactorProjects ), requestedPom }; } }