Copilot commented on code in PR #394:
URL:
https://github.com/apache/maven-build-cache-extension/pull/394#discussion_r2641943984
##########
src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java:
##########
@@ -567,6 +660,58 @@ public void save(
} catch (Exception ex) {
LOGGER.error("Failed to clean cache due to unexpected error:",
ex);
}
+ } finally {
+ // Cleanup project state to free memory, but preserve
stagingDirectory for restore
+ // Note: stagingDirectory must persist until
restoreStagedArtifacts() is called
+ state.attachedResourcesPathsById.clear();
+ state.attachedResourceCounter = 0;
+ state.restoredOutputClassifiers.clear();
+ // stagingDirectory is NOT cleared here - it's cleared in
restoreStagedArtifacts()
+ }
+ }
+
+ /**
+ * Saves a project artifact to cache, handling both regular files and
directory artifacts.
+ * Directory artifacts (e.g., target/classes from compile-only builds) are
zipped before saving
+ * since Files.copy() cannot handle directories.
+ */
+ private void saveProjectArtifact(
+ CacheResult cacheResult, org.apache.maven.artifact.Artifact
projectArtifact, MavenProject project)
+ throws IOException {
+ File originalFile = projectArtifact.getFile();
+ try {
+ if (originalFile.isDirectory()) {
+ saveDirectoryArtifact(cacheResult, projectArtifact, project,
originalFile);
+ } else {
+ // Regular file (JAR/WAR) - save directly
+ localCache.saveArtifactFile(cacheResult, projectArtifact);
+ }
+ } finally {
+ // Restore original file reference in case it was temporarily
changed
+ projectArtifact.setFile(originalFile);
+ }
+ }
+
+ /**
+ * Saves a directory artifact by zipping it first, then saving the zip to
cache.
+ */
+ private void saveDirectoryArtifact(
+ CacheResult cacheResult,
+ org.apache.maven.artifact.Artifact projectArtifact,
+ MavenProject project,
+ File originalFile)
+ throws IOException {
+ Path tempZip = Files.createTempFile("maven-cache-", "-" +
project.getArtifactId() + ".zip");
+ boolean hasFiles = CacheUtils.zip(originalFile.toPath(), tempZip, "*",
cacheConfig.isPreservePermissions());
+ if (hasFiles) {
+ // Temporarily replace artifact file with zip for saving
+ projectArtifact.setFile(tempZip.toFile());
+ localCache.saveArtifactFile(cacheResult, projectArtifact);
+ LOGGER.debug("Saved directory artifact as zip: {} -> {}",
originalFile, tempZip);
+ // Clean up temp file after it's been saved to cache
+ Files.deleteIfExists(tempZip);
+ } else {
+ LOGGER.warn("Directory artifact has no files to cache: {}",
originalFile);
Review Comment:
The warning message should clarify whether this is expected (e.g., clean
build with no sources) or indicates a problem. Consider rephrasing to 'Skipping
empty directory artifact: {}' to indicate this is a normal skip condition.
```suggestion
LOGGER.warn("Skipping empty directory artifact: {}",
originalFile);
```
##########
src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java:
##########
@@ -974,6 +1164,285 @@ public FileVisitResult visitFile(Path path,
BasicFileAttributes basicFileAttribu
return hasFiles.booleanValue();
}
+ /**
+ * Move pre-existing build artifacts to staging directory to prevent
caching stale files.
+ *
+ * <p><b>DESIGN RATIONALE - Staleness Detection via Staging Directory:</b>
+ *
+ * <p>This approach solves three critical problems that timestamp-based
checking cannot handle:
+ *
+ * <p><b>Problem 1: Future Timestamps from Clock Skew</b>
+ * <ul>
+ * <li>Machine A (clock ahead at 11:00 AM) builds and caches artifacts
+ * <li>Machine B (correct clock at 10:00 AM) restores cache
+ * <li>Restored files have timestamps from the future (11:00 AM)
+ * <li>User switches branches or updates sources (sources timestamped
10:02 AM)
+ * <li>Maven incremental compiler sees: sources (10:02 AM) < classes
(11:00 AM)
+ * <li>Maven skips compilation (thinks sources older than classes)
+ * <li>Wrong classes from old source version get cached!
+ * </ul>
+ *
+ * <p><b>Problem 2: Orphaned Class Files from Deleted Sources</b>
+ * <ul>
+ * <li>Version A has Foo.java → compiles Foo.class
+ * <li>Switch to Version B (no Foo.java)
+ * <li>Foo.class remains in target/classes (orphaned)
+ * <li>Cache miss on new version triggers mojos
+ * <li>Without protection, orphaned Foo.class gets cached
+ * <li>Future cache hits restore Foo.class (which shouldn't exist!)
+ * </ul>
+ *
+ * <p><b>Problem 3: Stale JARs/WARs from Previous Builds</b>
+ * <ul>
+ * <li>Yesterday: built myapp.jar on old version
+ * <li>Today: switched to new version, sources changed
+ * <li>mvn package runs (cache miss)
+ * <li>If JAR wasn't rebuilt, stale JAR could be cached
+ * </ul>
+ *
+ * <p><b>Solution: Staging Directory Physical Separation</b>
+ * <ul>
+ * <li>Before mojos run: Move pre-existing artifacts to
target/.maven-build-cache-stash/
+ * <li>Maven sees clean target/ with no pre-existing artifacts
+ * <li>Maven compiler MUST compile (can't skip based on timestamps)
+ * <li>Fresh correct files created in target/
+ * <li>save() only sees fresh files (stale ones are in staging directory)
+ * <li>After save(): Restore artifacts from staging (delete if fresh
version exists)
+ * </ul>
+ *
+ * <p><b>Why Better Than Timestamp Checking:</b>
+ * <ul>
+ * <li>No clock skew calculations needed
+ * <li>Physical file separation (not heuristics)
+ * <li>Forces correct incremental compilation
+ * <li>Handles interrupted builds gracefully (just delete staging
directory)
+ * <li>Simpler and more robust
+ * <li>Easier cleanup - delete one directory instead of filtering files
+ * </ul>
+ *
+ * <p><b>Interrupted Build Handling:</b>
+ * If staging directory exists from interrupted previous run, it's deleted
and recreated.
+ *
+ * @param session The Maven session
+ * @param project The Maven project being built
+ * @throws IOException if file move operations fail
+ */
+ public void stagePreExistingArtifacts(MavenSession session, MavenProject
project) throws IOException {
Review Comment:
The method documentation is comprehensive for the 'why' but should include
examples of which specific artifacts get staged (e.g., 'target/classes',
'target/test-classes', '*.jar'). This would make the behavior more concrete for
future maintainers.
##########
src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java:
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.buildcache.its;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that the maven.build.cache.cacheCompile property correctly disables
+ * caching of compile-phase outputs.
+ */
+@IntegrationTest("src/test/projects/issue-393-compile-restore")
+class CacheCompileDisabledTest {
+
+ @Test
+ void compileDoesNotCacheWhenDisabled(Verifier verifier) throws
VerificationException, IOException {
+ verifier.setAutoclean(false);
+
+ // The actual cache is stored in target/build-cache (relative to the
extension root, not test project)
+ Path localCache =
Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
+ .resolve("target/build-cache");
+
+ // Clean cache before test
+ if (Files.exists(localCache)) {
+ deleteDirectory(localCache);
+ }
+
+ // First compile with cacheCompile disabled - compile only the app
module to avoid dependency issues
+ verifier.setLogFileName("../log-compile-disabled.txt");
+ verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
+ verifier.addCliOption("-pl");
+ verifier.addCliOption("app");
+ verifier.executeGoals(Arrays.asList("clean", "compile"));
+ verifier.verifyErrorFreeLog();
+
+ // Verify NO cache entry was created (no buildinfo.xml in local cache)
+ boolean hasCacheEntry =
+ Files.walk(localCache).anyMatch(p ->
p.getFileName().toString().equals("buildinfo.xml"));
+ assertFalse(hasCacheEntry, "Cache entry should NOT be created when
maven.build.cache.cacheCompile=false");
Review Comment:
Unclosed stream from Files.walk() will leak resources. Wrap Files.walk() in
a try-with-resources block to ensure the stream is properly closed.
##########
src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java:
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.buildcache.its;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that the maven.build.cache.cacheCompile property correctly disables
+ * caching of compile-phase outputs.
+ */
+@IntegrationTest("src/test/projects/issue-393-compile-restore")
+class CacheCompileDisabledTest {
+
+ @Test
+ void compileDoesNotCacheWhenDisabled(Verifier verifier) throws
VerificationException, IOException {
+ verifier.setAutoclean(false);
+
+ // The actual cache is stored in target/build-cache (relative to the
extension root, not test project)
+ Path localCache =
Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
+ .resolve("target/build-cache");
+
+ // Clean cache before test
+ if (Files.exists(localCache)) {
+ deleteDirectory(localCache);
+ }
+
+ // First compile with cacheCompile disabled - compile only the app
module to avoid dependency issues
+ verifier.setLogFileName("../log-compile-disabled.txt");
+ verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
+ verifier.addCliOption("-pl");
+ verifier.addCliOption("app");
+ verifier.executeGoals(Arrays.asList("clean", "compile"));
+ verifier.verifyErrorFreeLog();
+
+ // Verify NO cache entry was created (no buildinfo.xml in local cache)
+ boolean hasCacheEntry =
+ Files.walk(localCache).anyMatch(p ->
p.getFileName().toString().equals("buildinfo.xml"));
+ assertFalse(hasCacheEntry, "Cache entry should NOT be created when
maven.build.cache.cacheCompile=false");
+
+ // Clean project and run compile again
+ verifier.setLogFileName("../log-compile-disabled-2.txt");
+ verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
+ verifier.addCliOption("-pl");
+ verifier.addCliOption("app");
+ verifier.executeGoals(Arrays.asList("clean", "compile"));
+ verifier.verifyErrorFreeLog();
+
+ // Verify cache miss (should NOT restore from cache)
+ Path logFile =
Paths.get(verifier.getBasedir()).getParent().resolve("log-compile-disabled-2.txt");
+ String logContent = new String(Files.readAllBytes(logFile));
+ assertFalse(
+ logContent.contains("Found cached build, restoring"),
+ "Should NOT restore from cache when cacheCompile was
disabled");
+ }
+
+ @Test
+ void compileCreatesCacheEntryWhenEnabled(Verifier verifier) throws
VerificationException, IOException {
+ verifier.setAutoclean(false);
+
+ // The actual cache is stored in target/build-cache (relative to the
extension root, not test project)
+ Path localCache =
Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
+ .resolve("target/build-cache");
+
+ // Clean cache before test
+ if (Files.exists(localCache)) {
+ deleteDirectory(localCache);
+ }
+
+ // First compile with cacheCompile enabled (default) - compile only
the app module
+ verifier.setLogFileName("../log-compile-enabled.txt");
+ verifier.addCliOption("-pl");
+ verifier.addCliOption("app");
+ verifier.executeGoals(Arrays.asList("clean", "compile"));
+ verifier.verifyErrorFreeLog();
+
+ // Verify cache entry WAS created
+ boolean hasCacheEntry =
+ Files.walk(localCache).anyMatch(p ->
p.getFileName().toString().equals("buildinfo.xml"));
Review Comment:
Unclosed stream from Files.walk() will leak resources. Wrap Files.walk() in
a try-with-resources block to ensure the stream is properly closed.
```suggestion
boolean hasCacheEntry;
try (Stream<Path> walk = Files.walk(localCache)) {
hasCacheEntry = walk.anyMatch(p ->
p.getFileName().toString().equals("buildinfo.xml"));
}
```
##########
src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java:
##########
@@ -974,6 +1164,285 @@ public FileVisitResult visitFile(Path path,
BasicFileAttributes basicFileAttribu
return hasFiles.booleanValue();
}
+ /**
+ * Move pre-existing build artifacts to staging directory to prevent
caching stale files.
+ *
+ * <p><b>DESIGN RATIONALE - Staleness Detection via Staging Directory:</b>
+ *
+ * <p>This approach solves three critical problems that timestamp-based
checking cannot handle:
+ *
+ * <p><b>Problem 1: Future Timestamps from Clock Skew</b>
+ * <ul>
+ * <li>Machine A (clock ahead at 11:00 AM) builds and caches artifacts
+ * <li>Machine B (correct clock at 10:00 AM) restores cache
+ * <li>Restored files have timestamps from the future (11:00 AM)
+ * <li>User switches branches or updates sources (sources timestamped
10:02 AM)
+ * <li>Maven incremental compiler sees: sources (10:02 AM) < classes
(11:00 AM)
+ * <li>Maven skips compilation (thinks sources older than classes)
+ * <li>Wrong classes from old source version get cached!
+ * </ul>
+ *
+ * <p><b>Problem 2: Orphaned Class Files from Deleted Sources</b>
+ * <ul>
+ * <li>Version A has Foo.java → compiles Foo.class
+ * <li>Switch to Version B (no Foo.java)
+ * <li>Foo.class remains in target/classes (orphaned)
+ * <li>Cache miss on new version triggers mojos
+ * <li>Without protection, orphaned Foo.class gets cached
+ * <li>Future cache hits restore Foo.class (which shouldn't exist!)
+ * </ul>
+ *
+ * <p><b>Problem 3: Stale JARs/WARs from Previous Builds</b>
+ * <ul>
+ * <li>Yesterday: built myapp.jar on old version
+ * <li>Today: switched to new version, sources changed
+ * <li>mvn package runs (cache miss)
+ * <li>If JAR wasn't rebuilt, stale JAR could be cached
+ * </ul>
+ *
+ * <p><b>Solution: Staging Directory Physical Separation</b>
+ * <ul>
+ * <li>Before mojos run: Move pre-existing artifacts to
target/.maven-build-cache-stash/
+ * <li>Maven sees clean target/ with no pre-existing artifacts
+ * <li>Maven compiler MUST compile (can't skip based on timestamps)
+ * <li>Fresh correct files created in target/
+ * <li>save() only sees fresh files (stale ones are in staging directory)
+ * <li>After save(): Restore artifacts from staging (delete if fresh
version exists)
+ * </ul>
+ *
+ * <p><b>Why Better Than Timestamp Checking:</b>
+ * <ul>
+ * <li>No clock skew calculations needed
+ * <li>Physical file separation (not heuristics)
+ * <li>Forces correct incremental compilation
+ * <li>Handles interrupted builds gracefully (just delete staging
directory)
+ * <li>Simpler and more robust
+ * <li>Easier cleanup - delete one directory instead of filtering files
+ * </ul>
+ *
+ * <p><b>Interrupted Build Handling:</b>
+ * If staging directory exists from interrupted previous run, it's deleted
and recreated.
+ *
+ * @param session The Maven session
+ * @param project The Maven project being built
+ * @throws IOException if file move operations fail
+ */
+ public void stagePreExistingArtifacts(MavenSession session, MavenProject
project) throws IOException {
+ final ProjectCacheState state = getProjectState(project);
+ final Path multimoduleRoot = CacheUtils.getMultimoduleRoot(session);
+ final Path stagingDir =
multimoduleRoot.resolve("target").resolve("maven-build-cache-extension");
+
+ // Create or reuse staging directory from interrupted previous run
+ Files.createDirectories(stagingDir);
+ state.stagingDirectory = stagingDir;
+
+ // Collect all paths that will be cached
+ Set<Path> pathsToProcess = collectCachedArtifactPaths(project);
+
+ int movedCount = 0;
+ for (Path path : pathsToProcess) {
+ // Calculate path relative to multimodule root (preserves full
path including submodule)
+ Path relativePath = multimoduleRoot.relativize(path);
+ Path stagedPath = stagingDir.resolve(relativePath);
+
+ if (Files.isDirectory(path)) {
+ // If directory already exists in staging (from interrupted
run), remove it first
+ if (Files.exists(stagedPath)) {
+ deleteDirectory(stagedPath);
+ LOGGER.debug("Removed existing staged directory: {}",
stagedPath);
+ }
+ // Move entire directory to staging
+ Files.createDirectories(stagedPath.getParent());
+ Files.move(path, stagedPath);
+ movedCount++;
+ LOGGER.debug("Moved directory to staging: {} → {}",
relativePath, stagedPath);
+ } else if (Files.isRegularFile(path)) {
+ // If file already exists in staging (from interrupted run),
remove it first
+ if (Files.exists(stagedPath)) {
+ Files.delete(stagedPath);
+ LOGGER.debug("Removed existing staged file: {}",
stagedPath);
+ }
+ // Move individual file (e.g., JAR) to staging
+ Files.createDirectories(stagedPath.getParent());
+ Files.move(path, stagedPath);
+ movedCount++;
+ LOGGER.debug("Moved file to staging: {} → {}", relativePath,
stagedPath);
+ }
+ }
+
+ if (movedCount > 0) {
+ LOGGER.info(
+ "Moved {} pre-existing artifacts to staging directory to
prevent caching stale files", movedCount);
+ }
+ }
+
+ /**
+ * Collect paths to all artifacts that will be cached (main artifact +
attachedOutputs).
Review Comment:
Missing javadoc. This private method should document what artifact paths it
collects (main artifact + attachedOutputs) and mention that it only includes
paths that currently exist on disk.
```suggestion
* Collects paths to all artifacts that will be considered for caching
for the given project.
* <p>
* This includes:
* <ul>
* <li>the main project artifact file (for example, the built JAR),
if it has been produced, and</li>
* <li>any attached output directories configured via {@code
cacheConfig.getAttachedOutputs()} under the
* project's target directory, when {@code
cacheConfig.isCacheCompile()} is enabled.</li>
* </ul>
* Only paths that currently exist on disk are included in the returned
set; non-existent files or directories
* are ignored.
*
* @param project the Maven project whose artifact and attached output
paths should be collected
* @return a set of existing filesystem paths for the project's main
artifact and configured attached outputs
```
##########
src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java:
##########
@@ -497,29 +552,69 @@ public void save(
final MavenProject project = context.getProject();
final MavenSession session = context.getSession();
+ final ProjectCacheState state = getProjectState(project);
try {
+ state.attachedResourcesPathsById.clear();
+ state.attachedResourceCounter = 0;
+
+ // Get build start time to filter out stale artifacts from
previous builds
+ final long buildStartTime =
session.getRequest().getStartTime().getTime();
+
final HashFactory hashFactory = cacheConfig.getHashFactory();
+ final HashAlgorithm algorithm = hashFactory.createAlgorithm();
final org.apache.maven.artifact.Artifact projectArtifact =
project.getArtifact();
- final List<org.apache.maven.artifact.Artifact> attachedArtifacts;
- final List<Artifact> attachedArtifactDtos;
- final Artifact projectArtifactDto;
- if (project.hasLifecyclePhase("package")) {
- final HashAlgorithm algorithm = hashFactory.createAlgorithm();
- attachGeneratedSources(project);
- attachOutputs(project);
- attachedArtifacts = project.getAttachedArtifacts() != null
- ? project.getAttachedArtifacts()
- : Collections.emptyList();
- attachedArtifactDtos = artifactDtos(attachedArtifacts,
algorithm, project);
- projectArtifactDto = artifactDto(project.getArtifact(),
algorithm, project);
- } else {
- attachedArtifacts = Collections.emptyList();
- attachedArtifactDtos = new ArrayList<>();
- projectArtifactDto = null;
+
+ // Cache compile outputs (classes, test-classes, generated
sources) if enabled
+ // This allows compile-only builds to create restorable cache
entries
+ // Can be disabled with -Dmaven.build.cache.cacheCompile=false to
reduce IO overhead
+ final boolean cacheCompile = cacheConfig.isCacheCompile();
+ if (cacheCompile) {
+ attachGeneratedSources(project, state, buildStartTime);
+ attachOutputs(project, state, buildStartTime);
}
+ final List<org.apache.maven.artifact.Artifact> attachedArtifacts =
+ project.getAttachedArtifacts() != null ?
project.getAttachedArtifacts() : Collections.emptyList();
+ final List<Artifact> attachedArtifactDtos =
artifactDtos(attachedArtifacts, algorithm, project, state);
+ // Always create artifact DTO - if package phase hasn't run, the
file will be null
+ // and restoration will safely skip it. This ensures all builds
have an artifact DTO.
+ final Artifact projectArtifactDto =
artifactDto(project.getArtifact(), algorithm, project, state);
+
List<CompletedExecution> completedExecution =
buildExecutionInfo(mojoExecutions, executionEvents);
+ // CRITICAL: Don't create incomplete cache entries!
+ // Only save cache entry if we have SOMETHING useful to restore.
+ // Exclude consumer POMs (Maven metadata) from the "useful
artifacts" check.
+ // This prevents the bug where:
+ // 1. mvn compile (cacheCompile=false) creates cache entry with
only metadata
+ // 2. mvn compile (cacheCompile=true) tries to restore
incomplete cache and fails
+ //
+ // Save cache entry if ANY of these conditions are met:
+ // 1. Project artifact file exists:
+ // a) Regular file (JAR/WAR/etc from package phase)
+ // b) Directory (target/classes from compile-only builds) -
only if cacheCompile=true
+ // 2. Has attached artifacts (classes/test-classes from
cacheCompile=true)
+ // 3. POM project with plugin executions (worth caching to skip
plugin execution on cache hit)
+ //
+ // NOTE: No timestamp checking needed -
stagePreExistingArtifacts() ensures only fresh files
+ // are visible (stale files are moved to staging directory).
+
+ // Check if project artifact is valid (exists and is correct type)
+ boolean hasArtifactFile = projectArtifact.getFile() != null
+ && projectArtifact.getFile().exists()
+ && (projectArtifact.getFile().isFile()
+ || (cacheCompile &&
projectArtifact.getFile().isDirectory()));
+ boolean hasAttachedArtifacts = !attachedArtifactDtos.isEmpty()
+ && attachedArtifactDtos.stream()
+ .anyMatch(a ->
!"consumer".equals(a.getClassifier()) || !"pom".equals(a.getType()));
+ // Only save POM projects if they executed plugins (not just
aggregator POMs with no work)
+ boolean isPomProjectWithWork =
"pom".equals(project.getPackaging()) && !completedExecution.isEmpty();
+
+ if (!hasArtifactFile && !hasAttachedArtifacts &&
!isPomProjectWithWork) {
+ LOGGER.info("Skipping cache save: no artifacts to save (only
metadata present)");
Review Comment:
This info message could be confusing when cacheCompile is disabled. Consider
adding context about why no artifacts exist, such as mentioning when
cacheCompile=false.
```suggestion
if (!cacheCompile) {
LOGGER.info(
"Skipping cache save: no artifacts to save
(cacheCompile=false, only metadata present)");
} else {
LOGGER.info("Skipping cache save: no artifacts to save
(only metadata present)");
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]