From 8c1813068809a1f9177a95c3491cff657ad85c5c Mon Sep 17 00:00:00 2001 From: Maksim Avilov Date: Sat, 22 Nov 2025 12:31:23 +0100 Subject: [PATCH 1/5] Fixes #220 --- .../BuildCacheMojosExecutionStrategy.java | 49 +++++++++++---- .../its/SkipBuildExtensionTest.java | 62 +++++++++++++++++++ 2 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/apache/maven/buildcache/its/SkipBuildExtensionTest.java diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index 0a2d4d73..d5c5a264 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -100,6 +100,7 @@ public BuildCacheMojosExecutionStrategy( public void execute( List mojoExecutions, MavenSession session, MojoExecutionRunner mojoExecutionRunner) throws LifecycleExecutionException { + try { final MavenProject project = session.getCurrentProject(); final Source source = getSource(mojoExecutions); @@ -107,10 +108,13 @@ public void execute( // execute clean bound goals before restoring to not interfere/slowdown clean CacheState cacheState = DISABLED; CacheResult result = CacheResult.empty(); - boolean skipCache = cacheConfig.isSkipCache() || MavenProjectInput.isSkipCache(project); + boolean skipCache = + cacheConfig.isSkipCache() || MavenProjectInput.isSkipCache(project) || isGoalClean(mojoExecutions); boolean cacheIsDisabled = MavenProjectInput.isCacheDisabled(project); - // Forked execution should be thought as a part of originating mojo internal implementation - // If forkedExecution is detected, it means that originating mojo is not cached so forks should rerun too + // Forked execution should be thought as a part of originating mojo internal + // implementation + // If forkedExecution is detected, it means that originating mojo is not cached + // so forks should rerun too boolean forkedExecution = lifecyclePhasesHelper.isForkedProject(project); String projectName = getVersionlessProjectKey(project); List cleanPhase = null; @@ -178,13 +182,30 @@ public void execute( } /** - * Cache configuration could demand to restore some files in the project directory (generated sources or even arbitrary content) - * If an error occurs during or after this kind of restoration AND a clean phase was required in the build : - * we execute an extra clean phase to remove any potential partially restored files + * Check if the current mojo execution is for the clean goal + * + * @param mojoExecutions the mojo executions + * @return true if the goal is clean and it is the only goal, false otherwise + */ + private boolean isGoalClean(List mojoExecutions) { + if (mojoExecutions.stream().allMatch(mojoExecution -> "clean".equals(mojoExecution.getLifecyclePhase()))) { + LOGGER.warn("Build cache is disabled for 'clean' goal."); + return true; + } + return false; + } + + /** + * Cache configuration could demand to restore some files in the project + * directory (generated sources or even arbitrary content) + * If an error occurs during or after this kind of restoration AND a clean phase + * was required in the build : + * we execute an extra clean phase to remove any potential partially restored + * files * * @param cacheRestorationStatus the restoration status - * @param cleanPhase clean phase mojos - * @param mojoExecutionRunner mojo runner + * @param cleanPhase clean phase mojos + * @param mojoExecutionRunner mojo runner * @throws LifecycleExecutionException */ private void executeExtraCleanPhaseIfNeeded( @@ -260,13 +281,14 @@ private CacheRestorationStatus restoreProject( cacheCandidate.getMojoDescriptor().getFullGoalName()); // need maven 4 as minumum // mojoExecutionScope.seed( - // org.apache.maven.api.plugin.Log.class, - // new DefaultLog(LoggerFactory.getLogger( - // cacheCandidate.getMojoDescriptor().getFullGoalName()))); + // org.apache.maven.api.plugin.Log.class, + // new DefaultLog(LoggerFactory.getLogger( + // cacheCandidate.getMojoDescriptor().getFullGoalName()))); // mojoExecutionScope.seed(Project.class, ((DefaultSession) // session.getSession()).getProject(project)); // mojoExecutionScope.seed( - // org.apache.maven.api.MojoExecution.class, new DefaultMojoExecution(cacheCandidate)); + // org.apache.maven.api.MojoExecution.class, new + // DefaultMojoExecution(cacheCandidate)); mojoExecutionRunner.run(cacheCandidate); } else { LOGGER.info( @@ -413,7 +435,8 @@ boolean isParamsMatched( * - all absolute paths under project root to be relativized for portability * - redundant '..' and '.' to be removed to have consistent views on all paths * - all relative paths are considered portable and should not be touched - * - absolute paths outside of project directory could not be deterministically relativized and not touched + * - absolute paths outside of project directory could not be deterministically + * relativized and not touched */ private static String normalizedPath(Path path, Path baseDirPath) { boolean isProjectSubdir = path.isAbsolute() && path.startsWith(baseDirPath); diff --git a/src/test/java/org/apache/maven/buildcache/its/SkipBuildExtensionTest.java b/src/test/java/org/apache/maven/buildcache/its/SkipBuildExtensionTest.java new file mode 100644 index 00000000..68f2da15 --- /dev/null +++ b/src/test/java/org/apache/maven/buildcache/its/SkipBuildExtensionTest.java @@ -0,0 +1,62 @@ +/* + * 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.util.List; + +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.Assertions; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.com.google.common.collect.Lists; + +import static org.apache.maven.buildcache.util.LogFileUtils.findFirstLineContainingTextsInLogs; + +@IntegrationTest("src/test/projects/build-extension") +class SkipBuildExtensionTest { + + @Test + void simple(Verifier verifier) throws VerificationException { + verifier.setAutoclean(false); + + verifier.setLogFileName("../log-1.txt"); + verifier.executeGoal("clean"); + verifier.verifyErrorFreeLog(); + + verifier.verifyTextInLog("Build cache is disabled for 'clean' goal."); + } + + @Test + void multipleGoals(Verifier verifier) throws VerificationException { + verifier.setAutoclean(false); + + verifier.setLogFileName("../log-2.txt"); + String[] goals = {"clean", "install"}; + List goalsList = Lists.newArrayList(goals); + verifier.executeGoals(goalsList); + verifier.verifyErrorFreeLog(); + + verifyNoTextInLog(verifier, "Build cache is disabled for 'clean' goal."); + } + + private static void verifyNoTextInLog(Verifier verifier, String text) throws VerificationException { + Assertions.assertNull(findFirstLineContainingTextsInLogs(verifier, text)); + } +} From 19f259e4b13ee5ef0a4692a22c2b1c384dd096fd Mon Sep 17 00:00:00 2001 From: Maksim Avilov Date: Mon, 1 Dec 2025 16:36:23 +0100 Subject: [PATCH 2/5] Changed log level to INFO --- .../maven/buildcache/BuildCacheMojosExecutionStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index d5c5a264..d65db98f 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -189,7 +189,7 @@ public void execute( */ private boolean isGoalClean(List mojoExecutions) { if (mojoExecutions.stream().allMatch(mojoExecution -> "clean".equals(mojoExecution.getLifecyclePhase()))) { - LOGGER.warn("Build cache is disabled for 'clean' goal."); + LOGGER.info("Build cache is disabled for 'clean' goal."); return true; } return false; From 2f8b70169e72b683ff552650d6fd4db20d7bd34a Mon Sep 17 00:00:00 2001 From: Maksim Avilov Date: Mon, 1 Dec 2025 16:38:14 +0100 Subject: [PATCH 3/5] Fix the JavaDoc comment --- .../BuildCacheMojosExecutionStrategy.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index d65db98f..da1e559a 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -108,8 +108,8 @@ public void execute( // execute clean bound goals before restoring to not interfere/slowdown clean CacheState cacheState = DISABLED; CacheResult result = CacheResult.empty(); - boolean skipCache = - cacheConfig.isSkipCache() || MavenProjectInput.isSkipCache(project) || isGoalClean(mojoExecutions); + boolean skipCache = cacheConfig.isSkipCache() || MavenProjectInput.isSkipCache(project) + || isGoalClean(mojoExecutions); boolean cacheIsDisabled = MavenProjectInput.isCacheDisabled(project); // Forked execution should be thought as a part of originating mojo internal // implementation @@ -143,8 +143,8 @@ public void execute( boolean restorable = result.isSuccess() || result.isPartialSuccess(); boolean restored = false; // if partially restored need to save increment if (restorable) { - CacheRestorationStatus cacheRestorationStatus = - restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig); + CacheRestorationStatus cacheRestorationStatus = restoreProject(result, mojoExecutions, + mojoExecutionRunner, cacheConfig); restored = CacheRestorationStatus.SUCCESS == cacheRestorationStatus; executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, cleanPhase, mojoExecutionRunner); } @@ -199,9 +199,8 @@ private boolean isGoalClean(List mojoExecutions) { * Cache configuration could demand to restore some files in the project * directory (generated sources or even arbitrary content) * If an error occurs during or after this kind of restoration AND a clean phase - * was required in the build : - * we execute an extra clean phase to remove any potential partially restored - * files + * was required in the build, we execute an extra clean phase to remove any + * potential partially restored files. * * @param cacheRestorationStatus the restoration status * @param cleanPhase clean phase mojos @@ -245,8 +244,8 @@ private CacheRestorationStatus restoreProject( final Build build = cacheResult.getBuildInfo(); final MavenProject project = cacheResult.getContext().getProject(); final MavenSession session = cacheResult.getContext().getSession(); - final List cachedSegment = - lifecyclePhasesHelper.getCachedSegment(project, mojoExecutions, build); + final List cachedSegment = lifecyclePhasesHelper.getCachedSegment(project, mojoExecutions, + build); // Verify cache consistency for cached mojos LOGGER.debug("Verify consistency on cached mojos"); @@ -302,8 +301,8 @@ private CacheRestorationStatus restoreProject( mojoExecutionScope.seed(MojoExecution.class, cacheCandidate); mojo = mavenPluginManager.getConfiguredMojo(Mojo.class, session, cacheCandidate); - MojoExecutionEvent mojoExecutionEvent = - new MojoExecutionEvent(session, project, cacheCandidate, mojo); + MojoExecutionEvent mojoExecutionEvent = new MojoExecutionEvent(session, project, cacheCandidate, + mojo); mojoListener.beforeMojoExecution(mojoExecutionEvent); } catch (PluginConfigurationException | PluginContainerException e) { throw new RuntimeException(e); @@ -318,8 +317,8 @@ private CacheRestorationStatus restoreProject( // Execute mojos after the cache segment LOGGER.debug("Execute mojos post cache segment"); - List postCachedSegment = - lifecyclePhasesHelper.getPostCachedSegment(project, mojoExecutions, build); + List postCachedSegment = lifecyclePhasesHelper.getPostCachedSegment(project, mojoExecutions, + build); for (MojoExecution mojoExecution : postCachedSegment) { mojoExecutionRunner.run(mojoExecution); } From c0688dec8bd09425464f4f77877a105577bd57fc Mon Sep 17 00:00:00 2001 From: Maksim Avilov Date: Mon, 1 Dec 2025 16:39:30 +0100 Subject: [PATCH 4/5] Delete commented code --- .../buildcache/BuildCacheMojosExecutionStrategy.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index da1e559a..fa28c685 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -278,16 +278,6 @@ private CacheRestorationStatus restoreProject( LOGGER.info( "Mojo execution is forced by project property: {}", cacheCandidate.getMojoDescriptor().getFullGoalName()); - // need maven 4 as minumum - // mojoExecutionScope.seed( - // org.apache.maven.api.plugin.Log.class, - // new DefaultLog(LoggerFactory.getLogger( - // cacheCandidate.getMojoDescriptor().getFullGoalName()))); - // mojoExecutionScope.seed(Project.class, ((DefaultSession) - // session.getSession()).getProject(project)); - // mojoExecutionScope.seed( - // org.apache.maven.api.MojoExecution.class, new - // DefaultMojoExecution(cacheCandidate)); mojoExecutionRunner.run(cacheCandidate); } else { LOGGER.info( From fa4936121cd5f79d3bf09eec28b2d35fee5d41d2 Mon Sep 17 00:00:00 2001 From: Maksim Avilov Date: Mon, 1 Dec 2025 17:43:15 +0100 Subject: [PATCH 5/5] Spotless applied --- .../BuildCacheMojosExecutionStrategy.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index fa28c685..863d3e41 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -108,8 +108,8 @@ public void execute( // execute clean bound goals before restoring to not interfere/slowdown clean CacheState cacheState = DISABLED; CacheResult result = CacheResult.empty(); - boolean skipCache = cacheConfig.isSkipCache() || MavenProjectInput.isSkipCache(project) - || isGoalClean(mojoExecutions); + boolean skipCache = + cacheConfig.isSkipCache() || MavenProjectInput.isSkipCache(project) || isGoalClean(mojoExecutions); boolean cacheIsDisabled = MavenProjectInput.isCacheDisabled(project); // Forked execution should be thought as a part of originating mojo internal // implementation @@ -143,8 +143,8 @@ public void execute( boolean restorable = result.isSuccess() || result.isPartialSuccess(); boolean restored = false; // if partially restored need to save increment if (restorable) { - CacheRestorationStatus cacheRestorationStatus = restoreProject(result, mojoExecutions, - mojoExecutionRunner, cacheConfig); + CacheRestorationStatus cacheRestorationStatus = + restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig); restored = CacheRestorationStatus.SUCCESS == cacheRestorationStatus; executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, cleanPhase, mojoExecutionRunner); } @@ -244,8 +244,8 @@ private CacheRestorationStatus restoreProject( final Build build = cacheResult.getBuildInfo(); final MavenProject project = cacheResult.getContext().getProject(); final MavenSession session = cacheResult.getContext().getSession(); - final List cachedSegment = lifecyclePhasesHelper.getCachedSegment(project, mojoExecutions, - build); + final List cachedSegment = + lifecyclePhasesHelper.getCachedSegment(project, mojoExecutions, build); // Verify cache consistency for cached mojos LOGGER.debug("Verify consistency on cached mojos"); @@ -291,8 +291,8 @@ private CacheRestorationStatus restoreProject( mojoExecutionScope.seed(MojoExecution.class, cacheCandidate); mojo = mavenPluginManager.getConfiguredMojo(Mojo.class, session, cacheCandidate); - MojoExecutionEvent mojoExecutionEvent = new MojoExecutionEvent(session, project, cacheCandidate, - mojo); + MojoExecutionEvent mojoExecutionEvent = + new MojoExecutionEvent(session, project, cacheCandidate, mojo); mojoListener.beforeMojoExecution(mojoExecutionEvent); } catch (PluginConfigurationException | PluginContainerException e) { throw new RuntimeException(e); @@ -307,8 +307,8 @@ private CacheRestorationStatus restoreProject( // Execute mojos after the cache segment LOGGER.debug("Execute mojos post cache segment"); - List postCachedSegment = lifecyclePhasesHelper.getPostCachedSegment(project, mojoExecutions, - build); + List postCachedSegment = + lifecyclePhasesHelper.getPostCachedSegment(project, mojoExecutions, build); for (MojoExecution mojoExecution : postCachedSegment) { mojoExecutionRunner.run(mojoExecution); }