diff --git a/src/it/MCOMPILER-275_separate-moduleinfo/verify.groovy b/src/it/MCOMPILER-275_separate-moduleinfo/verify.groovy
index baf07f7b..3bc4b84f 100644
--- a/src/it/MCOMPILER-275_separate-moduleinfo/verify.groovy
+++ b/src/it/MCOMPILER-275_separate-moduleinfo/verify.groovy
@@ -20,7 +20,7 @@ def log = new File( basedir, 'build.log').text
assert log.count( "[INFO] Toolchain in maven-compiler-plugin: JDK" ) == 1
-assert log.count( "[INFO] Changes detected - recompiling the module!" ) == 3
+assert log.count( "[INFO] Changes detected - recompiling the module" ) == 3
// major_version: 52 = java8 -> execution id "base-compile"
assert new File( basedir, 'target/classes/com/foo/MyClass.class' ).bytes[7] == 52
diff --git a/src/it/MCOMPILER-500-package-info-incr/verify.groovy b/src/it/MCOMPILER-500-package-info-incr/verify.groovy
index ecba9c29..987364f8 100644
--- a/src/it/MCOMPILER-500-package-info-incr/verify.groovy
+++ b/src/it/MCOMPILER-500-package-info-incr/verify.groovy
@@ -21,7 +21,7 @@ def logFile = new File( basedir, 'build.log' )
assert logFile.exists()
content = logFile.text
-assert 1 == content.count( 'Changes detected - recompiling the module!' )
+assert 1 == content.count( 'Changes detected - recompiling the module' )
assert 1 == content.count( 'Nothing to compile - all classes are up to date' )
diff --git a/src/it/MCOMPILER-525/invoker.properties b/src/it/MCOMPILER-525/invoker.properties
new file mode 100644
index 00000000..da0f0927
--- /dev/null
+++ b/src/it/MCOMPILER-525/invoker.properties
@@ -0,0 +1,21 @@
+# 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.
+
+# NOTE: The first time, we run up to "integration-test" phase which includes the AntRun execution which saves the
+# timestamp of the first JAR for comparison with the timestamp of the JAR from the final "package" invocation.
+# Note:
+invoker.goals = clean integration-test package -Dmaven.compiler.showCompilationChanges=true
diff --git a/src/it/MCOMPILER-525/pom.xml b/src/it/MCOMPILER-525/pom.xml
new file mode 100644
index 00000000..48de91e9
--- /dev/null
+++ b/src/it/MCOMPILER-525/pom.xml
@@ -0,0 +1,64 @@
+
+
+
+ 4.0.0
+ org.apache.maven.plugins
+ MCOMPILER-525-no-recreation
+ MCOMPILER-525-no-recreation
+ jar
+ 1.0-SNAPSHOT
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ @project.version@
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ 3.1.0
+
+
+ touch
+ integration-test
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/it/MCOMPILER-525/src/main/java/myproject/HelloWorld.java b/src/it/MCOMPILER-525/src/main/java/myproject/HelloWorld.java
new file mode 100644
index 00000000..05e16e02
--- /dev/null
+++ b/src/it/MCOMPILER-525/src/main/java/myproject/HelloWorld.java
@@ -0,0 +1,36 @@
+package myproject;
+
+/*
+ * 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.
+ */
+
+/**
+ * The classic Hello World App.
+ */
+public class HelloWorld {
+
+ /**
+ * Main method.
+ *
+ * @param args Not used
+ */
+ public static void main( String[] args )
+ {
+ System.out.println( "Hi!" );
+ }
+}
\ No newline at end of file
diff --git a/src/it/MCOMPILER-525/verify.groovy b/src/it/MCOMPILER-525/verify.groovy
new file mode 100644
index 00000000..0e97225d
--- /dev/null
+++ b/src/it/MCOMPILER-525/verify.groovy
@@ -0,0 +1,44 @@
+
+/*
+ * 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.nio.file.*;
+import java.time.*;
+import java.time.temporal.*
+import java.util.*;
+
+File target = new File( basedir, "target" );
+assert target.isDirectory()
+
+File jarFile = new File( target, "MCOMPILER-525-no-recreation-1.0-SNAPSHOT.jar" );
+assert jarFile.isFile()
+
+File refFile = new File( target, "reference.jar" );
+assert refFile.isFile()
+
+Instant referenceTimestamp = Files.getLastModifiedTime( refFile.toPath() )
+ .toInstant().truncatedTo( ChronoUnit.MILLIS );
+System.out.println( "Reference timestamp: " + referenceTimestamp );
+
+Instant actualTimestamp = Files.getLastModifiedTime( jarFile.toPath() )
+ .toInstant().truncatedTo( ChronoUnit.MILLIS );
+System.out.println( "Actual timestamp : " + actualTimestamp );
+
+assert referenceTimestamp.equals(actualTimestamp),
+ "Timestamps don't match, JAR was recreated although contents has not changed"
diff --git a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java
index 03cd6af1..ad832c60 100644
--- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java
+++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java
@@ -28,18 +28,24 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-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.Objects;
+import java.util.Optional;
import java.util.Properties;
import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.apache.commons.io.FilenameUtils;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.execution.MavenExecutionRequest;
@@ -180,29 +186,33 @@ public abstract class AbstractCompilerMojo
private boolean showWarnings;
/**
- *
The -source argument for the Java compiler.
+ * The {@code -source} argument for the Java compiler.
*
- * NOTE:
- * Since 3.8.0 the default value has changed from 1.5 to 1.6
- * Since 3.9.0 the default value has changed from 1.6 to 1.7
- * Since 3.11.0 the default value has changed from 1.7 to 1.8
+ * NOTE:
+ *
+ * - Since 3.8.0 the default value has changed from 1.5 to 1.6
+ * - Since 3.9.0 the default value has changed from 1.6 to 1.7
+ * - Since 3.11.0 the default value has changed from 1.7 to 1.8
+ *
*/
@Parameter( property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE )
protected String source;
/**
- * The -target argument for the Java compiler.
+ * The {@code -target} argument for the Java compiler.
*
- * NOTE:
- * Since 3.8.0 the default value has changed from 1.5 to 1.6
- * Since 3.9.0 the default value has changed from 1.6 to 1.7
- * Since 3.11.0 the default value has changed from 1.7 to 1.8
+ * NOTE:
+ *
+ * - Since 3.8.0 the default value has changed from 1.5 to 1.6
+ * - Since 3.9.0 the default value has changed from 1.6 to 1.7
+ * - Since 3.11.0 the default value has changed from 1.7 to 1.8
+ *
*/
@Parameter( property = "maven.compiler.target", defaultValue = DEFAULT_TARGET )
protected String target;
/**
- * The -release argument for the Java compiler, supported since Java9
+ * The {@code -release} argument for the Java compiler, supported since Java 9.
*
* @since 3.6
*/
@@ -210,7 +220,7 @@ public abstract class AbstractCompilerMojo
protected String release;
/**
- * The -encoding argument for the Java compiler.
+ * The {@code -encoding} argument for the Java compiler.
*
* @since 2.1
*/
@@ -414,7 +424,7 @@ public abstract class AbstractCompilerMojo
/**
* Keyword to be appended to the -implicit:
command-line switch.
*
- * @since 3.10.2
+ * @since 3.11.0
*/
@Parameter( property = "maven.compiler.implicit" )
private String implicit;
@@ -531,12 +541,11 @@ public abstract class AbstractCompilerMojo
/**
* File extensions to check timestamp for incremental build.
- * Default contains only class
and jar
.
*
* @since 3.1
*/
- @Parameter
- private List fileExtensions;
+ @Parameter( defaultValue = "class,jar" )
+ private Set fileExtensions;
/**
* to enable/disable incremental compilation feature.
@@ -886,30 +895,38 @@ else if ( CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy().eq
incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles( sources );
- DirectoryScanResult dsr = computeInputFileTreeChanges( incrementalBuildHelper, sources );
+ Supplier dsr =
+ () -> computeInputFileTreeChanges( incrementalBuildHelper, sources );
+
+ Supplier idk = () -> ( compiler.getCompilerOutputStyle()
+ .equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) && !canUpdateTarget )
+ ? "idk" : null;
+ Supplier dependencyChanged = () -> isDependencyChanged()
+ ? "dependency" : null;
+ Supplier sourceChanged = () -> isSourceChanged( compilerConfiguration, compiler )
+ ? "source" : null;
+ Supplier inputFileTreeChanged = () -> hasInputFileTreeChanged( dsr.get() )
+ ? "input tree" : null;
+
+ // Lazy evaluation of the incremental compilation detection.
+ String cause = Stream.of( idk, dependencyChanged, sourceChanged, inputFileTreeChanged )
+ .map( Supplier::get )
+ .filter( Objects::nonNull )
+ .findFirst().orElse( null );
- boolean idk = compiler.getCompilerOutputStyle()
- .equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) && !canUpdateTarget;
- boolean dependencyChanged = isDependencyChanged();
- boolean sourceChanged = isSourceChanged( compilerConfiguration, compiler );
- boolean inputFileTreeChanged = hasInputFileTreeChanged( dsr );
// CHECKSTYLE_OFF: LineLength
- if ( idk
- || dependencyChanged
- || sourceChanged
- || inputFileTreeChanged )
+ if ( cause != null )
// CHECKSTYLE_ON: LineLength
{
- String cause = idk ? "idk" : ( dependencyChanged ? "dependency"
- : ( sourceChanged ? "source" : "input tree" ) );
- getLog().info( "Changes detected - recompiling the module! :" + cause );
+ getLog().info( "Changes detected - recompiling the module due to: " + cause );
if ( showCompilationChanges )
{
- for ( String fileAdded : dsr.getFilesAdded() )
+ DirectoryScanResult inputTreeChanges = dsr.get();
+ for ( String fileAdded : inputTreeChanges.getFilesAdded() )
{
getLog().info( "\t+ " + fileAdded );
}
- for ( String fileRemoved : dsr.getFilesRemoved() )
+ for ( String fileRemoved : inputTreeChanges.getFilesRemoved() )
{
getLog().info( "\t- " + fileRemoved );
}
@@ -1274,7 +1291,8 @@ else if ( !values[0].equals( descriptor.name() ) )
if ( useIncrementalCompilation )
{
- if ( incrementalBuildHelperRequest.getOutputDirectory().exists() )
+ Path outputDirectory = incrementalBuildHelperRequest.getOutputDirectory().toPath();
+ if ( Files.exists( outputDirectory ) )
{
getLog().debug( "incrementalBuildHelper#afterRebuildExecution" );
// now scan the same directory again and create a diff
@@ -1513,10 +1531,19 @@ private Set getCompileSources( Compiler compiler, CompilerConfiguration co
* @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 ) );
+ Set staleSources = Collections.emptySet();
+ try
+ {
+ staleSources = computeStaleSources( compilerConfiguration, compiler,
+ getSourceInclusionScanner( staleMillis ) );
+ }
+ catch ( MojoExecutionException | CompilerException ex )
+ {
+ // we cannot get the mojo status dir, so don't do anything beside logging
+ getLog().warn( "Cannot detect stale sources." );
+ return false;
+ }
if ( getLog().isDebugEnabled() || showCompilationChanges )
{
@@ -1546,11 +1573,12 @@ protected int getRequestThreadCount()
return session.getRequest().getDegreeOfConcurrency();
}
- protected Date getBuildStartTime()
+ private Optional getBuildStartTime()
{
- MavenExecutionRequest request = session.getRequest();
- Date buildStartTime = request == null ? new Date() : request.getStartTime();
- return buildStartTime == null ? new Date() : buildStartTime;
+ return Optional.ofNullable( session.getRequest() )
+ .map( MavenExecutionRequest::getStartTime )
+ .map( java.util.Date::toInstant )
+ .map( i -> i.truncatedTo( ChronoUnit.MILLIS ) );
}
@@ -1717,45 +1745,51 @@ private static List removeEmptyCompileSourceRoots( List compileS
* generated classes and if we got a file which is >= the build-started timestamp, then we caught a file which
* got changed during this build.
*
- * @return true
if at least one single dependency has changed.
+ * @return <{@code true} if at least one single dependency has changed.
*/
- protected boolean isDependencyChanged()
+ private boolean isDependencyChanged()
{
- if ( session == null )
+ final Instant buildStartTime = getBuildStartTime().orElse( null );
+ if ( buildStartTime == null )
{
// we just cannot determine it, so don't do anything beside logging
- getLog().info( "Cannot determine build start date, skipping incremental build detection." );
+ getLog().info( "Cannot determine build start time, skipping incremental build detection." );
return false;
}
if ( fileExtensions == null || fileExtensions.isEmpty() )
{
- fileExtensions = Collections.unmodifiableList( Arrays.asList( "class", "jar" ) );
+ fileExtensions = new HashSet<>( Arrays.asList( "class", "jar" ) );
}
- Date buildStartTime = getBuildStartTime();
-
List pathElements = new ArrayList<>();
pathElements.addAll( getClasspathElements() );
pathElements.addAll( getModulepathElements() );
for ( String pathElement : pathElements )
{
- File artifactPath = new File( pathElement );
- if ( artifactPath.isDirectory() || artifactPath.isFile() )
+ Path artifactPath = Paths.get( pathElement );
+
+ // Search files only on dependencies (other modules), not on the current project,
+ if ( Files.isDirectory( artifactPath ) && !artifactPath.equals( getOutputDirectory().toPath() ) )
{
- if ( hasNewFile( artifactPath, buildStartTime ) )
+ try ( Stream walk = Files.walk( artifactPath ) )
{
- if ( showCompilationChanges )
- {
- getLog().info( "New dependency detected: " + artifactPath.getAbsolutePath() );
- }
- else
+ if ( walk.anyMatch( p -> hasNewFile( p, buildStartTime ) ) )
{
- getLog().debug( "New dependency detected: " + artifactPath.getAbsolutePath() );
+ return true;
}
- return true;
}
+ catch ( IOException ex )
+ {
+ // we just cannot determine it, so don't do anything beside logging
+ getLog().warn( "I/O error walking the path: " + ex.getMessage() );
+ return false;
+ }
+ }
+ else if ( hasNewFile( artifactPath, buildStartTime ) )
+ {
+ return true;
}
}
@@ -1767,27 +1801,35 @@ protected boolean isDependencyChanged()
* @param classPathEntry entry to check
* @param buildStartTime time build start
* @return if any changes occurred
+ * @throws IOException
*/
- private boolean hasNewFile( File classPathEntry, Date buildStartTime )
+ private boolean hasNewFile( Path classPathEntry, Instant 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 ( Files.isRegularFile( classPathEntry )
+ && fileExtensions.contains( FilenameUtils.getExtension( classPathEntry.toString() ) ) )
{
- if ( hasNewFile( child, buildStartTime ) )
+ try
{
- return true;
+ Instant lastModifiedTime = Files.getLastModifiedTime( classPathEntry ).toInstant()
+ .truncatedTo( ChronoUnit.MILLIS );
+ boolean isChanged = lastModifiedTime.isAfter( buildStartTime );
+ if ( isChanged )
+ {
+ if ( showCompilationChanges )
+ {
+ getLog().info( "New dependency detected: " + classPathEntry.toAbsolutePath() );
+ }
+ else
+ {
+ getLog().debug( "New dependency detected: " + classPathEntry.toAbsolutePath() );
+ }
+ }
+ return isChanged;
+ }
+ catch ( IOException ex )
+ {
+ // we just cannot determine it, so don't do anything beside logging
+ getLog().warn( "I/O error reading the lastModifiedTime: " + ex.getMessage() );
}
}
@@ -1902,9 +1944,18 @@ private String getMavenCompilerPluginVersion()
}
private DirectoryScanResult computeInputFileTreeChanges( IncrementalBuildHelper ibh, Set inputFiles )
- throws MojoExecutionException
{
- File mojoConfigBase = ibh.getMojoStatusDirectory();
+ File mojoConfigBase = null;
+ try
+ {
+ mojoConfigBase = ibh.getMojoStatusDirectory();
+ }
+ catch ( MojoExecutionException e )
+ {
+ // we cannot get the mojo status dir, so don't do anything beside logging
+ getLog().warn( "Error reading mojo status directory." );
+ return new DirectoryScanResult( new String[0], new String[0] );
+ }
File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
String[] oldInputFiles = new String[0];
@@ -1917,7 +1968,9 @@ private DirectoryScanResult computeInputFileTreeChanges( IncrementalBuildHelper
}
catch ( IOException e )
{
- throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
+ // we cannot read the mojo config file, so don't do anything beside logging
+ getLog().warn( "Error while reading old mojo status: " + mojoConfigFile );
+ return new DirectoryScanResult( new String[0], new String[0] );
}
}
@@ -1931,7 +1984,9 @@ private DirectoryScanResult computeInputFileTreeChanges( IncrementalBuildHelper
}
catch ( IOException e )
{
- throw new MojoExecutionException( "Error while storing the mojo status", e );
+ // we cannot write the mojo config file, so don't do anything beside logging
+ getLog().warn( "Error while storing old mojo status: " + mojoConfigFile );
+ return new DirectoryScanResult( new String[0], new String[0] );
}
return dsr;