Skip to content

Commit a0b689e

Browse files
authored
[MCOMPILER-578] Track outputs across compiler executions (#1091)
* [MCOMPILER-578] Add regression test for repeated compiler executions Run clean compile followed by compile and verify that Java 8 classes and the Java 11 module descriptor retain their configured class-file versions. The test exposes the incremental compilation failure before the production fix. * [MCOMPILER-578] Track outputs across compiler executions Record the last compiler execution that processed each mapped output in the shared plugin context. Recompile when a different execution overlaps that output while allowing repeated invocations of the same execution to remain up to date. Fixes #788
1 parent 2e81228 commit a0b689e

9 files changed

Lines changed: 517 additions & 1 deletion

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
invoker.java.version = 17+
19+
invoker.goals = clean compile
20+
invoker.goals.2 = compile

src/it/MCOMPILER-578/pom.xml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
21+
<project xmlns="http://maven.apache.org/POM/4.0.0"
22+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
24+
<modelVersion>4.0.0</modelVersion>
25+
26+
<groupId>org.apache.maven.plugins.compiler.it</groupId>
27+
<artifactId>mcompiler-578</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
30+
<properties>
31+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
32+
</properties>
33+
34+
<build>
35+
<plugins>
36+
<plugin>
37+
<groupId>org.apache.maven.plugins</groupId>
38+
<artifactId>maven-compiler-plugin</artifactId>
39+
<version>@project.version@</version>
40+
<configuration>
41+
<createMissingPackageInfoClass>false</createMissingPackageInfoClass>
42+
</configuration>
43+
<executions>
44+
<execution>
45+
<id>default-compile</id>
46+
<configuration>
47+
<release>${java.vm.specification.version}</release>
48+
</configuration>
49+
</execution>
50+
<execution>
51+
<id>base-modules-compile</id>
52+
<goals>
53+
<goal>compile</goal>
54+
</goals>
55+
<configuration>
56+
<release>11</release>
57+
<includes>
58+
<include>module-info.java</include>
59+
</includes>
60+
</configuration>
61+
</execution>
62+
<execution>
63+
<id>base-compile</id>
64+
<goals>
65+
<goal>compile</goal>
66+
</goals>
67+
<configuration>
68+
<release>8</release>
69+
<excludes>
70+
<exclude>module-info.java</exclude>
71+
</excludes>
72+
</configuration>
73+
</execution>
74+
</executions>
75+
</plugin>
76+
</plugins>
77+
</build>
78+
</project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
module org.example {
20+
exports org.example;
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.example;
20+
21+
public class Example {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.example;

src/it/MCOMPILER-578/verify.groovy

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
def majorVersion = { File classFile ->
21+
assert classFile.isFile()
22+
byte[] bytes = classFile.bytes
23+
assert bytes.length >= 8
24+
((bytes[6] & 0xFF) << 8) | (bytes[7] & 0xFF)
25+
}
26+
27+
def exampleClass = new File( basedir, 'target/classes/org/example/Example.class' )
28+
def exampleMajorVersion = majorVersion( exampleClass )
29+
// major_version: 52 = Java 8, from the base-compile execution.
30+
assert exampleMajorVersion == 52
31+
32+
def moduleInfoClass = new File( basedir, 'target/classes/module-info.class' )
33+
def moduleInfoMajorVersion = majorVersion( moduleInfoClass )
34+
// major_version: 55 = Java 11, from the base-modules-compile execution.
35+
assert moduleInfoMajorVersion == 55

src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,9 @@ private void executeReal() throws MojoExecutionException, CompilationFailureExce
956956

957957
final Set<File> sources;
958958

959+
Set<Path> outputs = Collections.emptySet();
960+
String compilerExecution = mojoExecution.getGoal() + '@' + mojoExecution.getExecutionId();
961+
959962
IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
960963

961964
if (useIncrementalCompilation) {
@@ -967,6 +970,9 @@ private void executeReal() throws MojoExecutionException, CompilationFailureExce
967970

968971
preparePaths(sources);
969972

973+
// Expected output paths let us detect overwrites that IncrementalBuildHelper cannot see.
974+
outputs = getOutputPaths(compilerConfiguration, compiler, sources);
975+
970976
incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles(sources);
971977

972978
// Strategies used to detect modifications.
@@ -980,9 +986,18 @@ private void executeReal() throws MojoExecutionException, CompilationFailureExce
980986
String inputFileTreeChanged = hasInputFileTreeChanged(incrementalBuildHelper, sources)
981987
? "added or removed source files"
982988
: null;
989+
// A different execution may have overwritten an otherwise up-to-date output.
990+
String outputChanged = hasPreviouslyCompiledOutput(compilerExecution, outputs)
991+
? "output from another compiler execution"
992+
: null;
983993

984994
// Get the first cause for the rebuild compilation detection.
985-
String cause = Stream.of(immutableOutputFile, dependencyChanged, sourceChanged, inputFileTreeChanged)
995+
String cause = Stream.of(
996+
immutableOutputFile,
997+
dependencyChanged,
998+
sourceChanged,
999+
inputFileTreeChanged,
1000+
outputChanged)
9861001
.filter(Objects::nonNull)
9871002
.findFirst()
9881003
.orElse(null);
@@ -1293,6 +1308,8 @@ private void executeReal() throws MojoExecutionException, CompilationFailureExce
12931308
getLog().debug(
12941309
"skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist");
12951310
}
1311+
// Make these outputs visible to later compiler executions in this Maven session.
1312+
recordCompiledOutputs(compilerExecution, outputs);
12961313
}
12971314

12981315
List<CompilerMessage> warnings = new ArrayList<>();
@@ -1617,6 +1634,56 @@ private Set<File> computeStaleSources(
16171634
return staleSources;
16181635
}
16191636

1637+
/**
1638+
* Maps selected sources to their expected compiler outputs.
1639+
*
1640+
* @param compilerConfiguration the compiler configuration
1641+
* @param compiler the selected compiler
1642+
* @param sources sources selected by the current execution
1643+
* @return normalized absolute output paths
1644+
*/
1645+
private Set<Path> getOutputPaths(CompilerConfiguration compilerConfiguration, Compiler compiler, Set<File> sources)
1646+
throws CompilerException, MojoExecutionException {
1647+
SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler);
1648+
1649+
File outputDirectory =
1650+
compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES
1651+
? buildDirectory
1652+
: getOutputDirectory();
1653+
try {
1654+
return CompilationOutputRegistry.mapOutputs(mapping, outputDirectory, getCompileSourceRoots(), sources);
1655+
} catch (InclusionScanException e) {
1656+
throw new MojoExecutionException("Error mapping sources to their outputs.", e);
1657+
}
1658+
}
1659+
1660+
/**
1661+
* Checks whether a different compiler execution last processed an expected output.
1662+
*
1663+
* @param compilerExecution the current execution
1664+
* @param outputs expected outputs of the current execution
1665+
* @return whether an output overlaps with another execution
1666+
*/
1667+
private boolean hasPreviouslyCompiledOutput(String compilerExecution, Set<Path> outputs) {
1668+
Optional<Path> output = CompilationOutputRegistry.find(getPluginContext(), compilerExecution, outputs);
1669+
if (output.isPresent() && showCompilationChanges) {
1670+
getLog().info("\tOutput from another compiler execution: " + output.get());
1671+
} else if (output.isPresent()) {
1672+
getLog().debug("\tOutput from another compiler execution: " + output.get());
1673+
}
1674+
return output.isPresent();
1675+
}
1676+
1677+
/**
1678+
* Registers the current execution as the last processor of its expected outputs.
1679+
*
1680+
* @param compilerExecution the current execution
1681+
* @param outputs expected outputs of the current execution
1682+
*/
1683+
private void recordCompiledOutputs(String compilerExecution, Set<Path> outputs) {
1684+
CompilationOutputRegistry.register(getPluginContext(), compilerExecution, outputs);
1685+
}
1686+
16201687
private SourceMapping getSourceMapping(CompilerConfiguration compilerConfiguration, Compiler compiler)
16211688
throws CompilerException, MojoExecutionException {
16221689
CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();

0 commit comments

Comments
 (0)