From 09961c3150b1a9ddaa88760d3a91f86542e5827d Mon Sep 17 00:00:00 2001 From: Jason van Zyl Date: Wed, 5 Feb 2014 22:43:04 -0500 Subject: [PATCH] MNG-5575: Separate distinct build strategies into their own new Builder implementation. The interface is not ideal yet, but gives us a cleaner separation of implementations and gives us a path forward. --- MavenLifecyle.md | 45 ++ .../maven/DefaultProjectDependencyGraph.java | 10 +- .../DefaultMavenExecutionRequest.java | 49 +- .../DefaultMavenExecutionResult.java | 8 +- .../execution/MavenExecutionRequest.java | 97 +++- .../maven/execution/MavenExecutionResult.java | 1 - .../apache/maven/execution/MavenSession.java | 1 + .../maven/execution/ReactorManager.java | 5 - .../maven/lifecycle/DefaultSchedules.java | 2 +- .../internal/BuildListCalculator.java | 1 + .../internal/BuildThreadFactory.java | 20 + ...faultLifecycleExecutionPlanCalculator.java | 1 + .../internal/LifecycleDebugLogger.java | 1 + .../internal/LifecycleModuleBuilder.java | 7 +- .../lifecycle/internal/LifecycleStarter.java | 114 +--- .../lifecycle/internal/ProjectBuildList.java | 12 + .../lifecycle/internal/ProjectIndex.java | 1 + .../internal/ReactorBuildStatus.java | 1 + .../lifecycle/internal/builder/Builder.java | 28 + .../internal/builder/BuilderCommon.java | 195 +++++++ .../builder/BuilderNotFoundException.java | 9 + .../ConcurrencyDependencyGraph.java | 153 +++++ .../multithreaded/MultiThreadedBuilder.java | 193 +++++++ .../singlethreaded/SingleThreadedBuilder.java | 46 ++ .../internal/builder/weave/BuildLogItem.java | 221 ++++++++ .../builder/weave/ConcurrentBuildLogger.java | 124 +++++ .../builder/weave/CurrentPhaseForThread.java | 44 ++ .../builder/weave/ThreadLockedArtifact.java | 320 +++++++++++ .../internal/builder/weave/WeaveBuilder.java | 521 ++++++++++++++++++ .../lifecycle/MavenExecutionPlanTest.java | 1 + .../lifecycle/internal/BuilderCommonTest.java | 2 + .../ConcurrencyDependencyGraphTest.java | 1 + .../internal/ConcurrentBuildLoggerTest.java | 7 +- .../internal/ExecutionPlanItemTest.java | 1 + .../internal/LifecycleWeaveBuilderTest.java | 11 +- .../lifecycle/internal/PhaseRecorderTest.java | 1 + .../stub/ProjectDependencyGraphStub.java | 1 + .../java/org/apache/maven/cli/CLIManager.java | 3 + .../java/org/apache/maven/cli/MavenCli.java | 53 +- .../maven/cli/event/ExecutionEventLogger.java | 2 +- 40 files changed, 2145 insertions(+), 168 deletions(-) create mode 100644 MavenLifecyle.md create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildThreadFactory.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/Builder.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderNotFoundException.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ConcurrencyDependencyGraph.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/singlethreaded/SingleThreadedBuilder.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/BuildLogItem.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ConcurrentBuildLogger.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/CurrentPhaseForThread.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ThreadLockedArtifact.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/WeaveBuilder.java diff --git a/MavenLifecyle.md b/MavenLifecyle.md new file mode 100644 index 0000000000..1007dea576 --- /dev/null +++ b/MavenLifecyle.md @@ -0,0 +1,45 @@ +Maven (DefaultMaven) : doExecute + - add a build timestamp to the request properties + - validateLocalRepository: validate local repository (check to make sure it exists and is writable) + - newRepositorySession: create the RepositorySystemSession + - AbstractLifecycleParticipant.afterSessionStart + - event: ExecutionEvent.Type.ProjectDiscoveryStarted + - set the RepositorySystemSession in the ProjectBuildingRequest + - getProjectsForMavenReactor + - this triggers the reading of all the POMs: (NOTE: this could be optimized looking at the POMs if they don't change) + - create the ReactorReader + - set RepositorySystemSession.setWorkspaceReader() + - make the RepositorySystemSession readonly + ? Can we remove all the @Deprecated methods from MavenSession (4 years, 7 months) + ? Can we make the MavenSession read-only, do we need a way to share state better + - AbstractLifecycleParticipant.afterProjectsRead() + - lifecycleStarter.execute(session) + - validateProfiles (NOTE: why does this happen at the end?) + +# Things to document +- How the ClassRealms are setup at each stage +- Having meaningful visuals for the lifecycle and stages +- explain forked executions +- remove aggregators, and what are they exactly + +# Questions + +? forked executions +? aggregators +? All the different resolvers: project, plugin, lifecycle +? Turn if all into JSR330 + +# Things to refactor + +# Isolate project dependency downloads from build execution +- project dependencies are resolved from the mojo executor +- separate dependency resolution from build execution, right now they are intertwined + - if separated then a calculation can be made for the whole set of dependencies + - make sure dependency resolution works before the build executes + - not sure there would be much of a speed impact if one assumes the best speeds will happen when everything is downloaded and the + conflation of these modes and the complexity it creates is not worth it + +- turn all to JSR330 +- use LifecycleModuleBuilder consistently instead of BuilderCommon +- the MavenExecution is calculated in each of the builders instead of once, the implication of this is that project dependency resolution will happen in parallel which means the local repository needs to be safe, and resolution in general. +- the weave builder uses BuilderCommon directly, should be used the same way the other builders work diff --git a/maven-core/src/main/java/org/apache/maven/DefaultProjectDependencyGraph.java b/maven-core/src/main/java/org/apache/maven/DefaultProjectDependencyGraph.java index 67ed3409e6..adf85da7ac 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultProjectDependencyGraph.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultProjectDependencyGraph.java @@ -113,13 +113,15 @@ private void getUpstreamProjects( String projectId, Collection projectId private List getProjects( Collection projectIds ) { - List projects = new ArrayList(); + List projects = new ArrayList( projectIds.size() ); - for ( MavenProject p : sorter.getSortedProjects() ) + for ( String projectId : projectIds ) { - if ( projectIds.contains( ProjectSorter.getId( p ) ) ) + MavenProject project = sorter.getProjectMap().get( projectId ); + + if ( project != null ) { - projects.add( p ); + projects.add( project ); } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java index 9919a1e2be..11329d0f51 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java @@ -101,7 +101,7 @@ public class DefaultMavenExecutionRequest private String reactorFailureBehavior = REACTOR_FAIL_FAST; private List selectedProjects; - + private List excludedProjects; private String resumeFrom; @@ -134,13 +134,13 @@ public class DefaultMavenExecutionRequest private ExecutionListener executionListener; - private String threadCount; + private int degreeOfConcurrency = 1; - private boolean perCoreThreadCount; + private String builderId = "singlethreaded"; /** * Suppress SNAPSHOT updates. - * + * * @issue MNG-2681 */ private boolean noSnapshotUpdates; @@ -169,8 +169,7 @@ public static MavenExecutionRequest copy( MavenExecutionRequest original ) copy.setUserSettingsFile( original.getUserSettingsFile() ); copy.setGlobalSettingsFile( original.getGlobalSettingsFile() ); copy.setUserToolchainsFile( original.getUserToolchainsFile() ); - copy.setBaseDirectory( ( original.getBaseDirectory() != null ) - ? new File( original.getBaseDirectory() ) : null ); + copy.setBaseDirectory( ( original.getBaseDirectory() != null ) ? new File( original.getBaseDirectory() ) : null ); copy.setGoals( original.getGoals() ); copy.setRecursive( original.isRecursive() ); copy.setPom( original.getPom() ); @@ -260,7 +259,7 @@ public List getSelectedProjects() return selectedProjects; } - + public List getExcludedProjects() { if ( excludedProjects == null ) @@ -981,7 +980,7 @@ public List getPluginArtifactRepositories() return pluginArtifactRepositories; } - //TODO: this does not belong here. + // TODO: this does not belong here. public ProjectBuildingRequest getProjectBuildingRequest() { if ( projectBuildingRequest == null ) @@ -1046,29 +1045,14 @@ public MavenExecutionRequest setExecutionListener( ExecutionListener executionLi return this; } - public String getThreadCount() + public void setDegreeOfConcurrency( final int degreeOfConcurrency ) { - return threadCount; + this.degreeOfConcurrency = degreeOfConcurrency; } - public void setThreadCount( String threadCount ) + public int getDegreeOfConcurrency() { - this.threadCount = threadCount; - } - - public boolean isThreadConfigurationPresent() - { - return getThreadCount() != null; - } - - public boolean isPerCoreThreadCount() - { - return perCoreThreadCount; - } - - public void setPerCoreThreadCount( boolean perCoreThreadCount ) - { - this.perCoreThreadCount = perCoreThreadCount; + return degreeOfConcurrency; } public WorkspaceReader getWorkspaceReader() @@ -1114,4 +1098,15 @@ public MavenExecutionRequest setUseLegacyLocalRepository( boolean useSimpleLocal this.useSimpleLocalRepositoryManager = useSimpleLocalRepositoryManager; return this; } + + public MavenExecutionRequest setBuilderId( String builderId ) + { + this.builderId = builderId; + return this; + } + + public String getBuilderId() + { + return builderId; + } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java index cb22b3872c..115cd7307f 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java @@ -28,6 +28,8 @@ import org.apache.maven.project.DependencyResolutionResult; import org.apache.maven.project.MavenProject; +import com.google.common.collect.Maps; + /** @author Jason van Zyl */ public class DefaultMavenExecutionResult implements MavenExecutionResult @@ -40,7 +42,7 @@ public class DefaultMavenExecutionResult private List exceptions = new CopyOnWriteArrayList(); - private Map buildSummaries; + private Map buildSummaries = Maps.newIdentityHashMap(); public MavenExecutionResult setProject( MavenProject project ) { @@ -63,7 +65,8 @@ public MavenExecutionResult setTopologicallySortedProjects( List t public List getTopologicallySortedProjects() { - return null == topologicallySortedProjects ? Collections. emptyList() : topologicallySortedProjects; + return null == topologicallySortedProjects ? Collections. emptyList() + : topologicallySortedProjects; } public DependencyResolutionResult getDependencyResolutionResult() @@ -108,5 +111,4 @@ public void addBuildSummary( BuildSummary summary ) } buildSummaries.put( summary.getProject(), summary ); } - } diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java index 70b77e1481..121e51acdf 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java @@ -91,14 +91,17 @@ public interface MavenExecutionRequest // Base directory MavenExecutionRequest setBaseDirectory( File basedir ); + String getBaseDirectory(); // Timing (remove this) MavenExecutionRequest setStartTime( Date start ); + Date getStartTime(); // Goals MavenExecutionRequest setGoals( List goals ); + List getGoals(); // Properties @@ -106,7 +109,7 @@ public interface MavenExecutionRequest /** * Sets the system properties to use for interpolation and profile activation. The system properties are collected * from the runtime environment like {@link System#getProperties()} and environment variables. - * + * * @param systemProperties The system properties, may be {@code null}. * @return This request, never {@code null}. */ @@ -115,7 +118,7 @@ public interface MavenExecutionRequest /** * Gets the system properties to use for interpolation and profile activation. The system properties are collected * from the runtime environment like {@link System#getProperties()} and environment variables. - * + * * @return The system properties, never {@code null}. */ Properties getSystemProperties(); @@ -124,7 +127,7 @@ public interface MavenExecutionRequest * Sets the user properties to use for interpolation and profile activation. The user properties have been * configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command * line. - * + * * @param userProperties The user properties, may be {@code null}. * @return This request, never {@code null}. */ @@ -134,165 +137,219 @@ public interface MavenExecutionRequest * Gets the user properties to use for interpolation and profile activation. The user properties have been * configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command * line. - * + * * @return The user properties, never {@code null}. */ Properties getUserProperties(); // Reactor MavenExecutionRequest setReactorFailureBehavior( String failureBehavior ); + String getReactorFailureBehavior(); MavenExecutionRequest setSelectedProjects( List projects ); + List getSelectedProjects(); - + /** - * * @param projects the projects to exclude * @return this MavenExecutionRequest * @since 3.2 */ MavenExecutionRequest setExcludedProjects( List projects ); + /** - * * @return the excluded projects, never {@code null} * @since 3.2 */ List getExcludedProjects(); MavenExecutionRequest setResumeFrom( String project ); + String getResumeFrom(); MavenExecutionRequest setMakeBehavior( String makeBehavior ); + String getMakeBehavior(); - void setThreadCount( String threadCount ); - String getThreadCount(); - boolean isThreadConfigurationPresent(); - void setPerCoreThreadCount( boolean perCoreThreadCount ); - boolean isPerCoreThreadCount(); + /** + * Set's the parallel degree of concurrency used by the build. + * + * @param degreeOfConcurrency + */ + public void setDegreeOfConcurrency( final int degreeOfConcurrency ); + + /** + * @return the degree of concurrency for the build. + */ + public int getDegreeOfConcurrency(); // Recursive (really to just process the top-level POM) MavenExecutionRequest setRecursive( boolean recursive ); + boolean isRecursive(); MavenExecutionRequest setPom( File pom ); + File getPom(); // Errors MavenExecutionRequest setShowErrors( boolean showErrors ); + boolean isShowErrors(); // Transfer listeners MavenExecutionRequest setTransferListener( TransferListener transferListener ); + TransferListener getTransferListener(); // Logging MavenExecutionRequest setLoggingLevel( int loggingLevel ); + int getLoggingLevel(); // Update snapshots MavenExecutionRequest setUpdateSnapshots( boolean updateSnapshots ); + boolean isUpdateSnapshots(); MavenExecutionRequest setNoSnapshotUpdates( boolean noSnapshotUpdates ); + boolean isNoSnapshotUpdates(); // Checksum policy MavenExecutionRequest setGlobalChecksumPolicy( String globalChecksumPolicy ); + String getGlobalChecksumPolicy(); // Local repository MavenExecutionRequest setLocalRepositoryPath( String localRepository ); + MavenExecutionRequest setLocalRepositoryPath( File localRepository ); + File getLocalRepositoryPath(); + MavenExecutionRequest setLocalRepository( ArtifactRepository repository ); + ArtifactRepository getLocalRepository(); // Interactive MavenExecutionRequest setInteractiveMode( boolean interactive ); + boolean isInteractiveMode(); // Offline MavenExecutionRequest setOffline( boolean offline ); + boolean isOffline(); boolean isCacheTransferError(); + MavenExecutionRequest setCacheTransferError( boolean cacheTransferError ); boolean isCacheNotFound(); + MavenExecutionRequest setCacheNotFound( boolean cacheNotFound ); // Profiles List getProfiles(); + MavenExecutionRequest addProfile( Profile profile ); + MavenExecutionRequest setProfiles( List profiles ); + MavenExecutionRequest addActiveProfile( String profile ); + MavenExecutionRequest addActiveProfiles( List profiles ); + MavenExecutionRequest setActiveProfiles( List profiles ); + List getActiveProfiles(); + MavenExecutionRequest addInactiveProfile( String profile ); + MavenExecutionRequest addInactiveProfiles( List profiles ); + MavenExecutionRequest setInactiveProfiles( List profiles ); + List getInactiveProfiles(); // Proxies List getProxies(); + MavenExecutionRequest setProxies( List proxies ); + MavenExecutionRequest addProxy( Proxy proxy ); // Servers List getServers(); + MavenExecutionRequest setServers( List servers ); + MavenExecutionRequest addServer( Server server ); // Mirrors List getMirrors(); + MavenExecutionRequest setMirrors( List mirrors ); + MavenExecutionRequest addMirror( Mirror mirror ); // Plugin groups List getPluginGroups(); + MavenExecutionRequest setPluginGroups( List pluginGroups ); + MavenExecutionRequest addPluginGroup( String pluginGroup ); + MavenExecutionRequest addPluginGroups( List pluginGroups ); boolean isProjectPresent(); + MavenExecutionRequest setProjectPresent( boolean isProjectPresent ); File getUserSettingsFile(); + MavenExecutionRequest setUserSettingsFile( File userSettingsFile ); File getGlobalSettingsFile(); + MavenExecutionRequest setGlobalSettingsFile( File globalSettingsFile ); MavenExecutionRequest addRemoteRepository( ArtifactRepository repository ); + MavenExecutionRequest addPluginArtifactRepository( ArtifactRepository repository ); /** * Set a new list of remote repositories to use the execution request. This is necessary if you perform * transformations on the remote repositories being used. For example if you replace existing repositories with * mirrors then it's easier to just replace the whole list with a new list of transformed repositories. - * + * * @param repositories * @return This request, never {@code null}. */ MavenExecutionRequest setRemoteRepositories( List repositories ); + List getRemoteRepositories(); MavenExecutionRequest setPluginArtifactRepositories( List repositories ); + List getPluginArtifactRepositories(); MavenExecutionRequest setRepositoryCache( RepositoryCache repositoryCache ); + RepositoryCache getRepositoryCache(); WorkspaceReader getWorkspaceReader(); + MavenExecutionRequest setWorkspaceReader( WorkspaceReader workspaceReader ); File getUserToolchainsFile(); + MavenExecutionRequest setUserToolchainsFile( File userToolchainsFile ); ExecutionListener getExecutionListener(); + MavenExecutionRequest setExecutionListener( ExecutionListener executionListener ); ProjectBuildingRequest getProjectBuildingRequest(); @@ -307,4 +364,18 @@ public interface MavenExecutionRequest */ MavenExecutionRequest setUseLegacyLocalRepository( boolean useLegacyLocalRepository ); + /** + * Controls the {@link Builder} used by Maven by specification of the builder's id. + * + * @since 3.2.0 + */ + MavenExecutionRequest setBuilderId( String builderId ); + + /** + * Controls the {@link Builder} used by Maven by specification of the builders id. + * + * @since 3.2.0 + */ + String getBuilderId(); + } diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java index dd0466567a..8adc7ac2d7 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java @@ -67,5 +67,4 @@ public interface MavenExecutionResult * @param summary The build summary to add, must not be {@code null}. */ void addBuildSummary( BuildSummary summary ); - } diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java b/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java index 214aed8261..93a1c7af9e 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java @@ -254,6 +254,7 @@ public String getExecutionRootDirectory() return request.getBaseDirectory(); } + @Deprecated public boolean isUsingPOMsFromFilesystem() { return request.isProjectPresent(); diff --git a/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java b/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java index 97d5857b32..bf089ab838 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java @@ -176,11 +176,6 @@ public List getSortedProjects() return sorter.getSortedProjects(); } - public MavenProject getTopLevelProject() - { - return sorter.getTopLevelProject(); - } - public boolean hasBuildSuccess( MavenProject project ) { return buildSuccessesByProject.containsKey( getProjectKey( project ) ); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultSchedules.java b/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultSchedules.java index b8ab35e65d..d583cc3f6e 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultSchedules.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultSchedules.java @@ -19,8 +19,8 @@ * under the License. */ -import org.apache.maven.lifecycle.internal.BuilderCommon; import org.apache.maven.lifecycle.internal.ExecutionPlanItem; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.project.MavenProject; diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildListCalculator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildListCalculator.java index 9c3a294748..771385cc5e 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildListCalculator.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildListCalculator.java @@ -20,6 +20,7 @@ */ import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.component.annotations.Component; diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildThreadFactory.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildThreadFactory.java new file mode 100644 index 0000000000..eb54418e4c --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/BuildThreadFactory.java @@ -0,0 +1,20 @@ +package org.apache.maven.lifecycle.internal; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Simple {@link ThreadFactory} implementation that ensures the corresponding threads have a meaningful name. + */ +public class BuildThreadFactory + implements ThreadFactory +{ + private final AtomicInteger ID = new AtomicInteger(); + + private String PREFIX = "BuilderThread"; + + public Thread newThread( Runnable r ) + { + return new Thread( r, String.format( "%s %d", PREFIX, ID.getAndIncrement() ) ); + } +} \ No newline at end of file diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java index 58a7a624af..52cf9608dc 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java @@ -36,6 +36,7 @@ import org.apache.maven.lifecycle.LifecycleNotFoundException; import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.BuildPluginManager; diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDebugLogger.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDebugLogger.java index 25a1e7d891..521753c31d 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDebugLogger.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDebugLogger.java @@ -22,6 +22,7 @@ import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.project.MavenProject; diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java index bf2bb21147..2b4ba0e6aa 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java @@ -29,6 +29,7 @@ import org.apache.maven.execution.ProjectExecutionEvent; import org.apache.maven.execution.ProjectExecutionListener; import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.component.annotations.Component; @@ -111,8 +112,7 @@ public void buildProject( MavenSession session, MavenSession rootSession, Reacto projectExecutionListener.afterProjectExecutionSuccess( new ProjectExecutionEvent( session, currentProject, mojoExecutions ) ); - reactorContext.getResult().addBuildSummary( - new BuildSuccess( currentProject, buildEndTime - buildStartTime ) ); + reactorContext.getResult().addBuildSummary( new BuildSuccess( currentProject, buildEndTime - buildStartTime ) ); eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, session, null ); } @@ -120,7 +120,8 @@ public void buildProject( MavenSession session, MavenSession rootSession, Reacto { builderCommon.handleBuildError( reactorContext, rootSession, session, currentProject, e, buildStartTime ); - projectExecutionListener.afterProjectExecutionFailure( new ProjectExecutionEvent( session, currentProject, e ) ); + projectExecutionListener.afterProjectExecutionFailure( new ProjectExecutionEvent( session, currentProject, + e ) ); } finally { diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java index f77ebd1e31..e33a0f1224 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java @@ -19,25 +19,24 @@ * under the License. */ +import java.util.List; +import java.util.Map; + import org.apache.maven.execution.ExecutionEvent; -import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.DefaultLifecycles; import org.apache.maven.lifecycle.MissingProjectException; import org.apache.maven.lifecycle.NoGoalSpecifiedException; +import org.apache.maven.lifecycle.internal.builder.Builder; +import org.apache.maven.lifecycle.internal.builder.BuilderNotFoundException; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.logging.Logger; -import java.util.List; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; - /** * Starts the build life cycle + * * @author Jason van Zyl * @author Benjamin Bentmann * @author Kristian Rosenvold @@ -45,7 +44,6 @@ @Component( role = LifecycleStarter.class ) public class LifecycleStarter { - @Requirement private ExecutionEventCatapult eventCatapult; @@ -55,15 +53,6 @@ public class LifecycleStarter @Requirement private Logger logger; - @Requirement - private LifecycleModuleBuilder lifecycleModuleBuilder; - - @Requirement - private LifecycleWeaveBuilder lifeCycleWeaveBuilder; - - @Requirement - private LifecycleThreadedBuilder lifecycleThreadedBuilder; - @Requirement private BuildListCalculator buildListCalculator; @@ -74,30 +63,27 @@ public class LifecycleStarter private LifecycleTaskSegmentCalculator lifecycleTaskSegmentCalculator; @Requirement - private ThreadConfigurationService threadConfigService; + private Map builders; public void execute( MavenSession session ) { eventCatapult.fire( ExecutionEvent.Type.SessionStarted, session, null ); + ReactorContext reactorContext = null; + ProjectBuildList projectBuilds = null; MavenExecutionResult result = session.getResult(); try { - if ( !session.isUsingPOMsFromFilesystem() && lifecycleTaskSegmentCalculator.requiresProject( session ) ) + if ( buildExecutionRequiresProject( session ) && projectIsNotPresent( session ) ) { throw new MissingProjectException( "The goal you specified requires a project to execute" + " but there is no POM in this directory (" + session.getExecutionRootDirectory() + ")." + " Please verify you invoked Maven from the correct directory." ); } - final MavenExecutionRequest executionRequest = session.getRequest(); - boolean isThreaded = executionRequest.isThreadConfigurationPresent(); - session.setParallel( isThreaded ); - List taskSegments = lifecycleTaskSegmentCalculator.calculateTaskSegments( session ); - - ProjectBuildList projectBuilds = buildListCalculator.calculateProjectBuilds( session, taskSegments ); + projectBuilds = buildListCalculator.calculateProjectBuilds( session, taskSegments ); if ( projectBuilds.isEmpty() ) { @@ -115,52 +101,20 @@ public void execute( MavenSession session ) } ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); - ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus( session.getProjectDependencyGraph() ); - ReactorContext callableContext = - new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus ); + reactorContext = new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus ); - if ( isThreaded ) + String builderId = session.getRequest().getBuilderId(); + Builder builder = builders.get( builderId ); + if ( builder == null ) { - ExecutorService executor = - threadConfigService.getExecutorService( executionRequest.getThreadCount(), - executionRequest.isPerCoreThreadCount(), - session.getProjects().size() ); - try - { - - final boolean isWeaveMode = LifecycleWeaveBuilder.isWeaveMode( executionRequest ); - if ( isWeaveMode ) - { - lifecycleDebugLogger.logWeavePlan( session ); - lifeCycleWeaveBuilder.build( projectBuilds, callableContext, taskSegments, session, executor, - reactorBuildStatus ); - } - else - { - ConcurrencyDependencyGraph analyzer = - new ConcurrencyDependencyGraph( projectBuilds, session.getProjectDependencyGraph() ); - - CompletionService service = - new ExecutorCompletionService( executor ); - - lifecycleThreadedBuilder.build( session, callableContext, projectBuilds, taskSegments, analyzer, - service ); - } - } - finally - { - executor.shutdown(); - // If the builder has terminated with an exception we want to catch any stray threads before going - // to System.exit in the mavencli. - executor.awaitTermination( 5, TimeUnit.SECONDS ) ; - } - } - else - { - singleThreadedBuild( session, callableContext, projectBuilds, taskSegments, reactorBuildStatus ); + throw new BuilderNotFoundException( String.format( "The builder requested using id = %s cannot be found", builderId ) ); } + logger.info( "" ); + logger.info( String.format( "Using the builder %s", builder.getClass().getName() ) ); + builder.build( session, reactorContext, projectBuilds, taskSegments, reactorBuildStatus ); + } catch ( Exception e ) { @@ -170,29 +124,13 @@ public void execute( MavenSession session ) eventCatapult.fire( ExecutionEvent.Type.SessionEnded, session, null ); } - private void singleThreadedBuild( MavenSession session, ReactorContext callableContext, - ProjectBuildList projectBuilds, List taskSegments, - ReactorBuildStatus reactorBuildStatus ) + private boolean buildExecutionRequiresProject( MavenSession session ) { - for ( TaskSegment taskSegment : taskSegments ) - { - for ( ProjectSegment projectBuild : projectBuilds.getByTaskSegment( taskSegment ) ) - { - try - { - lifecycleModuleBuilder.buildProject( session, callableContext, projectBuild.getProject(), - taskSegment ); - if ( reactorBuildStatus.isHalted() ) - { - break; - } - } - catch ( Exception e ) - { - break; // Why are we just ignoring this exception? Are exceptions are being used for flow control - } + return lifecycleTaskSegmentCalculator.requiresProject( session ); + } - } - } + private boolean projectIsNotPresent( MavenSession session ) + { + return !session.getRequest().isProjectPresent(); } } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectBuildList.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectBuildList.java index 011e5cd113..acea697b12 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectBuildList.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectBuildList.java @@ -141,4 +141,16 @@ public boolean isEmpty() { return items.isEmpty(); } + + /** + * @return a set of all the projects managed by the build + */ + public Set getProjects() { + Set projects = new HashSet(); + + for (ProjectSegment s : items) { + projects.add(s.getProject()); + } + return projects; + } } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectIndex.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectIndex.java index 254e868764..3f78baff9a 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectIndex.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ProjectIndex.java @@ -19,6 +19,7 @@ * under the License. */ +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.project.MavenProject; import java.util.HashMap; diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorBuildStatus.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorBuildStatus.java index b4e13a48b3..eff4e92f4f 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorBuildStatus.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorBuildStatus.java @@ -20,6 +20,7 @@ */ import org.apache.maven.execution.ProjectDependencyGraph; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.project.MavenProject; import java.util.Collection; diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/Builder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/Builder.java new file mode 100644 index 0000000000..d3a04403c7 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/Builder.java @@ -0,0 +1,28 @@ +package org.apache.maven.lifecycle.internal.builder; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.internal.ProjectBuildList; +import org.apache.maven.lifecycle.internal.ReactorBuildStatus; +import org.apache.maven.lifecycle.internal.ReactorContext; +import org.apache.maven.lifecycle.internal.TaskSegment; + +/** + * This is provisional API and is very likely to change in the near future. If you implement a builder expect it to + * change. + * + * @author jvanzyl + * + */ +public interface Builder +{ + // + // Be nice to whittle this down to Session, maybe add task segments to the session. The session really is the + // the place to store reactor related information. + // + public void build( MavenSession session, ReactorContext reactorContext, ProjectBuildList projectBuilds, + List taskSegments, ReactorBuildStatus reactorBuildStatus ) + throws ExecutionException, InterruptedException; +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java new file mode 100644 index 0000000000..81109022b9 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java @@ -0,0 +1,195 @@ +package org.apache.maven.lifecycle.internal.builder; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.InternalErrorException; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.execution.BuildFailure; +import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.lifecycle.LifecycleNotFoundException; +import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; +import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.internal.ExecutionEventCatapult; +import org.apache.maven.lifecycle.internal.LifecycleDebugLogger; +import org.apache.maven.lifecycle.internal.LifecycleExecutionPlanCalculator; +import org.apache.maven.lifecycle.internal.ReactorContext; +import org.apache.maven.lifecycle.internal.TaskSegment; +import org.apache.maven.model.Plugin; +import org.apache.maven.plugin.InvalidPluginDescriptorException; +import org.apache.maven.plugin.MojoNotFoundException; +import org.apache.maven.plugin.PluginDescriptorParsingException; +import org.apache.maven.plugin.PluginNotFoundException; +import org.apache.maven.plugin.PluginResolutionException; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException; +import org.apache.maven.plugin.version.PluginVersionResolutionException; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.classworlds.realm.ClassRealm; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +import java.util.Set; + +/** + * Common code that is shared by the LifecycleModuleBuilder and the LifeCycleWeaveBuilder + * + * @since 3.0 + * @author Kristian Rosenvold + * Builds one or more lifecycles for a full module + * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. + */ +@Component( role = BuilderCommon.class ) +public class BuilderCommon +{ + @Requirement + private LifecycleDebugLogger lifecycleDebugLogger; + + @Requirement + private LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator; + + @Requirement + private ExecutionEventCatapult eventCatapult; + + @Requirement + private Logger logger; + + + public BuilderCommon() + { + } + + public BuilderCommon( LifecycleDebugLogger lifecycleDebugLogger, + LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator, Logger logger ) + { + this.lifecycleDebugLogger = lifecycleDebugLogger; + this.lifeCycleExecutionPlanCalculator = lifeCycleExecutionPlanCalculator; + this.logger = logger; + } + + public MavenExecutionPlan resolveBuildPlan( MavenSession session, MavenProject project, TaskSegment taskSegment, + Set projectArtifacts ) + throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException, + PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException, + NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException, + LifecycleExecutionException + { + MavenExecutionPlan executionPlan = + lifeCycleExecutionPlanCalculator.calculateExecutionPlan( session, project, taskSegment.getTasks() ); + + lifecycleDebugLogger.debugProjectPlan( project, executionPlan ); + + if ( session.getRequest().getDegreeOfConcurrency() > 1 ) + { + final Set unsafePlugins = executionPlan.getNonThreadSafePlugins(); + if ( !unsafePlugins.isEmpty() && logger.isDebugEnabled() ) + { + logger.warn( "*****************************************************************" ); + logger.warn( "* Your build is requesting parallel execution, but project *" ); + logger.warn( "* contains the following plugin(s) that have goals not marked *" ); + logger.warn( "* as @threadSafe to support parallel building. *" ); + logger.warn( "* While this /may/ work fine, please look for plugin updates *" ); + logger.warn( "* and/or request plugins be made thread-safe. *" ); + logger.warn( "* If reporting an issue, report it against the plugin in *" ); + logger.warn( "* question, not against maven-core *" ); + logger.warn( "*****************************************************************" ); + if ( logger.isDebugEnabled() ) + { + final Set unsafeGoals = executionPlan.getNonThreadSafeMojos(); + logger.warn( "The following goals are not marked @threadSafe in " + project.getName() + ":" ); + for ( MojoDescriptor unsafeGoal : unsafeGoals ) + { + logger.warn( unsafeGoal.getId() ); + } + } + else + { + logger.warn( "The following plugins are not marked @threadSafe in " + project.getName() + ":" ); + for ( Plugin unsafePlugin : unsafePlugins ) + { + logger.warn( unsafePlugin.getId() ); + } + logger.warn( "Enable debug to see more precisely which goals are not marked @threadSafe." ); + } + logger.warn( "*****************************************************************" ); + } + } + + return executionPlan; + } + + public void handleBuildError( final ReactorContext buildContext, final MavenSession rootSession, + final MavenSession currentSession, final MavenProject mavenProject, Exception e, + final long buildStartTime ) + { + if ( e instanceof RuntimeException ) + { + e = new InternalErrorException( "Internal error: " + e, e ); + } + + buildContext.getResult().addException( e ); + + long buildEndTime = System.currentTimeMillis(); + + buildContext.getResult().addBuildSummary( new BuildFailure( mavenProject, buildEndTime - buildStartTime, e ) ); + + eventCatapult.fire( ExecutionEvent.Type.ProjectFailed, currentSession, null, e ); + + if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( rootSession.getReactorFailureBehavior() ) ) + { + // continue the build + } + else if ( MavenExecutionRequest.REACTOR_FAIL_AT_END.equals( rootSession.getReactorFailureBehavior() ) ) + { + // continue the build but ban all projects that depend on the failed one + buildContext.getReactorBuildStatus().blackList( mavenProject ); + } + else if ( MavenExecutionRequest.REACTOR_FAIL_FAST.equals( rootSession.getReactorFailureBehavior() ) ) + { + buildContext.getReactorBuildStatus().halt(); + } + else + { + throw new IllegalArgumentException( + "invalid reactor failure behavior " + rootSession.getReactorFailureBehavior() ); + } + } + + public static void attachToThread( MavenProject currentProject ) + { + ClassRealm projectRealm = currentProject.getClassRealm(); + if ( projectRealm != null ) + { + Thread.currentThread().setContextClassLoader( projectRealm ); + } + } + + // Todo: I'm really wondering where this method belongs; smells like it should be on MavenProject, but for some reason + // it isn't ? This localization is kind-of a code smell. + + public static String getKey( MavenProject project ) + { + return project.getGroupId() + ':' + project.getArtifactId() + ':' + project.getVersion(); + } + +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderNotFoundException.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderNotFoundException.java new file mode 100644 index 0000000000..a90c0d46f9 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderNotFoundException.java @@ -0,0 +1,9 @@ +package org.apache.maven.lifecycle.internal.builder; + +public class BuilderNotFoundException extends Exception +{ + public BuilderNotFoundException(String message) + { + super(message); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ConcurrencyDependencyGraph.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ConcurrencyDependencyGraph.java new file mode 100644 index 0000000000..53be344442 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ConcurrencyDependencyGraph.java @@ -0,0 +1,153 @@ +package org.apache.maven.lifecycle.internal.builder.multithreaded; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.execution.ProjectDependencyGraph; +import org.apache.maven.lifecycle.internal.ProjectBuildList; +import org.apache.maven.lifecycle.internal.ProjectSegment; +import org.apache.maven.project.MavenProject; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Presents a view of the Dependency Graph that is suited for concurrent building. + * + * @since 3.0 + * @author Kristian Rosenvold + *

+ * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. + */ +public class ConcurrencyDependencyGraph +{ + + private final ProjectBuildList projectBuilds; + + private final ProjectDependencyGraph projectDependencyGraph; + + private final HashSet finishedProjects = new HashSet(); + + public ConcurrencyDependencyGraph( ProjectBuildList projectBuilds, ProjectDependencyGraph projectDependencyGraph ) + { + this.projectDependencyGraph = projectDependencyGraph; + this.projectBuilds = projectBuilds; + } + + public int getNumberOfBuilds() + { + return projectBuilds.size(); + } + + /** + * Gets all the builds that have no reactor-dependencies + * + * @return A list of all the initial builds + */ + + public List getRootSchedulableBuilds() + { + List result = new ArrayList(); + for ( ProjectSegment projectBuild : projectBuilds ) + { + if ( projectDependencyGraph.getUpstreamProjects( projectBuild.getProject(), false ).size() == 0 ) + { + result.add( projectBuild.getProject() ); + } + } + return result; + } + + /** + * Marks the provided project as finished. Returns a list of + * + * @param mavenProject The project + * @return The list of builds that are eligible for starting now that the provided project is done + */ + public List markAsFinished( MavenProject mavenProject ) + { + finishedProjects.add( mavenProject ); + return getSchedulableNewProcesses( mavenProject ); + } + + private List getSchedulableNewProcesses( MavenProject finishedProject ) + { + List result = new ArrayList(); + // schedule dependent projects, if all of their requirements are met + for ( MavenProject dependentProject : projectDependencyGraph.getDownstreamProjects( finishedProject, false ) ) + { + final List upstreamProjects = + projectDependencyGraph.getUpstreamProjects( dependentProject, false ); + if ( finishedProjects.containsAll( upstreamProjects ) ) + { + result.add( dependentProject ); + } + } + return result; + } + + /** + * @return set of projects that have yet to be processed successfully by the build. + */ + public Set getUnfinishedProjects() + { + Set unfinished = new HashSet( projectBuilds.getProjects() ); + unfinished.remove( finishedProjects ); + return unfinished; + } + + /** + * @return set of projects that have been successfully processed by the build. + */ + protected Set getFinishedProjects() + { + return finishedProjects; + } + + protected ProjectBuildList getProjectBuilds() + { + return projectBuilds; + } + + /** + * For the given {@link MavenProject} {@code p}, return all of {@code p}'s dependencies. + * + * @param p + * @return List of prerequisite projects + */ + protected List getDependencies( MavenProject p ) + { + return projectDependencyGraph.getUpstreamProjects( p, false ); + } + + /** + * For the given {@link MavenProject} {@code p} return {@code p}'s uncompleted dependencies. + * + * @param p + * @return List of uncompleted prerequisite projects + */ + public List getActiveDependencies( MavenProject p ) + { + List activeDependencies = projectDependencyGraph.getUpstreamProjects( p, false ); + activeDependencies.removeAll( finishedProjects ); + return activeDependencies; + } +} \ No newline at end of file diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java new file mode 100644 index 0000000000..b89aa0e8d3 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java @@ -0,0 +1,193 @@ +package org.apache.maven.lifecycle.internal.builder.multithreaded; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.internal.BuildThreadFactory; +import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder; +import org.apache.maven.lifecycle.internal.ProjectBuildList; +import org.apache.maven.lifecycle.internal.ProjectSegment; +import org.apache.maven.lifecycle.internal.ReactorBuildStatus; +import org.apache.maven.lifecycle.internal.ReactorContext; +import org.apache.maven.lifecycle.internal.TaskSegment; +import org.apache.maven.lifecycle.internal.ThreadOutputMuxer; +import org.apache.maven.lifecycle.internal.builder.Builder; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +/** + * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project) + * + * @since 3.0 + * @author Kristian Rosenvold + * Builds one or more lifecycles for a full module + *

+ * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. + */ +@Component( role = Builder.class, hint = "multithreaded" ) +public class MultiThreadedBuilder implements Builder +{ + + @Requirement + private Logger logger; + + @Requirement + private LifecycleModuleBuilder lifecycleModuleBuilder; + + + public MultiThreadedBuilder() + { + } + + @Override + public void build( MavenSession session, ReactorContext reactorContext, ProjectBuildList projectBuilds, + List taskSegments, ReactorBuildStatus reactorBuildStatus ) + throws ExecutionException, InterruptedException + { + ExecutorService executor = Executors.newFixedThreadPool(Math.min(session.getRequest().getDegreeOfConcurrency(), session.getProjects().size()), new BuildThreadFactory()); + CompletionService service = new ExecutorCompletionService(executor); + ConcurrencyDependencyGraph analyzer = new ConcurrencyDependencyGraph(projectBuilds, session.getProjectDependencyGraph()); + + // Currently disabled + ThreadOutputMuxer muxer = null; // new ThreadOutputMuxer( analyzer.getProjectBuilds(), System.out ); + + for ( TaskSegment taskSegment : taskSegments ) + { + Map projectBuildMap = projectBuilds.selectSegment( taskSegment ); + try + { + multiThreadedProjectTaskSegmentBuild( analyzer, reactorContext, session, service, taskSegment, + projectBuildMap, muxer ); + if ( reactorContext.getReactorBuildStatus().isHalted() ) + { + break; + } + } + catch ( Exception e ) + { + session.getResult().addException( e ); + break; + } + + } + } + + private void multiThreadedProjectTaskSegmentBuild( ConcurrencyDependencyGraph analyzer, + ReactorContext reactorContext, MavenSession rootSession, + CompletionService service, + TaskSegment taskSegment, + Map projectBuildList, + ThreadOutputMuxer muxer ) + { + + // schedule independent projects + for ( MavenProject mavenProject : analyzer.getRootSchedulableBuilds() ) + { + ProjectSegment projectSegment = projectBuildList.get( mavenProject ); + logger.debug( "Scheduling: " + projectSegment.getProject() ); + Callable cb = + createBuildCallable( rootSession, projectSegment, reactorContext, taskSegment, muxer ); + service.submit( cb ); + } + + // for each finished project + for ( int i = 0; i < analyzer.getNumberOfBuilds(); i++ ) + { + try + { + ProjectSegment projectBuild = service.take().get(); + if ( reactorContext.getReactorBuildStatus().isHalted() ) + { + break; + } + final List newItemsThatCanBeBuilt = + analyzer.markAsFinished( projectBuild.getProject() ); + for ( MavenProject mavenProject : newItemsThatCanBeBuilt ) + { + ProjectSegment scheduledDependent = projectBuildList.get( mavenProject ); + logger.debug( "Scheduling: " + scheduledDependent ); + Callable cb = + createBuildCallable( rootSession, scheduledDependent, reactorContext, taskSegment, muxer ); + service.submit( cb ); + } + } + catch ( InterruptedException e ) + { + rootSession.getResult().addException( e ); + break; + } + catch ( ExecutionException e ) + { + rootSession.getResult().addException( e ); + break; + } + } + + // cancel outstanding builds (if any) - this can happen if an exception is thrown in above block + + Future unprocessed; + while ( ( unprocessed = service.poll() ) != null ) + { + try + { + unprocessed.get(); + } + catch ( InterruptedException e ) + { + throw new RuntimeException( e ); + } + catch ( ExecutionException e ) + { + throw new RuntimeException( e ); + } + } + } + + private Callable createBuildCallable( final MavenSession rootSession, + final ProjectSegment projectBuild, + final ReactorContext reactorContext, + final TaskSegment taskSegment, final ThreadOutputMuxer muxer ) + { + return new Callable() + { + public ProjectSegment call() + { + // muxer.associateThreadWithProjectSegment( projectBuild ); + lifecycleModuleBuilder.buildProject( projectBuild.getSession(), rootSession, reactorContext, + projectBuild.getProject(), taskSegment ); + // muxer.setThisModuleComplete( projectBuild ); + + return projectBuild; + } + }; + } +} \ No newline at end of file diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/singlethreaded/SingleThreadedBuilder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/singlethreaded/SingleThreadedBuilder.java new file mode 100644 index 0000000000..a6c9d033e1 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/singlethreaded/SingleThreadedBuilder.java @@ -0,0 +1,46 @@ +package org.apache.maven.lifecycle.internal.builder.singlethreaded; + +import java.util.List; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder; +import org.apache.maven.lifecycle.internal.ProjectBuildList; +import org.apache.maven.lifecycle.internal.ProjectSegment; +import org.apache.maven.lifecycle.internal.ReactorBuildStatus; +import org.apache.maven.lifecycle.internal.ReactorContext; +import org.apache.maven.lifecycle.internal.TaskSegment; +import org.apache.maven.lifecycle.internal.builder.Builder; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; + +@Component( role = Builder.class, hint = "singlethreaded" ) +public class SingleThreadedBuilder + implements Builder +{ + @Requirement + private LifecycleModuleBuilder lifecycleModuleBuilder; + + public void build( MavenSession session, ReactorContext reactorContext, ProjectBuildList projectBuilds, + List taskSegments, ReactorBuildStatus reactorBuildStatus ) + { + for ( TaskSegment taskSegment : taskSegments ) + { + for ( ProjectSegment projectBuild : projectBuilds.getByTaskSegment( taskSegment ) ) + { + try + { + lifecycleModuleBuilder.buildProject( session, reactorContext, projectBuild.getProject(), + taskSegment ); + if ( reactorBuildStatus.isHalted() ) + { + break; + } + } + catch ( Exception e ) + { + break; // Why are we just ignoring this exception? Are exceptions are being used for flow control + } + } + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/BuildLogItem.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/BuildLogItem.java new file mode 100644 index 0000000000..9b5b321e78 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/BuildLogItem.java @@ -0,0 +1,221 @@ +package org.apache.maven.lifecycle.internal.builder.weave; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.lifecycle.internal.ExecutionPlanItem; +import org.apache.maven.project.MavenProject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @since 3.0 + * @author Kristian Rosenvold + * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. + */ +public class BuildLogItem +{ + private final ExecutionPlanItem executionPlanItem; + + private final MavenProject project; + + private final long startTime; + + private long endTime; + + private final List dependencies = + Collections.synchronizedList( new ArrayList() ); + + public BuildLogItem( MavenProject project, ExecutionPlanItem executionPlanItem ) + { + this.executionPlanItem = executionPlanItem; + this.project = project; + startTime = System.currentTimeMillis(); + + } + + + public MavenProject getProject() + { + return project; + } + + public void setComplete() + { + endTime = System.currentTimeMillis(); + } + + public void addWait( MavenProject upstreamProject, ExecutionPlanItem inSchedule, long startWait ) + { + long now = System.currentTimeMillis(); + dependencies.add( new DependencyLogEntry( upstreamProject, inSchedule, startWait, now, null ) ); + } + + public void addDependency( MavenProject upstreamProject, String message ) + { + dependencies.add( new DependencyLogEntry( upstreamProject, message ) ); + } + + public String toString( long rootStart ) + { + StringBuilder result = new StringBuilder(); + result.append( String.format( "%1d %2d ", startTime - rootStart, endTime - rootStart ) ); + result.append( project.getName() ); + result.append( " " ); + result.append( getMojoExecutionDescription( executionPlanItem ) ); + if ( dependencies.size() > 0 ) + { + result.append( "\n" ); + for ( DependencyLogEntry waitLogEntry : dependencies ) + { + result.append( " " ); + result.append( waitLogEntry.toString() ); + result.append( "\n" ); + } + } + return result.toString(); + } + + + public Object toGraph( long rootStart ) + { + StringBuilder result = new StringBuilder(); + if ( dependencies.size() > 0 ) + { + for ( DependencyLogEntry waitLogEntry : dependencies ) + { + result.append( " " ); + result.append( nodeKey( project, executionPlanItem ) ); + result.append( " -> " ); + result.append( waitLogEntry.toNodeKey() ); + result.append( waitLogEntry.toNodeDescription( rootStart ) ); + result.append( "\n" ); + } + } + else + { + result.append( " " ); + result.append( nodeKey( project, executionPlanItem ) ); + result.append( "\n" ); + } + return result.toString(); + } + + private static String nodeKey( MavenProject mavenProject, ExecutionPlanItem executionPlanItem ) + { + String key = mavenProject.getArtifactId(); + if ( executionPlanItem != null ) + { + key += "_" + getMojoExecutionDescription( executionPlanItem ); + } + return key.replace( ".", "_" ).replace( ":", "_" ); + } + + private static String getMojoExecutionDescription( ExecutionPlanItem executionPlanItem ) + { + if ( executionPlanItem.getMojoExecution() != null ) + { + return executionPlanItem.getMojoExecution().getArtifactId() + getLifeCyclePhase( executionPlanItem ); + } + else + { + return ""; + } + } + + private static String getLifeCyclePhase( ExecutionPlanItem executionPlanItem ) + { + return executionPlanItem.getLifecyclePhase() != null ? "[" + executionPlanItem.getLifecyclePhase() + "]" : ""; + } + + + class DependencyLogEntry + { + private final ExecutionPlanItem executionPlanItem; + + private final MavenProject upstreamProject; + + private final Long start; + + private final Long stop; + + private final String message; + + DependencyLogEntry( MavenProject upstreamProject, ExecutionPlanItem executionPlanItem, Long start, Long stop, + String message ) + { + this.upstreamProject = upstreamProject; + this.executionPlanItem = executionPlanItem; + this.start = start; + this.stop = stop; + this.message = message; + } + + DependencyLogEntry( MavenProject upstreamProject, String message ) + { + this( upstreamProject, null, null, null, message ); + } + + public String toString() + { + return upstreamProject.getName() + ":" + getExecutionPlanItem() + getElapsed() + getMessage(); + } + + public String toNodeKey() + { + return nodeKey( upstreamProject, executionPlanItem ); + } + + public String toNodeDescription( long rootStart ) + { + return ""; + } + + + private String getMessage() + { + return message != null ? message : ""; + } + + private String getExecutionPlanItem() + { + if ( executionPlanItem != null ) + { + return getMojoExecutionDescription( executionPlanItem ); + } + else + { + return ""; + } + } + + private String getElapsed() + { + if ( start != null && stop != null ) + { + long elapsed = stop - start; + return elapsed > 0 ? ", wait=" + elapsed : ""; + } + return ""; + } + } + +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ConcurrentBuildLogger.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ConcurrentBuildLogger.java new file mode 100644 index 0000000000..2dcd348941 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ConcurrentBuildLogger.java @@ -0,0 +1,124 @@ +package org.apache.maven.lifecycle.internal.builder.weave; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.lifecycle.internal.ExecutionPlanItem; +import org.apache.maven.project.MavenProject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Handles all concurrency-related logging. + *

+ * The logging/diagnostic needs of a concurrent build are different from a linear build. This + * delta required to analyze a concurrent build is located here. + *

+ * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. + * + * @since 3.0 + * @author Kristian Rosenvold + */ +public class ConcurrentBuildLogger +{ + private final long startTime; + + private final Map threadMap = new ConcurrentHashMap(); + + public ConcurrentBuildLogger() + { + startTime = System.currentTimeMillis(); + } + + + List items = Collections.synchronizedList( new ArrayList() ); + + public BuildLogItem createBuildLogItem( MavenProject project, ExecutionPlanItem current ) + { + threadMap.put( project, Thread.currentThread() ); + BuildLogItem result = new BuildLogItem( project, current ); + items.add( result ); + return result; + } + + public String toString() + { + StringBuilder result = new StringBuilder(); + for ( Map.Entry mavenProjectThreadEntry : threadMap.entrySet() ) + { + result.append( mavenProjectThreadEntry.getKey().getName() ); + result.append( " ran on " ); + result.append( mavenProjectThreadEntry.getValue().getName() ); + result.append( "\n" ); + } + + for ( BuildLogItem builtLogItem : items ) + { + result.append( builtLogItem.toString( startTime ) ); + result.append( "\n" ); + } + return result.toString(); + } + + public String toGraph() + { + StringBuilder result = new StringBuilder(); + + Map> multiMap = new HashMap>(); + for ( BuildLogItem builtLogItem : items ) + { + MavenProject project = builtLogItem.getProject(); + Collection bag = multiMap.get( project ); + if ( bag == null ) + { + bag = new ArrayList(); + multiMap.put( project, bag ); + } + bag.add( builtLogItem ); + } + + result.append( "digraph build" ); + result.append( " {\n " ); + + for ( MavenProject mavenProject : multiMap.keySet() ) + { + final Collection builtLogItems = multiMap.get( mavenProject ); + result.append( " subgraph " ); + result.append( mavenProject.getArtifactId() ); + result.append( " {\n" ); + + for ( BuildLogItem builtLogItem : builtLogItems ) + { + result.append( builtLogItem.toGraph( startTime ) ); + } + + result.append( "\n }\n" ); + } + + result.append( "\n}\n " ); + return result.toString(); + } + +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/CurrentPhaseForThread.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/CurrentPhaseForThread.java new file mode 100644 index 0000000000..f52f4e019f --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/CurrentPhaseForThread.java @@ -0,0 +1,44 @@ +package org.apache.maven.lifecycle.internal.builder.weave; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Knows the phase the current thread is executing. + *

+ * This class is used in weave-mode only , there may be better ways of doing this once the dust settles. + * + * @since 3.0 + * @author Kristian Rosenvold + */ +class CurrentPhaseForThread +{ + private static final InheritableThreadLocal THREAD_PHASE = new InheritableThreadLocal(); + + public static void setPhase( String phase ) + { + THREAD_PHASE.set( phase ); + } + + public static boolean isPhase( String phase ) + { + return phase.equals( THREAD_PHASE.get() ); + } + +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ThreadLockedArtifact.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ThreadLockedArtifact.java new file mode 100644 index 0000000000..7a0e117018 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/ThreadLockedArtifact.java @@ -0,0 +1,320 @@ +package org.apache.maven.lifecycle.internal.builder.weave; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.handler.ArtifactHandler; +import org.apache.maven.artifact.metadata.ArtifactMetadata; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.filter.ArtifactFilter; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.OverConstrainedVersionException; +import org.apache.maven.artifact.versioning.VersionRange; + +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +/** + * An artifact that conditionally suspends on getFile for anything but the thread it is locked to. + * + * @since 3.0 + */ +class ThreadLockedArtifact + implements Artifact +{ + private final Artifact real; + + private final CountDownLatch artifactLocked = new CountDownLatch( 1 ); + + ThreadLockedArtifact( Artifact real ) + { + this.real = real; + } + + public boolean hasReal() + { + return real != null + && ( !( real instanceof ThreadLockedArtifact ) || ( (ThreadLockedArtifact) real ).hasReal() ); + } + + public String getGroupId() + { + return real.getGroupId(); + } + + public String getArtifactId() + { + return real.getArtifactId(); + } + + public String getVersion() + { + return real.getVersion(); + } + + public void setVersion( String version ) + { + real.setVersion( version ); + } + + public String getScope() + { + return real.getScope(); + } + + public String getType() + { + return real.getType(); + } + + public String getClassifier() + { + return real.getClassifier(); + } + + public boolean hasClassifier() + { + return real.hasClassifier(); + } + + private static final InheritableThreadLocal THREAD_ARTIFACT = + new InheritableThreadLocal(); + + public void attachToThread() + { + THREAD_ARTIFACT.set( this ); + } + + public File getFile() + { + final ThreadLockedArtifact lockedArtifact = THREAD_ARTIFACT.get(); + if ( lockedArtifact != null && this != lockedArtifact && mustLock() ) + { + try + { + artifactLocked.await(); + } + catch ( InterruptedException e ) + { + // Ignore and go on to real.getFile(); + } + } + return real.getFile(); + } + + private boolean mustLock() + { + boolean dontNeedLock = CurrentPhaseForThread.isPhase( "compile" ) || CurrentPhaseForThread.isPhase( "test" ); + return !dontNeedLock; + } + + public void setFile( File destination ) + { + if ( destination != null && destination.exists() && destination.isFile() ) + { + artifactLocked.countDown(); + } + real.setFile( destination ); + } + + public String getBaseVersion() + { + return real.getBaseVersion(); + } + + public void setBaseVersion( String baseVersion ) + { + real.setBaseVersion( baseVersion ); + } + + public String getId() + { + return real.getId(); + } + + public String getDependencyConflictId() + { + return real.getDependencyConflictId(); + } + + public void addMetadata( ArtifactMetadata metadata ) + { + real.addMetadata( metadata ); + } + + public Collection getMetadataList() + { + return real.getMetadataList(); + } + + public void setRepository( ArtifactRepository remoteRepository ) + { + real.setRepository( remoteRepository ); + } + + public ArtifactRepository getRepository() + { + return real.getRepository(); + } + + public void updateVersion( String version, ArtifactRepository localRepository ) + { + real.updateVersion( version, localRepository ); + } + + public String getDownloadUrl() + { + return real.getDownloadUrl(); + } + + public void setDownloadUrl( String downloadUrl ) + { + real.setDownloadUrl( downloadUrl ); + } + + public ArtifactFilter getDependencyFilter() + { + return real.getDependencyFilter(); + } + + public void setDependencyFilter( ArtifactFilter artifactFilter ) + { + real.setDependencyFilter( artifactFilter ); + } + + public ArtifactHandler getArtifactHandler() + { + return real.getArtifactHandler(); + } + + public List getDependencyTrail() + { + return real.getDependencyTrail(); + } + + public void setDependencyTrail( List dependencyTrail ) + { + real.setDependencyTrail( dependencyTrail ); + } + + public void setScope( String scope ) + { + real.setScope( scope ); + } + + public VersionRange getVersionRange() + { + return real.getVersionRange(); + } + + public void setVersionRange( VersionRange newRange ) + { + real.setVersionRange( newRange ); + } + + public void selectVersion( String version ) + { + real.selectVersion( version ); + } + + public void setGroupId( String groupId ) + { + real.setGroupId( groupId ); + } + + public void setArtifactId( String artifactId ) + { + real.setArtifactId( artifactId ); + } + + public boolean isSnapshot() + { + return real.isSnapshot(); + } + + public void setResolved( boolean resolved ) + { + real.setResolved( resolved ); + } + + public boolean isResolved() + { + return real.isResolved(); + } + + public void setResolvedVersion( String version ) + { + real.setResolvedVersion( version ); + } + + public void setArtifactHandler( ArtifactHandler handler ) + { + real.setArtifactHandler( handler ); + } + + public boolean isRelease() + { + return real.isRelease(); + } + + public void setRelease( boolean release ) + { + real.setRelease( release ); + } + + public List getAvailableVersions() + { + return real.getAvailableVersions(); + } + + public void setAvailableVersions( List versions ) + { + real.setAvailableVersions( versions ); + } + + public boolean isOptional() + { + return real.isOptional(); + } + + public void setOptional( boolean optional ) + { + real.setOptional( optional ); + } + + public ArtifactVersion getSelectedVersion() + throws OverConstrainedVersionException + { + return real.getSelectedVersion(); + } + + public boolean isSelectedVersionKnown() + throws OverConstrainedVersionException + { + return real.isSelectedVersionKnown(); + } + + public int compareTo( Artifact o ) + { + return real.compareTo( o ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/WeaveBuilder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/WeaveBuilder.java new file mode 100644 index 0000000000..193eca67f9 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/weave/WeaveBuilder.java @@ -0,0 +1,521 @@ +package org.apache.maven.lifecycle.internal.builder.weave; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.execution.BuildSuccess; +import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.Schedule; +import org.apache.maven.lifecycle.internal.BuildThreadFactory; +import org.apache.maven.lifecycle.internal.DependencyContext; +import org.apache.maven.lifecycle.internal.ExecutionEventCatapult; +import org.apache.maven.lifecycle.internal.ExecutionPlanItem; +import org.apache.maven.lifecycle.internal.LifecycleDebugLogger; +import org.apache.maven.lifecycle.internal.MojoExecutor; +import org.apache.maven.lifecycle.internal.PhaseRecorder; +import org.apache.maven.lifecycle.internal.ProjectBuildList; +import org.apache.maven.lifecycle.internal.ProjectSegment; +import org.apache.maven.lifecycle.internal.ReactorBuildStatus; +import org.apache.maven.lifecycle.internal.ReactorContext; +import org.apache.maven.lifecycle.internal.TaskSegment; +import org.apache.maven.lifecycle.internal.builder.Builder; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project) + *

+ * NOTE: Weave mode is still experimental. It may be either promoted to first class citizen at some later point in time, + * and it may also be removed entirely. Weave mode has much more aggressive concurrency behaviour than regular threaded + * mode, and as such is still under test wrt cross platform stability. + *

+ * To remove weave mode from m3, the following should be removed: ExecutionPlanItem.schedule w/setters and getters + * DefaultLifeCycles.getScheduling() and all its use ReactorArtifactRepository has a reference to isWeave too. This + * class and its usage + * + * @since 3.0 + * @author Kristian Rosenvold Builds one or more lifecycles for a full module + *

+ * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. + */ +@Component( role = Builder.class, hint = "weave" ) +public class WeaveBuilder + implements Builder +{ + + @Requirement + private MojoExecutor mojoExecutor; + + @Requirement + private BuilderCommon builderCommon; + + @Requirement + private Logger logger; + + @Requirement + private ExecutionEventCatapult eventCatapult; + + @Requirement + private LifecycleDebugLogger lifecycleDebugLogger; + + private Map executionPlans = new HashMap(); + + public WeaveBuilder() + { + } + + public WeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger, + ExecutionEventCatapult eventCatapult, LifecycleDebugLogger lifecycleDebugLogger ) + { + this.mojoExecutor = mojoExecutor; + this.builderCommon = builderCommon; + this.logger = logger; + this.eventCatapult = eventCatapult; + this.lifecycleDebugLogger = lifecycleDebugLogger; + } + + public void build( MavenSession session, ReactorContext buildContext, ProjectBuildList projectBuilds, + List taskSegments, ReactorBuildStatus reactorBuildStatus ) + throws ExecutionException, InterruptedException + { + lifecycleDebugLogger.logWeavePlan( session ); + + ExecutorService executor = + Executors.newFixedThreadPool( Math.min( session.getRequest().getDegreeOfConcurrency(), + session.getProjects().size() ), new BuildThreadFactory() ); + + try + { + + ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger(); + CompletionService service = new ExecutorCompletionService( executor ); + + try + { + for ( MavenProject mavenProject : session.getProjects() ) + { + Artifact mainArtifact = mavenProject.getArtifact(); + if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) ) + { + ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact ); + mavenProject.setArtifact( threadLockedArtifact ); + } + } + + final List> futures = new ArrayList>(); + final Map> plans = + new HashMap>(); + + for ( TaskSegment taskSegment : taskSegments ) + { + ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment ); + Set projectArtifacts = new HashSet(); + for ( ProjectSegment segmentChunk : segmentChunks ) + { + Artifact artifact = segmentChunk.getProject().getArtifact(); + if ( artifact != null ) + { + projectArtifacts.add( artifact ); + } + } + for ( ProjectSegment projectBuild : segmentChunks ) + { + plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) ); + } + + for ( ProjectSegment projectSegment : plans.keySet() ) + { + executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() ); + + } + for ( ProjectSegment projectBuild : segmentChunks ) + { + try + { + final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get(); + + DependencyContext dependencyContext = + mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() ); + + final Callable projectBuilder = + createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus, + executionPlan, projectBuild, dependencyContext, + concurrentBuildLogger ); + + futures.add( service.submit( projectBuilder ) ); + } + catch ( Exception e ) + { + throw new ExecutionException( e ); + } + } + + for ( Future buildFuture : futures ) + { + buildFuture.get(); // At this point, this build *is* finished. + // Do not leak threads past here or evil gremlins will get you! + } + futures.clear(); + } + } + finally + { + projectBuilds.closeAll(); + } + logger.info( concurrentBuildLogger.toString() ); + } + finally + { + executor.shutdown(); + // If the builder has terminated with an exception we want to catch any stray threads before going + // to System.exit in the mavencli. + executor.awaitTermination( 5, TimeUnit.SECONDS ); + } + } + + private Callable createEPFuture( final ProjectSegment projectSegment, + final Set projectArtifacts ) + { + return new Callable() + { + public MavenExecutionPlan call() + throws Exception + { + return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(), + projectSegment.getTaskSegment(), projectArtifacts ); + } + }; + } + + private Callable createCallableForBuildingOneFullModule( final ReactorContext reactorContext, + final MavenSession rootSession, + final ReactorBuildStatus reactorBuildStatus, + final MavenExecutionPlan executionPlan, + final ProjectSegment projectBuild, + final DependencyContext dependencyContext, + final ConcurrentBuildLogger concurrentBuildLogger ) + { + return new Callable() + { + public ProjectSegment call() + throws Exception + { + Iterator planItems = executionPlan.iterator(); + ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null; + ThreadLockedArtifact threadLockedArtifact = + (ThreadLockedArtifact) projectBuild.getProject().getArtifact(); + if ( threadLockedArtifact != null ) + { + threadLockedArtifact.attachToThread(); + } + long buildStartTime = System.currentTimeMillis(); + + // muxer.associateThreadWithProjectSegment( projectBuild ); + + if ( reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) ) + { + eventCatapult.fire( ExecutionEvent.Type.ProjectSkipped, projectBuild.getSession(), null ); + return null; + } + + eventCatapult.fire( ExecutionEvent.Type.ProjectStarted, projectBuild.getSession(), null ); + + Collection dependencyLinks = getUpstreamReactorDependencies( projectBuild ); + + try + { + PhaseRecorder phaseRecorder = new PhaseRecorder( projectBuild.getProject() ); + long totalMojoTime = 0; + long mojoStart; + while ( current != null && !reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) ) + { + + BuildLogItem builtLogItem = + concurrentBuildLogger.createBuildLogItem( projectBuild.getProject(), current ); + final Schedule schedule = current.getSchedule(); + + mojoStart = System.currentTimeMillis(); + buildExecutionPlanItem( current, phaseRecorder, schedule, reactorContext, projectBuild, + dependencyContext ); + totalMojoTime += ( System.currentTimeMillis() - mojoStart ); + + current.setComplete(); + builtLogItem.setComplete(); + + ExecutionPlanItem nextPlanItem = planItems.hasNext() ? planItems.next() : null; + if ( nextPlanItem != null && phaseRecorder.isDifferentPhase( nextPlanItem.getMojoExecution() ) ) + { + + final Schedule scheduleOfNext = nextPlanItem.getSchedule(); + if ( scheduleOfNext == null || !scheduleOfNext.isParallel() ) + { + waitForAppropriateUpstreamExecutionsToFinish( builtLogItem, nextPlanItem, projectBuild, + scheduleOfNext ); + } + + for ( ArtifactLink dependencyLink : dependencyLinks ) + { + dependencyLink.resolveFromUpstream(); + } + } + current = nextPlanItem; + } + + final BuildSuccess summary = new BuildSuccess( projectBuild.getProject(), totalMojoTime ); // - + // waitingTime + reactorContext.getResult().addBuildSummary( summary ); + eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, projectBuild.getSession(), null ); + } + catch ( Exception e ) + { + builderCommon.handleBuildError( reactorContext, rootSession, projectBuild.getSession(), + projectBuild.getProject(), e, buildStartTime ); + } + finally + { + if ( current != null ) + { + executionPlan.forceAllComplete(); + } + // muxer.setThisModuleComplete( projectBuild ); + } + return null; + } + + }; + } + + private void waitForAppropriateUpstreamExecutionsToFinish( BuildLogItem builtLogItem, + ExecutionPlanItem nextPlanItem, + ProjectSegment projectBuild, Schedule scheduleOfNext ) + throws InterruptedException + { + for ( MavenProject upstreamProject : projectBuild.getImmediateUpstreamProjects() ) + { + final MavenExecutionPlan upstreamPlan = executionPlans.get( upstreamProject ); + final String nextPhase = + scheduleOfNext != null && scheduleOfNext.hasUpstreamPhaseDefined() ? scheduleOfNext.getUpstreamPhase() + : nextPlanItem.getLifecyclePhase(); + final ExecutionPlanItem upstream = upstreamPlan.findLastInPhase( nextPhase ); + + if ( upstream != null ) + { + long startWait = System.currentTimeMillis(); + upstream.waitUntilDone(); + builtLogItem.addWait( upstreamProject, upstream, startWait ); + } + else if ( !upstreamPlan.containsPhase( nextPhase ) ) + { + // Still a bit of a kludge; if we cannot connect in a sensible way to + // the upstream build plan we just revert to waiting for it all to + // complete. Real problem is per-mojo phase->lifecycle mapping + builtLogItem.addDependency( upstreamProject, "No phase tracking possible " ); + upstreamPlan.waitUntilAllDone(); + } + else + { + builtLogItem.addDependency( upstreamProject, "No schedule" ); + } + } + } + + private Collection getUpstreamReactorDependencies( ProjectSegment projectBuild ) + { + Collection result = new ArrayList(); + for ( MavenProject upstreamProject : projectBuild.getTransitiveUpstreamProjects() ) + { + Artifact upStreamArtifact = upstreamProject.getArtifact(); + if ( upStreamArtifact != null ) + { + Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact ); + if ( dependencyArtifact != null ) + { + result.add( new ArtifactLink( dependencyArtifact, upStreamArtifact ) ); + } + } + + Artifact upStreamTestScopedArtifact = findTestScopedArtifact( upstreamProject ); + if ( upStreamTestScopedArtifact != null ) + { + Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact ); + if ( dependencyArtifact != null ) + { + result.add( new ArtifactLink( dependencyArtifact, upStreamTestScopedArtifact ) ); + } + } + } + return result; + } + + private Artifact findTestScopedArtifact( MavenProject upstreamProject ) + { + if ( upstreamProject == null ) + { + return null; + } + + List artifactList = upstreamProject.getAttachedArtifacts(); + for ( Artifact artifact : artifactList ) + { + if ( Artifact.SCOPE_TEST.equals( artifact.getScope() ) ) + { + return artifact; + } + } + return null; + } + + private static boolean isThreadLockedAndEmpty( Artifact artifact ) + { + return artifact instanceof ThreadLockedArtifact && !( (ThreadLockedArtifact) artifact ).hasReal(); + } + + private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact ) + { + if ( upStreamArtifact == null || isThreadLockedAndEmpty( upStreamArtifact ) ) + { + return null; + } + + String key = + ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(), + upStreamArtifact.getVersion() ); + final Set deps = project.getDependencyArtifacts(); + for ( Artifact dep : deps ) + { + String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() ); + if ( key.equals( depKey ) ) + { + return dep; + } + } + return null; + + } + + private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule, + ReactorContext reactorContext, ProjectSegment projectBuild, + DependencyContext dependencyContext ) + throws LifecycleExecutionException + { + if ( schedule != null && schedule.isMojoSynchronized() ) + { + synchronized ( current.getPlugin() ) + { + buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder ); + } + } + else + { + buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder ); + } + } + + private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node, + ProjectSegment projectBuild, DependencyContext dependencyContext, + PhaseRecorder phaseRecorder ) + throws LifecycleExecutionException + { + + MavenProject currentProject = projectBuild.getProject(); + + long buildStartTime = System.currentTimeMillis(); + + CurrentPhaseForThread.setPhase( node.getLifecyclePhase() ); + + MavenSession sessionForThisModule = projectBuild.getSession(); + try + { + + if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) ) + { + return; + } + + BuilderCommon.attachToThread( currentProject ); + + mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(), + dependencyContext, phaseRecorder ); + + final BuildSuccess summary = new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime ); + reactorContext.getResult().addBuildSummary( summary ); + } + finally + { + Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() ); + } + } + + public static boolean isWeaveMode( MavenExecutionRequest request ) + { + return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) ); + } + + public static void setWeaveMode( Properties properties ) + { + properties.setProperty( "maven3.weaveMode", "true" ); + } + + static class ArtifactLink + { + private final Artifact artifactInThis; + + private final Artifact upstream; + + ArtifactLink( Artifact artifactInThis, Artifact upstream ) + { + this.artifactInThis = artifactInThis; + this.upstream = upstream; + } + + public void resolveFromUpstream() + { + artifactInThis.setFile( upstream.getFile() ); + artifactInThis.setRepository( upstream.getRepository() ); + artifactInThis.setResolved( true ); // Or maybe upstream.isResolved().... + } + } +} diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/MavenExecutionPlanTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/MavenExecutionPlanTest.java index 5728c80a71..c7c19da961 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/MavenExecutionPlanTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/MavenExecutionPlanTest.java @@ -16,6 +16,7 @@ package org.apache.maven.lifecycle; import junit.framework.TestCase; + import org.apache.maven.lifecycle.internal.ExecutionPlanItem; import org.apache.maven.lifecycle.internal.stub.DefaultLifecyclesStub; import org.apache.maven.lifecycle.internal.stub.LifecycleExecutionPlanCalculatorStub; diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/BuilderCommonTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/BuilderCommonTest.java index a474848315..6f896d8527 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/BuilderCommonTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/BuilderCommonTest.java @@ -16,9 +16,11 @@ */ import junit.framework.TestCase; + import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; import org.apache.maven.lifecycle.internal.stub.LifecycleExecutionPlanCalculatorStub; import org.apache.maven.lifecycle.internal.stub.LoggerStub; import org.apache.maven.lifecycle.internal.stub.ProjectDependencyGraphStub; diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrencyDependencyGraphTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrencyDependencyGraphTest.java index 0bb122754f..9ab0601162 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrencyDependencyGraphTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrencyDependencyGraphTest.java @@ -19,6 +19,7 @@ import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.lifecycle.LifecycleNotFoundException; import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; +import org.apache.maven.lifecycle.internal.builder.multithreaded.ConcurrencyDependencyGraph; import org.apache.maven.lifecycle.internal.stub.ProjectDependencyGraphStub; import org.apache.maven.plugin.InvalidPluginDescriptorException; import org.apache.maven.plugin.MojoNotFoundException; diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrentBuildLoggerTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrentBuildLoggerTest.java index 7eb7348667..5a7e105fa6 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrentBuildLoggerTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ConcurrentBuildLoggerTest.java @@ -15,14 +15,17 @@ * the License. */ +import java.util.Iterator; + import junit.framework.TestCase; + import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.MavenExecutionPlan; +import org.apache.maven.lifecycle.internal.builder.weave.BuildLogItem; +import org.apache.maven.lifecycle.internal.builder.weave.ConcurrentBuildLogger; import org.apache.maven.lifecycle.internal.stub.ProjectDependencyGraphStub; import org.apache.maven.project.MavenProject; -import java.util.Iterator; - /** * @author Kristian Rosenvold */ diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ExecutionPlanItemTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ExecutionPlanItemTest.java index 57caf95857..9fa969374d 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ExecutionPlanItemTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/ExecutionPlanItemTest.java @@ -16,6 +16,7 @@ */ import junit.framework.TestCase; + import org.apache.maven.lifecycle.Schedule; import org.apache.maven.lifecycle.internal.stub.MojoExecutorStub; import org.apache.maven.plugin.MojoExecution; diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleWeaveBuilderTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleWeaveBuilderTest.java index 5c5e7e8a81..44f231123b 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleWeaveBuilderTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleWeaveBuilderTest.java @@ -16,11 +16,14 @@ */ import junit.framework.TestCase; + import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.LifecycleNotFoundException; import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; +import org.apache.maven.lifecycle.internal.builder.weave.WeaveBuilder; import org.apache.maven.lifecycle.internal.stub.ExecutionEventCatapultStub; import org.apache.maven.lifecycle.internal.stub.LifecycleExecutionPlanCalculatorStub; import org.apache.maven.lifecycle.internal.stub.LifecycleTaskSegmentCalculatorStub; @@ -93,10 +96,10 @@ private ProjectBuildList runWithCompletionService( ExecutorService service ) ProjectBuildList projectBuildList = buildListCalculator.calculateProjectBuilds( session, taskSegments ); final MojoExecutorStub mojoExecutorStub = new MojoExecutorStub(); - final LifecycleWeaveBuilder builder = getWeaveBuilder( mojoExecutorStub ); + final WeaveBuilder builder = getWeaveBuilder( mojoExecutorStub ); final ReactorContext buildContext = createBuildContext( session ); ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus( session.getProjectDependencyGraph() ); - builder.build( projectBuildList, buildContext, taskSegments, session, service, reactorBuildStatus ); + builder.build( session, buildContext, projectBuildList, taskSegments, reactorBuildStatus ); LifecycleExecutionPlanCalculatorStub lifecycleExecutionPlanCalculatorStub = new LifecycleExecutionPlanCalculatorStub(); @@ -123,11 +126,11 @@ private ReactorContext createBuildContext( MavenSession session ) return new ReactorContext( mavenExecutionResult, null, null, reactorBuildStatus ); } - private LifecycleWeaveBuilder getWeaveBuilder( MojoExecutor mojoExecutor ) + private WeaveBuilder getWeaveBuilder( MojoExecutor mojoExecutor ) { final BuilderCommon builderCommon = getBuilderCommon(); final LoggerStub loggerStub = new LoggerStub(); - return new LifecycleWeaveBuilder( mojoExecutor, builderCommon, loggerStub, new ExecutionEventCatapultStub() ); + return new WeaveBuilder( mojoExecutor, builderCommon, loggerStub, new ExecutionEventCatapultStub(), new LifecycleDebugLogger( loggerStub ) ); } private BuilderCommon getBuilderCommon() diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/PhaseRecorderTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/PhaseRecorderTest.java index 12e1f86778..f3d6422c66 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/PhaseRecorderTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/PhaseRecorderTest.java @@ -16,6 +16,7 @@ */ import junit.framework.TestCase; + import org.apache.maven.lifecycle.MavenExecutionPlan; import org.apache.maven.lifecycle.internal.stub.LifecycleExecutionPlanCalculatorStub; import org.apache.maven.lifecycle.internal.stub.ProjectDependencyGraphStub; diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/ProjectDependencyGraphStub.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/ProjectDependencyGraphStub.java index 1977be7aa5..38e9fcade4 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/ProjectDependencyGraphStub.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/ProjectDependencyGraphStub.java @@ -216,6 +216,7 @@ public static MavenSession getMavenSession() MavenExecutionRequest mavenExecutionRequest = new DefaultMavenExecutionRequest(); mavenExecutionRequest.setExecutionListener( new AbstractExecutionListener() ); mavenExecutionRequest.setGoals( Arrays.asList( "clean", "aggr", "install" ) ); + mavenExecutionRequest.setDegreeOfConcurrency( 1 ); final MavenSession session = new MavenSession( null, null, mavenExecutionRequest, defaultMavenExecutionResult ); final ProjectDependencyGraphStub dependencyGraphStub = new ProjectDependencyGraphStub(); session.setProjectDependencyGraph( dependencyGraphStub ); diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java index c6bdd4ab19..aa22d235a4 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java @@ -99,6 +99,8 @@ public class CLIManager public static final String LEGACY_LOCAL_REPOSITORY = "llr"; + public static final String BUILDER = "b"; + protected Options options; @SuppressWarnings( "static-access" ) @@ -136,6 +138,7 @@ public CLIManager() options.addOption( OptionBuilder.withLongOpt( "encrypt-password" ).hasOptionalArg().withDescription( "Encrypt server password" ).create( ENCRYPT_PASSWORD ) ); options.addOption( OptionBuilder.withLongOpt( "threads" ).hasArg().withDescription( "Thread count, for instance 2.0C where C is core multiplied" ).create( THREADS ) ); options.addOption( OptionBuilder.withLongOpt( "legacy-local-repository" ).withDescription( "Use Maven 2 Legacy Local Repository behaviour, ie no use of _remote.repositories. Can also be activated by using -Dmaven.legacyLocalRepo=true" ).create( LEGACY_LOCAL_REPOSITORY ) ); + options.addOption( OptionBuilder.withLongOpt( "builder" ).hasArg().withDescription( "The id of the build strategy to use." ).create( BUILDER ) ); // Adding this back in for compatibility with the verifier that hard codes this option. options.addOption( OptionBuilder.withLongOpt( "no-plugin-registry" ).withDescription( "Ineffective, only kept for backward compatibility" ).create( "npr" ) ); diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java index 1615d58aaf..8ee2830bd0 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java @@ -56,7 +56,6 @@ import org.apache.maven.execution.MavenExecutionRequestPopulator; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.lifecycle.internal.LifecycleWeaveBuilder; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.project.MavenProject; import org.apache.maven.properties.internal.EnvironmentUtils; @@ -1068,25 +1067,47 @@ else if ( commandLine.hasOption( CLIManager.ALSO_MAKE ) { request.setLocalRepositoryPath( localRepoProperty ); } - - final String threadConfiguration = commandLine.hasOption( CLIManager.THREADS ) - ? commandLine.getOptionValue( CLIManager.THREADS ) - : request.getSystemProperties().getProperty( - MavenCli.THREADS_DEPRECATED ); // TODO: Remove this setting. Note that the int-tests use it - - if ( threadConfiguration != null ) - { - request.setPerCoreThreadCount( threadConfiguration.contains( "C" ) ); - if ( threadConfiguration.contains( "W" ) ) - { - LifecycleWeaveBuilder.setWeaveMode( request.getUserProperties() ); - } - request.setThreadCount( threadConfiguration.replace( "C", "" ).replace( "W", "" ).replace( "auto", "" ) ); - } + request.setCacheNotFound( true ); request.setCacheTransferError( false ); + // + // Builder, concurrency and parallelism + // + // We preserve the existing methods for builder selection which is to look for various inputs in the threading + // configuration. We don't have an easy way to allow a pluggable builder to provide its own configuration parameters + // but this is sufficient for now. Ultimately we want components like Builders to provide a way to extend the command + // line to accept its own configuration parameters. + // + final String threadConfiguration = commandLine.hasOption( CLIManager.THREADS ) + ? commandLine.getOptionValue( CLIManager.THREADS ) + : request.getSystemProperties().getProperty( + MavenCli.THREADS_DEPRECATED ); // TODO: Remove this setting. Note that the int-tests use it + + if ( threadConfiguration != null ) + { + if ( threadConfiguration.contains( "W" ) ) + { + request.setBuilderId( "weave" ); + } + else + { + request.setBuilderId( "multithreaded" ); + } + + int threads = + threadConfiguration.contains( "C" ) ? Integer.valueOf( threadConfiguration.replace( "C", "" ) ) + * Runtime.getRuntime().availableProcessors() : Integer.valueOf( threadConfiguration ); + + request.setDegreeOfConcurrency(threads); + } + + if ( commandLine.hasOption( CLIManager.BUILDER ) ) + { + request.setBuilderId( commandLine.getOptionValue( CLIManager.BUILDER ) ); + } + return request; } diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java index f55141d4c3..3891172ea2 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java @@ -212,7 +212,7 @@ private void logStats( MavenSession session ) long time = finish.getTime() - session.getRequest().getStartTime().getTime(); - String wallClock = session.getRequest().isThreadConfigurationPresent() ? " (Wall Clock)" : ""; + String wallClock = session.getRequest().getDegreeOfConcurrency() > 1 ? " (Wall Clock)" : ""; logger.info( "Total time: " + getFormattedTime( time ) + wallClock );