背景:有一些maven工程比较大,构建时间非常长,希望能提升这些项目的构建效率?
方法:
通过阅读maven的源码,分析maven工程没有步执行的时间长度,确定具体耗时,然后分析最长耗时。
由于maven主要执行life周期,本次分析暂时延着这个顺序吧。
具体分析过程
下载maven3.3.0版本源码到本地。其实所有版本都是可以下载的maven3各版本源码
将maven工程导入到idea后,可以看到整个项目的结构如下:
打开apache-maven/bin/mvn脚本
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "${M2_HOME}"/boot/plexus-classworlds-*.jar \
"-Dclassworlds.conf=${M2_HOME}/bin/m2.conf" \
"-Dmaven.home=${M2_HOME}" \
${CLASSWORLDS_LAUNCHER} "$@"
这段命令是我们运行mvn命令时候的启动命令,通过plexus-classworlds来启动maven,具体的配置在bin/m2.conf里面
main is org.apache.maven.cli.MavenCli from plexus.core
set maven.home default ${user.home}/m2
[plexus.core]
optionally ${maven.home}/lib/ext/*.jar
load ${maven.home}/lib/*.jar
load ${maven.home}/conf/logging
分析conf文件,org.apache.maven.cli.MavenCli里main函数是入口函数。终于可以开始愉快的阅读java代码啦。
个人阅读风格注:一般先延主线阅读,对某个问题想不通时再慢慢阅读思考。那就开始啦。
//这里是maven的execute前的一些准备事项
public int doMain( CliRequest cliRequest )
{
PlexusContainer localContainer = null;
try
{
initialize( cliRequest );
cli( cliRequest );
logging( cliRequest );
version( cliRequest );
properties( cliRequest );
localContainer = container( cliRequest );
commands( cliRequest );
settings( cliRequest );
populateRequest( cliRequest );
encryption( cliRequest );
repository( cliRequest );
return execute( cliRequest );
}
......终于到项目开始处"Scanning for projects..."
private MavenExecutionResult doExecute( MavenExecutionRequest request )
{
request.setStartTime( new Date() );
MavenExecutionResult result = new DefaultMavenExecutionResult();
try
{
// 校验localRepository
validateLocalRepository( request );
}
catch ( LocalRepositoryNotAccessibleException e )
{
return addExceptionToResult( result, e );
}
DefaultRepositorySystemSession repoSession = (DefaultRepositorySystemSession) newRepositorySession( request );
MavenSession session = new MavenSession( container, repoSession, request, result );
legacySupport.setSession( session );
try
{
for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( Collections.<MavenProject> emptyList() ) )
{
listener.afterSessionStart( session );
}
}
catch ( MavenExecutionException e )
{
return addExceptionToResult( result, e );
}
//开始scaning 所有项目罗
eventCatapult.fire( ExecutionEvent.Type.ProjectDiscoveryStarted, session, null );
List<MavenProject> projects;
try
{
//获取所有project
projects = getProjectsForMavenReactor( session );
//
// Capture the full set of projects before any potential constraining is performed by --projects
//
session.setAllProjects( projects );
}
catch ( ProjectBuildingException e )
{
return addExceptionToResult( result, e );
}
validateProjects( projects );
//
// This creates the graph and trims the projects down based on the user request using something like:
//
// -pl project0,project2 eclipse:eclipse
//
ProjectDependencyGraph projectDependencyGraph = createProjectDependencyGraph( projects, request, result, true );
if ( result.hasExceptions() )
{
return result;
}
session.setProjects( projectDependencyGraph.getSortedProjects() );
try
{
session.setProjectMap( getProjectMap( session.getProjects() ) );
}
catch ( DuplicateProjectException e )
{
return addExceptionToResult( result, e );
}
WorkspaceReader reactorWorkspace;
sessionScope.enter();
sessionScope.seed( MavenSession.class, session );
try
{
reactorWorkspace = container.lookup( WorkspaceReader.class, ReactorReader.HINT );
}
catch ( ComponentLookupException e )
{
return addExceptionToResult( result, e );
}
//
// Desired order of precedence for local artifact repositories
//
// Reactor
// Workspace
// User Local Repository
//
repoSession.setWorkspaceReader( ChainedWorkspaceReader.newInstance( reactorWorkspace,
repoSession.getWorkspaceReader() ) );
repoSession.setReadOnly();
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try
{
for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( projects ) )
{
Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
listener.afterProjectsRead( session );
}
}
catch ( MavenExecutionException e )
{
return addExceptionToResult( result, e );
}
finally
{
Thread.currentThread().setContextClassLoader( originalClassLoader );
}
//
// The projects need to be topologically after the participants have run their afterProjectsRead(session)
// because the participant is free to change the dependencies of a project which can potentially change the
// topological order of the projects, and therefore can potentially change the build order.
//
// Note that participants may affect the topological order of the projects but it is
// not expected that a participant will add or remove projects from the session.
//
projectDependencyGraph = createProjectDependencyGraph( session.getProjects(), request, result, false );
try
{
if ( result.hasExceptions() )
{
return result;
}
session.setProjects( projectDependencyGraph.getSortedProjects() );
session.setProjectDependencyGraph( projectDependencyGraph );
result.setTopologicallySortedProjects( session.getProjects() );
result.setProject( session.getTopLevelProject() );
lifecycleStarter.execute( session );
validateActivatedProfiles( session.getProjects(), request.getActiveProfiles() );
if ( session.getResult().hasExceptions() )
{
return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) );
}
}
finally
{
try
{
afterSessionEnd( projects, session );
}
catch ( MavenExecutionException e )
{
return addExceptionToResult( result, e );
}
finally
{
sessionScope.exit();
}
}
return result;
}
上面分析出了所有的project,工作目录等一堆准备工作, lifecycleStarter.execute( session );生命周期马上开始罗
public void execute( MavenSession session )
{
eventCatapult.fire( ExecutionEvent.Type.SessionStarted, session, null );
ReactorContext reactorContext = null;
ProjectBuildList projectBuilds = null;
MavenExecutionResult result = session.getResult();
try
{
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." );
}
//分析需要的taskSegments
List<TaskSegment> taskSegments = lifecycleTaskSegmentCalculator.calculateTaskSegments( session );
projectBuilds = buildListCalculator.calculateProjectBuilds( session, taskSegments );
if ( projectBuilds.isEmpty() )
{
throw new NoGoalSpecifiedException( "No goals have been specified for this build."
+ " You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or"
+ " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>."
+ " Available lifecycle phases are: " + defaultLifeCycles.getLifecyclePhaseList() + "." );
}
ProjectIndex projectIndex = new ProjectIndex( session.getProjects() );
if ( logger.isDebugEnabled() )
{
lifecycleDebugLogger.debugReactorPlan( projectBuilds );
}
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus( session.getProjectDependencyGraph() );
reactorContext = new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus );
这里便是获取哪一种builder,是单线程运行,还是多线程运行
String builderId = session.getRequest().getBuilderId();
Builder builder = builders.get( builderId );
if ( builder == null )
{
throw new BuilderNotFoundException( String.format( "The builder requested using id = %s cannot be found", builderId ) );
}
int degreeOfConcurrency = session.getRequest().getDegreeOfConcurrency();
if ( degreeOfConcurrency >= 2 )
{
logger.info( "" );
logger.info( String.format( "Using the %s implementation with a thread count of %d", builder.getClass().getSimpleName(), degreeOfConcurrency ) );
}
builder.build( session, reactorContext, projectBuilds, taskSegments, reactorBuildStatus );
}
catch ( Exception e )
{
result.addException( e );
}
finally
{
eventCatapult.fire( ExecutionEvent.Type.SessionEnded, session, null );
}
}
这里需要注意是单线程执行,还是多线程执行。你可以通过源码看到,builder有这2种实现。如果有兴趣,可以分析多线程执行,为了更清晰运行过程,我们选择分析单线程执行过程。
.....省略一下中间过程
public void execute( MavenSession session, List<MojoExecution> mojoExecutions, ProjectIndex projectIndex )
throws LifecycleExecutionException
{
DependencyContext dependencyContext = newDependencyContext( session, mojoExecutions );
PhaseRecorder phaseRecorder = new PhaseRecorder( session.getCurrentProject() );
for ( MojoExecution mojoExecution : mojoExecutions )
{
logger.info("Aone content - Mojo Execution start." );
long start = System.currentTimeMillis();
execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
long end = System.currentTimeMillis();
logger.info("Aone content - Mojo Execution end . time : " + ( end - start ) );
}
}
从这里可以看到maven其实每个project就是运行一组mojo来工作哒,顺序执行。如果你看多线程,其实最后也是调用到这里。
logger.info("Aone content - Mojo Execution end . time : " + ( end - start ) );
大致分析结论
可以打印出每个mojo的执行时间,精确的时间。 通过打印,可以看到在整个生命周期过程中:第一次分析依赖的时间比较长、编译时间很长,打war包,deploy到远端时间较长。
日志截图图如下:
想要加速构建,我们分析那部分能让我们加速?
通过对多个构建时间长的项目进行日志分析,其实他们会花近一般1/3的时间进行依赖分析。
过程如下:
private void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
DependencyContext dependencyContext )
throws LifecycleExecutionException
{
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
try
{
mavenPluginManager.checkRequiredMavenVersion( mojoDescriptor.getPluginDescriptor() );
}
catch ( PluginIncompatibleException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
if ( mojoDescriptor.isProjectRequired() && !session.isUsingPOMsFromFilesystem() )
{
Throwable cause =
new MissingProjectException( "Goal requires a project to execute"
+ " but there is no POM in this directory (" + session.getExecutionRootDirectory() + ")."
+ " Please verify you invoked Maven from the correct directory." );
throw new LifecycleExecutionException( mojoExecution, null, cause );
}
if ( mojoDescriptor.isOnlineRequired() && session.isOffline() )
{
if ( MojoExecution.Source.CLI.equals( mojoExecution.getSource() ) )
{
Throwable cause =
new IllegalStateException( "Goal requires online mode for execution"
+ " but Maven is currently offline." );
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), cause );
}
else
{
eventCatapult.fire( ExecutionEvent.Type.MojoSkipped, session, mojoExecution );
return;
}
}
List<MavenProject> forkedProjects = executeForkedExecutions( mojoExecution, session, projectIndex );
ensureDependenciesAreResolved( mojoDescriptor, session, dependencyContext );
eventCatapult.fire( ExecutionEvent.Type.MojoStarted, session, mojoExecution );
try
{
try
{
pluginManager.executeMojo( session, mojoExecution );
}
catch ( MojoFailureException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
catch ( MojoExecutionException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
catch ( PluginConfigurationException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
catch ( PluginManagerException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
eventCatapult.fire( ExecutionEvent.Type.MojoSucceeded, session, mojoExecution );
}
catch ( LifecycleExecutionException e )
{
eventCatapult.fire( ExecutionEvent.Type.MojoFailed, session, mojoExecution, e );
throw e;
}
finally
{
for ( MavenProject forkedProject : forkedProjects )
{
forkedProject.setExecutionProject( null );
}
}
}
这是每个mojo执行过程的步骤,都有依赖分析,第一个mojo执行后会缓存结果,分析一下依赖分析的结果吧。
MojoExecutor 208行
// 依赖分析入口
ensureDependenciesAreResolved( mojoDescriptor, session, dependencyContext );
lifeCycleDependencyResolver.resolveProjectDependencies( aggregatedProject, scopesToCollect, scopesToResolve, session, aggregating,Collections.<Artifact> emptySet() );
// 这里是依赖分析的过程
long start = System.currentTimeMillis();
Set<Artifact> artifacts =
getDependencies( project, scopesToCollect, scopesToResolve, session, aggregating, projectArtifacts );
long end = System.currentTimeMillis();
logger.info( "Aone content - get Dependencies time : " + ( end - start ) );
这里可以看到所有project在依赖分析的时间。
依赖分析过程图:
构建组件源码拉出来溜溜,对理解构建过程有一定帮助(这是我第一段阅读的代码)
// mojo执行入口
public void execute()
throws MojoExecutionException, CompilationFailureException
{
if ( skipMain )
{
getLog().info( "Not compiling main sources" );
return;
}
super.execute();
if ( outputDirectory.isDirectory() )
{
projectArtifact.setFile( outputDirectory );
}
}
接下来是我看到过写的非常长的一段代码,主要思考两点:1、拼接参数,环境,2执行罗
public void execute()
throws MojoExecutionException, CompilationFailureException
{
// ----------------------------------------------------------------------
// Look up the compiler. This is done before other code than can
// cause the mojo to return before the lookup is done possibly resulting
// in misconfigured POMs still building.
// ----------------------------------------------------------------------
long start = System.currentTimeMillis();
Compiler compiler;
getLog().debug( "Using compiler '" + compilerId + "'." );
try
{
compiler = compilerManager.getCompiler( compilerId );
}
catch ( NoSuchCompilerException e )
{
throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
}
//-----------toolchains start here ----------------------------------
//use the compilerId as identifier for toolchains as well.
Toolchain tc = getToolchain();
if ( tc != null )
{
getLog().info( "Toolchain in maven-compiler-plugin: " + tc );
if ( executable != null )
{
getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
}
else
{
fork = true;
//TODO somehow shaky dependency between compilerId and tool executable.
executable = tc.findTool( compilerId );
}
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );
if ( compileSourceRoots.isEmpty() )
{
getLog().info( "No sources to compile" );
return;
}
// ----------------------------------------------------------------------
// Create the compiler configuration
// ----------------------------------------------------------------------
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );
compilerConfiguration.setOptimize( optimize );
compilerConfiguration.setDebug( debug );
if ( debug && StringUtils.isNotEmpty( debuglevel ) )
{
String[] split = StringUtils.split( debuglevel, "," );
for ( String aSplit : split )
{
if ( !( aSplit.equalsIgnoreCase( "none" ) || aSplit.equalsIgnoreCase( "lines" )
|| aSplit.equalsIgnoreCase( "vars" ) || aSplit.equalsIgnoreCase( "source" ) ) )
{
throw new IllegalArgumentException( "The specified debug level: '" + aSplit + "' is unsupported. "
+ "Legal values are 'none', 'lines', 'vars', and 'source'." );
}
}
compilerConfiguration.setDebugLevel( debuglevel );
}
compilerConfiguration.setParameters( parameters );
compilerConfiguration.setVerbose( verbose );
compilerConfiguration.setShowWarnings( showWarnings );
compilerConfiguration.setFailOnWarning( failOnWarning );
compilerConfiguration.setShowDeprecation( showDeprecation );
compilerConfiguration.setSourceVersion( getSource() );
compilerConfiguration.setTargetVersion( getTarget() );
compilerConfiguration.setReleaseVersion( getRelease() );
compilerConfiguration.setProc( proc );
File generatedSourcesDirectory = getGeneratedSourcesDirectory();
compilerConfiguration.setGeneratedSourcesDirectory( generatedSourcesDirectory != null
? generatedSourcesDirectory.getAbsoluteFile() : null );
if ( generatedSourcesDirectory != null )
{
String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
compileSourceRoots.add( generatedSourcesPath );
if ( isTestCompile() )
{
getLog().debug( "Adding " + generatedSourcesPath + " to test-compile source roots:\n "
+ StringUtils.join( project.getTestCompileSourceRoots()
.iterator(), "\n " ) );
project.addTestCompileSourceRoot( generatedSourcesPath );
getLog().debug( "New test-compile source roots:\n "
+ StringUtils.join( project.getTestCompileSourceRoots()
.iterator(), "\n " ) );
}
else
{
getLog().debug( "Adding " + generatedSourcesPath + " to compile source roots:\n "
+ StringUtils.join( project.getCompileSourceRoots()
.iterator(), "\n " ) );
project.addCompileSourceRoot( generatedSourcesPath );
getLog().debug( "New compile source roots:\n " + StringUtils.join( project.getCompileSourceRoots()
.iterator(), "\n " ) );
}
}
compilerConfiguration.setSourceLocations( compileSourceRoots );
compilerConfiguration.setAnnotationProcessors( annotationProcessors );
compilerConfiguration.setProcessorPathEntries( resolveProcessorPathEntries() );
compilerConfiguration.setSourceEncoding( encoding );
compilerConfiguration.setFork( fork );
if ( fork )
{
if ( !StringUtils.isEmpty( meminitial ) )
{
String value = getMemoryValue( meminitial );
if ( value != null )
{
compilerConfiguration.setMeminitial( value );
}
else
{
getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
}
}
if ( !StringUtils.isEmpty( maxmem ) )
{
String value = getMemoryValue( maxmem );
if ( value != null )
{
compilerConfiguration.setMaxmem( value );
}
else
{
getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
}
}
}
compilerConfiguration.setExecutable( executable );
compilerConfiguration.setWorkingDirectory( basedir );
compilerConfiguration.setCompilerVersion( compilerVersion );
compilerConfiguration.setBuildDirectory( buildDirectory );
compilerConfiguration.setOutputFileName( outputFileName );
if ( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals( this.compilerReuseStrategy ) )
{
compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew );
}
else if ( CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy().equals(
this.compilerReuseStrategy ) )
{
if ( getRequestThreadCount() > 1 )
{
if ( !skipMultiThreadWarning )
{
getLog().warn( "You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
+ " This can cause issues in some environments (os/jdk)!"
+ " Consider using reuseCreated strategy."
+ System.getProperty( "line.separator" )
+ "If your env is fine with reuseSame, you can skip this warning with the "
+ "configuration field skipMultiThreadWarning "
+ "or -Dmaven.compiler.skipMultiThreadWarning=true" );
}
}
compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseSame );
}
else
{
compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseCreated );
}
getLog().debug( "CompilerReuseStrategy: " + compilerConfiguration.getCompilerReuseStrategy().getStrategy() );
compilerConfiguration.setForceJavacCompilerUse( forceJavacCompilerUse );
boolean canUpdateTarget;
IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper( mojoExecution, session );
Set<File> sources;
IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
if ( useIncrementalCompilation )
{
getLog().debug( "useIncrementalCompilation enabled" );
try
{
canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
sources = getCompileSources( compiler, compilerConfiguration );
preparePaths( sources );
incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles( sources );
// CHECKSTYLE_OFF: LineLength
if ( ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) && !canUpdateTarget )
|| isDependencyChanged()
|| isSourceChanged( compilerConfiguration, compiler )
|| incrementalBuildHelper.inputFileTreeChanged( incrementalBuildHelperRequest ) )
// CHECKSTYLE_ON: LineLength
{
getLog().info( "Changes detected - recompiling the module!" );
compilerConfiguration.setSourceFiles( sources );
}
else
{
getLog().info( "Nothing to compile - all classes are up to date" );
return;
}
}
catch ( CompilerException e )
{
throw new MojoExecutionException( "Error while computing stale sources.", e );
}
}
else
{
getLog().debug( "useIncrementalCompilation disabled" );
Set<File> staleSources;
try
{
staleSources =
computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
&& !canUpdateTarget )
{
getLog().info( "RESCANNING!" );
// TODO: This second scan for source files is sub-optimal
String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
sources = computeStaleSources( compilerConfiguration, compiler,
getSourceInclusionScanner( inputFileEnding ) );
compilerConfiguration.setSourceFiles( sources );
}
else
{
compilerConfiguration.setSourceFiles( staleSources );
}
preparePaths( compilerConfiguration.getSourceFiles() );
}
catch ( CompilerException e )
{
throw new MojoExecutionException( "Error while computing stale sources.", e );
}
if ( staleSources.isEmpty() )
{
getLog().info( "Nothing to compile - all classes are up to date" );
return;
}
}
// Dividing pathElements of classPath and modulePath is based on sourceFiles
compilerConfiguration.setClasspathEntries( getClasspathElements() );
compilerConfiguration.setModulepathEntries( getModulepathElements() );
Map<String, String> effectiveCompilerArguments = getCompilerArguments();
String effectiveCompilerArgument = getCompilerArgument();
if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null )
|| ( compilerArgs != null ) )
{
if ( effectiveCompilerArguments != null )
{
for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
{
String key = me.getKey();
String value = me.getValue();
if ( !key.startsWith( "-" ) )
{
key = "-" + key;
}
if ( key.startsWith( "-A" ) && StringUtils.isNotEmpty( value ) )
{
compilerConfiguration.addCompilerCustomArgument( key + "=" + value, null );
}
else
{
compilerConfiguration.addCompilerCustomArgument( key, value );
}
}
}
if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
{
compilerConfiguration.addCompilerCustomArgument( effectiveCompilerArgument, null );
}
if ( compilerArgs != null )
{
for ( String arg : compilerArgs )
{
compilerConfiguration.addCompilerCustomArgument( arg, null );
}
}
}
// ----------------------------------------------------------------------
// Dump configuration
// ----------------------------------------------------------------------
if ( getLog().isDebugEnabled() )
{
getLog().debug( "Classpath:" );
for ( String s : getClasspathElements() )
{
getLog().debug( " " + s );
}
if ( !getModulepathElements().isEmpty() )
{
getLog().debug( "Modulepath:" );
for ( String s : getModulepathElements() )
{
getLog().debug( " " + s );
}
}
getLog().debug( "Source roots:" );
for ( String root : getCompileSourceRoots() )
{
getLog().debug( " " + root );
}
try
{
if ( fork )
{
if ( compilerConfiguration.getExecutable() != null )
{
getLog().debug( "Excutable: " );
getLog().debug( " " + compilerConfiguration.getExecutable() );
}
}
String[] cl = compiler.createCommandLine( compilerConfiguration );
if ( getLog().isDebugEnabled() && cl != null && cl.length > 0 )
{
StringBuilder sb = new StringBuilder();
sb.append( cl[0] );
for ( int i = 1; i < cl.length; i++ )
{
sb.append( " " );
sb.append( cl[i] );
}
getLog().debug( "Command line options:" );
getLog().debug( sb );
}
}
catch ( CompilerException ce )
{
getLog().debug( ce );
}
}
List<String> jpmsLines = new ArrayList<String>();
// See http://openjdk.java.net/jeps/261
final List<String> runtimeArgs = Arrays.asList( "--upgrade-module-path",
"--add-exports",
"--add-reads",
"--add-modules",
"--limit-modules" );
// Custom arguments are all added as keys to an ordered Map
Iterator<Map.Entry<String, String>> entryIter =
compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
while ( entryIter.hasNext() )
{
Map.Entry<String, String> entry = entryIter.next();
if ( runtimeArgs.contains( entry.getKey() ) )
{
jpmsLines.add( entry.getKey() );
String value = entry.getValue();
if ( value == null )
{
entry = entryIter.next();
value = entry.getKey();
}
jpmsLines.add( value );
}
else if ( "--patch-module".equals( entry.getKey() ) )
{
jpmsLines.add( "--patch-module" );
String value = entry.getValue();
if ( value == null )
{
entry = entryIter.next();
value = entry.getKey();
}
String[] values = value.split( "=" );
StringBuilder patchModule = new StringBuilder( values[0] );
patchModule.append( '=' );
Set<String> patchModules = new LinkedHashSet<>();
Set<Path> sourceRoots = new HashSet<>( getCompileSourceRoots().size() );
for ( String sourceRoot : getCompileSourceRoots() )
{
sourceRoots.add( Paths.get( sourceRoot ) );
}
String[] files = values[1].split( PS );
for ( String file : files )
{
Path filePath = Paths.get( file );
if ( getOutputDirectory().toPath().equals( filePath ) )
{
patchModules.add( "_" ); // this jar
}
else if ( sourceRoots.contains( filePath ) )
{
patchModules.add( "_" ); // this jar
}
else
{
JavaModuleDescriptor descriptor = getPathElements().get( file );
if ( descriptor == null )
{
getLog().warn( "Can't locate " + file );
}
else if ( !values[0].equals( descriptor.name() ) )
{
patchModules.add( descriptor.name() );
}
}
}
StringBuilder sb = new StringBuilder();
for ( String mod : patchModules )
{
if ( sb.length() > 0 )
{
sb.append( ", " );
}
// use 'invalid' separator to ensure values are transformed
sb.append( mod );
}
jpmsLines.add( patchModule + sb.toString() );
}
}
if ( !jpmsLines.isEmpty() )
{
Path jpmsArgs = Paths.get( getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args" );
try
{
Files.createDirectories( jpmsArgs.getParent() );
Files.write( jpmsArgs, jpmsLines, Charset.defaultCharset() );
}
catch ( IOException e )
{
getLog().warn( e.getMessage() );
}
}
long end = System.currentTimeMillis();
getLog().debug( "aone log - compiler Configuration tiem: " + ( end - start ) );
// ----------------------------------------------------------------------
// Compile!
// ----------------------------------------------------------------------
long s1 = System.currentTimeMillis();
if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
{
getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
+ ", i.e. build is platform dependent!" );
}
CompilerResult compilerResult;
if ( useIncrementalCompilation )
{
incrementalBuildHelperRequest.outputDirectory( getOutputDirectory() );
incrementalBuildHelper.beforeRebuildExecution( incrementalBuildHelperRequest );
getLog().debug( "incrementalBuildHelper#beforeRebuildExecution" );
}
try
{
try
{
compilerResult = compiler.performCompile( compilerConfiguration );
}
catch ( CompilerNotImplementedException cnie )
{
List<CompilerError> messages = compiler.compile( compilerConfiguration );
compilerResult = convertToCompilerResult( messages );
}
}
catch ( Exception e )
{
// TODO: don't catch Exception
throw new MojoExecutionException( "Fatal error compiling", e );
}
long s2 = System.currentTimeMillis();
getLog().debug( "aone log - compiler time: " + ( s2 - s1 ) );
getLog().debug( "aone log - compiler common time: " + ( s2 - start ) );
if ( useIncrementalCompilation )
{
if ( incrementalBuildHelperRequest.getOutputDirectory().exists() )
{
getLog().debug( "incrementalBuildHelper#afterRebuildExecution" );
// now scan the same directory again and create a diff
incrementalBuildHelper.afterRebuildExecution( incrementalBuildHelperRequest );
}
else
{
getLog().debug(
"skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist" );
}
}
List<CompilerMessage> warnings = new ArrayList<CompilerMessage>();
List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
List<CompilerMessage> others = new ArrayList<CompilerMessage>();
for ( CompilerMessage message : compilerResult.getCompilerMessages() )
{
if ( message.getKind() == CompilerMessage.Kind.ERROR )
{
errors.add( message );
}
else if ( message.getKind() == CompilerMessage.Kind.WARNING
|| message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING )
{
warnings.add( message );
}
else
{
others.add( message );
}
}
if ( failOnError && !compilerResult.isSuccess() )
{
for ( CompilerMessage message : others )
{
assert message.getKind() != CompilerMessage.Kind.ERROR
&& message.getKind() != CompilerMessage.Kind.WARNING
&& message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING;
getLog().info( message.toString() );
}
if ( !warnings.isEmpty() )
{
getLog().info( "-------------------------------------------------------------" );
getLog().warn( "COMPILATION WARNING : " );
getLog().info( "-------------------------------------------------------------" );
for ( CompilerMessage warning : warnings )
{
getLog().warn( warning.toString() );
}
getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
getLog().info( "-------------------------------------------------------------" );
}
if ( !errors.isEmpty() )
{
getLog().info( "-------------------------------------------------------------" );
getLog().error( "COMPILATION ERROR : " );
getLog().info( "-------------------------------------------------------------" );
for ( CompilerMessage error : errors )
{
getLog().error( error.toString() );
}
getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
getLog().info( "-------------------------------------------------------------" );
}
if ( !errors.isEmpty() )
{
throw new CompilationFailureException( errors );
}
else
{
throw new CompilationFailureException( warnings );
}
}
else
{
for ( CompilerMessage message : compilerResult.getCompilerMessages() )
{
switch ( message.getKind() )
{
case NOTE:
case OTHER:
getLog().info( message.toString() );
break;
case ERROR:
getLog().error( message.toString() );
break;
case MANDATORY_WARNING:
case WARNING:
default:
getLog().warn( message.toString() );
break;
}
}
}
}
从这段代码你可以看出构建需要哪些东西,有兴趣可以细致分析
这里涉及到java编译器, 通过阅读源码你可以发现,并行执行的时候起了一个进程,而单线程执行的时候,直接javaCompile走起。而且直接调用java自己的编译工具,所以没有优化空间。
总结
通过阅读、分析源码,优化依赖分析部分可以得到比较高的收益,而且snapshot版本包含越多,收益越高。
本次分析基本上是纵向分析,主要是lifecycle部分比较多一点,pom模型...等许多部分没有详尽描述,后面有计划再细致分析