001package org.apache.maven; 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.Collections; 027import java.util.Date; 028import java.util.HashSet; 029import java.util.LinkedHashMap; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033 034import org.apache.maven.artifact.ArtifactUtils; 035import org.apache.maven.execution.DefaultMavenExecutionResult; 036import org.apache.maven.execution.ExecutionEvent; 037import org.apache.maven.execution.MavenExecutionRequest; 038import org.apache.maven.execution.MavenExecutionResult; 039import org.apache.maven.execution.MavenSession; 040import org.apache.maven.execution.ProjectDependencyGraph; 041import org.apache.maven.graph.GraphBuilder; 042import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory; 043import org.apache.maven.lifecycle.internal.ExecutionEventCatapult; 044import org.apache.maven.lifecycle.internal.LifecycleStarter; 045import org.apache.maven.model.building.ModelProblem; 046import org.apache.maven.model.building.Result; 047import org.apache.maven.plugin.LegacySupport; 048import org.apache.maven.project.MavenProject; 049import org.apache.maven.project.ProjectBuilder; 050import org.apache.maven.repository.LocalRepositoryNotAccessibleException; 051import org.apache.maven.session.scope.internal.SessionScope; 052import org.codehaus.plexus.PlexusContainer; 053import org.codehaus.plexus.component.annotations.Component; 054import org.codehaus.plexus.component.annotations.Requirement; 055import org.codehaus.plexus.component.repository.exception.ComponentLookupException; 056import org.codehaus.plexus.logging.Logger; 057import org.eclipse.aether.DefaultRepositorySystemSession; 058import org.eclipse.aether.RepositorySystemSession; 059import org.eclipse.aether.repository.WorkspaceReader; 060import org.eclipse.aether.util.repository.ChainedWorkspaceReader; 061 062import com.google.common.collect.Iterables; 063 064/** 065 * @author Jason van Zyl 066 */ 067@Component( role = Maven.class ) 068public class DefaultMaven 069 implements Maven 070{ 071 072 @Requirement 073 private Logger logger; 074 075 @Requirement 076 protected ProjectBuilder projectBuilder; 077 078 @Requirement 079 private LifecycleStarter lifecycleStarter; 080 081 @Requirement 082 protected PlexusContainer container; 083 084 @Requirement 085 private ExecutionEventCatapult eventCatapult; 086 087 @Requirement 088 private LegacySupport legacySupport; 089 090 @Requirement 091 private SessionScope sessionScope; 092 093 @Requirement 094 private DefaultRepositorySystemSessionFactory repositorySessionFactory; 095 096 @Requirement( hint = GraphBuilder.HINT ) 097 private GraphBuilder graphBuilder; 098 099 @Override 100 public MavenExecutionResult execute( MavenExecutionRequest request ) 101 { 102 MavenExecutionResult result; 103 104 try 105 { 106 result = doExecute( request ); 107 } 108 catch ( OutOfMemoryError e ) 109 { 110 result = addExceptionToResult( new DefaultMavenExecutionResult(), e ); 111 } 112 catch ( RuntimeException e ) 113 { 114 //TODO Hack to make the cycle detection the same for the new graph builder 115 if ( e.getCause() instanceof ProjectCycleException ) 116 { 117 result = addExceptionToResult( new DefaultMavenExecutionResult(), e.getCause() ); 118 } 119 else 120 { 121 result = addExceptionToResult( new DefaultMavenExecutionResult(), 122 new InternalErrorException( "Internal error: " + e, e ) ); 123 } 124 } 125 finally 126 { 127 legacySupport.setSession( null ); 128 } 129 130 return result; 131 } 132 133 // 134 // 1) Setup initial properties. 135 // 136 // 2) Validate local repository directory is accessible. 137 // 138 // 3) Create RepositorySystemSession. 139 // 140 // 4) Create MavenSession. 141 // 142 // 5) Execute AbstractLifecycleParticipant.afterSessionStart(session) 143 // 144 // 6) Get reactor projects looking for general POM errors 145 // 146 // 7) Create ProjectDependencyGraph using trimming which takes into account --projects and reactor mode. 147 // This ensures that the projects passed into the ReactorReader are only those specified. 148 // 149 // 8) Create ReactorReader with the getProjectMap( projects ). NOTE that getProjectMap(projects) is the code that 150 // checks for duplicate projects definitions in the build. Ideally this type of duplicate checking should be 151 // part of getting the reactor projects in 6). The duplicate checking is conflated with getProjectMap(projects). 152 // 153 // 9) Execute AbstractLifecycleParticipant.afterProjectsRead(session) 154 // 155 // 10) Create ProjectDependencyGraph without trimming (as trimming was done in 7). A new topological sort is 156 // required after the execution of 9) as the AbstractLifecycleParticipants are free to mutate the MavenProject 157 // instances, which may change dependencies which can, in turn, affect the build order. 158 // 159 // 11) Execute LifecycleStarter.start() 160 // 161 @SuppressWarnings( "checkstyle:methodlength" ) 162 private MavenExecutionResult doExecute( MavenExecutionRequest request ) 163 { 164 request.setStartTime( new Date() ); 165 166 MavenExecutionResult result = new DefaultMavenExecutionResult(); 167 168 try 169 { 170 validateLocalRepository( request ); 171 } 172 catch ( LocalRepositoryNotAccessibleException e ) 173 { 174 return addExceptionToResult( result, e ); 175 } 176 177 // 178 // We enter the session scope right after the MavenSession creation and before any of the 179 // AbstractLifecycleParticipant lookups 180 // so that @SessionScoped components can be @Injected into AbstractLifecycleParticipants. 181 // 182 sessionScope.enter(); 183 try 184 { 185 DefaultRepositorySystemSession repoSession = 186 (DefaultRepositorySystemSession) newRepositorySession( request ); 187 MavenSession session = new MavenSession( container, repoSession, request, result ); 188 189 sessionScope.seed( MavenSession.class, session ); 190 191 legacySupport.setSession( session ); 192 193 return doExecute( request, session, result, repoSession ); 194 } 195 finally 196 { 197 sessionScope.exit(); 198 } 199 } 200 201 private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSession session, 202 MavenExecutionResult result, DefaultRepositorySystemSession repoSession ) 203 { 204 try 205 { 206 for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( Collections 207 .<MavenProject>emptyList() ) ) 208 { 209 listener.afterSessionStart( session ); 210 } 211 } 212 catch ( MavenExecutionException e ) 213 { 214 return addExceptionToResult( result, e ); 215 } 216 217 eventCatapult.fire( ExecutionEvent.Type.ProjectDiscoveryStarted, session, null ); 218 219 Result<? extends ProjectDependencyGraph> graphResult = buildGraph( session, result ); 220 221 if ( graphResult.hasErrors() ) 222 { 223 return addExceptionToResult( result, 224 Iterables.toArray( graphResult.getProblems(), ModelProblem.class )[0] 225 .getException() ); 226 } 227 228 try 229 { 230 session.setProjectMap( getProjectMap( session.getProjects() ) ); 231 } 232 catch ( DuplicateProjectException e ) 233 { 234 return addExceptionToResult( result, e ); 235 } 236 237 WorkspaceReader reactorWorkspace; 238 try 239 { 240 reactorWorkspace = container.lookup( WorkspaceReader.class, ReactorReader.HINT ); 241 } 242 catch ( ComponentLookupException e ) 243 { 244 return addExceptionToResult( result, e ); 245 } 246 247 // 248 // Desired order of precedence for local artifact repositories 249 // 250 // Reactor 251 // Workspace 252 // User Local Repository 253 // 254 repoSession.setWorkspaceReader( ChainedWorkspaceReader.newInstance( reactorWorkspace, 255 repoSession.getWorkspaceReader() ) ); 256 257 repoSession.setReadOnly(); 258 259 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); 260 try 261 { 262 for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( session.getProjects() ) ) 263 { 264 Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() ); 265 266 listener.afterProjectsRead( session ); 267 } 268 } 269 catch ( MavenExecutionException e ) 270 { 271 return addExceptionToResult( result, e ); 272 } 273 finally 274 { 275 Thread.currentThread().setContextClassLoader( originalClassLoader ); 276 } 277 278 // 279 // The projects need to be topologically after the participants have run their afterProjectsRead(session) 280 // because the participant is free to change the dependencies of a project which can potentially change the 281 // topological order of the projects, and therefore can potentially change the build order. 282 // 283 // Note that participants may affect the topological order of the projects but it is 284 // not expected that a participant will add or remove projects from the session. 285 // 286 287 graphResult = buildGraph( session, result ); 288 289 if ( graphResult.hasErrors() ) 290 { 291 return addExceptionToResult( result, 292 Iterables.toArray( graphResult.getProblems(), ModelProblem.class )[0] 293 .getException() ); 294 } 295 296 try 297 { 298 if ( result.hasExceptions() ) 299 { 300 return result; 301 } 302 303 result.setTopologicallySortedProjects( session.getProjects() ); 304 305 result.setProject( session.getTopLevelProject() ); 306 307 lifecycleStarter.execute( session ); 308 309 validateActivatedProfiles( session.getProjects(), request.getActiveProfiles() ); 310 311 if ( session.getResult().hasExceptions() ) 312 { 313 return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) ); 314 } 315 } 316 finally 317 { 318 try 319 { 320 afterSessionEnd( session.getProjects(), session ); 321 } 322 catch ( MavenExecutionException e ) 323 { 324 return addExceptionToResult( result, e ); 325 } 326 } 327 328 return result; 329 } 330 331 private void afterSessionEnd( Collection<MavenProject> projects, MavenSession session ) 332 throws MavenExecutionException 333 { 334 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); 335 try 336 { 337 for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( projects ) ) 338 { 339 Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() ); 340 341 listener.afterSessionEnd( session ); 342 } 343 } 344 finally 345 { 346 Thread.currentThread().setContextClassLoader( originalClassLoader ); 347 } 348 } 349 350 public RepositorySystemSession newRepositorySession( MavenExecutionRequest request ) 351 { 352 return repositorySessionFactory.newRepositorySession( request ); 353 } 354 355 private void validateLocalRepository( MavenExecutionRequest request ) 356 throws LocalRepositoryNotAccessibleException 357 { 358 File localRepoDir = request.getLocalRepositoryPath(); 359 360 logger.debug( "Using local repository at " + localRepoDir ); 361 362 localRepoDir.mkdirs(); 363 364 if ( !localRepoDir.isDirectory() ) 365 { 366 throw new LocalRepositoryNotAccessibleException( "Could not create local repository at " + localRepoDir ); 367 } 368 } 369 370 private Collection<AbstractMavenLifecycleParticipant> getLifecycleParticipants( Collection<MavenProject> projects ) 371 { 372 Collection<AbstractMavenLifecycleParticipant> lifecycleListeners = 373 new LinkedHashSet<>(); 374 375 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); 376 try 377 { 378 try 379 { 380 lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) ); 381 } 382 catch ( ComponentLookupException e ) 383 { 384 // this is just silly, lookupList should return an empty list! 385 logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() ); 386 } 387 388 Collection<ClassLoader> scannedRealms = new HashSet<>(); 389 390 for ( MavenProject project : projects ) 391 { 392 ClassLoader projectRealm = project.getClassRealm(); 393 394 if ( projectRealm != null && scannedRealms.add( projectRealm ) ) 395 { 396 Thread.currentThread().setContextClassLoader( projectRealm ); 397 398 try 399 { 400 lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) ); 401 } 402 catch ( ComponentLookupException e ) 403 { 404 // this is just silly, lookupList should return an empty list! 405 logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() ); 406 } 407 } 408 } 409 } 410 finally 411 { 412 Thread.currentThread().setContextClassLoader( originalClassLoader ); 413 } 414 415 return lifecycleListeners; 416 } 417 418 private MavenExecutionResult addExceptionToResult( MavenExecutionResult result, Throwable e ) 419 { 420 if ( !result.getExceptions().contains( e ) ) 421 { 422 result.addException( e ); 423 } 424 425 return result; 426 } 427 428 private void validateActivatedProfiles( List<MavenProject> projects, List<String> activeProfileIds ) 429 { 430 Collection<String> notActivatedProfileIds = new LinkedHashSet<>( activeProfileIds ); 431 432 for ( MavenProject project : projects ) 433 { 434 for ( List<String> profileIds : project.getInjectedProfileIds().values() ) 435 { 436 notActivatedProfileIds.removeAll( profileIds ); 437 } 438 } 439 440 for ( String notActivatedProfileId : notActivatedProfileIds ) 441 { 442 logger.warn( "The requested profile \"" + notActivatedProfileId 443 + "\" could not be activated because it does not exist." ); 444 } 445 } 446 447 private Map<String, MavenProject> getProjectMap( Collection<MavenProject> projects ) 448 throws DuplicateProjectException 449 { 450 Map<String, MavenProject> index = new LinkedHashMap<>(); 451 Map<String, List<File>> collisions = new LinkedHashMap<>(); 452 453 for ( MavenProject project : projects ) 454 { 455 String projectId = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() ); 456 457 MavenProject collision = index.get( projectId ); 458 459 if ( collision == null ) 460 { 461 index.put( projectId, project ); 462 } 463 else 464 { 465 List<File> pomFiles = collisions.get( projectId ); 466 467 if ( pomFiles == null ) 468 { 469 pomFiles = new ArrayList<>( Arrays.asList( collision.getFile(), project.getFile() ) ); 470 collisions.put( projectId, pomFiles ); 471 } 472 else 473 { 474 pomFiles.add( project.getFile() ); 475 } 476 } 477 } 478 479 if ( !collisions.isEmpty() ) 480 { 481 throw new DuplicateProjectException( "Two or more projects in the reactor" 482 + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>" 483 + " is unique for each project: " + collisions, collisions ); 484 } 485 486 return index; 487 } 488 489 private Result<? extends ProjectDependencyGraph> buildGraph( MavenSession session, MavenExecutionResult result ) 490 { 491 Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build( session ); 492 for ( ModelProblem problem : graphResult.getProblems() ) 493 { 494 if ( problem.getSeverity() == ModelProblem.Severity.WARNING ) 495 { 496 logger.warn( problem.toString() ); 497 } 498 else 499 { 500 logger.error( problem.toString() ); 501 } 502 } 503 504 if ( !graphResult.hasErrors() ) 505 { 506 ProjectDependencyGraph projectDependencyGraph = graphResult.get(); 507 session.setProjects( projectDependencyGraph.getSortedProjects() ); 508 session.setAllProjects( projectDependencyGraph.getSortedProjects() ); 509 session.setProjectDependencyGraph( projectDependencyGraph ); 510 } 511 512 return graphResult; 513 } 514 515 @Deprecated 516 // 5 January 2014 517 protected Logger getLogger() 518 { 519 return logger; 520 } 521}