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    }