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    }