001 package org.apache.maven.lifecycle.internal; 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 022 import org.apache.maven.artifact.Artifact; 023 import org.apache.maven.artifact.ArtifactUtils; 024 import org.apache.maven.execution.BuildSuccess; 025 import org.apache.maven.execution.ExecutionEvent; 026 import org.apache.maven.execution.MavenExecutionRequest; 027 import org.apache.maven.execution.MavenSession; 028 import org.apache.maven.lifecycle.LifecycleExecutionException; 029 import org.apache.maven.lifecycle.MavenExecutionPlan; 030 import org.apache.maven.lifecycle.Schedule; 031 import org.apache.maven.project.MavenProject; 032 import org.codehaus.plexus.component.annotations.Component; 033 import org.codehaus.plexus.component.annotations.Requirement; 034 import org.codehaus.plexus.logging.Logger; 035 036 import java.util.ArrayList; 037 import java.util.Collection; 038 import java.util.HashMap; 039 import java.util.HashSet; 040 import java.util.Iterator; 041 import java.util.List; 042 import java.util.Map; 043 import java.util.Properties; 044 import java.util.Set; 045 import java.util.concurrent.Callable; 046 import java.util.concurrent.CompletionService; 047 import java.util.concurrent.ExecutionException; 048 import java.util.concurrent.ExecutorCompletionService; 049 import java.util.concurrent.ExecutorService; 050 import java.util.concurrent.Future; 051 052 /** 053 * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project) 054 * <p/> 055 * NOTE: Weave mode is still experimental. It may be either promoted to first class citizen 056 * at some later point in time, and it may also be removed entirely. Weave mode has much more aggressive 057 * concurrency behaviour than regular threaded mode, and as such is still under test wrt cross platform stability. 058 * <p/> 059 * To remove weave mode from m3, the following should be removed: 060 * ExecutionPlanItem.schedule w/setters and getters 061 * DefaultLifeCycles.getScheduling() and all its use 062 * ReactorArtifactRepository has a reference to isWeave too. 063 * This class and its usage 064 * 065 * @since 3.0 066 * @author Kristian Rosenvold 067 * Builds one or more lifecycles for a full module 068 * <p/> 069 * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. 070 */ 071 @Component( role = LifecycleWeaveBuilder.class ) 072 public class LifecycleWeaveBuilder 073 { 074 075 @Requirement 076 private MojoExecutor mojoExecutor; 077 078 @Requirement 079 private BuilderCommon builderCommon; 080 081 @Requirement 082 private Logger logger; 083 084 @Requirement 085 private ExecutionEventCatapult eventCatapult; 086 087 private Map<MavenProject, MavenExecutionPlan> executionPlans = new HashMap<MavenProject, MavenExecutionPlan>(); 088 089 090 @SuppressWarnings( { "UnusedDeclaration" } ) 091 public LifecycleWeaveBuilder() 092 { 093 } 094 095 public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger, 096 ExecutionEventCatapult eventCatapult ) 097 { 098 this.mojoExecutor = mojoExecutor; 099 this.builderCommon = builderCommon; 100 this.logger = logger; 101 this.eventCatapult = eventCatapult; 102 } 103 104 public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments, 105 MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus ) 106 throws ExecutionException, InterruptedException 107 { 108 ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger(); 109 CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor ); 110 111 try 112 { 113 for ( MavenProject mavenProject : session.getProjects() ) 114 { 115 Artifact mainArtifact = mavenProject.getArtifact(); 116 if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) ) 117 { 118 ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact ); 119 mavenProject.setArtifact( threadLockedArtifact ); 120 } 121 } 122 123 final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>(); 124 final Map<ProjectSegment, Future<MavenExecutionPlan>> plans = 125 new HashMap<ProjectSegment, Future<MavenExecutionPlan>>(); 126 127 for ( TaskSegment taskSegment : taskSegments ) 128 { 129 ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment ); 130 Set<Artifact> projectArtifacts = new HashSet<Artifact>(); 131 for ( ProjectSegment segmentChunk : segmentChunks ) 132 { 133 Artifact artifact = segmentChunk.getProject().getArtifact(); 134 if ( artifact != null ) 135 { 136 projectArtifacts.add( artifact ); 137 } 138 } 139 for ( ProjectSegment projectBuild : segmentChunks ) 140 { 141 plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) ); 142 } 143 144 for ( ProjectSegment projectSegment : plans.keySet() ) 145 { 146 executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() ); 147 148 } 149 for ( ProjectSegment projectBuild : segmentChunks ) 150 { 151 try 152 { 153 final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get(); 154 155 DependencyContext dependencyContext = 156 mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() ); 157 158 final Callable<ProjectSegment> projectBuilder = 159 createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus, 160 executionPlan, projectBuild, dependencyContext, 161 concurrentBuildLogger ); 162 163 futures.add( service.submit( projectBuilder ) ); 164 } 165 catch ( Exception e ) 166 { 167 throw new ExecutionException( e ); 168 } 169 } 170 171 for ( Future<ProjectSegment> buildFuture : futures ) 172 { 173 buildFuture.get(); // At this point, this build *is* finished. 174 // Do not leak threads past here or evil gremlins will get you! 175 } 176 futures.clear(); 177 } 178 } 179 finally 180 { 181 projectBuilds.closeAll(); 182 } 183 logger.info( concurrentBuildLogger.toString() ); 184 } 185 186 private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment, 187 final Set<Artifact> projectArtifacts ) 188 { 189 return new Callable<MavenExecutionPlan>() 190 { 191 public MavenExecutionPlan call() 192 throws Exception 193 { 194 return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(), 195 projectSegment.getTaskSegment(), projectArtifacts ); 196 } 197 }; 198 } 199 200 201 private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext, 202 final MavenSession rootSession, 203 final ReactorBuildStatus reactorBuildStatus, 204 final MavenExecutionPlan executionPlan, 205 final ProjectSegment projectBuild, 206 final DependencyContext dependencyContext, 207 final ConcurrentBuildLogger concurrentBuildLogger ) 208 { 209 return new Callable<ProjectSegment>() 210 { 211 public ProjectSegment call() 212 throws Exception 213 { 214 Iterator<ExecutionPlanItem> planItems = executionPlan.iterator(); 215 ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null; 216 ThreadLockedArtifact threadLockedArtifact = (ThreadLockedArtifact)projectBuild.getProject().getArtifact(); 217 if ( threadLockedArtifact != null ) 218 { 219 threadLockedArtifact.attachToThread(); 220 } 221 long buildStartTime = System.currentTimeMillis(); 222 223 //muxer.associateThreadWithProjectSegment( projectBuild ); 224 225 if ( reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) ) 226 { 227 eventCatapult.fire( ExecutionEvent.Type.ProjectSkipped, projectBuild.getSession(), null ); 228 return null; 229 } 230 231 eventCatapult.fire( ExecutionEvent.Type.ProjectStarted, projectBuild.getSession(), null ); 232 233 Collection<ArtifactLink> dependencyLinks = getUpstreamReactorDependencies( projectBuild ); 234 235 try 236 { 237 PhaseRecorder phaseRecorder = new PhaseRecorder( projectBuild.getProject() ); 238 long totalMojoTime = 0; 239 long mojoStart; 240 while ( current != null && !reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) ) 241 { 242 243 BuildLogItem builtLogItem = 244 concurrentBuildLogger.createBuildLogItem( projectBuild.getProject(), current ); 245 final Schedule schedule = current.getSchedule(); 246 247 mojoStart = System.currentTimeMillis(); 248 buildExecutionPlanItem( current, phaseRecorder, schedule, reactorContext, projectBuild, 249 dependencyContext ); 250 totalMojoTime += ( System.currentTimeMillis() - mojoStart ); 251 252 current.setComplete(); 253 builtLogItem.setComplete(); 254 255 ExecutionPlanItem nextPlanItem = planItems.hasNext() ? planItems.next() : null; 256 if ( nextPlanItem != null && phaseRecorder.isDifferentPhase( nextPlanItem.getMojoExecution() ) ) 257 { 258 259 final Schedule scheduleOfNext = nextPlanItem.getSchedule(); 260 if ( scheduleOfNext == null || !scheduleOfNext.isParallel() ) 261 { 262 waitForAppropriateUpstreamExecutionsToFinish( builtLogItem, nextPlanItem, projectBuild, 263 scheduleOfNext ); 264 } 265 266 for ( ArtifactLink dependencyLink : dependencyLinks ) 267 { 268 dependencyLink.resolveFromUpstream(); 269 } 270 } 271 current = nextPlanItem; 272 } 273 274 final BuildSuccess summary = 275 new BuildSuccess( projectBuild.getProject(), totalMojoTime ); // - waitingTime 276 reactorContext.getResult().addBuildSummary( summary ); 277 eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, projectBuild.getSession(), null ); 278 } 279 catch ( Exception e ) 280 { 281 builderCommon.handleBuildError( reactorContext, rootSession, projectBuild.getProject(), e, 282 buildStartTime ); 283 } 284 finally 285 { 286 if ( current != null ) 287 { 288 executionPlan.forceAllComplete(); 289 } 290 // muxer.setThisModuleComplete( projectBuild ); 291 } 292 return null; 293 } 294 295 }; 296 } 297 298 private void waitForAppropriateUpstreamExecutionsToFinish( BuildLogItem builtLogItem, 299 ExecutionPlanItem nextPlanItem, 300 ProjectSegment projectBuild, Schedule scheduleOfNext ) 301 throws InterruptedException 302 { 303 for ( MavenProject upstreamProject : projectBuild.getImmediateUpstreamProjects() ) 304 { 305 final MavenExecutionPlan upstreamPlan = executionPlans.get( upstreamProject ); 306 final String nextPhase = scheduleOfNext != null && scheduleOfNext.hasUpstreamPhaseDefined() 307 ? scheduleOfNext.getUpstreamPhase() 308 : nextPlanItem.getLifecyclePhase(); 309 final ExecutionPlanItem upstream = upstreamPlan.findLastInPhase( nextPhase ); 310 311 if ( upstream != null ) 312 { 313 long startWait = System.currentTimeMillis(); 314 upstream.waitUntilDone(); 315 builtLogItem.addWait( upstreamProject, upstream, startWait ); 316 } 317 else if ( !upstreamPlan.containsPhase( nextPhase ) ) 318 { 319 // Still a bit of a kludge; if we cannot connect in a sensible way to 320 // the upstream build plan we just revert to waiting for it all to 321 // complete. Real problem is per-mojo phase->lifecycle mapping 322 builtLogItem.addDependency( upstreamProject, "No phase tracking possible " ); 323 upstreamPlan.waitUntilAllDone(); 324 } 325 else 326 { 327 builtLogItem.addDependency( upstreamProject, "No schedule" ); 328 } 329 } 330 } 331 332 private Collection<ArtifactLink> getUpstreamReactorDependencies( ProjectSegment projectBuild ) 333 { 334 Collection<ArtifactLink> result = new ArrayList<ArtifactLink>(); 335 for ( MavenProject upstreamProject : projectBuild.getTransitiveUpstreamProjects() ) 336 { 337 Artifact upStreamArtifact = upstreamProject.getArtifact(); 338 if ( upStreamArtifact != null ) 339 { 340 Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact ); 341 if ( dependencyArtifact != null ) 342 { 343 result.add( new ArtifactLink( dependencyArtifact, upStreamArtifact ) ); 344 } 345 } 346 347 Artifact upStreamTestScopedArtifact = findTestScopedArtifact( upstreamProject ); 348 if ( upStreamTestScopedArtifact != null ) 349 { 350 Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact ); 351 if ( dependencyArtifact != null ) 352 { 353 result.add( new ArtifactLink( dependencyArtifact, upStreamTestScopedArtifact ) ); 354 } 355 } 356 } 357 return result; 358 } 359 360 361 private Artifact findTestScopedArtifact( MavenProject upstreamProject ) 362 { 363 if ( upstreamProject == null ) 364 { 365 return null; 366 } 367 368 List<Artifact> artifactList = upstreamProject.getAttachedArtifacts(); 369 for ( Artifact artifact : artifactList ) 370 { 371 if ( Artifact.SCOPE_TEST.equals( artifact.getScope() ) ) 372 { 373 return artifact; 374 } 375 } 376 return null; 377 } 378 379 private static boolean isThreadLockedAndEmpty(Artifact artifact){ 380 return artifact instanceof ThreadLockedArtifact && !((ThreadLockedArtifact) artifact).hasReal(); 381 } 382 383 private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact ) 384 { 385 if ( upStreamArtifact == null || isThreadLockedAndEmpty(upStreamArtifact)) 386 { 387 return null; 388 } 389 390 String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(), 391 upStreamArtifact.getVersion() ); 392 final Set<Artifact> deps = project.getDependencyArtifacts(); 393 for ( Artifact dep : deps ) 394 { 395 String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() ); 396 if ( key.equals( depKey ) ) 397 { 398 return dep; 399 } 400 } 401 return null; 402 403 } 404 405 private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule, 406 ReactorContext reactorContext, ProjectSegment projectBuild, 407 DependencyContext dependencyContext ) 408 throws LifecycleExecutionException 409 { 410 if ( schedule != null && schedule.isMojoSynchronized() ) 411 { 412 synchronized ( current.getPlugin() ) 413 { 414 buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder ); 415 } 416 } 417 else 418 { 419 buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder ); 420 } 421 } 422 423 424 private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node, 425 ProjectSegment projectBuild, DependencyContext dependencyContext, 426 PhaseRecorder phaseRecorder ) 427 throws LifecycleExecutionException 428 { 429 430 MavenProject currentProject = projectBuild.getProject(); 431 432 long buildStartTime = System.currentTimeMillis(); 433 434 CurrentPhaseForThread.setPhase( node.getLifecyclePhase() ); 435 436 MavenSession sessionForThisModule = projectBuild.getSession(); 437 try 438 { 439 440 if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) ) 441 { 442 return; 443 } 444 445 BuilderCommon.attachToThread( currentProject ); 446 447 mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(), 448 dependencyContext, phaseRecorder ); 449 450 final BuildSuccess summary = 451 new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime ); 452 reactorContext.getResult().addBuildSummary( summary ); 453 } 454 finally 455 { 456 Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() ); 457 } 458 } 459 460 public static boolean isWeaveMode( MavenExecutionRequest request ) 461 { 462 return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) ); 463 } 464 465 public static void setWeaveMode( Properties properties ) 466 { 467 properties.setProperty( "maven3.weaveMode", "true" ); 468 } 469 470 static class ArtifactLink 471 { 472 private final Artifact artifactInThis; 473 474 private final Artifact upstream; 475 476 ArtifactLink( Artifact artifactInThis, Artifact upstream ) 477 { 478 this.artifactInThis = artifactInThis; 479 this.upstream = upstream; 480 } 481 482 public void resolveFromUpstream() 483 { 484 artifactInThis.setFile( upstream.getFile() ); 485 artifactInThis.setRepository( upstream.getRepository() ); 486 artifactInThis.setResolved( true ); // Or maybe upstream.isResolved().... 487 488 } 489 } 490 491 }