diff --git a/.asf.yaml b/.asf.yaml
index a95f99ea..c87faefa 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -13,6 +13,8 @@ github:
rebase: true
autolink_jira:
- MBUILDCACHE
+ del_branch_on_merge: true
+
notifications:
commits: commits@maven.apache.org
issues: issues@maven.apache.org
diff --git a/.github/workflows/maven-verify-3.9.x.yml b/.github/workflows/maven-verify-3.9.x.yml
index 10dab2d3..3b4b9d09 100644
--- a/.github/workflows/maven-verify-3.9.x.yml
+++ b/.github/workflows/maven-verify-3.9.x.yml
@@ -24,11 +24,11 @@ on:
jobs:
build:
name: Verify
- uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v3
+ uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v4
with:
maven-args: '-D"maven.test.redirectTestOutputToFile=false -Pmaven3"'
- maven-matrix: '[ "3.9.5", "4.0.0-alpha-8" ]'
- jdk-matrix: '[ "11", "17" ]'
+ maven-matrix: '[ "3.9.9" ]'
+ jdk-matrix: '[ "11", "17", "21" ]'
ff-goal: '-P run-its verify javadoc:jar'
verify-goal: '-P run-its verify javadoc:jar'
diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml
index 0665f5c2..247b1cf1 100644
--- a/.github/workflows/maven-verify.yml
+++ b/.github/workflows/maven-verify.yml
@@ -24,10 +24,11 @@ on:
jobs:
build:
name: Verify
- uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v3
+ uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v4
with:
maven-args: '-D"maven.test.redirectTestOutputToFile=false"'
- maven-matrix: '[ "3.9.5", "4.0.0-alpha-8" ]'
- jdk-matrix: '[ "11", "17" ]'
+ maven-matrix: '[ "3.9.9", "4.0.0-rc-3" ]'
+ maven4-enabled: true
+ matrix-exclude: '[ {"jdk": "8"} ]'
ff-goal: '-P run-its verify javadoc:jar'
- verify-goal: '-P run-its verify javadoc:jar'
\ No newline at end of file
+ verify-goal: '-P run-its verify javadoc:jar'
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index b44872cf..1049eaa3 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -22,4 +22,4 @@ on:
- master
jobs:
update_release_draft:
- uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@v3
+ uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@v4
diff --git a/Jenkinsfile b/Jenkinsfile
index bcfdaeb8..079fa482 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -17,4 +17,4 @@
* under the License.
*/
-asfMavenTlpStdBuild( 'jdks' : [ "11", "17" ] )
+asfMavenTlpStdBuild( 'jdks' : [ "11", "17", "21" ] )
diff --git a/pom.xml b/pom.xml
index 4ea7d506..b2502e66 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@ under the License.
org.apache.maven.extensionsmaven-build-cache-extension
- 1.1.1-SNAPSHOT
+ 1.2.1-SNAPSHOTjarApache Maven Build Cache Extension
@@ -40,7 +40,7 @@ under the License.
scm:git:https://github.com/apache/maven-build-cache-extension.gitscm:git:https://github.com/apache/maven-build-cache-extension.git
- maven-build-cache-extension-1.0.0
+ HEADhttps://github.com/apache/maven-build-cache-extension/tree/${project.scm.tag}
@@ -59,7 +59,7 @@ under the License.
- 2023-11-27T01:58:16Z
+ 2024-05-09T00:27:12Z83.9.011
@@ -71,18 +71,17 @@ under the License.
${project.build.directory}/${maven.dir}1.4
- 3.14.0
- 5.10.1
+ 3.17.0
+ 5.12.24.11.0
- 3.5.13.4.32.02.01.111.3
- 1.9.18
+ 1.9.221.7.36
- 2.35.1
+ 2.35.22.6.4trueextensions-archives/${project.artifactId}-LATEST
@@ -93,7 +92,7 @@ under the License.
org.testcontainerstestcontainers-bom
- 1.19.4
+ 1.21.0pomimport
@@ -114,12 +113,6 @@ under the License.
${mavenVersion}provided
-
- org.apache.maven
- maven-compat
- ${mavenVersion}
- provided
- org.apache.maven.resolvermaven-resolver-transport-http
@@ -129,12 +122,12 @@ under the License.
net.openhftzero-allocation-hashing
- 0.16
+ 0.27ea0com.github.albfernandezjuniversalchardet
- 2.4.0
+ 2.5.0org.apache.commons
@@ -144,7 +137,7 @@ under the License.
commons-iocommons-io
- 2.15.1
+ 2.19.0javax.annotation
@@ -154,7 +147,10 @@ under the License.
org.codehaus.plexusplexus-utils
- ${plexusUtilsVersion}
+
+
+ org.codehaus.plexus
+ plexus-xmlcom.google.code.findbugs
@@ -221,7 +217,7 @@ under the License.
org.assertjassertj-core
- 3.25.2
+ 3.27.3test
@@ -307,7 +303,7 @@ under the License.
org.apache.maven.pluginsmaven-invoker-plugin
- 3.6.0
+ 3.9.0org.apache.rat
@@ -338,7 +334,7 @@ under the License.
src/main/mdo/build-cache-diff.mdosrc/main/mdo/build-cache-report.mdo
- 1.0.0
+ 1.2.0
@@ -373,7 +369,7 @@ under the License.
org.codehaus.mojobuild-helper-maven-plugin
- 3.5.0
+ 3.6.0add-resources
@@ -394,7 +390,7 @@ under the License.
org.apache.maven.pluginsmaven-dependency-plugin
- 3.6.1
+ 3.8.1copy-maven-distribution
@@ -418,6 +414,19 @@ under the License.
+
+
+ org.eclipse.sisu
+ sisu-maven-plugin
+
+
+ generate-index
+
+ main-index
+
+
+
+
@@ -456,7 +465,7 @@ under the License.
maven3
- 3.9.5
+ 3.9.9maven3${project.build.directory}/${maven.dir}
diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
index 6112e293..5790d19f 100644
--- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
+++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
@@ -79,7 +79,7 @@ public class BuildCacheMojosExecutionStrategy implements MojosExecutionStrategy
private final MojoParametersListener mojoListener;
private final LifecyclePhasesHelper lifecyclePhasesHelper;
private final MavenPluginManager mavenPluginManager;
- private MojoExecutionScope mojoExecutionScope;
+ private final MojoExecutionScope mojoExecutionScope;
@Inject
public BuildCacheMojosExecutionStrategy(
@@ -130,11 +130,11 @@ public void execute(
}
boolean restorable = result.isSuccess() || result.isPartialSuccess();
- boolean restored = result.isSuccess(); // if partially restored need to save increment
+ boolean restored = false; // if partially restored need to save increment
if (restorable) {
CacheRestorationStatus cacheRestorationStatus =
restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig);
- restored &= CacheRestorationStatus.SUCCESS == cacheRestorationStatus;
+ restored = CacheRestorationStatus.SUCCESS == cacheRestorationStatus;
executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, cleanPhase, mojoExecutionRunner);
}
if (!restored) {
@@ -147,9 +147,18 @@ public void execute(
}
}
- if (cacheState == INITIALIZED && (!restorable || !restored)) {
- final Map executionEvents = mojoListener.getProjectExecutions(project);
- cacheController.save(result, mojoExecutions, executionEvents);
+ if (cacheState == INITIALIZED && (!result.isSuccess() || !restored)) {
+ if (cacheConfig.isSkipSave()) {
+ LOGGER.info("Cache saving is disabled.");
+ } else if (cacheConfig.isMandatoryClean()
+ && lifecyclePhasesHelper
+ .getCleanSegment(project, mojoExecutions)
+ .isEmpty()) {
+ LOGGER.info("Cache storing is skipped since there was no \"clean\" phase.");
+ } else {
+ final Map executionEvents = mojoListener.getProjectExecutions(project);
+ cacheController.save(result, mojoExecutions, executionEvents);
+ }
}
if (cacheConfig.isFailFast() && !result.isSuccess() && !skipCache && !forkedExecution) {
@@ -262,6 +271,20 @@ private CacheRestorationStatus restoreProject(
LOGGER.info(
"Skipping plugin execution (cached): {}",
cacheCandidate.getMojoDescriptor().getFullGoalName());
+ // Need to populate cached candidate executions for the build cache save result
+ Mojo mojo = null;
+ try {
+ mojo = mavenPluginManager.getConfiguredMojo(Mojo.class, session, cacheCandidate);
+ MojoExecutionEvent mojoExecutionEvent =
+ new MojoExecutionEvent(session, project, cacheCandidate, mojo);
+ mojoListener.beforeMojoExecution(mojoExecutionEvent);
+ } catch (PluginConfigurationException | PluginContainerException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (mojo != null) {
+ mavenPluginManager.releaseMojo(mojo, cacheCandidate);
+ }
+ }
}
}
@@ -411,6 +434,6 @@ private static String normalizedPath(Path path, Path baseDirPath) {
private enum CacheRestorationStatus {
SUCCESS,
FAILURE,
- FAILURE_NEEDS_CLEAN;
+ FAILURE_NEEDS_CLEAN
}
}
diff --git a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
index 9779a482..5a774735 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
@@ -34,9 +34,11 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -46,20 +48,20 @@
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
import java.util.regex.Pattern;
-import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.maven.SessionScoped;
-import org.apache.maven.artifact.InvalidArtifactRTException;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.buildcache.artifact.ArtifactRestorationReport;
+import org.apache.maven.buildcache.artifact.OutputType;
import org.apache.maven.buildcache.artifact.RestoredArtifact;
import org.apache.maven.buildcache.checksum.MavenProjectInput;
import org.apache.maven.buildcache.hash.HashAlgorithm;
@@ -74,6 +76,7 @@
import org.apache.maven.buildcache.xml.build.DigestItem;
import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
import org.apache.maven.buildcache.xml.build.Scm;
+import org.apache.maven.buildcache.xml.config.DirName;
import org.apache.maven.buildcache.xml.config.PropertyName;
import org.apache.maven.buildcache.xml.config.TrackedProperty;
import org.apache.maven.buildcache.xml.diff.Diff;
@@ -85,15 +88,14 @@
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
-import org.apache.maven.repository.RepositorySystem;
import org.codehaus.plexus.util.ReflectionUtils;
+import org.eclipse.aether.RepositorySystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
-import static org.apache.commons.lang3.StringUtils.replace;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.maven.buildcache.CacheResult.empty;
import static org.apache.maven.buildcache.CacheResult.failure;
@@ -112,13 +114,10 @@
@SuppressWarnings("unused")
public class CacheControllerImpl implements CacheController {
- public static final String FILE_SEPARATOR_SUBST = "_";
- /**
- * Prefix for generated sources stored as a separate artifact in cache
- */
- private static final String BUILD_PREFIX = "build" + FILE_SEPARATOR_SUBST;
-
private static final Logger LOGGER = LoggerFactory.getLogger(CacheControllerImpl.class);
+ private static final String DEFAULT_FILE_GLOB = "*";
+ public static final String ERROR_MSG_RESTORATION_OUTSIDE_PROJECT =
+ "Blocked an attempt to restore files outside of a project directory: ";
private final MavenProjectHelper projectHelper;
private final ArtifactHandlerManager artifactHandlerManager;
@@ -133,6 +132,15 @@ public class CacheControllerImpl implements CacheController {
private final RestoredArtifactHandler restoreArtifactHandler;
private volatile Scm scm;
+ /**
+ * A map dedicated to store the base path of resources stored to the cache which are not original artifacts
+ * (ex : generated source basedir).
+ * Used to link the resource to its path on disk
+ */
+ private final Map attachedResourcesPathsById = new HashMap<>();
+
+ private int attachedResourceCounter = 0;
+ // CHECKSTYLE_OFF: ParameterNumber
@Inject
public CacheControllerImpl(
MavenProjectHelper projectHelper,
@@ -146,6 +154,7 @@ public CacheControllerImpl(
RestoredArtifactHandler restoreArtifactHandler,
LifecyclePhasesHelper lifecyclePhasesHelper,
MavenSession session) {
+ // CHECKSTYLE_OFF: ParameterNumber
this.projectHelper = projectHelper;
this.localCache = localCache;
this.remoteCache = remoteCache;
@@ -297,6 +306,45 @@ private boolean canIgnoreMissingSegment(MavenProject project, Build info, List createRestorationToDiskConsumer(final MavenProject project, final Artifact artifact) {
+
+ if (cacheConfig.isRestoreOnDiskArtifacts() && MavenProjectInput.isRestoreOnDiskArtifacts(project)) {
+
+ Path restorationPath = project.getBasedir().toPath().resolve(artifact.getFilePath());
+ final AtomicBoolean restored = new AtomicBoolean(false);
+ return file -> {
+ // Set to restored even if it fails later, we don't want multiple try
+ if (restored.compareAndSet(false, true)) {
+ verifyRestorationInsideProject(project, restorationPath);
+ try {
+ Files.createDirectories(restorationPath.getParent());
+ Files.copy(file.toPath(), restorationPath, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ LOGGER.error("Cannot restore file " + artifact.getFileName(), e);
+ throw new RuntimeException(e);
+ }
+ LOGGER.debug("Restored file on disk ({} to {})", artifact.getFileName(), restorationPath);
+ }
+ return restorationPath.toFile();
+ };
+ }
+ // Return a consumer doing nothing
+ return file -> file;
+ }
+
+ private boolean isPathInsideProject(final MavenProject project, Path path) {
+ Path restorationPath = path.toAbsolutePath().normalize();
+ return restorationPath.startsWith(project.getBasedir().toPath());
+ }
+
+ private void verifyRestorationInsideProject(final MavenProject project, Path path) {
+ if (!isPathInsideProject(project, path)) {
+ Path normalized = path.toAbsolutePath().normalize();
+ LOGGER.error(ERROR_MSG_RESTORATION_OUTSIDE_PROJECT + normalized);
+ throw new RuntimeException(ERROR_MSG_RESTORATION_OUTSIDE_PROJECT + normalized);
+ }
+ }
+
@Override
public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult) {
@@ -318,15 +366,23 @@ public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult
final Future downloadTask =
createDownloadTask(cacheResult, context, project, artifactInfo, originalVersion);
restoredProjectArtifact = restoredArtifact(
- project.getArtifact(), artifactInfo.getType(), artifactInfo.getClassifier(), downloadTask);
+ project.getArtifact(),
+ artifactInfo.getType(),
+ artifactInfo.getClassifier(),
+ downloadTask,
+ createRestorationToDiskConsumer(project, artifactInfo));
+ if (!cacheConfig.isLazyRestore()) {
+ restoredProjectArtifact.getFile();
+ }
}
for (Artifact attachedArtifactInfo : build.getAttachedArtifacts()) {
String originalVersion = attachedArtifactInfo.getVersion();
attachedArtifactInfo.setVersion(project.getVersion());
if (isNotBlank(attachedArtifactInfo.getFileName())) {
- if (StringUtils.startsWith(attachedArtifactInfo.getClassifier(), BUILD_PREFIX)) {
- // restoring generated sources might be unnecessary in CI, could be disabled for
+ OutputType outputType = OutputType.fromClassifier(attachedArtifactInfo.getClassifier());
+ if (OutputType.ARTIFACT != outputType) {
+ // restoring generated sources / extra output might be unnecessary in CI, could be disabled for
// performance reasons
// it may also be disabled on a per-project level (defaults to true - enable)
if (cacheConfig.isRestoreGeneratedSources()
@@ -345,7 +401,11 @@ public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult
restoredProjectArtifact == null ? project.getArtifact() : restoredProjectArtifact,
attachedArtifactInfo.getType(),
attachedArtifactInfo.getClassifier(),
- downloadTask);
+ downloadTask,
+ createRestorationToDiskConsumer(project, attachedArtifactInfo));
+ if (!cacheConfig.isLazyRestore()) {
+ restoredAttachedArtifact.getFile();
+ }
restoredAttachedArtifacts.add(restoredAttachedArtifact);
}
}
@@ -354,6 +414,10 @@ public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult
// in which case, the project is unmodified and we continue with normal build.
if (restoredProjectArtifact != null) {
project.setArtifact(restoredProjectArtifact);
+ // need to include package lifecycle to save build info for incremental builds
+ if (!project.hasLifecyclePhase("package")) {
+ project.addLifecyclePhase("package");
+ }
}
restoredAttachedArtifacts.forEach(project::addAttachedArtifact);
restorationReport.setSuccess(true);
@@ -371,7 +435,8 @@ private RestoredArtifact restoredArtifact(
org.apache.maven.artifact.Artifact parent,
String artifactType,
String artifactClassifier,
- Future artifactFile) {
+ Future artifactFile,
+ Function restoreToDiskConsumer) {
ArtifactHandler handler = null;
if (artifactType != null) {
@@ -383,8 +448,8 @@ private RestoredArtifact restoredArtifact(
}
// todo: probably need update download url to cache
- RestoredArtifact artifact =
- new RestoredArtifact(parent, artifactFile, artifactType, artifactClassifier, handler);
+ RestoredArtifact artifact = new RestoredArtifact(
+ parent, artifactFile, artifactType, artifactClassifier, handler, restoreToDiskConsumer);
artifact.setResolved(true);
return artifact;
@@ -399,36 +464,17 @@ private Future createDownloadTask(
final FutureTask downloadTask = new FutureTask<>(() -> {
LOGGER.debug("Downloading artifact {}", artifact.getArtifactId());
final Path artifactFile = localCache.getArtifactFile(context, cacheResult.getSource(), artifact);
+
if (!Files.exists(artifactFile)) {
throw new FileNotFoundException("Missing file for cached build, cannot restore. File: " + artifactFile);
}
- LOGGER.debug("Downloaded artifact " + artifact.getArtifactId() + " to: " + artifactFile);
+ LOGGER.debug("Downloaded artifact {} to: {}", artifact.getArtifactId(), artifactFile);
return restoreArtifactHandler
.adjustArchiveArtifactVersion(project, originalVersion, artifactFile)
.toFile();
});
if (!cacheConfig.isLazyRestore()) {
downloadTask.run();
- try {
- downloadTask.get();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new InvalidArtifactRTException(
- artifact.getGroupId(),
- artifact.getArtifactId(),
- artifact.getVersion(),
- artifact.getType(),
- RestoredArtifact.MSG_INTERRUPTED_WHILE_RETRIEVING_ARTIFACT_FILE,
- e);
- } catch (ExecutionException e) {
- throw new InvalidArtifactRTException(
- artifact.getGroupId(),
- artifact.getArtifactId(),
- artifact.getVersion(),
- artifact.getType(),
- RestoredArtifact.MSG_ERROR_RETRIEVING_ARTIFACT_FILE,
- e.getCause());
- }
}
return downloadTask;
}
@@ -460,8 +506,8 @@ public void save(
attachedArtifacts = project.getAttachedArtifacts() != null
? project.getAttachedArtifacts()
: Collections.emptyList();
- attachedArtifactDtos = artifactDtos(attachedArtifacts, algorithm);
- projectArtifactDto = artifactDto(project.getArtifact(), algorithm);
+ attachedArtifactDtos = artifactDtos(attachedArtifacts, algorithm, project);
+ projectArtifactDto = artifactDto(project.getArtifact(), algorithm, project);
} else {
attachedArtifacts = Collections.emptyList();
attachedArtifactDtos = new ArrayList<>();
@@ -485,7 +531,6 @@ public void save(
// if package phase presence means new artifacts were packaged
if (project.hasLifecyclePhase("package")) {
- localCache.saveBuildInfo(cacheResult, build);
if (projectArtifact.getFile() != null) {
localCache.saveArtifactFile(cacheResult, projectArtifact);
}
@@ -503,10 +548,10 @@ public void save(
}
}
}
- } else {
- localCache.saveBuildInfo(cacheResult, build);
}
+ localCache.saveBuildInfo(cacheResult, build);
+
if (cacheConfig.isBaselineDiffEnabled()) {
produceDiffReport(cacheResult, build);
}
@@ -575,24 +620,35 @@ public void produceDiffReport(CacheResult cacheResult, Build build) {
}
private List artifactDtos(
- List attachedArtifacts, HashAlgorithm digest) throws IOException {
+ List attachedArtifacts, HashAlgorithm digest, MavenProject project)
+ throws IOException {
List result = new ArrayList<>();
for (org.apache.maven.artifact.Artifact attachedArtifact : attachedArtifacts) {
if (attachedArtifact.getFile() != null
&& isOutputArtifact(attachedArtifact.getFile().getName())) {
- result.add(artifactDto(attachedArtifact, digest));
+ result.add(artifactDto(attachedArtifact, digest, project));
}
}
return result;
}
- private Artifact artifactDto(org.apache.maven.artifact.Artifact projectArtifact, HashAlgorithm algorithm)
+ private Artifact artifactDto(
+ org.apache.maven.artifact.Artifact projectArtifact, HashAlgorithm algorithm, MavenProject project)
throws IOException {
final Artifact dto = DtoUtils.createDto(projectArtifact);
if (projectArtifact.getFile() != null && projectArtifact.getFile().isFile()) {
final Path file = projectArtifact.getFile().toPath();
dto.setFileHash(algorithm.hash(file));
dto.setFileSize(Files.size(file));
+
+ // Get the relative path of any extra zip directory added to the cache
+ Path relativePath = attachedResourcesPathsById.get(projectArtifact.getClassifier());
+ if (relativePath == null) {
+ // If the path was not a member of this map, we are in presence of an original artifact.
+ // we get its location on the disk
+ relativePath = project.getBasedir().toPath().relativize(file.toAbsolutePath());
+ }
+ dto.setFilePath(FilenameUtils.separatorsToUnix(relativePath.toString()));
}
return dto;
}
@@ -817,36 +873,23 @@ private void populateGitInfo(Build build, MavenSession session) {
build.getDto().setScm(scm);
}
- private void zipAndAttachArtifact(MavenProject project, Path dir, String classifier) throws IOException {
- Path temp = Files.createTempFile("maven-incremental", project.getArtifactId());
+ private boolean zipAndAttachArtifact(MavenProject project, Path dir, String classifier, final String glob)
+ throws IOException {
+ Path temp = Files.createTempFile("maven-incremental-", project.getArtifactId());
temp.toFile().deleteOnExit();
- CacheUtils.zip(dir, temp);
- projectHelper.attachArtifact(project, "zip", classifier, temp.toFile());
- }
-
- private String pathToClassifier(Path relative) {
- final int nameCount = relative.getNameCount();
- List segments = new ArrayList<>(nameCount + 1);
- for (int i = 0; i < nameCount; i++) {
- segments.add(relative.getName(i).toFile().getName());
+ boolean hasFile = CacheUtils.zip(dir, temp, glob);
+ if (hasFile) {
+ projectHelper.attachArtifact(project, "zip", classifier, temp.toFile());
}
- // todo handle _ in file names
- return BUILD_PREFIX + StringUtils.join(segments.iterator(), FILE_SEPARATOR_SUBST);
- }
-
- private Path classifierToPath(Path outputDir, String classifier) {
- classifier = StringUtils.removeStart(classifier, BUILD_PREFIX);
- final String relPath = replace(classifier, FILE_SEPARATOR_SUBST, File.separator);
- return outputDir.resolve(relPath);
+ return hasFile;
}
private void restoreGeneratedSources(Artifact artifact, Path artifactFilePath, MavenProject project)
throws IOException {
- final Path targetDir = Paths.get(project.getBuild().getDirectory());
- final Path outputDir = classifierToPath(targetDir, artifact.getClassifier());
- if (Files.exists(outputDir)) {
- FileUtils.cleanDirectory(outputDir.toFile());
- } else {
+ final Path baseDir = project.getBasedir().toPath();
+ final Path outputDir = baseDir.resolve(FilenameUtils.separatorsToSystem(artifact.getFilePath()));
+ verifyRestorationInsideProject(project, outputDir);
+ if (!Files.exists(outputDir)) {
Files.createDirectories(outputDir);
}
CacheUtils.unzip(artifactFilePath, outputDir);
@@ -857,10 +900,11 @@ public void attachGeneratedSources(MavenProject project) throws IOException {
final Path targetDir = Paths.get(project.getBuild().getDirectory());
final Path generatedSourcesDir = targetDir.resolve("generated-sources");
- attachDirIfNotEmpty(generatedSourcesDir, targetDir, project);
+ attachDirIfNotEmpty(generatedSourcesDir, targetDir, project, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB);
final Path generatedTestSourcesDir = targetDir.resolve("generated-test-sources");
- attachDirIfNotEmpty(generatedTestSourcesDir, targetDir, project);
+ attachDirIfNotEmpty(
+ generatedTestSourcesDir, targetDir, project, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB);
Set sourceRoots = new TreeSet<>();
if (project.getCompileSourceRoots() != null) {
@@ -876,26 +920,40 @@ public void attachGeneratedSources(MavenProject project) throws IOException {
&& sourceRootPath.startsWith(targetDir)
&& !(sourceRootPath.startsWith(generatedSourcesDir)
|| sourceRootPath.startsWith(generatedTestSourcesDir))) { // dir within target
- attachDirIfNotEmpty(sourceRootPath, targetDir, project);
+ attachDirIfNotEmpty(sourceRootPath, targetDir, project, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB);
}
}
}
private void attachOutputs(MavenProject project) throws IOException {
- final List attachedDirs = cacheConfig.getAttachedOutputs();
- for (String dir : attachedDirs) {
+ final List attachedDirs = cacheConfig.getAttachedOutputs();
+ for (DirName dir : attachedDirs) {
final Path targetDir = Paths.get(project.getBuild().getDirectory());
- final Path outputDir = targetDir.resolve(dir);
- attachDirIfNotEmpty(outputDir, targetDir, project);
+ final Path outputDir = targetDir.resolve(dir.getValue());
+ if (isPathInsideProject(project, outputDir)) {
+ attachDirIfNotEmpty(outputDir, targetDir, project, OutputType.EXTRA_OUTPUT, dir.getGlob());
+ } else {
+ LOGGER.warn("Outside project output candidate directory discarded ({})", outputDir.normalize());
+ }
}
}
- private void attachDirIfNotEmpty(Path candidateSubDir, Path parentDir, MavenProject project) throws IOException {
+ private void attachDirIfNotEmpty(
+ Path candidateSubDir,
+ Path parentDir,
+ MavenProject project,
+ final OutputType attachedOutputType,
+ final String glob)
+ throws IOException {
if (Files.isDirectory(candidateSubDir) && hasFiles(candidateSubDir)) {
- final Path relativePath = parentDir.relativize(candidateSubDir);
- final String classifier = pathToClassifier(relativePath);
- zipAndAttachArtifact(project, candidateSubDir, classifier);
- LOGGER.debug("Attached directory: {}", candidateSubDir);
+ final Path relativePath = project.getBasedir().toPath().relativize(candidateSubDir);
+ attachedResourceCounter++;
+ final String classifier = attachedOutputType.getClassifierPrefix() + attachedResourceCounter;
+ boolean success = zipAndAttachArtifact(project, candidateSubDir, classifier, glob);
+ if (success) {
+ attachedResourcesPathsById.put(classifier, relativePath);
+ LOGGER.debug("Attached directory: {}", candidateSubDir);
+ }
}
}
diff --git a/src/main/java/org/apache/maven/buildcache/CacheDiff.java b/src/main/java/org/apache/maven/buildcache/CacheDiff.java
index 4c9c63b5..a378804c 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheDiff.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheDiff.java
@@ -68,6 +68,7 @@ public Diff compare() {
compareExecutions(current.getExecutions(), baseline.getExecutions());
compareFiles(current.getProjectsInputInfo(), baseline.getProjectsInputInfo());
compareDependencies(current.getProjectsInputInfo(), baseline.getProjectsInputInfo());
+ comparePluginDependencies(current.getProjectsInputInfo(), baseline.getProjectsInputInfo());
final Diff buildDiffType = new Diff();
buildDiffType.getMismatches().addAll(report);
@@ -157,20 +158,40 @@ private void compareFiles(ProjectsInputInfo current, ProjectsInputInfo baseline)
}
private void compareDependencies(ProjectsInputInfo current, ProjectsInputInfo baseline) {
- final Map currentDependencies = current.getItems().stream()
- .filter(item -> "dependency".equals(item.getType()))
- .collect(Collectors.toMap(DigestItem::getValue, item -> item));
+ compareDependencies(
+ "dependencies files",
+ current.getItems().stream()
+ .filter(item -> "dependency".equals(item.getType()))
+ .collect(Collectors.toList()),
+ baseline.getItems().stream()
+ .filter(item -> "dependency".equals(item.getType()))
+ .collect(Collectors.toList()));
+ }
- final Map baselineDependencies = baseline.getItems().stream()
- .filter(item -> "dependency".equals(item.getType()))
- .collect(Collectors.toMap(DigestItem::getValue, item -> item));
+ private void comparePluginDependencies(ProjectsInputInfo current, ProjectsInputInfo baseline) {
+ compareDependencies(
+ "plugin dependencies files",
+ current.getItems().stream()
+ .filter(item -> "pluginDependency".equals(item.getType()))
+ .collect(Collectors.toList()),
+ baseline.getItems().stream()
+ .filter(item -> "pluginDependency".equals(item.getType()))
+ .collect(Collectors.toList()));
+ }
+
+ private void compareDependencies(String property, List current, List baseline) {
+ final Map currentDependencies =
+ current.stream().collect(Collectors.toMap(DigestItem::getValue, item -> item));
+
+ final Map baselineDependencies =
+ baseline.stream().collect(Collectors.toMap(DigestItem::getValue, item -> item));
if (!Objects.equals(currentDependencies.keySet(), baselineDependencies.keySet())) {
Set currentVsBaseline = diff(currentDependencies.keySet(), baselineDependencies.keySet());
Set baselineVsCurrent = diff(baselineDependencies.keySet(), currentDependencies.keySet());
addNewMismatch(
- "dependencies files",
+ property,
"Remote and local builds contain different sets of dependencies and cannot be matched. "
+ "Added dependencies: " + currentVsBaseline + ". Removed dependencies: "
+ baselineVsCurrent,
diff --git a/src/main/java/org/apache/maven/buildcache/CacheUtils.java b/src/main/java/org/apache/maven/buildcache/CacheUtils.java
index 2396a0bc..61b9bc55 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheUtils.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheUtils.java
@@ -21,10 +21,13 @@
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Arrays;
@@ -37,6 +40,7 @@
import java.util.zip.ZipOutputStream;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.buildcache.xml.build.Scm;
@@ -160,21 +164,39 @@ public static boolean isArchive(File file) {
return StringUtils.endsWithAny(fileName, ".jar", ".zip", ".war", ".ear");
}
- public static void zip(Path dir, Path zip) throws IOException {
+ /**
+ * Put every matching files of a directory in a zip.
+ * @param dir directory to zip
+ * @param zip zip to populate
+ * @param glob glob to apply to filenames
+ * @return true if at least one file has been included in the zip.
+ * @throws IOException
+ */
+ public static boolean zip(final Path dir, final Path zip, final String glob) throws IOException {
+ final MutableBoolean hasFiles = new MutableBoolean();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zip))) {
+
+ PathMatcher matcher =
+ "*".equals(glob) ? null : FileSystems.getDefault().getPathMatcher("glob:" + glob);
Files.walkFileTree(dir, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
throws IOException {
- final ZipEntry zipEntry = new ZipEntry(dir.relativize(path).toString());
- zipOutputStream.putNextEntry(zipEntry);
- Files.copy(path, zipOutputStream);
- zipOutputStream.closeEntry();
+
+ if (matcher == null || matcher.matches(path.getFileName())) {
+ final ZipEntry zipEntry =
+ new ZipEntry(dir.relativize(path).toString());
+ zipOutputStream.putNextEntry(zipEntry);
+ Files.copy(path, zipOutputStream);
+ hasFiles.setTrue();
+ zipOutputStream.closeEntry();
+ }
return FileVisitResult.CONTINUE;
}
});
}
+ return hasFiles.booleanValue();
}
public static void unzip(Path zip, Path out) throws IOException {
@@ -190,7 +212,7 @@ public static void unzip(Path zip, Path out) throws IOException {
} else {
Path parent = file.getParent();
Files.createDirectories(parent);
- Files.copy(zis, file);
+ Files.copy(zis, file, StandardCopyOption.REPLACE_EXISTING);
}
Files.setLastModifiedTime(file, FileTime.fromMillis(entry.getTime()));
entry = zis.getNextEntry();
diff --git a/src/main/java/org/apache/maven/buildcache/DefaultMultiModuleSupport.java b/src/main/java/org/apache/maven/buildcache/DefaultMultiModuleSupport.java
index 7693b3d6..41cb0217 100644
--- a/src/main/java/org/apache/maven/buildcache/DefaultMultiModuleSupport.java
+++ b/src/main/java/org/apache/maven/buildcache/DefaultMultiModuleSupport.java
@@ -24,6 +24,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -132,7 +133,7 @@ private synchronized void buildModel(MavenSession session) {
File multiModulePomFile = getMultiModulePomFile(session);
ProjectBuildingRequest projectBuildingRequest = currentProject.getProjectBuildingRequest();
- boolean profilesMatched = projectBuildingRequest.getActiveProfileIds().containsAll(scanProfiles);
+ boolean profilesMatched = new HashSet<>(projectBuildingRequest.getActiveProfileIds()).containsAll(scanProfiles);
// we are building from root with the same profiles, no need to re-scan the whole multi-module project
if (currentProject.getFile().getAbsolutePath().equals(multiModulePomFile.getAbsolutePath())
diff --git a/src/main/java/org/apache/maven/buildcache/DefaultProjectInputCalculator.java b/src/main/java/org/apache/maven/buildcache/DefaultProjectInputCalculator.java
index 2a1217fd..3e974fc0 100644
--- a/src/main/java/org/apache/maven/buildcache/DefaultProjectInputCalculator.java
+++ b/src/main/java/org/apache/maven/buildcache/DefaultProjectInputCalculator.java
@@ -27,13 +27,14 @@
import java.util.concurrent.ConcurrentMap;
import org.apache.maven.SessionScoped;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.buildcache.checksum.MavenProjectInput;
import org.apache.maven.buildcache.xml.CacheConfig;
import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.repository.RepositorySystem;
+import org.eclipse.aether.RepositorySystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,6 +50,7 @@ public class DefaultProjectInputCalculator implements ProjectInputCalculator {
private final RepositorySystem repoSystem;
private final NormalizedModelProvider normalizedModelProvider;
private final MultiModuleSupport multiModuleSupport;
+ private final ArtifactHandlerManager artifactHandlerManager;
private final ConcurrentMap checkSumMap = new ConcurrentHashMap<>();
@@ -61,13 +63,15 @@ public DefaultProjectInputCalculator(
CacheConfig cacheConfig,
RepositorySystem repoSystem,
NormalizedModelProvider rawModelProvider,
- MultiModuleSupport multiModuleSupport) {
+ MultiModuleSupport multiModuleSupport,
+ ArtifactHandlerManager artifactHandlerManager) {
this.mavenSession = mavenSession;
this.remoteCache = remoteCache;
this.cacheConfig = cacheConfig;
this.repoSystem = repoSystem;
this.normalizedModelProvider = rawModelProvider;
this.multiModuleSupport = multiModuleSupport;
+ this.artifactHandlerManager = artifactHandlerManager;
}
@Override
@@ -108,7 +112,8 @@ private ProjectsInputInfo calculateInputInternal(String key, MavenProject projec
mavenSession,
cacheConfig,
repoSystem,
- remoteCache);
+ remoteCache,
+ artifactHandlerManager);
return input.calculateChecksum();
} catch (Exception e) {
throw new RuntimeException("Failed to calculate checksums for " + project.getArtifactId(), e);
diff --git a/src/main/java/org/apache/maven/buildcache/DefaultRestoredArtifactHandler.java b/src/main/java/org/apache/maven/buildcache/DefaultRestoredArtifactHandler.java
index 0742a304..96af8d90 100644
--- a/src/main/java/org/apache/maven/buildcache/DefaultRestoredArtifactHandler.java
+++ b/src/main/java/org/apache/maven/buildcache/DefaultRestoredArtifactHandler.java
@@ -24,10 +24,10 @@
import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
@@ -96,7 +96,7 @@ public Path adjustArchiveArtifactVersion(MavenProject project, String originalAr
String pomPropsVersion = "version=" + currentVersion;
try (JarFile jarFile = new JarFile(artifactFile.toFile())) {
try (JarOutputStream jos =
- new JarOutputStream(new BufferedOutputStream(new FileOutputStream(tmpJarFile)))) {
+ new JarOutputStream(new BufferedOutputStream(Files.newOutputStream(tmpJarFile.toPath())))) {
// Copy original jar file to the temporary one.
Enumeration jarEntries = jarFile.entries();
byte[] buffer = new byte[1024];
@@ -140,7 +140,7 @@ public Path adjustArchiveArtifactVersion(MavenProject project, String originalAr
private static void replaceEntry(
JarFile jarFile, JarEntry entry, String toReplace, String replacement, JarOutputStream jos)
throws IOException {
- String fullManifest = IOUtils.toString(jarFile.getInputStream(entry), StandardCharsets.UTF_8.name());
+ String fullManifest = IOUtils.toString(jarFile.getInputStream(entry), StandardCharsets.UTF_8);
String modified = fullManifest.replaceAll(toReplace, replacement);
byte[] bytes = modified.getBytes(StandardCharsets.UTF_8);
diff --git a/src/main/java/org/apache/maven/buildcache/MojoParametersListener.java b/src/main/java/org/apache/maven/buildcache/MojoParametersListener.java
index 97036ba5..edc0e22d 100644
--- a/src/main/java/org/apache/maven/buildcache/MojoParametersListener.java
+++ b/src/main/java/org/apache/maven/buildcache/MojoParametersListener.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.buildcache;
+import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -25,6 +26,9 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import org.apache.maven.buildcache.checksum.MavenProjectInput;
+import org.apache.maven.buildcache.xml.CacheConfig;
+import org.apache.maven.buildcache.xml.CacheState;
import org.apache.maven.execution.MojoExecutionEvent;
import org.apache.maven.execution.MojoExecutionListener;
import org.apache.maven.plugin.MojoExecutionException;
@@ -32,6 +36,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.apache.maven.buildcache.xml.CacheState.DISABLED;
+import static org.apache.maven.buildcache.xml.CacheState.INITIALIZED;
+
/**
* MojoParametersListener
*/
@@ -45,6 +52,13 @@ public class MojoParametersListener implements MojoExecutionListener {
private final ConcurrentMap> projectExecutions =
new ConcurrentHashMap<>();
+ private final CacheConfig cacheConfig;
+
+ @Inject
+ public MojoParametersListener(CacheConfig cacheConfig) {
+ this.cacheConfig = cacheConfig;
+ }
+
@Override
public void beforeMojoExecution(MojoExecutionEvent event) {
final String executionKey = CacheUtils.mojoExecutionKey(event.getExecution());
@@ -53,15 +67,23 @@ public void beforeMojoExecution(MojoExecutionEvent event) {
executionKey,
event.getMojo().getClass());
final MavenProject project = event.getProject();
- Map projectEvents = projectExecutions.get(project);
- if (projectEvents == null) {
- Map candidate = new ConcurrentHashMap<>();
- projectEvents = projectExecutions.putIfAbsent(project, candidate);
+ CacheState cacheState = DISABLED;
+ boolean cacheIsDisabled = MavenProjectInput.isCacheDisabled(project);
+ if (!cacheIsDisabled) {
+ cacheState = cacheConfig.initialize();
+ }
+ LOGGER.debug("cacheState: {}", cacheState);
+ if (cacheState == INITIALIZED) {
+ Map projectEvents = projectExecutions.get(project);
if (projectEvents == null) {
- projectEvents = candidate;
+ Map candidate = new ConcurrentHashMap<>();
+ projectEvents = projectExecutions.putIfAbsent(project, candidate);
+ if (projectEvents == null) {
+ projectEvents = candidate;
+ }
}
+ projectEvents.put(executionKey, event);
}
- projectEvents.put(executionKey, event);
}
@Override
diff --git a/src/main/java/org/apache/maven/buildcache/RemoteCacheRepositoryNoOp.java b/src/main/java/org/apache/maven/buildcache/RemoteCacheRepositoryNoOp.java
index 3808696b..e66d0e52 100644
--- a/src/main/java/org/apache/maven/buildcache/RemoteCacheRepositoryNoOp.java
+++ b/src/main/java/org/apache/maven/buildcache/RemoteCacheRepositoryNoOp.java
@@ -58,7 +58,6 @@ public boolean getArtifactContent(
return false;
}
- @Nonnull
@Override
public String getResourceUrl(CacheContext context, String filename) {
return null;
diff --git a/src/main/java/org/apache/maven/buildcache/artifact/OutputType.java b/src/main/java/org/apache/maven/buildcache/artifact/OutputType.java
new file mode 100644
index 00000000..0a557f4d
--- /dev/null
+++ b/src/main/java/org/apache/maven/buildcache/artifact/OutputType.java
@@ -0,0 +1,49 @@
+/*
+ * 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.artifact;
+
+public enum OutputType {
+ // generated project artifact
+ ARTIFACT(""),
+ // generated source (regular and test)
+ GENERATED_SOURCE("mvn-cache-ext-generated-source-"),
+ // any unclassified output added by configuration
+ EXTRA_OUTPUT("mvn-cache-ext-extra-output-");
+
+ private String classifierPrefix;
+
+ OutputType(String getClassifierPrefix) {
+ this.classifierPrefix = getClassifierPrefix;
+ }
+
+ public String getClassifierPrefix() {
+ return classifierPrefix;
+ }
+
+ public static OutputType fromClassifier(String classifier) {
+ if (classifier != null) {
+ if (classifier.startsWith(GENERATED_SOURCE.classifierPrefix)) {
+ return GENERATED_SOURCE;
+ } else if (classifier.startsWith(EXTRA_OUTPUT.classifierPrefix)) {
+ return EXTRA_OUTPUT;
+ }
+ }
+ return ARTIFACT;
+ }
+}
diff --git a/src/main/java/org/apache/maven/buildcache/artifact/RestoredArtifact.java b/src/main/java/org/apache/maven/buildcache/artifact/RestoredArtifact.java
index a74dce35..596fb1f7 100644
--- a/src/main/java/org/apache/maven/buildcache/artifact/RestoredArtifact.java
+++ b/src/main/java/org/apache/maven/buildcache/artifact/RestoredArtifact.java
@@ -23,6 +23,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RunnableFuture;
+import java.util.function.Function;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
@@ -46,8 +47,15 @@ public class RestoredArtifact extends DefaultArtifact {
private volatile Future fileFuture;
+ private Function restoreToDiskConsumer;
+
public RestoredArtifact(
- Artifact parent, Future fileFuture, String type, String classifier, ArtifactHandler handler) {
+ Artifact parent,
+ Future fileFuture,
+ String type,
+ String classifier,
+ ArtifactHandler handler,
+ Function restoreToDiskConsumer) {
super(
parent.getGroupId(),
parent.getArtifactId(),
@@ -58,6 +66,7 @@ public RestoredArtifact(
handler,
parent.isOptional());
this.fileFuture = requireNonNull(fileFuture, "fileFuture == null");
+ this.restoreToDiskConsumer = restoreToDiskConsumer;
}
/**
@@ -89,7 +98,8 @@ public File getFile() {
}
try {
- return fileFuture.get();
+ File file = fileFuture.get();
+ return restoreToDiskConsumer.apply(file);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new InvalidArtifactRTException(
diff --git a/src/main/java/org/apache/maven/buildcache/checksum/DigestUtils.java b/src/main/java/org/apache/maven/buildcache/checksum/DigestUtils.java
index da0f3f26..ee787330 100644
--- a/src/main/java/org/apache/maven/buildcache/checksum/DigestUtils.java
+++ b/src/main/java/org/apache/maven/buildcache/checksum/DigestUtils.java
@@ -56,6 +56,7 @@ public static DigestItem pom(HashChecksum checksum, String effectivePom) {
public static DigestItem file(HashChecksum checksum, Path basedir, Path file) throws IOException {
byte[] content = Files.readAllBytes(file);
String normalized = normalize(basedir, file);
+ checksum.update(normalized.getBytes(UTF_8));
DigestItem item = item("file", normalized, checksum.update(content));
try {
populateContentDetails(file, content, item);
@@ -126,6 +127,10 @@ public static DigestItem dependency(HashChecksum checksum, String key, String ha
return item("dependency", key, checksum.update(hash));
}
+ public static DigestItem pluginDependency(HashChecksum checksum, String key, String hash) {
+ return item("pluginDependency", key, checksum.update(hash));
+ }
+
private static String normalize(Path basedirPath, Path file) {
return FilenameUtils.separatorsToUnix(relativize(basedirPath, file).toString());
}
diff --git a/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java b/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java
index b6df5ec6..a3cb065e 100644
--- a/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java
+++ b/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java
@@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -45,12 +46,17 @@
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
-import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.buildcache.CacheUtils;
import org.apache.maven.buildcache.MultiModuleSupport;
import org.apache.maven.buildcache.NormalizedModelProvider;
@@ -69,15 +75,19 @@
import org.apache.maven.buildcache.xml.config.Include;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.Resource;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.repository.RepositorySystem;
-import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.WriterFactory;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.DefaultArtifactType;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -93,6 +103,7 @@
import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME;
import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_SKIP;
import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_GENERATED_SOURCES_PROPERTY_NAME;
+import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME;
/**
* MavenProjectInput
@@ -100,9 +111,9 @@
public class MavenProjectInput {
/**
- * Version of hashing algorithm implementation. It is recommended to change to simplify remote cache maintenance
+ * Version of cache implementation. It is recommended to change to simplify remote cache maintenance
*/
- public static final String CACHE_IMPLEMENTATION_VERSION = "v1";
+ public static final String CACHE_IMPLEMENTATION_VERSION = "v1.1";
/**
* property name to pass glob value. The glob to be used to list directory files in plugins scanning
@@ -130,6 +141,8 @@ public class MavenProjectInput {
private final MultiModuleSupport multiModuleSupport;
private final ProjectInputCalculator projectInputCalculator;
private final Path baseDirPath;
+ private final ArtifactHandlerManager artifactHandlerManager;
+
/**
* The project glob to use every time there is no override
*/
@@ -149,7 +162,8 @@ public MavenProjectInput(
MavenSession session,
CacheConfig config,
RepositorySystem repoSystem,
- RemoteCacheRepository remoteCache) {
+ RemoteCacheRepository remoteCache,
+ ArtifactHandlerManager artifactHandlerManager) {
this.project = project;
this.normalizedModelProvider = normalizedModelProvider;
this.multiModuleSupport = multiModuleSupport;
@@ -168,6 +182,7 @@ public MavenProjectInput(
this.exclusionResolver = new ExclusionResolver(project, config);
this.fileComparator = new PathIgnoringCaseComparator();
+ this.artifactHandlerManager = artifactHandlerManager;
}
public ProjectsInputInfo calculateChecksum() throws IOException {
@@ -176,14 +191,16 @@ public ProjectsInputInfo calculateChecksum() throws IOException {
final String effectivePom = getEffectivePom(normalizedModelProvider.normalizedModel(project));
final SortedSet inputFiles = isPom(project) ? Collections.emptySortedSet() : getInputFiles();
final SortedMap dependenciesChecksum = getMutableDependencies();
+ final SortedMap pluginDependenciesChecksum = getMutablePluginDependencies();
final long t1 = System.currentTimeMillis();
- // hash items: effective pom + version + input files + dependencies
+ // hash items: effective pom + version + input files paths + input files contents + dependencies
final int count = 1
+ (config.calculateProjectVersionChecksum() ? 1 : 0)
- + inputFiles.size()
- + dependenciesChecksum.size();
+ + 2 * inputFiles.size()
+ + dependenciesChecksum.size()
+ + pluginDependenciesChecksum.size();
final List items = new ArrayList<>(count);
final HashChecksum checksum = config.getHashFactory().createChecksum(count);
@@ -236,6 +253,19 @@ public ProjectsInputInfo calculateChecksum() throws IOException {
LOGGER.info("Dependencies: {}", dependenciesMatched ? "MATCHED" : "OUT OF DATE");
}
+ boolean pluginDependenciesMatched = true;
+ for (Map.Entry entry : pluginDependenciesChecksum.entrySet()) {
+ DigestItem dependencyDigest = DigestUtils.pluginDependency(checksum, entry.getKey(), entry.getValue());
+ items.add(dependencyDigest);
+ if (compareWithBaseline) {
+ pluginDependenciesMatched &= checkItemMatchesBaseline(baselineHolder.get(), dependencyDigest);
+ }
+ }
+
+ if (compareWithBaseline) {
+ LOGGER.info("Plugin dependencies: {}", pluginDependenciesMatched ? "MATCHED" : "OUT OF DATE");
+ }
+
final ProjectsInputInfo projectsInputInfoType = new ProjectsInputInfo();
projectsInputInfoType.setChecksum(checksum.digest());
projectsInputInfoType.getItems().addAll(items);
@@ -313,19 +343,13 @@ private boolean checkItemMatchesBaseline(ProjectsInputInfo baselineBuild, Digest
private String getEffectivePom(Model prototype) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
- Writer writer = null;
- try {
- writer = WriterFactory.newXmlWriter(output);
+ try (Writer writer = WriterFactory.newXmlWriter(output)) {
new MavenXpp3Writer().write(writer, prototype);
// normalize env specifics
final String[] searchList = {baseDirPath.toString(), "\\", "windows", "linux"};
final String[] replacementList = {"", "/", "os.classifier", "os.classifier"};
-
return replaceEachRepeatedly(output.toString(), searchList, replacementList);
-
- } finally {
- IOUtil.close(writer);
}
}
@@ -489,7 +513,7 @@ public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFil
return FileVisitResult.SKIP_SUBTREE;
}
- walkDirectoryFiles(path, collectedFiles, key.getGlob(), entry -> exclusionResolver.excludesPath(entry));
+ walkDirectoryFiles(path, collectedFiles, key.getGlob(), exclusionResolver::excludesPath);
if (!key.isRecursive()) {
LOGGER.debug("Skipping subtree (non recursive): {}", path);
@@ -615,9 +639,137 @@ private static boolean isReadable(Path entry) throws IOException {
}
private SortedMap getMutableDependencies() throws IOException {
+ return getMutableDependenciesHashes("", project.getDependencies());
+ }
+
+ private SortedMap getMutablePluginDependencies() throws IOException {
+ Map keyPrefixOccurrenceIndex = new HashMap<>();
+ SortedMap fullMap = new TreeMap<>();
+ for (Plugin plugin : project.getBuildPlugins()) {
+ if (config.isPluginDependenciesExcluded(plugin)) {
+ continue;
+ }
+
+ String rawKeyPrefix = KeyUtils.getVersionlessArtifactKey(createPluginArtifact(plugin));
+ int occurrenceIndex = keyPrefixOccurrenceIndex
+ .computeIfAbsent(rawKeyPrefix, k -> new AtomicInteger())
+ .getAndIncrement();
+ fullMap.putAll(
+ getMutableDependenciesHashes(rawKeyPrefix + "|" + occurrenceIndex + "|", plugin.getDependencies()));
+ }
+ return fullMap;
+ }
+
+ public Artifact createPluginArtifact(Plugin plugin) {
+
+ VersionRange versionRange;
+ try {
+ versionRange = VersionRange.createFromVersionSpec(plugin.getVersion());
+ } catch (InvalidVersionSpecificationException e) {
+ LOGGER.error(
+ String.format(
+ "Invalid version specification '%s' creating plugin artifact '%s'.",
+ plugin.getVersion(), plugin),
+ e);
+ // should not happen here
+ throw new RuntimeException(e);
+ }
+
+ return createArtifact(
+ plugin.getGroupId(),
+ plugin.getArtifactId(),
+ versionRange,
+ "maven-plugin",
+ null,
+ Artifact.SCOPE_RUNTIME,
+ null,
+ false);
+ }
+ // CHECKSTYLE_OFF: ParameterNumber
+ private Artifact createArtifact(
+ String groupId,
+ String artifactId,
+ VersionRange versionRange,
+ String type,
+ String classifier,
+ String scope,
+ String inheritedScope,
+ boolean optional) {
+ // CHECKSTYLE_OFF: ParameterNumber
+ String desiredScope = Artifact.SCOPE_RUNTIME;
+
+ if (inheritedScope == null) {
+ desiredScope = scope;
+ } else if (Artifact.SCOPE_TEST.equals(scope) || Artifact.SCOPE_PROVIDED.equals(scope)) {
+ return null;
+ } else if (Artifact.SCOPE_COMPILE.equals(scope) && Artifact.SCOPE_COMPILE.equals(inheritedScope)) {
+ // added to retain compile artifactScope. Remove if you want compile inherited as runtime
+ desiredScope = Artifact.SCOPE_COMPILE;
+ }
+
+ if (Artifact.SCOPE_TEST.equals(inheritedScope)) {
+ desiredScope = Artifact.SCOPE_TEST;
+ }
+
+ if (Artifact.SCOPE_PROVIDED.equals(inheritedScope)) {
+ desiredScope = Artifact.SCOPE_PROVIDED;
+ }
+
+ if (Artifact.SCOPE_SYSTEM.equals(scope)) {
+ // system scopes come through unchanged...
+ desiredScope = Artifact.SCOPE_SYSTEM;
+ }
+ ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(type);
+
+ return new DefaultArtifact(
+ groupId, artifactId, versionRange, desiredScope, type, classifier, handler, optional);
+ }
+
+ public Artifact createDependencyArtifact(Dependency d) {
+ VersionRange versionRange;
+ try {
+ versionRange = VersionRange.createFromVersionSpec(d.getVersion());
+ } catch (InvalidVersionSpecificationException e) {
+ LOGGER.error(
+ String.format(
+ "Invalid version specification '%s' creating dependency artifact '%s'.", d.getVersion(), d),
+ e);
+ // should not happen here ?
+ throw new RuntimeException(e);
+ }
+
+ Artifact artifact = createArtifact(
+ d.getGroupId(),
+ d.getArtifactId(),
+ versionRange,
+ d.getType(),
+ d.getClassifier(),
+ d.getScope(),
+ null,
+ d.isOptional());
+
+ if (Artifact.SCOPE_SYSTEM.equals(d.getScope()) && d.getSystemPath() != null) {
+ artifact.setFile(new File(d.getSystemPath()));
+ }
+
+ if (!d.getExclusions().isEmpty()) {
+ List exclusions = new ArrayList<>();
+
+ for (Exclusion exclusion : d.getExclusions()) {
+ exclusions.add(exclusion.getGroupId() + ':' + exclusion.getArtifactId());
+ }
+
+ artifact.setDependencyFilter(new ExcludesArtifactFilter(exclusions));
+ }
+
+ return artifact;
+ }
+
+ private SortedMap getMutableDependenciesHashes(String keyPrefix, List dependencies)
+ throws IOException {
SortedMap result = new TreeMap<>();
- for (Dependency dependency : project.getDependencies()) {
+ for (Dependency dependency : dependencies) {
if (CacheUtils.isPom(dependency)) {
// POM dependency will be resolved by maven system to actual dependencies
@@ -643,50 +795,58 @@ private SortedMap getMutableDependencies() throws IOException {
projectInputCalculator.calculateInput(dependencyProject).getChecksum();
} else // this is a snapshot dependency
{
- DigestItem resolved = resolveArtifact(repoSystem.createDependencyArtifact(dependency), false);
+ DigestItem resolved = null;
+ try {
+ resolved = resolveArtifact(dependency);
+ } catch (ArtifactResolutionException | InvalidVersionSpecificationException e) {
+ throw new IOException(e);
+ }
projectHash = resolved.getHash();
}
result.put(
- KeyUtils.getVersionlessArtifactKey(repoSystem.createDependencyArtifact(dependency)), projectHash);
+ keyPrefix + KeyUtils.getVersionlessArtifactKey(createDependencyArtifact(dependency)), projectHash);
}
return result;
}
@Nonnull
- private DigestItem resolveArtifact(final Artifact dependencyArtifact, boolean isOffline) throws IOException {
- ArtifactResolutionRequest request = new ArtifactResolutionRequest()
- .setArtifact(dependencyArtifact)
- .setResolveRoot(true)
- .setResolveTransitively(false)
- .setLocalRepository(session.getLocalRepository())
- .setRemoteRepositories(project.getRemoteArtifactRepositories())
- .setOffline(session.isOffline() || isOffline)
- .setForceUpdate(session.getRequest().isUpdateSnapshots())
- .setServers(session.getRequest().getServers())
- .setMirrors(session.getRequest().getMirrors())
- .setProxies(session.getRequest().getProxies());
-
- final ArtifactResolutionResult result = repoSystem.resolve(request);
-
- if (!result.isSuccess()) {
+ private DigestItem resolveArtifact(final Dependency dependency)
+ throws IOException, ArtifactResolutionException, InvalidVersionSpecificationException {
+
+ org.eclipse.aether.artifact.Artifact dependencyArtifact = new org.eclipse.aether.artifact.DefaultArtifact(
+ dependency.getGroupId(),
+ dependency.getArtifactId(),
+ dependency.getClassifier(),
+ null,
+ dependency.getVersion(),
+ new DefaultArtifactType(dependency.getType()));
+ ArtifactRequest artifactRequest = new ArtifactRequest().setArtifact(dependencyArtifact);
+
+ ArtifactResult result = repoSystem.resolveArtifact(session.getRepositorySession(), artifactRequest);
+
+ if (!result.isResolved()) {
throw new DependencyNotResolvedException("Cannot resolve in-project dependency: " + dependencyArtifact);
}
- if (!result.getMissingArtifacts().isEmpty()) {
- throw new DependencyNotResolvedException(
- "Cannot resolve artifact: " + dependencyArtifact + ", missing: " + result.getMissingArtifacts());
+ if (result.isMissing()) {
+ throw new DependencyNotResolvedException("Cannot resolve missing artifact: " + dependencyArtifact);
}
- if (result.getArtifacts().size() != 1) {
- throw new IllegalStateException("Unexpected number of artifacts returned. Requested: " + dependencyArtifact
- + ", expected: 1, actual: " + result.getArtifacts());
- }
+ org.eclipse.aether.artifact.Artifact resolved = result.getArtifact();
- final Artifact resolved = result.getArtifacts().iterator().next();
+ Artifact artifact = createArtifact(
+ resolved.getGroupId(),
+ resolved.getArtifactId(),
+ VersionRange.createFromVersionSpec(resolved.getVersion()),
+ dependency.getType(),
+ resolved.getClassifier(),
+ dependency.getType(),
+ dependency.getScope(),
+ false);
final HashAlgorithm algorithm = config.getHashFactory().createAlgorithm();
final String hash = algorithm.hash(resolved.getFile().toPath());
- return DtoUtils.createDigestedFile(resolved, hash);
+ return DtoUtils.createDigestedFile(artifact, hash);
}
/**
@@ -728,6 +888,18 @@ public static boolean isRestoreGeneratedSources(MavenProject project) {
project.getProperties().getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, "true"));
}
+ /**
+ * Allow skipping artifacts restoration on a per-project level via a property (which defaults to true)
+ * e.g. {@code false}.
+ *
+ * @param project
+ * @return
+ */
+ public static boolean isRestoreOnDiskArtifacts(MavenProject project) {
+ return Boolean.parseBoolean(
+ project.getProperties().getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, "true"));
+ }
+
/**
* Allow disabling caching entirely on a per-project level via a property - both artifact lookup and upload
* Defaults to false
diff --git a/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java b/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java
index f89bc361..26988b39 100644
--- a/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java
+++ b/src/main/java/org/apache/maven/buildcache/checksum/exclude/Exclusion.java
@@ -146,12 +146,12 @@ public boolean excludesPath(Path parentPath, Path path) {
public enum MatcherType {
FILENAME,
- PATH;
+ PATH
}
public enum EntryType {
FILE,
DIRECTORY,
- ALL;
+ ALL
}
}
diff --git a/src/main/java/org/apache/maven/buildcache/hash/CloseableBuffer.java b/src/main/java/org/apache/maven/buildcache/hash/CloseableBuffer.java
index 791168de..7b7c60db 100644
--- a/src/main/java/org/apache/maven/buildcache/hash/CloseableBuffer.java
+++ b/src/main/java/org/apache/maven/buildcache/hash/CloseableBuffer.java
@@ -23,9 +23,7 @@
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
-import java.security.PrivilegedAction;
-import static java.security.AccessController.doPrivileged;
import static org.apache.maven.buildcache.hash.ReflectionUtils.getField;
import static org.apache.maven.buildcache.hash.ReflectionUtils.getMethod;
@@ -34,18 +32,16 @@
*/
public class CloseableBuffer implements AutoCloseable {
- private static final Cleaner CLEANER = doPrivileged(new PrivilegedAction() {
+ private static final Cleaner CLEANER = getJVMDependentCleaner();
- @Override
- public Cleaner run() {
- final String jsv = System.getProperty("java.specification.version", "9");
- if (jsv.startsWith("1.")) {
- return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
- } else {
- return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner();
- }
+ private static Cleaner getJVMDependentCleaner() {
+ final String jsv = System.getProperty("java.specification.version", "9");
+ if (jsv.startsWith("1.")) {
+ return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
+ } else {
+ return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner();
}
- });
+ }
public static CloseableBuffer directBuffer(int capacity) {
return new CloseableBuffer(ByteBuffer.allocateDirect(capacity));
@@ -74,14 +70,7 @@ public ByteBuffer getBuffer() {
@Override
public void close() {
- // Java 8: () -> CLEANER.clean(buffer)
- boolean done = doPrivileged(new PrivilegedAction() {
-
- @Override
- public Boolean run() {
- return CLEANER.clean(buffer);
- }
- });
+ boolean done = CLEANER.clean(buffer);
if (done) {
buffer = null;
}
diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java b/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java
index 2569a167..d86b15d4 100644
--- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java
+++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java
@@ -26,6 +26,7 @@
import org.apache.maven.buildcache.PluginScanConfig;
import org.apache.maven.buildcache.hash.HashFactory;
+import org.apache.maven.buildcache.xml.config.DirName;
import org.apache.maven.buildcache.xml.config.Exclude;
import org.apache.maven.buildcache.xml.config.Include;
import org.apache.maven.buildcache.xml.config.MultiModule;
@@ -57,6 +58,8 @@ public interface CacheConfig {
@Nonnull
List getEffectivePomExcludeProperties(Plugin plugin);
+ boolean isPluginDependenciesExcluded(Plugin plugin);
+
@Nullable
MultiModule getMultiModule();
@@ -103,7 +106,7 @@ public interface CacheConfig {
String getLocalRepositoryLocation();
- List getAttachedOutputs();
+ List getAttachedOutputs();
boolean adjustMetaInfVersion();
@@ -133,5 +136,20 @@ public interface CacheConfig {
*/
boolean isRestoreGeneratedSources();
+ /**
+ * Flag to restore (default) or not generated artifacts
+ */
+ boolean isRestoreOnDiskArtifacts();
+
String getAlwaysRunPlugins();
+
+ /**
+ * Flag to disable cache saving
+ */
+ boolean isSkipSave();
+
+ /**
+ * Flag to save in cache only if a build went through the clean lifecycle
+ */
+ boolean isMandatoryClean();
}
diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java
index 75b16afc..566a067a 100644
--- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java
+++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java
@@ -42,6 +42,7 @@
import org.apache.maven.buildcache.xml.config.CacheConfig;
import org.apache.maven.buildcache.xml.config.Configuration;
import org.apache.maven.buildcache.xml.config.CoordinatesBase;
+import org.apache.maven.buildcache.xml.config.DirName;
import org.apache.maven.buildcache.xml.config.Exclude;
import org.apache.maven.buildcache.xml.config.Executables;
import org.apache.maven.buildcache.xml.config.ExecutionConfigurationScan;
@@ -90,8 +91,10 @@ public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheCon
public static final String FAIL_FAST_PROPERTY_NAME = "maven.build.cache.failFast";
public static final String BASELINE_BUILD_URL_PROPERTY_NAME = "maven.build.cache.baselineUrl";
public static final String LAZY_RESTORE_PROPERTY_NAME = "maven.build.cache.lazyRestore";
+ public static final String RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME = "maven.build.cache.restoreOnDiskArtifacts";
public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = "maven.build.cache.restoreGeneratedSources";
public static final String ALWAYS_RUN_PLUGINS = "maven.build.cache.alwaysRunPlugins";
+ public static final String MANDATORY_CLEAN = "maven.build.cache.mandatoryClean";
/**
* Flag to control if we should skip lookup for cached artifacts globally or for a particular project even if
@@ -103,6 +106,11 @@ public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheCon
*/
public static final String CACHE_SKIP = "maven.build.cache.skipCache";
+ /**
+ * Flag to disable cache saving
+ */
+ public static final String SKIP_SAVE = "maven.build.cache.skipSave";
+
private static final Logger LOGGER = LoggerFactory.getLogger(CacheConfigImpl.class);
private final XmlService xmlService;
@@ -130,8 +138,9 @@ public CacheState initialize() {
final boolean enabled = getProperty(CACHE_ENABLED_PROPERTY_NAME, true);
if (!rtInfo.isMavenVersion("[3.9.0,)")) {
- LOGGER.warn("Cache requires Maven >= 3.9, but version is " + rtInfo.getMavenVersion()
- + ". Disabling cache.");
+ LOGGER.warn(
+ "Cache requires Maven >= 3.9, but version is {}. Disabling cache.",
+ rtInfo.getMavenVersion());
state = CacheState.DISABLED;
} else if (!enabled) {
LOGGER.info("Cache disabled by command line flag, project will be built fully and not cached");
@@ -296,6 +305,17 @@ public List getEffectivePomExcludeProperties(Plugin plugin) {
return Collections.emptyList();
}
+ @Override
+ public boolean isPluginDependenciesExcluded(Plugin plugin) {
+ checkInitializedState();
+ final PluginConfigurationScan pluginConfig = findPluginScanConfig(plugin);
+
+ if (pluginConfig != null) {
+ return pluginConfig.isExcludeDependencies();
+ }
+ return false;
+ }
+
@Nullable
@Override
public MultiModule getMultiModule() {
@@ -498,11 +518,26 @@ public boolean isRestoreGeneratedSources() {
return getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, true);
}
+ @Override
+ public boolean isRestoreOnDiskArtifacts() {
+ return getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, true);
+ }
+
@Override
public String getAlwaysRunPlugins() {
return getProperty(ALWAYS_RUN_PLUGINS, null);
}
+ @Override
+ public boolean isSkipSave() {
+ return getProperty(SKIP_SAVE, false);
+ }
+
+ @Override
+ public boolean isMandatoryClean() {
+ return getProperty(MANDATORY_CLEAN, getConfiguration().isMandatoryClean());
+ }
+
@Override
public String getId() {
checkInitializedState();
@@ -534,7 +569,7 @@ public String getLocalRepositoryLocation() {
}
@Override
- public List getAttachedOutputs() {
+ public List getAttachedOutputs() {
checkInitializedState();
final AttachedOutputs attachedOutputs = getConfiguration().getAttachedOutputs();
return attachedOutputs == null ? Collections.emptyList() : attachedOutputs.getDirNames();
diff --git a/src/main/mdo/build-cache-build.mdo b/src/main/mdo/build-cache-build.mdo
index c9febc20..0e639352 100644
--- a/src/main/mdo/build-cache-build.mdo
+++ b/src/main/mdo/build-cache-build.mdo
@@ -240,6 +240,10 @@ under the License.
fileSizelong
+
+ filePath
+ String
+
diff --git a/src/main/mdo/build-cache-config.mdo b/src/main/mdo/build-cache-config.mdo
index 65f2ec7b..399c820e 100644
--- a/src/main/mdo/build-cache-config.mdo
+++ b/src/main/mdo/build-cache-config.mdo
@@ -160,8 +160,8 @@ under the License.
0.0.0+
- FileHash (causes file hash is saved in build metadata) or
- EffectivePom (causes effective pom info is saved in build metadata)
+ FileHash (causes file hash to be saved in build metadata) or
+ EffectivePom (causes effective pom info to be saved in build metadata)
@@ -194,6 +194,12 @@ under the License.
TODO: not implemented
+
+ mandatoryClean
+ boolean
+ false
+ Enable the cache storing ability only if a build went through the clean lifecycle.
+ multiModule
@@ -232,8 +238,8 @@ under the License.
String*
- FileHash (causes file hash is saved in build metadata) or
- EffectivePom (causes effective pom info is saved in build metadata)
+ FileHash (causes file hash to be saved in build metadata) or
+ EffectivePom (causes effective pom info to be saved in build metadata)
@@ -368,14 +374,42 @@ under the License.
-->
AttachedOutputs
+ Section relative to outputs which are not artifacts but need to be saved/restored.dirNames
- String
+ DirName*
- Directory name in build output directory to attach to cached artifacts
+ Path to a directory containing files which needs to be saved/restored (relative to the build directory).
+
+
+
+
+ DirName
+
+ Examples :
+
+
<dirName>classes</dirName> : files in ${project.basedir}/target/classes
+
<dirName glob="jacoco.xml"></dirName> : jacoco report files in ${project.basedir}/target
+
<dirName>../src/main/javagen</dirName> : files in ${project.basedir}/src/main/javagen (in this example, javagen is a folder saved in git but erased on clean)
+
+
+
+ ]]>
+
+
+ value
+ String
+ Directory name in build output directory to attach to cached artifacts.
+
+
+ glob
+ String
+ *
+ Entries are filtered by matching this glob.
@@ -460,6 +494,12 @@ under the License.
+
+
+ 0.0.0+
+ True to exclude plugin dependencies from the calculation rules.
+
+
@@ -506,6 +546,11 @@ under the License.
PluginConfigurationScanCoordinatesBase
+
+ excludeDependencies
+ boolean
+ True to exclude plugin dependencies from the calculation rules
+ effectivePom
@@ -874,7 +919,7 @@ under the License.
Include
- file or a directory path to add to checksum computation. Relative path are relative to each module basedir.
+ A file or a directory path to add to checksum computation. Relative paths are relative to each module basedir.
Include elements can also be added per project with the use of maven properties.
]]>
@@ -1260,7 +1305,7 @@ under the License.
- Executions ids list with plugin identifier
+ Execution ids list with plugin identifier
@@ -1280,7 +1325,7 @@ under the License.
String*
- Executions ids list with plugin identifier
+ Execution ids list with plugin identifier
diff --git a/src/site/markdown/getting-started.md.vm b/src/site/markdown/getting-started.md.vm
index c056afb8..8e7d3df6 100644
--- a/src/site/markdown/getting-started.md.vm
+++ b/src/site/markdown/getting-started.md.vm
@@ -28,7 +28,6 @@ To onboard the build-cache extension you need to complete several steps:
#[[###]]# Declaring build cache extension
```xml
-
org.apache.maven.extensionsmaven-build-cache-extension
@@ -78,6 +77,7 @@ by [Maven Resolver](https://maven.apache.org/resolver/) will suffice. In the sim
server
supporting get/put operations. Working examples:
+* Sonatype Nexus Repository (raw repository)
* Artifactory (generic repository)
* [Nginx OSS](http://nginx.org/en/) with fs module
diff --git a/src/site/markdown/how-to.md b/src/site/markdown/how-to.md
index 879ccf2a..a4ebbb78 100644
--- a/src/site/markdown/how-to.md
+++ b/src/site/markdown/how-to.md
@@ -26,8 +26,8 @@ Minimal config
```xml
-
+true
@@ -168,8 +168,8 @@ to `executionControl`:
```xml
-
+
...
@@ -214,3 +214,16 @@ git reset --hard
Solution: set `-Dmaven.build.cache.remote.save.final=true` to nodes that produce final builds. Such builds will not be overridden
and eventually will replace all interim builds
+
+### I want to disable dependencies checksum calculation of one plugin
+
+Set attribute `excludeDependencies` to `true` in `input/plugins/plugin` section:
+
+```xml
+
+
+
+
+
+
+```
diff --git a/src/site/markdown/parameters.md b/src/site/markdown/parameters.md
index 16269f1a..e248b006 100644
--- a/src/site/markdown/parameters.md
+++ b/src/site/markdown/parameters.md
@@ -21,21 +21,24 @@ This document contains various configuration parameters supported by the cache e
### Command line flags
-| Parameter | Description | Usage Scenario |
-|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
-| `-Dmaven.build.cache.configPath=path to file` | Location of cache configuration file | Cache config is not in default location |
-| `-Dmaven.build.cache.enabled=(true/false)` | Cache and associated features disabled/enabled | To remove noise from logs when the remote cache is not available |
-| `-Dmaven.build.cache.remote.enabled=(true/false)` | Checks and downloads artifacts from the remote cache (overrides ) | To control remote cache access by node, if, say, some nodes lack reliable access |
-| `-Dmaven.build.cache.remote.save.enabled=(true/false)` | Remote cache save allowed or not | To designate nodes which allowed to push in remote shared cache |
-| `-Dmaven.build.cache.remote.save.final=(true/false)` | Prohibit to override remote cache | Prevents cache records from being overridden by subsequent builds |
-| `-Dmaven.build.cache.remote.url=` | Url of the remote cache (overrides ) | To override url of remote cache from command line |
-| `-Dmaven.build.cache.remote.server.id=` | Id of the remote cache server (overrides ) | To override id of remote cache server from command line |
-| `-Dmaven.build.cache.failFast=(true/false)` | Fail on the first module which cannot be restored from cache | Remote cache setup/tuning/troubleshooting |
-| `-Dmaven.build.cache.baselineUrl=` | Location of baseline build for comparison | Remote cache setup/tuning/troubleshooting |
-| `-Dmaven.build.cache.lazyRestore=(true/false)` | Restore artifacts from remote cache lazily | Performance optimization |
-| `-Dmaven.build.cache.restoreGeneratedSources=(true/false)` | Do not restore generated sources and directly attached files | Performance optimization |
+| Parameter | Description | Usage Scenario |
+|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
+| `-Dmaven.build.cache.configPath=path to file` | Location of cache configuration file | Cache config is not in default location |
+| `-Dmaven.build.cache.enabled=(true/false)` | Cache and associated features disabled/enabled | To remove noise from logs when the remote cache is not available |
+| `-Dmaven.build.cache.remote.enabled=(true/false)` | Checks and downloads artifacts from the remote cache (overrides ) | To control remote cache access by node, if, say, some nodes lack reliable access |
+| `-Dmaven.build.cache.remote.save.enabled=(true/false)` | Remote cache save allowed or not | To designate nodes which allowed to push in remote shared cache |
+| `-Dmaven.build.cache.remote.save.final=(true/false)` | Prohibit to override remote cache | Prevents cache records from being overridden by subsequent builds |
+| `-Dmaven.build.cache.remote.url=` | Url of the remote cache (overrides ) | To override url of remote cache from command line |
+| `-Dmaven.build.cache.remote.server.id=` | Id of the remote cache server (overrides ) | To override id of remote cache server from command line |
+| `-Dmaven.build.cache.failFast=(true/false)` | Fail on the first module which cannot be restored from cache | Remote cache setup/tuning/troubleshooting |
+| `-Dmaven.build.cache.baselineUrl=` | Location of baseline build for comparison | Remote cache setup/tuning/troubleshooting |
+| `-Dmaven.build.cache.lazyRestore=(true/false)` | Restore artifacts from remote cache lazily | Performance optimization |
+| `-Dmaven.build.cache.restoreGeneratedSources=(true/false)` | Restore generated sources and directly attached files in the corresponding project directories. (default is true) | Performance optimization |
+| `-Dmaven.build.cache.restoreOnDiskArtifacts=(true/false)` | Restore generated artifacts in the project build directory. (default is true) | Performance optimization |
| `-Dmaven.build.cache.alwaysRunPlugins=` | Comma separated list of plugins to always run regardless of cache state. An example: `maven-deploy-plugin:*,maven-install-plugin:install` | Remote cache setup/tuning/troubleshooting |
| `-Dmaven.build.cache.skipCache=(true/false)` | Skip looking up artifacts in caches. Does not affect writing artifacts to caches, disables only reading when set to `true` | May be used to trigger a forced rebuild when matching artifacts do exist in caches |
+| `-Dmaven.build.cache.skipSave=(true/false)` | Skip writing build result in caches. Does not affect reading from the cache. | Configuring MR builds to benefits from the cache, but restricting writes to the `master` branch |
+| `-Dmaven.build.cache.mandatoryClean=(true/false)` | Enable or disable the necessity to execute the `clean` phase in order to store the build result in cache | Reducing the risk to save "wrong" files in cache in a local dev environnement |
### Project-level properties
@@ -52,11 +55,13 @@ project properties:
```
-| Parameter | Description |
-|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `maven.build.cache.input.glob` | Project specific glob to select sources. Overrides the global glob. |
-| `maven.build.cache.input` | Additional inputs
Example : ```src/main/scala``` ```assembly-conf``` |
-| `maven.build.cache.exclude.xxx` | Additional exclusion.
Example : ```src/main/java/package-info.java``` ```src/main/resources``` ```*.md``` Produce two project exclusions : ```src/main/java/package-info.java``` ```src/main/resources``` |
-| `maven.build.cache.processPlugins` | Introspect plugins to find inputs or not. The default value is true. |
-| `maven.build.cache.skipCache` | Skip looking up artifacts for a particular project in caches. The default value is false. |
-| `maven.build.cache.restoreGeneratedSources` | Restore generated sources and directly attached files. The default value is true. |
+| Parameter | Description |
+|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `maven.build.cache.input.glob` | Project specific glob to select sources. Overrides the global glob. |
+| `maven.build.cache.input` | Additional inputs
Example : ```src/main/scala``` ```assembly-conf``` |
+| `maven.build.cache.exclude.xxx` | Additional exclusion.
Example : ```src/main/java/package-info.java``` ```src/main/resources``` ```*.md``` Produce two project exclusions : ```src/main/java/package-info.java``` ```src/main/resources``` |
+| `maven.build.cache.processPlugins` | Introspect plugins to find inputs or not. The default value is true. |
+| `maven.build.cache.skipCache` | Skip looking up artifacts for a particular project in caches. The default value is false. |
+| `maven.build.cache.restoreGeneratedSources` | Restore generated sources and directly attached files in the corresponding project directories. The default value is true. |
+| `maven.build.cache.restoreOnDiskArtifacts` | Restore generated artifacts in the project build directory. The default value is true. |
+
diff --git a/src/site/markdown/performance.md b/src/site/markdown/performance.md
index 6031e908..ffd9ce41 100644
--- a/src/site/markdown/performance.md
+++ b/src/site/markdown/performance.md
@@ -23,7 +23,7 @@ Cache tuning could significantly reduce resource consumption and build execution
### Hash algorithm selection
-By default, the cache uses the [XX](https://cyan4973.github.io/xxHash/) algorithm, which is a very fast hash algorithm and should be enough for most use cases.
+By default, the cache uses the [XX](https://cyan4973.github.io/xxHash/) algorithm, which is a very fast hash algorithm and should be enough for most use cases.
In projects with a large codebase, the performance of hash algorithms becomes more critical, and other algorithms like
XXMM (XX with memory-mapped files) could provide better performance, depending on the environment.
@@ -31,7 +31,7 @@ XXMM (XX with memory-mapped files) could provide better performance, depending o
XX
```
-Also note that the usage of the XXMM or METRO+MM algorithms require the creation of a file `.mvn/jvm.config` in the
+Also note that the usage of the XXMM or METRO+MM algorithms require the creation of a file `.mvn/jvm.config` in the
top directory with the following line to run successfully on JDK >= 17.
```
--add-opens java.base/sun.nio.ch=ALL-UNNAMED
@@ -47,7 +47,9 @@ EAR or ZIP archive locally is more efficient than writing to soring in cache bun
@@ -81,7 +83,7 @@ The cache could be configured to auto-correct metadata (most notably [MANIFEST.M
...
-
+
...
...
diff --git a/src/site/resources/maven-build-cache-config.xml b/src/site/resources/maven-build-cache-config.xml
index 615cf384..1fb0ab97 100644
--- a/src/site/resources/maven-build-cache-config.xml
+++ b/src/site/resources/maven-build-cache-config.xml
@@ -15,8 +15,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
-
+
src/
@@ -56,7 +56,7 @@
-
+ 111
diff --git a/src/site/site.xml b/src/site/site.xml
index 2325ecef..52ad71cf 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -34,7 +34,6 @@ under the License.
-