diff --git a/src/it/MCOMPILER-561/invoker.properties b/src/it/MCOMPILER-561/invoker.properties new file mode 100644 index 00000000..e08ae5c7 --- /dev/null +++ b/src/it/MCOMPILER-561/invoker.properties @@ -0,0 +1,22 @@ +# 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. + +invoker.debug = false +invoker.java.version = 11+ +invoker.goals = compile -Dmaven.compiler.release=8 -Dmaven.compiler.showCompilationChanges=true -ntp +invoker.goals.2 = compile -Dmaven.compiler.release=11 -Dmaven.compiler.showCompilationChanges=true -ntp +invoker.goals.3 = compile -Dmaven.compiler.release=9 -Dmaven.compiler.showCompilationChanges=true -ntp diff --git a/src/it/MCOMPILER-561/pom.xml b/src/it/MCOMPILER-561/pom.xml new file mode 100644 index 00000000..55e875f6 --- /dev/null +++ b/src/it/MCOMPILER-561/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + org.apache.maven.plugins + MCOMPILER-561 + MCOMPILER-561 + jar + 1.0-SNAPSHOT + + + UTF-8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @project.version@ + + + + diff --git a/src/it/MCOMPILER-561/src/main/java/myproject/HelloWorld.java b/src/it/MCOMPILER-561/src/main/java/myproject/HelloWorld.java new file mode 100644 index 00000000..8318e9fb --- /dev/null +++ b/src/it/MCOMPILER-561/src/main/java/myproject/HelloWorld.java @@ -0,0 +1,34 @@ +/* + * 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. + */ +package myproject; + +/** + * The classic Hello World App. + */ +public class HelloWorld { + + /** + * Main method. + * + * @param args Not used + */ + public static void main(String[] args) { + System.out.println("Hi!"); + } +} diff --git a/src/it/MCOMPILER-561/verify.groovy b/src/it/MCOMPILER-561/verify.groovy new file mode 100644 index 00000000..0eb2283b --- /dev/null +++ b/src/it/MCOMPILER-561/verify.groovy @@ -0,0 +1,27 @@ + +/* + * 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. + */ + +def log = new File(basedir, 'build.log').text + +assert log.count('[INFO] Recompiling the module because of changed source code.') == 1 +assert log.count('[INFO] Recompiling the module because of bytecode version changed.') == 2 + +assert log.count('from 8 to 11') == 1 +assert log.count('from 11 to 9') == 1 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 fb285afb..552b8e33 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.Charset; @@ -43,6 +44,8 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,6 +83,7 @@ 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.JavaClassfileVersion; import org.codehaus.plexus.languages.java.version.JavaVersion; import org.codehaus.plexus.util.FileUtils; import org.eclipse.aether.RepositorySystem; @@ -906,12 +910,17 @@ public void execute() throws MojoExecutionException, CompilationFailureException String inputFileTreeChanged = hasInputFileTreeChanged(incrementalBuildHelper, sources) ? "added or removed source files" : null; + Supplier bytecodeChanged = () -> hasBytecodeChanged() ? "bytecode version changed" : null; // Get the first cause for the rebuild compilation detection. String cause = Stream.of(immutableOutputFile, dependencyChanged, sourceChanged, inputFileTreeChanged) .filter(Objects::nonNull) .findFirst() - .orElse(null); + .orElseGet(() -> Stream.of(bytecodeChanged) + .map(Supplier::get) + .filter(Objects::nonNull) + .findFirst() + .orElse(null)); if (cause != null) { getLog().info("Recompiling the module because of " @@ -1842,6 +1851,48 @@ private boolean hasInputFileTreeChanged(IncrementalBuildHelper ibh, Set in return inputTreeChanges.hasChanged(); } + private static final int MAX_FILE_WALK_LIMIT = 20; + + /** + * Performs a check on compiled class files to ensure that the bytecode version + * hasn't changed between runs. + * + *

This is limited to check a maximum of {@link #MAX_FILE_WALK_LIMIT}. + * + * @return true if a bytecode version differs from the actual release version. + */ + private boolean hasBytecodeChanged() { + String currentVersion = getVersionRelease(); + JavaVersion javaVersion = JavaVersion.parse(currentVersion).asMajor(); + + try (Stream walk = Files.walk(getOutputDirectory().toPath())) { + Map pathVersionMap = walk.filter(file -> "class" + .equals(FileUtils.extension(file.getFileName().toString()))) + .limit(MAX_FILE_WALK_LIMIT) + .collect(Collectors.toMap(Function.identity(), JavaClassfileVersion::of)); + for (Map.Entry entry : pathVersionMap.entrySet()) { + Path path = entry.getKey(); + JavaClassfileVersion classFileVersion = entry.getValue(); + JavaVersion javaFileVersion = classFileVersion.javaVersion().asMajor(); + if (enablePreview != classFileVersion.isPreview() || javaFileVersion.compareTo(javaVersion) != 0) { + if (getLog().isDebugEnabled() || showCompilationChanges) { + getLog().info(String.format( + "\tBytecode file change: %s from %s to %s", path, javaFileVersion, javaVersion)); + } + return true; + } + } + return false; + } catch (UncheckedIOException | IOException e) { + getLog().warn("Error reading bytecode version", e); + return false; + } + } + + private String getVersionRelease() { + return getRelease() != null ? getRelease() : getTarget() != null ? getTarget() : DEFAULT_TARGET; + } + public void setTarget(String target) { this.target = target; targetOrReleaseSet = true;