apache / apache/maven-build-cache-extension

Plugin config directory probing hashes other modules' target/ output as inputs, permanently invalidating the cache

Open
#525 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.