Skip to content

Commit 2e81228

Browse files
authored
Fix incremental detection of empty sources, 3.x (#1075)
* Fix incremental detection of empty sources The stale source scanner assumes every Java source produces a mapped output, causing zero-byte compilation units to trigger recompilation on every invocation. Filter zero-byte stale candidates only for one-output-per-input compilers and only when no mapped output exists. This preserves detection when an existing source is truncated and leaves aggregate-output compilers unchanged. Add a regression test that compiles an empty source twice and verifies that the second invocation is up to date. Fixes #1000.
1 parent 2132f5b commit 2e81228

3 files changed

Lines changed: 83 additions & 2 deletions

File tree

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,8 +1585,30 @@ private Set<File> computeStaleSources(
15851585
}
15861586

15871587
try {
1588-
staleSources.addAll(scanner.getIncludedSources(rootFile, outputDirectory));
1589-
} catch (InclusionScanException e) {
1588+
Set<File> includedSources = scanner.getIncludedSources(rootFile, outputDirectory);
1589+
// The stale source scanner assumes that every source produces an output file. Filter its result only
1590+
// when the compiler provides an individual source-to-output mapping; aggregate outputs are ambiguous.
1591+
if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
1592+
for (File source : includedSources) {
1593+
if (Files.size(source.toPath()) != 0) {
1594+
staleSources.add(source);
1595+
} else {
1596+
String relativePath = rootFile.toPath()
1597+
.relativize(source.toPath())
1598+
.toString();
1599+
boolean outputExists = mapping.getTargetFiles(outputDirectory, relativePath).stream()
1600+
.anyMatch(File::exists);
1601+
// A zero-byte compilation unit legitimately produces no class. Keep it stale if an output
1602+
// exists, however, so that truncating an existing source is still detected as a change.
1603+
if (outputExists) {
1604+
staleSources.add(source);
1605+
}
1606+
}
1607+
}
1608+
} else {
1609+
staleSources.addAll(includedSources);
1610+
}
1611+
} catch (InclusionScanException | IOException e) {
15901612
throw new MojoExecutionException(
15911613
"Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e);
15921614
}

src/test/java/org/apache/maven/plugin/compiler/CompilerMojoTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.maven.plugin.compiler;
2020

2121
import java.io.File;
22+
import java.nio.file.Files;
2223
import java.util.Arrays;
2324
import java.util.HashSet;
2425
import java.util.Set;
@@ -43,6 +44,7 @@
4344
import static org.junit.jupiter.api.Assertions.assertTrue;
4445
import static org.junit.jupiter.api.Assertions.fail;
4546
import static org.mockito.ArgumentMatchers.startsWith;
47+
import static org.mockito.Mockito.clearInvocations;
4648
import static org.mockito.Mockito.mock;
4749
import static org.mockito.Mockito.never;
4850
import static org.mockito.Mockito.verify;
@@ -135,6 +137,27 @@ void testCompilerEmptySource(CompilerMojo compilerMojo) throws Exception {
135137
projectArtifact.getFile(), "MCOMPILER-94: artifact file should be null if there is nothing to compile");
136138
}
137139

140+
/**
141+
* Tests that an empty source file does not cause compilation every time because it has no class file.
142+
*/
143+
@Test
144+
@InjectMojo(goal = COMPILE, pom = "classpath:/unit/compiler-empty-source-change-detection-test/plugin-config.xml")
145+
void testCompilerEmptySourceChangeDetection(CompilerMojo compilerMojo) throws Exception {
146+
setUpCompilerMojoTestEnv(compilerMojo);
147+
148+
File source = new File(compilerMojo.getCompileSourceRoots().get(0), "Empty.java");
149+
Files.createDirectories(source.getParentFile().toPath());
150+
Files.write(source.toPath(), new byte[0]);
151+
152+
Log log = mock(Log.class);
153+
compilerMojo.setLog(log);
154+
compilerMojo.execute();
155+
156+
clearInvocations(log);
157+
compilerMojo.execute();
158+
verify(log).info("Nothing to compile - all classes are up to date.");
159+
}
160+
138161
/**
139162
* tests the ability of the plugin to respond to includes and excludes correctly
140163
*
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
<project>
21+
<build>
22+
<plugins>
23+
<plugin>
24+
<artifactId>maven-compiler-plugin</artifactId>
25+
<configuration>
26+
<compileSourceRoots>
27+
<compileSourceRoot>${basedir}/target/test-classes/unit/compiler-empty-source-change-detection-test/src/main/java</compileSourceRoot>
28+
</compileSourceRoots>
29+
<compilerId>javac</compilerId>
30+
<outputDirectory>${basedir}/target/test/unit/compiler-empty-source-change-detection-test/target/classes</outputDirectory>
31+
<buildDirectory>${basedir}/target/test/unit/compiler-empty-source-change-detection-test/target</buildDirectory>
32+
</configuration>
33+
</plugin>
34+
</plugins>
35+
</build>
36+
</project>

0 commit comments

Comments
 (0)