001package org.apache.maven.graph; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.File; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030 031import org.apache.maven.DefaultMaven; 032import org.apache.maven.MavenExecutionException; 033import org.apache.maven.ProjectCycleException; 034import org.apache.maven.artifact.ArtifactUtils; 035import org.apache.maven.execution.MavenExecutionRequest; 036import org.apache.maven.execution.MavenExecutionResult; 037import org.apache.maven.execution.MavenSession; 038import org.apache.maven.execution.ProjectDependencyGraph; 039import org.apache.maven.model.Plugin; 040import org.apache.maven.model.building.DefaultModelProblem; 041import org.apache.maven.model.building.ModelProblem; 042import org.apache.maven.model.building.ModelProblemUtils; 043import org.apache.maven.model.building.ModelSource; 044import org.apache.maven.model.building.Result; 045import org.apache.maven.model.building.UrlModelSource; 046import org.apache.maven.project.MavenProject; 047import org.apache.maven.project.ProjectBuilder; 048import org.apache.maven.project.ProjectBuildingException; 049import org.apache.maven.project.ProjectBuildingRequest; 050import org.apache.maven.project.ProjectBuildingResult; 051import org.codehaus.plexus.component.annotations.Component; 052import org.codehaus.plexus.component.annotations.Requirement; 053import org.codehaus.plexus.logging.Logger; 054import org.codehaus.plexus.util.StringUtils; 055import org.codehaus.plexus.util.dag.CycleDetectedException; 056 057import com.google.common.collect.Lists; 058 059@Component( role = GraphBuilder.class, hint = GraphBuilder.HINT ) 060public class DefaultGraphBuilder 061 implements GraphBuilder 062{ 063 @Requirement 064 private Logger logger; 065 066 @Requirement 067 protected ProjectBuilder projectBuilder; 068 069 @Override 070 public Result<ProjectDependencyGraph> build( MavenSession session ) 071 { 072 if ( session.getProjectDependencyGraph() != null ) 073 { 074 return dependencyGraph( session, session.getProjects(), false ); 075 } 076 077 List<MavenProject> projects = session.getProjects(); 078 079 if ( projects == null ) 080 { 081 try 082 { 083 projects = getProjectsForMavenReactor( session ); 084 } 085 catch ( ProjectBuildingException e ) 086 { 087 return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) ); 088 } 089 090 validateProjects( projects ); 091 092 return dependencyGraph( session, projects, true ); 093 } 094 else 095 { 096 return dependencyGraph( session, projects, false ); 097 } 098 } 099 100 private Result<ProjectDependencyGraph> dependencyGraph( MavenSession session, List<MavenProject> projects, 101 boolean applyMakeBehaviour ) 102 { 103 MavenExecutionRequest request = session.getRequest(); 104 105 ProjectDependencyGraph projectDependencyGraph = null; 106 107 try 108 { 109 projectDependencyGraph = new DefaultProjectDependencyGraph( projects ); 110 111 if ( applyMakeBehaviour ) 112 { 113 List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects(); 114 115 activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, request ); 116 activeProjects = trimExcludedProjects( activeProjects, request ); 117 activeProjects = trimResumedProjects( activeProjects, request ); 118 119 if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() ) 120 { 121 projectDependencyGraph = 122 new FilteredProjectDependencyGraph( projectDependencyGraph, activeProjects ); 123 } 124 } 125 } 126 catch ( CycleDetectedException e ) 127 { 128 String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage(); 129 ProjectCycleException error = new ProjectCycleException( message, e ); 130 return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, error ) ) ); 131 } 132 catch ( org.apache.maven.project.DuplicateProjectException e ) 133 { 134 return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) ); 135 } 136 catch ( MavenExecutionException e ) 137 { 138 return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) ); 139 } 140 141 session.setProjects( projectDependencyGraph.getSortedProjects() ); 142 session.setProjectDependencyGraph( projectDependencyGraph ); 143 144 return Result.success( projectDependencyGraph ); 145 } 146 147 private List<MavenProject> trimSelectedProjects( List<MavenProject> projects, ProjectDependencyGraph graph, 148 MavenExecutionRequest request ) 149 throws MavenExecutionException 150 { 151 List<MavenProject> result = projects; 152 153 if ( !request.getSelectedProjects().isEmpty() ) 154 { 155 File reactorDirectory = null; 156 if ( request.getBaseDirectory() != null ) 157 { 158 reactorDirectory = new File( request.getBaseDirectory() ); 159 } 160 161 Collection<MavenProject> selectedProjects = new LinkedHashSet<MavenProject>( projects.size() ); 162 163 for ( String selector : request.getSelectedProjects() ) 164 { 165 MavenProject selectedProject = null; 166 167 for ( MavenProject project : projects ) 168 { 169 if ( isMatchingProject( project, selector, reactorDirectory ) ) 170 { 171 selectedProject = project; 172 break; 173 } 174 } 175 176 if ( selectedProject != null ) 177 { 178 selectedProjects.add( selectedProject ); 179 } 180 else 181 { 182 throw new MavenExecutionException( "Could not find the selected project in the reactor: " 183 + selector, request.getPom() ); 184 } 185 } 186 187 boolean makeUpstream = false; 188 boolean makeDownstream = false; 189 190 if ( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( request.getMakeBehavior() ) ) 191 { 192 makeUpstream = true; 193 } 194 else if ( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( request.getMakeBehavior() ) ) 195 { 196 makeDownstream = true; 197 } 198 else if ( MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( request.getMakeBehavior() ) ) 199 { 200 makeUpstream = true; 201 makeDownstream = true; 202 } 203 else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) ) 204 { 205 throw new MavenExecutionException( "Invalid reactor make behavior: " + request.getMakeBehavior(), 206 request.getPom() ); 207 } 208 209 if ( makeUpstream || makeDownstream ) 210 { 211 for ( MavenProject selectedProject : new ArrayList<MavenProject>( selectedProjects ) ) 212 { 213 if ( makeUpstream ) 214 { 215 selectedProjects.addAll( graph.getUpstreamProjects( selectedProject, true ) ); 216 } 217 if ( makeDownstream ) 218 { 219 selectedProjects.addAll( graph.getDownstreamProjects( selectedProject, true ) ); 220 } 221 } 222 } 223 224 result = new ArrayList<MavenProject>( selectedProjects.size() ); 225 226 for ( MavenProject project : projects ) 227 { 228 if ( selectedProjects.contains( project ) ) 229 { 230 result.add( project ); 231 } 232 } 233 } 234 235 return result; 236 } 237 238 private List<MavenProject> trimExcludedProjects( List<MavenProject> projects, MavenExecutionRequest request ) 239 throws MavenExecutionException 240 { 241 List<MavenProject> result = projects; 242 243 if ( !request.getExcludedProjects().isEmpty() ) 244 { 245 File reactorDirectory = null; 246 247 if ( request.getBaseDirectory() != null ) 248 { 249 reactorDirectory = new File( request.getBaseDirectory() ); 250 } 251 252 Collection<MavenProject> excludedProjects = new LinkedHashSet<MavenProject>( projects.size() ); 253 254 for ( String selector : request.getExcludedProjects() ) 255 { 256 MavenProject excludedProject = null; 257 258 for ( MavenProject project : projects ) 259 { 260 if ( isMatchingProject( project, selector, reactorDirectory ) ) 261 { 262 excludedProject = project; 263 break; 264 } 265 } 266 267 if ( excludedProject != null ) 268 { 269 excludedProjects.add( excludedProject ); 270 } 271 else 272 { 273 throw new MavenExecutionException( "Could not find the selected project in the reactor: " 274 + selector, request.getPom() ); 275 } 276 } 277 278 result = new ArrayList<MavenProject>( projects.size() ); 279 for ( MavenProject project : projects ) 280 { 281 if ( !excludedProjects.contains( project ) ) 282 { 283 result.add( project ); 284 } 285 } 286 } 287 288 return result; 289 } 290 291 private List<MavenProject> trimResumedProjects( List<MavenProject> projects, MavenExecutionRequest request ) 292 throws MavenExecutionException 293 { 294 List<MavenProject> result = projects; 295 296 if ( StringUtils.isNotEmpty( request.getResumeFrom() ) ) 297 { 298 File reactorDirectory = null; 299 if ( request.getBaseDirectory() != null ) 300 { 301 reactorDirectory = new File( request.getBaseDirectory() ); 302 } 303 304 String selector = request.getResumeFrom(); 305 306 result = new ArrayList<MavenProject>( projects.size() ); 307 308 boolean resumed = false; 309 310 for ( MavenProject project : projects ) 311 { 312 if ( !resumed && isMatchingProject( project, selector, reactorDirectory ) ) 313 { 314 resumed = true; 315 } 316 317 if ( resumed ) 318 { 319 result.add( project ); 320 } 321 } 322 323 if ( !resumed ) 324 { 325 throw new MavenExecutionException( "Could not find project to resume reactor build from: " + selector 326 + " vs " + projects, request.getPom() ); 327 } 328 } 329 330 return result; 331 } 332 333 private boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory ) 334 { 335 // [groupId]:artifactId 336 if ( selector.indexOf( ':' ) >= 0 ) 337 { 338 String id = ':' + project.getArtifactId(); 339 340 if ( id.equals( selector ) ) 341 { 342 return true; 343 } 344 345 id = project.getGroupId() + id; 346 347 if ( id.equals( selector ) ) 348 { 349 return true; 350 } 351 } 352 353 // relative path, e.g. "sub", "../sub" or "." 354 else if ( reactorDirectory != null ) 355 { 356 File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() ); 357 358 if ( selectedProject.isFile() ) 359 { 360 return selectedProject.equals( project.getFile() ); 361 } 362 else if ( selectedProject.isDirectory() ) 363 { 364 return selectedProject.equals( project.getBasedir() ); 365 } 366 } 367 368 return false; 369 } 370 371 private MavenExecutionResult addExceptionToResult( MavenExecutionResult result, Throwable e ) 372 { 373 if ( !result.getExceptions().contains( e ) ) 374 { 375 result.addException( e ); 376 } 377 378 return result; 379 } 380 381 // //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 382 // 383 // Project collection 384 // 385 // //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 386 387 private List<MavenProject> getProjectsForMavenReactor( MavenSession session ) 388 throws ProjectBuildingException 389 { 390 MavenExecutionRequest request = session.getRequest(); 391 392 request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() ); 393 394 List<MavenProject> projects = new ArrayList<MavenProject>(); 395 396 // We have no POM file. 397 // 398 if ( request.getPom() == null ) 399 { 400 ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) ); 401 MavenProject project = projectBuilder.build( modelSource, request.getProjectBuildingRequest() ) 402 .getProject(); 403 project.setExecutionRoot( true ); 404 projects.add( project ); 405 request.setProjectPresent( false ); 406 return projects; 407 } 408 409 List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() ); 410 collectProjects( projects, files, request ); 411 return projects; 412 } 413 414 private void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request ) 415 throws ProjectBuildingException 416 { 417 ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest(); 418 419 List<ProjectBuildingResult> results = projectBuilder.build( files, request.isRecursive(), 420 projectBuildingRequest ); 421 422 boolean problems = false; 423 424 for ( ProjectBuildingResult result : results ) 425 { 426 projects.add( result.getProject() ); 427 428 if ( !result.getProblems().isEmpty() && logger.isWarnEnabled() ) 429 { 430 logger.warn( "" ); 431 logger.warn( "Some problems were encountered while building the effective model for " 432 + result.getProject().getId() ); 433 434 for ( ModelProblem problem : result.getProblems() ) 435 { 436 String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() ); 437 logger.warn( problem.getMessage() + ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) ); 438 } 439 440 problems = true; 441 } 442 } 443 444 if ( problems ) 445 { 446 logger.warn( "" ); 447 logger.warn( "It is highly recommended to fix these problems" 448 + " because they threaten the stability of your build." ); 449 logger.warn( "" ); 450 logger.warn( "For this reason, future Maven versions might no" 451 + " longer support building such malformed projects." ); 452 logger.warn( "" ); 453 } 454 } 455 456 private void validateProjects( List<MavenProject> projects ) 457 { 458 Map<String, MavenProject> projectsMap = new HashMap<String, MavenProject>(); 459 460 for ( MavenProject p : projects ) 461 { 462 String projectKey = ArtifactUtils.key( p.getGroupId(), p.getArtifactId(), p.getVersion() ); 463 464 projectsMap.put( projectKey, p ); 465 } 466 467 for ( MavenProject project : projects ) 468 { 469 // MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor 470 for ( Plugin plugin : project.getBuildPlugins() ) 471 { 472 if ( plugin.isExtensions() ) 473 { 474 String pluginKey = ArtifactUtils.key( plugin.getGroupId(), plugin.getArtifactId(), 475 plugin.getVersion() ); 476 477 if ( projectsMap.containsKey( pluginKey ) ) 478 { 479 logger.warn( project.getName() + " uses " + plugin.getKey() 480 + " as extensions, which is not possible within the same reactor build. " 481 + "This plugin was pulled from the local repository!" ); 482 } 483 } 484 } 485 } 486 } 487 488}