You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sj...@apache.org on 2026/08/25 21:21:42 UTC
(maven-compiler-plugin) branch maven-compiler-plugin-3.x updated: [MCOMPILER-578] Track outputs across compiler executions (#1091)
This is an automated email from the ASF dual-hosted git repository.
slawekjaranowski pushed a commit to branch maven-compiler-plugin-3.x
in repository https://gitbox.apache.org/repos/asf/maven-compiler-plugin.git
The following commit(s) were added to refs/heads/maven-compiler-plugin-3.x by this push:
new a0b689e [MCOMPILER-578] Track outputs across compiler executions (#1091)
a0b689e is described below
commit a0b689e0e7f13d3a7dded7847a807fe6453e0358
Author: Václav Haisman <vh...@gmail.com>
AuthorDate: Tue Aug 25 23:21:36 2026 +0200
[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
---
src/it/MCOMPILER-578/invoker.properties | 20 +++
src/it/MCOMPILER-578/pom.xml | 78 +++++++++++
.../MCOMPILER-578/src/main/java/module-info.java | 21 +++
.../src/main/java/org/example/Example.java | 21 +++
.../src/main/java/org/example/package-info.java | 19 +++
src/it/MCOMPILER-578/verify.groovy | 35 +++++
.../plugin/compiler/AbstractCompilerMojo.java | 69 +++++++++-
.../plugin/compiler/CompilationOutputRegistry.java | 150 +++++++++++++++++++++
.../compiler/CompilationOutputRegistryTest.java | 105 +++++++++++++++
9 files changed, 517 insertions(+), 1 deletion(-)
diff --git a/src/it/MCOMPILER-578/invoker.properties b/src/it/MCOMPILER-578/invoker.properties
new file mode 100644
index 0000000..b5bbe7d
--- /dev/null
+++ b/src/it/MCOMPILER-578/invoker.properties
@@ -0,0 +1,20 @@
+# 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.java.version = 17+
+invoker.goals = clean compile
+invoker.goals.2 = compile
diff --git a/src/it/MCOMPILER-578/pom.xml b/src/it/MCOMPILER-578/pom.xml
new file mode 100644
index 0000000..b3db4fc
--- /dev/null
+++ b/src/it/MCOMPILER-578/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.plugins.compiler.it</groupId>
+ <artifactId>mcompiler-578</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>@project.version@</version>
+ <configuration>
+ <createMissingPackageInfoClass>false</createMissingPackageInfoClass>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-compile</id>
+ <configuration>
+ <release>${java.vm.specification.version}</release>
+ </configuration>
+ </execution>
+ <execution>
+ <id>base-modules-compile</id>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <release>11</release>
+ <includes>
+ <include>module-info.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ <execution>
+ <id>base-compile</id>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <release>8</release>
+ <excludes>
+ <exclude>module-info.java</exclude>
+ </excludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/MCOMPILER-578/src/main/java/module-info.java b/src/it/MCOMPILER-578/src/main/java/module-info.java
new file mode 100644
index 0000000..1e1d899
--- /dev/null
+++ b/src/it/MCOMPILER-578/src/main/java/module-info.java
@@ -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.
+ */
+module org.example {
+ exports org.example;
+}
diff --git a/src/it/MCOMPILER-578/src/main/java/org/example/Example.java b/src/it/MCOMPILER-578/src/main/java/org/example/Example.java
new file mode 100644
index 0000000..c520a4f
--- /dev/null
+++ b/src/it/MCOMPILER-578/src/main/java/org/example/Example.java
@@ -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.
+ */
+package org.example;
+
+public class Example {}
diff --git a/src/it/MCOMPILER-578/src/main/java/org/example/package-info.java b/src/it/MCOMPILER-578/src/main/java/org/example/package-info.java
new file mode 100644
index 0000000..9517db2
--- /dev/null
+++ b/src/it/MCOMPILER-578/src/main/java/org/example/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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 org.example;
diff --git a/src/it/MCOMPILER-578/verify.groovy b/src/it/MCOMPILER-578/verify.groovy
new file mode 100644
index 0000000..279dd9c
--- /dev/null
+++ b/src/it/MCOMPILER-578/verify.groovy
@@ -0,0 +1,35 @@
+/*
+ * 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 majorVersion = { File classFile ->
+ assert classFile.isFile()
+ byte[] bytes = classFile.bytes
+ assert bytes.length >= 8
+ ((bytes[6] & 0xFF) << 8) | (bytes[7] & 0xFF)
+}
+
+def exampleClass = new File( basedir, 'target/classes/org/example/Example.class' )
+def exampleMajorVersion = majorVersion( exampleClass )
+// major_version: 52 = Java 8, from the base-compile execution.
+assert exampleMajorVersion == 52
+
+def moduleInfoClass = new File( basedir, 'target/classes/module-info.class' )
+def moduleInfoMajorVersion = majorVersion( moduleInfoClass )
+// major_version: 55 = Java 11, from the base-modules-compile execution.
+assert moduleInfoMajorVersion == 55
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 2066f0e..709dd89 100644
--- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java
+++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java
@@ -956,6 +956,9 @@ public abstract class AbstractCompilerMojo extends AbstractMojo {
final Set<File> sources;
+ Set<Path> outputs = Collections.emptySet();
+ String compilerExecution = mojoExecution.getGoal() + '@' + mojoExecution.getExecutionId();
+
IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
if (useIncrementalCompilation) {
@@ -967,6 +970,9 @@ public abstract class AbstractCompilerMojo extends AbstractMojo {
preparePaths(sources);
+ // Expected output paths let us detect overwrites that IncrementalBuildHelper cannot see.
+ outputs = getOutputPaths(compilerConfiguration, compiler, sources);
+
incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles(sources);
// Strategies used to detect modifications.
@@ -980,9 +986,18 @@ public abstract class AbstractCompilerMojo extends AbstractMojo {
String inputFileTreeChanged = hasInputFileTreeChanged(incrementalBuildHelper, sources)
? "added or removed source files"
: null;
+ // A different execution may have overwritten an otherwise up-to-date output.
+ String outputChanged = hasPreviouslyCompiledOutput(compilerExecution, outputs)
+ ? "output from another compiler execution"
+ : null;
// Get the first cause for the rebuild compilation detection.
- String cause = Stream.of(immutableOutputFile, dependencyChanged, sourceChanged, inputFileTreeChanged)
+ String cause = Stream.of(
+ immutableOutputFile,
+ dependencyChanged,
+ sourceChanged,
+ inputFileTreeChanged,
+ outputChanged)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
@@ -1293,6 +1308,8 @@ public abstract class AbstractCompilerMojo extends AbstractMojo {
getLog().debug(
"skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist");
}
+ // Make these outputs visible to later compiler executions in this Maven session.
+ recordCompiledOutputs(compilerExecution, outputs);
}
List<CompilerMessage> warnings = new ArrayList<>();
@@ -1617,6 +1634,56 @@ public abstract class AbstractCompilerMojo extends AbstractMojo {
return staleSources;
}
+ /**
+ * Maps selected sources to their expected compiler outputs.
+ *
+ * @param compilerConfiguration the compiler configuration
+ * @param compiler the selected compiler
+ * @param sources sources selected by the current execution
+ * @return normalized absolute output paths
+ */
+ private Set<Path> getOutputPaths(CompilerConfiguration compilerConfiguration, Compiler compiler, Set<File> sources)
+ throws CompilerException, MojoExecutionException {
+ SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler);
+
+ File outputDirectory =
+ compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES
+ ? buildDirectory
+ : getOutputDirectory();
+ try {
+ return CompilationOutputRegistry.mapOutputs(mapping, outputDirectory, getCompileSourceRoots(), sources);
+ } catch (InclusionScanException e) {
+ throw new MojoExecutionException("Error mapping sources to their outputs.", e);
+ }
+ }
+
+ /**
+ * Checks whether a different compiler execution last processed an expected output.
+ *
+ * @param compilerExecution the current execution
+ * @param outputs expected outputs of the current execution
+ * @return whether an output overlaps with another execution
+ */
+ private boolean hasPreviouslyCompiledOutput(String compilerExecution, Set<Path> outputs) {
+ Optional<Path> output = CompilationOutputRegistry.find(getPluginContext(), compilerExecution, outputs);
+ if (output.isPresent() && showCompilationChanges) {
+ getLog().info("\tOutput from another compiler execution: " + output.get());
+ } else if (output.isPresent()) {
+ getLog().debug("\tOutput from another compiler execution: " + output.get());
+ }
+ return output.isPresent();
+ }
+
+ /**
+ * Registers the current execution as the last processor of its expected outputs.
+ *
+ * @param compilerExecution the current execution
+ * @param outputs expected outputs of the current execution
+ */
+ private void recordCompiledOutputs(String compilerExecution, Set<Path> outputs) {
+ CompilationOutputRegistry.register(getPluginContext(), compilerExecution, outputs);
+ }
+
private SourceMapping getSourceMapping(CompilerConfiguration compilerConfiguration, Compiler compiler)
throws CompilerException, MojoExecutionException {
CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
diff --git a/src/main/java/org/apache/maven/plugin/compiler/CompilationOutputRegistry.java b/src/main/java/org/apache/maven/plugin/compiler/CompilationOutputRegistry.java
new file mode 100644
index 0000000..110ad9f
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugin/compiler/CompilationOutputRegistry.java
@@ -0,0 +1,150 @@
+/*
+ * 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 org.apache.maven.plugin.compiler;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
+import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
+
+/**
+ * Tracks output files processed by compiler executions for the current project and Maven session.
+ *
+ * <p>Multiple compiler executions may share an output directory while using different compiler options.
+ * {@link org.apache.maven.shared.incremental.IncrementalBuildHelper} compares output file names before and after
+ * compilation, so it cannot detect an existing output overwritten by another execution. This registry lets a later
+ * execution detect that overlap without relying on file timestamps, whose precision varies between file systems.</p>
+ *
+ * <p>The registry is stored in Maven's plugin context, which is shared by all executions of this plugin for one
+ * project and discarded after the Maven session.</p>
+ */
+final class CompilationOutputRegistry {
+ private static final String KEY = CompilationOutputRegistry.class.getName() + ".compiledOutputs";
+
+ /**
+ * Prevents instantiation.
+ */
+ private CompilationOutputRegistry() {}
+
+ /**
+ * Maps sources to normalized absolute output paths.
+ *
+ * @param mapping mapping from source paths to output paths
+ * @param outputDirectory compiler output directory
+ * @param sourceRoots configured source roots
+ * @param sources sources selected by the compiler execution
+ * @return expected output paths
+ * @throws InclusionScanException if a source cannot be mapped
+ */
+ static Set<Path> mapOutputs(
+ SourceMapping mapping, File outputDirectory, List<String> sourceRoots, Set<File> sources)
+ throws InclusionScanException {
+ Set<Path> outputs = new HashSet<>();
+ List<Path> roots = new ArrayList<>(sourceRoots.size());
+ for (String sourceRoot : sourceRoots) {
+ roots.add(Paths.get(sourceRoot).toAbsolutePath().normalize());
+ }
+ for (File source : sources) {
+ Path path = source.toPath().toAbsolutePath().normalize();
+ Path matchingRoot = null;
+ for (Path root : roots) {
+ if (path.startsWith(root)
+ && (matchingRoot == null || root.getNameCount() > matchingRoot.getNameCount())) {
+ matchingRoot = root;
+ }
+ }
+ if (matchingRoot != null) {
+ for (File output : mapping.getTargetFiles(
+ outputDirectory, matchingRoot.relativize(path).toString())) {
+ outputs.add(output.toPath().toAbsolutePath().normalize());
+ }
+ }
+ }
+ return outputs;
+ }
+
+ /**
+ * Finds an output most recently processed by a different compiler execution.
+ *
+ * @param pluginContext Maven's context for this plugin and project
+ * @param compilerExecution the current compiler execution, formatted as {@code goal@executionId}
+ * @param outputs outputs mapped from the current execution's sources
+ * @return an overlapping output, or an empty value if there is none
+ */
+ static Optional<Path> find(Map<?, ?> pluginContext, String compilerExecution, Set<Path> outputs) {
+ Map<Path, String> compiledOutputs = get(pluginContext, false);
+ return compiledOutputs != null
+ ? outputs.stream()
+ .filter(output -> {
+ String previousExecution = compiledOutputs.get(output);
+ return previousExecution != null && !previousExecution.equals(compilerExecution);
+ })
+ .findFirst()
+ : Optional.empty();
+ }
+
+ /**
+ * Associates outputs with the compiler execution that most recently processed them.
+ *
+ * @param pluginContext Maven's context for this plugin and project
+ * @param compilerExecution the compiler execution, formatted as {@code goal@executionId}
+ * @param outputs outputs mapped from the execution's sources
+ */
+ static void register(Map<?, ?> pluginContext, String compilerExecution, Set<Path> outputs) {
+ Map<Path, String> compiledOutputs = get(pluginContext, true);
+ if (compiledOutputs != null) {
+ for (Path output : outputs) {
+ compiledOutputs.put(output, compilerExecution);
+ }
+ }
+ }
+
+ /**
+ * Returns the output registry from the plugin context.
+ *
+ * @param pluginContext Maven's context for this plugin and project
+ * @param create whether to create the registry when absent
+ * @return the registry, or {@code null} if the context or registry is absent
+ */
+ @SuppressWarnings("unchecked")
+ private static Map<Path, String> get(Map<?, ?> pluginContext, boolean create) {
+ if (pluginContext == null) {
+ return null;
+ }
+ Object value = pluginContext.get(KEY);
+ if (value == null && create) {
+ Map<Path, String> outputs = new ConcurrentHashMap<>();
+ Map<Object, Object> context = (Map<Object, Object>) pluginContext;
+ value = context.putIfAbsent(KEY, outputs);
+ if (value == null) {
+ return outputs;
+ }
+ }
+ return (Map<Path, String>) value;
+ }
+}
diff --git a/src/test/java/org/apache/maven/plugin/compiler/CompilationOutputRegistryTest.java b/src/test/java/org/apache/maven/plugin/compiler/CompilationOutputRegistryTest.java
new file mode 100644
index 0000000..a76ab2d
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugin/compiler/CompilationOutputRegistryTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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 org.apache.maven.plugin.compiler;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
+import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+class CompilationOutputRegistryTest {
+ private static final String FIRST_EXECUTION = "compile@first";
+
+ private static final String SECOND_EXECUTION = "compile@second";
+
+ private static final Path OUTPUT =
+ Paths.get("target/classes/Example.class").toAbsolutePath().normalize();
+
+ private static final Path OTHER_OUTPUT =
+ Paths.get("target/classes/Other.class").toAbsolutePath().normalize();
+
+ private final Map<String, Object> pluginContext = new HashMap<>();
+
+ private final Set<Path> outputs = Collections.singleton(OUTPUT);
+
+ @Test
+ void shouldMapSourceAgainstMostSpecificRoot() throws InclusionScanException {
+ Path sourceRoot = Paths.get("src").toAbsolutePath().normalize();
+ Path nestedSourceRoot = sourceRoot.resolve("main/java");
+ Path outputDirectory = Paths.get("target/classes").toAbsolutePath().normalize();
+ Path source = nestedSourceRoot.resolve("org/example/Example.java");
+
+ assertEquals(
+ Collections.singleton(outputDirectory.resolve("org/example/Example.class")),
+ CompilationOutputRegistry.mapOutputs(
+ new SuffixMapping(".java", ".class"),
+ outputDirectory.toFile(),
+ Arrays.asList(sourceRoot.toString(), nestedSourceRoot.toString()),
+ Collections.singleton(source.toFile())));
+ }
+
+ @Test
+ void shouldIgnoreOutputsFromTheSameExecution() {
+ CompilationOutputRegistry.register(pluginContext, FIRST_EXECUTION, outputs);
+
+ assertFalse(CompilationOutputRegistry.find(pluginContext, FIRST_EXECUTION, outputs)
+ .isPresent());
+ }
+
+ @Test
+ void shouldFindOutputsFromAnotherExecution() {
+ CompilationOutputRegistry.register(pluginContext, FIRST_EXECUTION, outputs);
+
+ assertEquals(
+ OUTPUT,
+ CompilationOutputRegistry.find(pluginContext, SECOND_EXECUTION, outputs)
+ .orElse(null));
+ }
+
+ @Test
+ void shouldIgnoreDisjointOutputs() {
+ CompilationOutputRegistry.register(pluginContext, FIRST_EXECUTION, outputs);
+
+ assertFalse(CompilationOutputRegistry.find(pluginContext, SECOND_EXECUTION, Collections.singleton(OTHER_OUTPUT))
+ .isPresent());
+ }
+
+ @Test
+ void shouldTrackTheLastExecution() {
+ CompilationOutputRegistry.register(pluginContext, FIRST_EXECUTION, outputs);
+ CompilationOutputRegistry.register(pluginContext, SECOND_EXECUTION, outputs);
+
+ assertEquals(
+ OUTPUT,
+ CompilationOutputRegistry.find(pluginContext, FIRST_EXECUTION, outputs)
+ .orElse(null));
+ assertFalse(CompilationOutputRegistry.find(pluginContext, SECOND_EXECUTION, outputs)
+ .isPresent());
+ }
+}