All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugin.compiler.AbstractCompilerMojo Maven / Gradle / Ivy

There is a newer version: 4.0.0-beta-1
Show newest version
package org.apache.maven.plugin.compiler;

/*
 * 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.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.shared.incremental.IncrementalBuildHelper;
import org.apache.maven.shared.incremental.IncrementalBuildHelperRequest;
import org.apache.maven.shared.utils.ReaderFactory;
import org.apache.maven.shared.utils.StringUtils;
import org.apache.maven.shared.utils.io.FileUtils;
import org.apache.maven.shared.utils.logging.MessageBuilder;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.compiler.Compiler;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerError;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerMessage;
import org.codehaus.plexus.compiler.CompilerNotImplementedException;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.codehaus.plexus.compiler.CompilerResult;
import org.codehaus.plexus.compiler.manager.CompilerManager;
import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
import org.codehaus.plexus.languages.java.version.JavaVersion;

/**
 * TODO: At least one step could be optimized, currently the plugin will do two
 * scans of all the source code if the compiler has to have the entire set of
 * sources. This is currently the case for at least the C# compiler and most
 * likely all the other .NET compilers too.
 *
 * @author others
 * @author Trygve Laugstøl
 * @version $Id$
 * @since 2.0
 */
public abstract class AbstractCompilerMojo
    extends AbstractMojo
{
    protected static final String PS = System.getProperty( "path.separator" );

    static final String DEFAULT_SOURCE = "1.6";
    
    static final String DEFAULT_TARGET = "1.6";
    
    // Used to compare with older targets
    static final String MODULE_INFO_TARGET = "1.9";
    
    // ----------------------------------------------------------------------
    // Configurables
    // ----------------------------------------------------------------------

    /**
     * Indicates whether the build will continue even if there are compilation errors.
     *
     * @since 2.0.2
     */
    @Parameter( property = "maven.compiler.failOnError", defaultValue = "true" )
    private boolean failOnError = true;
    
    /**
     * Indicates whether the build will continue even if there are compilation warnings.
     *
     * @since 3.6
     */
    @Parameter( property = "maven.compiler.failOnWarning", defaultValue = "false" )
    private boolean failOnWarning;  

    /**
     * Set to true to include debugging information in the compiled class files.
     */
    @Parameter( property = "maven.compiler.debug", defaultValue = "true" )
    private boolean debug = true;

    /**
     * Set to true to generate metadata for reflection on method parameters.
     * @since 3.6.2
     */
    @Parameter( property = "maven.compiler.parameters", defaultValue = "false" )
    private boolean parameters;

    /**
     * Set to true to show messages about what the compiler is doing.
     */
    @Parameter( property = "maven.compiler.verbose", defaultValue = "false" )
    private boolean verbose;

    /**
     * Sets whether to show source locations where deprecated APIs are used.
     */
    @Parameter( property = "maven.compiler.showDeprecation", defaultValue = "false" )
    private boolean showDeprecation;

    /**
     * Set to true to optimize the compiled code using the compiler's optimization methods.
     * @deprecated This property is a no-op in {@code javac}.
     */
    @Deprecated
    @Parameter( property = "maven.compiler.optimize", defaultValue = "false" )
    private boolean optimize;

    /**
     * Set to true to show compilation warnings.
     */
    @Parameter( property = "maven.compiler.showWarnings", defaultValue = "false" )
    private boolean showWarnings;

    /**
     * 

The -source argument for the Java compiler.

* * NOTE: Since 3.8.0 the default value has changed from 1.5 to 1.6 */ @Parameter( property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE ) protected String source; /** *

The -target argument for the Java compiler.

* * NOTE: Since 3.8.0 the default value has changed from 1.5 to 1.6 */ @Parameter( property = "maven.compiler.target", defaultValue = DEFAULT_TARGET ) protected String target; /** * The -release argument for the Java compiler, supported since Java9 * * @since 3.6 */ @Parameter( property = "maven.compiler.release" ) protected String release; /** * The -encoding argument for the Java compiler. * * @since 2.1 */ @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" ) private String encoding; /** * Sets the granularity in milliseconds of the last modification * date for testing whether a source needs recompilation. */ @Parameter( property = "lastModGranularityMs", defaultValue = "0" ) private int staleMillis; /** * The compiler id of the compiler to use. See this * guide for more information. */ @Parameter( property = "maven.compiler.compilerId", defaultValue = "javac" ) private String compilerId; /** * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to true. */ @Parameter( property = "maven.compiler.compilerVersion" ) private String compilerVersion; /** * Allows running the compiler in a separate process. * If false it uses the built in compiler, while if true it will use an executable. */ @Parameter( property = "maven.compiler.fork", defaultValue = "false" ) private boolean fork; /** * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m" * if {@link #fork} is set to true. * * @since 2.0.1 */ @Parameter( property = "maven.compiler.meminitial" ) private String meminitial; /** * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m" * if {@link #fork} is set to true. * * @since 2.0.1 */ @Parameter( property = "maven.compiler.maxmem" ) private String maxmem; /** * Sets the executable of the compiler to use when {@link #fork} is true. */ @Parameter( property = "maven.compiler.executable" ) private String executable; /** *

* Sets whether annotation processing is performed or not. Only applies to JDK 1.6+ * If not set, both compilation and annotation processing are performed at the same time. *

*

Allowed values are:

*
    *
  • none - no annotation processing is performed.
  • *
  • only - only annotation processing is done, no compilation.
  • *
* * @since 2.2 */ @Parameter private String proc; /** *

* Names of annotation processors to run. Only applies to JDK 1.6+ * If not set, the default annotation processors discovery process applies. *

* * @since 2.2 */ @Parameter private String[] annotationProcessors; /** *

* Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation * processors. The detection itself depends on the configuration of {@code annotationProcessors}. *

*

* Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier, * type). Transitive dependencies are added automatically. Example: *

* *
     * <configuration>
     *   <annotationProcessorPaths>
     *     <path>
     *       <groupId>org.sample</groupId>
     *       <artifactId>sample-annotation-processor</artifactId>
     *       <version>1.2.3</version>
     *     </path>
     *     <!-- ... more ... -->
     *   </annotationProcessorPaths>
     * </configuration>
     * 
* * @since 3.5 */ @Parameter private List annotationProcessorPaths; /** *

* Sets the arguments to be passed to the compiler (prepending a dash) if {@link #fork} is set to true. *

*

* This is because the list of valid arguments passed to a Java compiler varies based on the compiler version. *

*

* To pass -Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true you should include the following: *

* *
     * <compilerArguments>
     *   <Xmaxerrs>1000</Xmaxerrs>
     *   <Xlint/>
     *   <Xlint:-path/>
     *   <Averbose>true</Averbose>
     * </compilerArguments>
     * 
* * @since 2.0.1 * @deprecated use {@link #compilerArgs} instead. */ @Parameter @Deprecated protected Map compilerArguments; /** *

* Sets the arguments to be passed to the compiler if {@link #fork} is set to true. * Example: *

     * <compilerArgs>
     *   <arg>-Xmaxerrs=1000</arg>
     *   <arg>-Xlint</arg>
     *   <arg>-J-Duser.language=en_us</arg>
     * </compilerArgs>
     * 
* * @since 3.1 */ @Parameter protected List compilerArgs; /** *

* Sets the unformatted single argument string to be passed to the compiler if {@link #fork} is set to * true. To pass multiple arguments such as -Xmaxerrs 1000 (which are actually two * arguments) you have to use {@link #compilerArguments}. *

*

* This is because the list of valid arguments passed to a Java compiler varies based on the compiler version. *

*/ @Parameter protected String compilerArgument; /** * Sets the name of the output file when compiling a set of * sources to a single file. *

* expression="${project.build.finalName}" */ @Parameter private String outputFileName; /** * Keyword list to be appended to the -g command-line switch. Legal values are none or a * comma-separated list of the following keywords: lines, vars, and source. * If debug level is not specified, by default, nothing will be appended to -g. * If debug is not turned on, this attribute will be ignored. * * @since 2.1 */ @Parameter( property = "maven.compiler.debuglevel" ) private String debuglevel; /** * */ @Component private ToolchainManager toolchainManager; /** *

* Specify the requirements for this jdk toolchain. * This overrules the toolchain selected by the maven-toolchain-plugin. *

* note: requires at least Maven 3.3.1 * * @since 3.6 */ @Parameter private Map jdkToolchain; // ---------------------------------------------------------------------- // Read-only parameters // ---------------------------------------------------------------------- /** * The directory to run the compiler from if fork is true. */ @Parameter( defaultValue = "${basedir}", required = true, readonly = true ) private File basedir; /** * The target directory of the compiler if fork is true. */ @Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true ) private File buildDirectory; /** * Plexus compiler manager. */ @Component private CompilerManager compilerManager; /** * The current build session instance. This is used for toolchain manager API calls. */ @Parameter( defaultValue = "${session}", readonly = true, required = true ) private MavenSession session; /** * The current project instance. This is used for propagating generated-sources paths as compile/testCompile source * roots. */ @Parameter( defaultValue = "${project}", readonly = true, required = true ) private MavenProject project; /** * Strategy to re use javacc class created: *
    *
  • reuseCreated (default): will reuse already created but in case of multi-threaded builds, each * thread will have its own instance
  • *
  • reuseSame: the same Javacc class will be used for each compilation even for multi-threaded build *
  • *
  • alwaysNew: a new Javacc class will be created for each compilation
  • *
* Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env. * * @since 2.5 */ @Parameter( defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy" ) private String compilerReuseStrategy = "reuseCreated"; /** * @since 2.5 */ @Parameter( defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning" ) private boolean skipMultiThreadWarning; /** * compiler can now use javax.tools if available in your current jdk, you can disable this feature * using -Dmaven.compiler.forceJavacCompilerUse=true or in the plugin configuration * * @since 3.0 */ @Parameter( defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse" ) private boolean forceJavacCompilerUse; /** * @since 3.0 needed for storing the status for the incremental build support. */ @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true ) private MojoExecution mojoExecution; /** * file extensions to check timestamp for incremental build * default contains only .class * * @since 3.1 */ @Parameter private List fileExtensions; /** * to enable/disable incrementation compilation feature * @since 3.1 */ @Parameter( defaultValue = "true", property = "maven.compiler.useIncrementalCompilation" ) private boolean useIncrementalCompilation = true; /** * Resolves the artifacts needed. */ @Component private RepositorySystem repositorySystem; /** * Artifact handler manager. */ @Component private ArtifactHandlerManager artifactHandlerManager; /** * Throws an exception on artifact resolution errors. */ @Component private ResolutionErrorHandler resolutionErrorHandler; protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis ); protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding ); protected abstract List getClasspathElements(); protected abstract List getModulepathElements(); protected abstract Map getPathElements(); protected abstract List getCompileSourceRoots(); protected abstract void preparePaths( Set sourceFiles ); protected abstract File getOutputDirectory(); protected abstract String getSource(); protected abstract String getTarget(); protected abstract String getRelease(); protected abstract String getCompilerArgument(); protected abstract Map getCompilerArguments(); protected abstract File getGeneratedSourcesDirectory(); protected final MavenProject getProject() { return project; } private boolean targetOrReleaseSet; @Override 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. // ---------------------------------------------------------------------- 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 compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() ); if ( compileSourceRoots.isEmpty() ) { getLog().info( "No sources to compile" ); return; } // Verify that target or release is set if ( !targetOrReleaseSet ) { MessageBuilder mb = MessageUtils.buffer().a( "No explicit value set for target or release! " ) .a( "To ensure the same result even after upgrading this plugin, please add " ).newline() .newline(); writePlugin( mb ); getLog().warn( mb.toString() ); } // ---------------------------------------------------------------------- // 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 ); final Set 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 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 ); staleSources = computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( inputFileEnding ) ); } } 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; } compilerConfiguration.setSourceFiles( staleSources ); try { // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath sources = getCompileSources( compiler, compilerConfiguration ); getLog().debug( "#sources: " + sources.size() ); for ( File file : sources ) { getLog().debug( file.getPath() ); } preparePaths( sources ); } catch ( CompilerException e ) { throw new MojoExecutionException( "Error while computing stale sources.", e ); } } // Dividing pathElements of classPath and modulePath is based on sourceFiles compilerConfiguration.setClasspathEntries( getClasspathElements() ); compilerConfiguration.setModulepathEntries( getModulepathElements() ); Map effectiveCompilerArguments = getCompilerArguments(); String effectiveCompilerArgument = getCompilerArgument(); if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null ) || ( compilerArgs != null ) ) { if ( effectiveCompilerArguments != null ) { for ( Map.Entry 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 jpmsLines = new ArrayList(); // See http://openjdk.java.net/jeps/261 final List 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> entryIter = compilerConfiguration.getCustomCompilerArgumentsEntries().iterator(); while ( entryIter.hasNext() ) { Map.Entry 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() ) ) { 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 patchModules = new LinkedHashSet<>(); Set 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 ( getOutputDirectory().toPath().startsWith( filePath ) ) { // multirelease, can be ignored continue; } else if ( sourceRoots.contains( filePath ) ) { patchModules.add( "_" ); // this jar } else { JavaModuleDescriptor descriptor = getPathElements().get( file ); if ( descriptor == null ) { if ( Files.isDirectory( filePath ) ) { patchModules.add( file ); } else { getLog().warn( "Can't locate " + file ); } } else if ( !values[0].equals( descriptor.name() ) ) { patchModules.add( descriptor.name() ); } } } StringBuilder sb = new StringBuilder(); if ( patchModules.size() > 0 ) { for ( String mod : patchModules ) { if ( sb.length() > 0 ) { sb.append( ", " ); } // use 'invalid' separator to ensure values are transformed sb.append( mod ); } jpmsLines.add( "--patch-module" ); 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() ); } } // ---------------------------------------------------------------------- // Compile! // ---------------------------------------------------------------------- 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 messages = compiler.compile( compilerConfiguration ); compilerResult = convertToCompilerResult( messages ); } } catch ( Exception e ) { // TODO: don't catch Exception throw new MojoExecutionException( "Fatal error compiling", e ); } 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 warnings = new ArrayList(); List errors = new ArrayList(); List others = new ArrayList(); 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; } } } } protected boolean isTestCompile() { return false; } protected CompilerResult convertToCompilerResult( List compilerErrors ) { if ( compilerErrors == null ) { return new CompilerResult(); } List messages = new ArrayList( compilerErrors.size() ); boolean success = true; for ( CompilerError compilerError : compilerErrors ) { messages.add( new CompilerMessage( compilerError.getFile(), compilerError.getKind(), compilerError.getStartLine(), compilerError.getStartColumn(), compilerError.getEndLine(), compilerError.getEndColumn(), compilerError.getMessage() ) ); if ( compilerError.isError() ) { success = false; } } return new CompilerResult( success, messages ); } /** * @return all source files for the compiler */ private Set getCompileSources( Compiler compiler, CompilerConfiguration compilerConfiguration ) throws MojoExecutionException, CompilerException { String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration ); if ( StringUtils.isEmpty( inputFileEnding ) ) { // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding // so we can presume it's all files from the source directory inputFileEnding = ".*"; } SourceInclusionScanner scanner = getSourceInclusionScanner( inputFileEnding ); SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler ); scanner.addSourceMapping( mapping ); Set compileSources = new HashSet(); for ( String sourceRoot : getCompileSourceRoots() ) { File rootFile = new File( sourceRoot ); if ( !rootFile.isDirectory() || rootFile.getAbsoluteFile().equals( compilerConfiguration.getGeneratedSourcesDirectory() ) ) { continue; } try { compileSources.addAll( scanner.getIncludedSources( rootFile, null ) ); } catch ( InclusionScanException e ) { throw new MojoExecutionException( "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e ); } } return compileSources; } /** * @param compilerConfiguration * @param compiler * @return true if at least a single source file is newer than it's class file */ private boolean isSourceChanged( CompilerConfiguration compilerConfiguration, Compiler compiler ) throws CompilerException, MojoExecutionException { Set staleSources = computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) ); if ( getLog().isDebugEnabled() ) { for ( File f : staleSources ) { getLog().debug( "Stale source detected: " + f.getAbsolutePath() ); } } return staleSources != null && staleSources.size() > 0; } /** * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent * * @return number of thread for this build or 1 if not multi-thread build */ protected int getRequestThreadCount() { try { Method getRequestMethod = session.getClass().getMethod( "getRequest" ); Object mavenExecutionRequest = getRequestMethod.invoke( this.session ); Method getThreadCountMethod = mavenExecutionRequest.getClass().getMethod( "getThreadCount" ); String threadCount = (String) getThreadCountMethod.invoke( mavenExecutionRequest ); return Integer.valueOf( threadCount ); } catch ( Exception e ) { getLog().debug( "unable to get threadCount for the current build: " + e.getMessage() ); } return 1; } protected Date getBuildStartTime() { Date buildStartTime = null; try { Method getRequestMethod = session.getClass().getMethod( "getRequest" ); Object mavenExecutionRequest = getRequestMethod.invoke( session ); Method getStartTimeMethod = mavenExecutionRequest.getClass().getMethod( "getStartTime" ); buildStartTime = (Date) getStartTimeMethod.invoke( mavenExecutionRequest ); } catch ( Exception e ) { getLog().debug( "unable to get start time for the current build: " + e.getMessage() ); } if ( buildStartTime == null ) { return new Date(); } return buildStartTime; } private String getMemoryValue( String setting ) { String value = null; // Allow '128' or '128m' if ( isDigits( setting ) ) { value = setting + "m"; } else if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) ) && ( setting.toLowerCase().endsWith( "m" ) ) ) { value = setting; } return value; } //TODO remove the part with ToolchainManager lookup once we depend on //3.0.9 (have it as prerequisite). Define as regular component field then. protected final Toolchain getToolchain() { Toolchain tc = null; if ( jdkToolchain != null ) { // Maven 3.3.1 has plugin execution scoped Toolchain Support try { Method getToolchainsMethod = toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class, Map.class ); @SuppressWarnings( "unchecked" ) List tcs = (List) getToolchainsMethod.invoke( toolchainManager, session, "jdk", jdkToolchain ); if ( tcs != null && tcs.size() > 0 ) { tc = tcs.get( 0 ); } } catch ( NoSuchMethodException e ) { // ignore } catch ( SecurityException e ) { // ignore } catch ( IllegalAccessException e ) { // ignore } catch ( IllegalArgumentException e ) { // ignore } catch ( InvocationTargetException e ) { // ignore } } if ( tc == null ) { tc = toolchainManager.getToolchainFromBuildContext( "jdk", session ); } return tc; } private boolean isDigits( String string ) { for ( int i = 0; i < string.length(); i++ ) { if ( !Character.isDigit( string.charAt( i ) ) ) { return false; } } return true; } private Set computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler, SourceInclusionScanner scanner ) throws MojoExecutionException, CompilerException { SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler ); File outputDirectory; CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle(); if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) { outputDirectory = buildDirectory; } else { outputDirectory = getOutputDirectory(); } scanner.addSourceMapping( mapping ); Set staleSources = new HashSet(); for ( String sourceRoot : getCompileSourceRoots() ) { File rootFile = new File( sourceRoot ); if ( !rootFile.isDirectory() ) { continue; } try { staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) ); } catch ( InclusionScanException e ) { throw new MojoExecutionException( "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e ); } } return staleSources; } private SourceMapping getSourceMapping( CompilerConfiguration compilerConfiguration, Compiler compiler ) throws CompilerException, MojoExecutionException { CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle(); SourceMapping mapping; if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE ) { mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ), compiler.getOutputFileEnding( compilerConfiguration ) ); } else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) { mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ), compiler.getOutputFile( compilerConfiguration ) ); } else { throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." ); } return mapping; } /** * @todo also in ant plugin. This should be resolved at some point so that it does not need to * be calculated continuously - or should the plugins accept empty source roots as is? */ private static List removeEmptyCompileSourceRoots( List compileSourceRootsList ) { List newCompileSourceRootsList = new ArrayList(); if ( compileSourceRootsList != null ) { // copy as I may be modifying it for ( String srcDir : compileSourceRootsList ) { if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() ) { newCompileSourceRootsList.add( srcDir ); } } } return newCompileSourceRootsList; } /** * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own * generated classes and if we got a file which is >= the buid-started timestamp, then we catched a file which * got changed during this build. * * @return true if at least one single dependency has changed. */ protected boolean isDependencyChanged() { if ( session == null ) { // we just cannot determine it, so don't do anything beside logging getLog().info( "Cannot determine build start date, skipping incremental build detection." ); return false; } if ( fileExtensions == null || fileExtensions.isEmpty() ) { fileExtensions = new ArrayList(); fileExtensions.add( ".class" ); } Date buildStartTime = getBuildStartTime(); List pathElements = new ArrayList(); pathElements.addAll( getClasspathElements() ); pathElements.addAll( getModulepathElements() ); for ( String pathElement : pathElements ) { // ProjectArtifacts are artifacts which are available in the local project // that's the only ones we are interested in now. File artifactPath = new File( pathElement ); if ( artifactPath.isDirectory() ) { if ( hasNewFile( artifactPath, buildStartTime ) ) { getLog().debug( "New dependency detected: " + artifactPath.getAbsolutePath() ); return true; } } } // obviously there was no new file detected. return false; } /** * @param classPathEntry entry to check * @param buildStartTime time build start * @return if any changes occurred */ private boolean hasNewFile( File classPathEntry, Date buildStartTime ) { if ( !classPathEntry.exists() ) { return false; } if ( classPathEntry.isFile() ) { return classPathEntry.lastModified() >= buildStartTime.getTime() && fileExtensions.contains( FileUtils.getExtension( classPathEntry.getName() ) ); } File[] children = classPathEntry.listFiles(); for ( File child : children ) { if ( hasNewFile( child, buildStartTime ) ) { return true; } } return false; } private List resolveProcessorPathEntries() throws MojoExecutionException { if ( annotationProcessorPaths == null || annotationProcessorPaths.isEmpty() ) { return null; } try { Set requiredArtifacts = new LinkedHashSet(); for ( DependencyCoordinate coord : annotationProcessorPaths ) { ArtifactHandler handler = artifactHandlerManager.getArtifactHandler( coord.getType() ); Artifact artifact = new DefaultArtifact( coord.getGroupId(), coord.getArtifactId(), VersionRange.createFromVersionSpec( coord.getVersion() ), Artifact.SCOPE_RUNTIME, coord.getType(), coord.getClassifier(), handler, false ); requiredArtifacts.add( artifact ); } ArtifactResolutionRequest request = new ArtifactResolutionRequest() .setArtifact( requiredArtifacts.iterator().next() ) .setResolveRoot( true ) .setResolveTransitively( true ) .setArtifactDependencies( requiredArtifacts ) .setLocalRepository( session.getLocalRepository() ) .setRemoteRepositories( project.getRemoteArtifactRepositories() ); ArtifactResolutionResult resolutionResult = repositorySystem.resolve( request ); resolutionErrorHandler.throwErrors( request, resolutionResult ); List elements = new ArrayList( resolutionResult.getArtifacts().size() ); for ( Object resolved : resolutionResult.getArtifacts() ) { elements.add( ( (Artifact) resolved ).getFile().getAbsolutePath() ); } return elements; } catch ( Exception e ) { throw new MojoExecutionException( "Resolution of annotationProcessorPath dependencies failed: " + e.getLocalizedMessage(), e ); } } private void writePlugin( MessageBuilder mb ) { mb.a( " " ).newline(); mb.a( " org.apache.maven.plugins" ).newline(); mb.a( " maven-compiler-plugin" ).newline(); String version = getMavenCompilerPluginVersion(); if ( version != null ) { mb.a( " " ).a( version ).a( "" ).newline(); } writeConfig( mb ); mb.a( " " ).newline(); } private void writeConfig( MessageBuilder mb ) { mb.a( " " ).newline(); if ( release != null ) { mb.a( " " ).a( release ).a( "" ).newline(); } else if ( JavaVersion.JAVA_VERSION.isAtLeast( "9" ) ) { String rls = target.replaceAll( ".\\.", "" ); // when using Java9+, motivate to use release instead of source/target mb.a( " " ).a( rls ).a( "" ).newline(); } else { mb.a( " " ).a( source ).a( "" ).newline(); mb.a( " " ).a( target ).a( "" ).newline(); } mb.a( " " ).newline(); } private String getMavenCompilerPluginVersion() { Properties pomProperties = new Properties(); try ( InputStream is = AbstractCompilerMojo.class .getResourceAsStream( "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties" ) ) { if ( is != null ) { pomProperties.load( is ); } } catch ( IOException e ) { // noop } return pomProperties.getProperty( "version" ); } public void setTarget( String target ) { this.target = target; targetOrReleaseSet = true; } public void setRelease( String release ) { this.release = release; targetOrReleaseSet = true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy