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 public LifecycleWeaveBuilder() 091 { 092 } 093 094 public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger, 095 ExecutionEventCatapult eventCatapult ) 096 { 097 this.mojoExecutor = mojoExecutor; 098 this.builderCommon = builderCommon; 099 this.logger = logger; 100 this.eventCatapult = eventCatapult; 101 } 102 103 public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments, 104 MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus ) 105 throws ExecutionException, InterruptedException 106 { 107 ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger(); 108 CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor ); 109 110 try 111 { 112 for ( MavenProject mavenProject : session.getProjects() ) 113 { 114 Artifact mainArtifact = mavenProject.getArtifact(); 115 if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) ) 116 { 117 ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact ); 118 mavenProject.setArtifact( threadLockedArtifact ); 119 } 120 } 121 122 final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>(); 123 final Map<ProjectSegment, Future<MavenExecutionPlan>> plans = 124 new HashMap<ProjectSegment, Future<MavenExecutionPlan>>(); 125 126 for ( TaskSegment taskSegment : taskSegments ) 127 { 128 ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment ); 129 Set<Artifact> projectArtifacts = new HashSet<Artifact>(); 130 for ( ProjectSegment segmentChunk : segmentChunks ) 131 { 132 Artifact artifact = segmentChunk.getProject().getArtifact(); 133 if ( artifact != null ) 134 { 135 projectArtifacts.add( artifact ); 136 } 137 } 138 for ( ProjectSegment projectBuild : segmentChunks ) 139 { 140 plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) ); 141 } 142 143 for ( ProjectSegment projectSegment : plans.keySet() ) 144 { 145 executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() ); 146 147 } 148 for ( ProjectSegment projectBuild : segmentChunks ) 149 { 150 try 151 { 152 final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get(); 153 154 DependencyContext dependencyContext = 155 mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() ); 156 157 final Callable<ProjectSegment> projectBuilder = 158 createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus, 159 executionPlan, projectBuild, dependencyContext, 160 concurrentBuildLogger ); 161 162 futures.add( service.submit( projectBuilder ) ); 163 } 164 catch ( Exception e ) 165 { 166 throw new ExecutionException( e ); 167 } 168 } 169 170 for ( Future<ProjectSegment> buildFuture : futures ) 171 { 172 buildFuture.get(); // At this point, this build *is* finished. 173 // Do not leak threads past here or evil gremlins will get you! 174 } 175 futures.clear(); 176 } 177 } 178 finally 179 { 180 projectBuilds.closeAll(); 181 } 182 logger.info( concurrentBuildLogger.toString() ); 183 } 184 185 private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment, 186 final Set<Artifact> projectArtifacts ) 187 { 188 return new Callable<MavenExecutionPlan>() 189 { 190 public MavenExecutionPlan call() 191 throws Exception 192 { 193 return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(), 194 projectSegment.getTaskSegment(), projectArtifacts ); 195 } 196 }; 197 } 198 199 200 private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext, 201 final MavenSession rootSession, 202 final ReactorBuildStatus reactorBuildStatus, 203 final MavenExecutionPlan executionPlan, 204 final ProjectSegment projectBuild, 205 final DependencyContext dependencyContext, 206 final ConcurrentBuildLogger concurrentBuildLogger ) 207 { 208 return new Callable<ProjectSegment>() 209 { 210 public ProjectSegment call() 211 throws Exception 212 { 213 Iterator<ExecutionPlanItem> planItems = executionPlan.iterator(); 214 ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null; 215 ThreadLockedArtifact threadLockedArtifact = 216 (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.getSession(), 282 projectBuild.getProject(), e, 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 { 381 return artifact instanceof ThreadLockedArtifact && !( (ThreadLockedArtifact) artifact ).hasReal(); 382 } 383 384 private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact ) 385 { 386 if ( upStreamArtifact == null || isThreadLockedAndEmpty( upStreamArtifact ) ) 387 { 388 return null; 389 } 390 391 String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(), 392 upStreamArtifact.getVersion() ); 393 final Set<Artifact> deps = project.getDependencyArtifacts(); 394 for ( Artifact dep : deps ) 395 { 396 String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() ); 397 if ( key.equals( depKey ) ) 398 { 399 return dep; 400 } 401 } 402 return null; 403 404 } 405 406 private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule, 407 ReactorContext reactorContext, ProjectSegment projectBuild, 408 DependencyContext dependencyContext ) 409 throws LifecycleExecutionException 410 { 411 if ( schedule != null && schedule.isMojoSynchronized() ) 412 { 413 synchronized ( current.getPlugin() ) 414 { 415 buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder ); 416 } 417 } 418 else 419 { 420 buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder ); 421 } 422 } 423 424 425 private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node, 426 ProjectSegment projectBuild, DependencyContext dependencyContext, 427 PhaseRecorder phaseRecorder ) 428 throws LifecycleExecutionException 429 { 430 431 MavenProject currentProject = projectBuild.getProject(); 432 433 long buildStartTime = System.currentTimeMillis(); 434 435 CurrentPhaseForThread.setPhase( node.getLifecyclePhase() ); 436 437 MavenSession sessionForThisModule = projectBuild.getSession(); 438 try 439 { 440 441 if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) ) 442 { 443 return; 444 } 445 446 BuilderCommon.attachToThread( currentProject ); 447 448 mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(), 449 dependencyContext, phaseRecorder ); 450 451 final BuildSuccess summary = 452 new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime ); 453 reactorContext.getResult().addBuildSummary( summary ); 454 } 455 finally 456 { 457 Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() ); 458 } 459 } 460 461 public static boolean isWeaveMode( MavenExecutionRequest request ) 462 { 463 return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) ); 464 } 465 466 public static void setWeaveMode( Properties properties ) 467 { 468 properties.setProperty( "maven3.weaveMode", "true" ); 469 } 470 471 static class ArtifactLink 472 { 473 private final Artifact artifactInThis; 474 475 private final Artifact upstream; 476 477 ArtifactLink( Artifact artifactInThis, Artifact upstream ) 478 { 479 this.artifactInThis = artifactInThis; 480 this.upstream = upstream; 481 } 482 483 public void resolveFromUpstream() 484 { 485 artifactInThis.setFile( upstream.getFile() ); 486 artifactInThis.setRepository( upstream.getRepository() ); 487 artifactInThis.setResolved( true ); // Or maybe upstream.isResolved().... 488 } 489 } 490 }