apache / apache/maven-build-cache-extension
Plugin config directory probing hashes other modules' target/ output as inputs, permanently invalidating the cache
- Dominant language
- Java
- Stars
- 163
- Forks
- 77
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 4
Description
### Summary
When the input scanner probes plugin configuration for paths, a `` (or any path-valued tag) that points at **another module's directory** is walked recursively, and that module's `target/` is **not** excluded. The other module's build output — jar, `target/classes/*.class`, `maven-archiver/pom.properties`, `maven-status/**` — therefore becomes part of the consuming module's checksum.
Build output is not stable across builds (jars carry timestamps unless `project.build.outputTimestamp` is set, and `maven-status` files change whenever the module is actually compiled rather than restored). The result is a module that can never get a cache hit, with no warning — it just looks like the remote cache is broken.
`ExclusionResolver.addDefaultExcludes()` builds the `target` / `target/classes` / `target/test-classes` exclusions from **the current project only**:
```java
Path buildDirectoryPath = absoluteNormalizedPath(build.getDirectory());
Path outputDirectoryPath = absoluteNormalizedPath(build.getOutputDirectory());
Path testOutputDirectoryPath = absoluteNormalizedPath(build.getTestOutputDirectory());
```
They are absolute paths, so they only ever match the project being hashed. Any *other* reactor module reached through plugin-config probing has its build output treated as source input.
### Reproducer
Note: create it **outside** `java.io.tmpdir` — `getPathOrNull()` silently skips anything under the temp dir, so a repro in `/tmp` will not trigger the scan.
```bash
mkdir -p ~/mbc-repro/{.mvn,mod-a/src/main/java,mod-b/src/main/java} && cd ~/mbc-repro
cat > .mvn/extensions.xml <<'EOF'
org.apache.maven.extensions
maven-build-cache-extension
1.3.0
EOF
cat > .mvn/maven-build-cache-config.xml <<'EOF'
true
XX
{*.java,*.xml,*.properties}
EOF
cat > pom.xml <<'EOF'
4.0.0
org.example
parent
1.0-SNAPSHOT
pom
17
UTF-8
mod-a
mod-b
EOF
cat > mod-a/pom.xml <<'EOF'
4.0.0
org.example
parent
1.0-SNAPSHOT
mod-a
EOF
echo 'public class A {}' > mod-a/src/main/java/A.java
echo 'shared note' > mod-a/notes.txt
# mod-b has NO dependency on mod-a. It only wants one file out of mod-a's directory.
cat > mod-b/pom.xml <<'EOF'
4.0.0
org.example
parent
1.0-SNAPSHOT
mod-b
org.apache.maven.plugins
maven-resources-plugin
copy-shared-note
prepare-package
copy-resources
${project.build.directory}
${maven.multiModuleProjectDirectory}/mod-a
notes.txt
EOF
echo 'public class B {}' > mod-b/src/main/java/B.java
mvn -B -ntp -Dmaven.build.cache.location=$PWD/cache clean install # warm
mvn -B -ntp -Dmaven.build.cache.location=$PWD/cache clean install # mod-b hits
# a CI agent that has to rebuild mod-a. No source change anywhere.
rm -rf cache/v1.2/org.example/mod-a
mvn -B -ntp -Dmaven.build.cache.location=$PWD/cache clean install
```
### Actual
The last build misses on `mod-b`, although nothing in `mod-b` — or in any *source* file — changed:
```
[INFO] Local build was not found by checksum 65e6bd150bbbd545 for org.example:mod-b
```
`mod-b`'s `buildinfo.xml` shows why:
```
file 2eaf0644a54b6fa5 ../mod-a/target/classes/A.class
file d4bdf912ad00f859 ../mod-a/target/maven-archiver/pom.properties
file b12c0741ec50f3fc ../mod-a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
file 847b95fc10a3e8b6 ../mod-a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
file c918f0a5600bfbf0 ../mod-a/target/mod-a-1.0-SNAPSHOT.jar
file ... ../mod-a/notes.txt, ../mod-a/pom.xml, ../mod-a/src/main/java/A.java
file ... src/main/java/B.java
```
Editing anything in `mod-a` also invalidates `mod-b` the same way, even though `mod-b` does not depend on `mod-a`.
The input set additionally differs depending on whether `mod-a` was **restored** (only the jar is present in `target/`) or **rebuilt** (whole `target/` tree present), so the two paths produce different checksums for the same sources.
### Expected
Another module's build output directory should never be a checksum input. Either exclude every reactor project's `build.directory` when walking, or apply the owning project's default exclusions when the walked path belongs to a different `MavenProject` in the session — `MultiModuleSupport` already has the project list.
### Secondary: the configured `` is bypassed for plugin-config scans
`notes.txt` above is collected even though the configured glob is `{*.java,*.xml,*.properties}`, because `DefaultPluginScanConfig` — used whenever a plugin has no explicit `` — hardcodes `*`:
```java
// DefaultPluginScanConfig
public ScanConfigProperties getTagScanProperties(String tagName) {
return new ScanConfigProperties(true, "*");
}
```
whereas `PluginScanConfigImpl.defaultScanConfig()` returns `null`, which `MavenProjectInput` then resolves to the project glob:
```java
final String glob = defaultIfEmpty(propertyConfig.getGlob(), projectGlob);
```
So adding an otherwise-empty `` for a plugin silently *narrows* the scanned file set from `*` to the project glob. That asymmetry looks unintentional.
### Real-world impact
Found while investigating why Eclipse Jetty's CI never got a single cache hit. Jetty's parent pom has an inherited `maven-resources-plugin` execution pointing at `${maven.multiModuleProjectDirectory}/build`, which contains a helper module. Every one of the ~400 reactor modules therefore hashed `build/build-resources/target/build-resources-.jar`, which is not reproducible — so the entire reactor invalidated itself on every build, on every agent, forever. Diagnosing it took diffing `buildinfo.xml` between two runs, because nothing in the log hints that an input lives under another module's `target/`.
A warning when a collected input falls under any reactor project's build directory would make this class of problem self-diagnosing.
### Environment
- maven-build-cache-extension 1.3.0
- Maven 3.9.16
- Java 25.0.3 (Temurin); also observed on 17 and 21
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with ExclusionResolver.addDefaultExcludes() and trace plugin-config directory scanning through DefaultPluginScanConfig, PluginScanConfigImpl, and MavenProjectInput; inspect MultiModuleSupport for the reactor project list. Run the supplied reproducer outside java.io.tmpdir and compare buildinfo.xml before and after rebuilding mod-a. Done means another reactor module's target output is absent from inputs and the configured global glob is applied consistently.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100