blob: 4f812c4abc68a0473b3ee7f92e6901628ad20cba [file] [log] [blame]
/*
* 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.checksum;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
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.buildcache.CacheUtils;
import org.apache.maven.buildcache.MultiModuleSupport;
import org.apache.maven.buildcache.NormalizedModelProvider;
import org.apache.maven.buildcache.PluginScanConfig;
import org.apache.maven.buildcache.ProjectInputCalculator;
import org.apache.maven.buildcache.RemoteCacheRepository;
import org.apache.maven.buildcache.ScanConfigProperties;
import org.apache.maven.buildcache.Xpp3DomUtils;
import org.apache.maven.buildcache.hash.HashAlgorithm;
import org.apache.maven.buildcache.hash.HashChecksum;
import org.apache.maven.buildcache.xml.CacheConfig;
import org.apache.maven.buildcache.xml.DtoUtils;
import org.apache.maven.buildcache.xml.build.DigestItem;
import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
import org.apache.maven.buildcache.xml.config.Exclude;
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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.commons.lang3.StringUtils.contains;
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.replaceEachRepeatedly;
import static org.apache.commons.lang3.StringUtils.startsWithAny;
import static org.apache.commons.lang3.StringUtils.stripToEmpty;
import static org.apache.maven.buildcache.CacheUtils.isPom;
import static org.apache.maven.buildcache.CacheUtils.isSnapshot;
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;
/**
* MavenProjectInput
*/
public class MavenProjectInput {
/**
* Version of hashing algorithm implementation. It is recommended to change to simplify remote cache maintenance
*/
public static final String CACHE_IMPLEMENTATION_VERSION = "v1";
/**
* property name to pass glob value. The glob to be used to list directory files in plugins scanning
*/
private static final String CACHE_INPUT_GLOB_NAME = "maven.build.cache.input.glob";
/**
* default glob, bbsdk/abfx specific
*/
public static final String DEFAULT_GLOB = "{*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly"
+ "*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat}";
/**
* property name prefix to pass input files with project properties. smth like maven.build.cache.input.1 will be
* accepted
*/
private static final String CACHE_INPUT_NAME = "maven.build.cache.input";
/**
* property name prefix to exclude files from input. smth like maven.build.cache.exclude.1 should be set in project
* props
*/
private static final String CACHE_EXCLUDE_NAME = "maven.build.cache.exclude";
/**
* Flag to control if we should check values from plugin configs as file system objects
*/
private static final String CACHE_PROCESS_PLUGINS = "maven.build.cache.processPlugins";
private static final Logger LOGGER = LoggerFactory.getLogger(MavenProjectInput.class);
private final MavenProject project;
private final MavenSession session;
private final RemoteCacheRepository remoteCache;
private final RepositorySystem repoSystem;
private final CacheConfig config;
private final PathIgnoringCaseComparator fileComparator;
private final List<Path> filteredOutPaths;
private final NormalizedModelProvider normalizedModelProvider;
private final MultiModuleSupport multiModuleSupport;
private final ProjectInputCalculator projectInputCalculator;
private final Path baseDirPath;
private final String dirGlob;
private final boolean processPlugins;
private final String tmpDir;
@SuppressWarnings("checkstyle:parameternumber")
public MavenProjectInput(
MavenProject project,
NormalizedModelProvider normalizedModelProvider,
MultiModuleSupport multiModuleSupport,
ProjectInputCalculator projectInputCalculator,
MavenSession session,
CacheConfig config,
RepositorySystem repoSystem,
RemoteCacheRepository remoteCache) {
this.project = project;
this.normalizedModelProvider = normalizedModelProvider;
this.multiModuleSupport = multiModuleSupport;
this.projectInputCalculator = projectInputCalculator;
this.session = session;
this.config = config;
this.baseDirPath = project.getBasedir().toPath().toAbsolutePath();
this.repoSystem = repoSystem;
this.remoteCache = remoteCache;
Properties properties = project.getProperties();
this.dirGlob = properties.getProperty(CACHE_INPUT_GLOB_NAME, config.getDefaultGlob());
this.processPlugins =
Boolean.parseBoolean(properties.getProperty(CACHE_PROCESS_PLUGINS, config.isProcessPlugins()));
this.tmpDir = System.getProperty("java.io.tmpdir");
org.apache.maven.model.Build build = project.getBuild();
filteredOutPaths = new ArrayList<>(Arrays.asList(
normalizedPath(build.getDirectory()), // target by default
normalizedPath(build.getOutputDirectory()),
normalizedPath(build.getTestOutputDirectory())));
List<Exclude> excludes = config.getGlobalExcludePaths();
for (Exclude excludePath : excludes) {
filteredOutPaths.add(Paths.get(excludePath.getValue()));
}
for (String propertyName : properties.stringPropertyNames()) {
if (propertyName.startsWith(CACHE_EXCLUDE_NAME)) {
String propertyValue = properties.getProperty(propertyName);
Path path = Paths.get(propertyValue);
filteredOutPaths.add(path);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Adding an excludePath from property '{}', values is '{}', path is '{}' ",
propertyName,
propertyValue,
path);
}
}
}
CacheUtils.debugPrintCollection(
LOGGER,
filteredOutPaths,
"List of excluded paths (checked either by fileName or by startsWith prefix)",
"Path entry");
this.fileComparator = new PathIgnoringCaseComparator();
}
public ProjectsInputInfo calculateChecksum() throws IOException {
final long t0 = System.currentTimeMillis();
final String effectivePom = getEffectivePom(normalizedModelProvider.normalizedModel(project));
final SortedSet<Path> inputFiles = isPom(project) ? Collections.emptySortedSet() : getInputFiles();
final SortedMap<String, String> dependenciesChecksum = getMutableDependencies();
final long t1 = System.currentTimeMillis();
// hash items: effective pom + input files + dependencies
final int count = 1 + inputFiles.size() + dependenciesChecksum.size();
final List<DigestItem> items = new ArrayList<>(count);
final HashChecksum checksum = config.getHashFactory().createChecksum(count);
Optional<ProjectsInputInfo> baselineHolder = Optional.empty();
if (config.isBaselineDiffEnabled()) {
baselineHolder =
remoteCache.findBaselineBuild(project).map(b -> b.getDto().getProjectsInputInfo());
}
DigestItem effectivePomChecksum = DigestUtils.pom(checksum, effectivePom);
items.add(effectivePomChecksum);
final boolean compareWithBaseline = config.isBaselineDiffEnabled() && baselineHolder.isPresent();
if (compareWithBaseline) {
checkEffectivePomMatch(baselineHolder.get(), effectivePomChecksum);
}
boolean sourcesMatched = true;
for (Path file : inputFiles) {
DigestItem fileDigest = DigestUtils.file(checksum, baseDirPath, file);
items.add(fileDigest);
if (compareWithBaseline) {
sourcesMatched &= checkItemMatchesBaseline(baselineHolder.get(), fileDigest);
}
}
if (compareWithBaseline) {
LOGGER.info("Source code: {}", sourcesMatched ? "MATCHED" : "OUT OF DATE");
}
boolean dependenciesMatched = true;
for (Map.Entry<String, String> entry : dependenciesChecksum.entrySet()) {
DigestItem dependencyDigest = DigestUtils.dependency(checksum, entry.getKey(), entry.getValue());
items.add(dependencyDigest);
if (compareWithBaseline) {
dependenciesMatched &= checkItemMatchesBaseline(baselineHolder.get(), dependencyDigest);
}
}
if (compareWithBaseline) {
LOGGER.info("Dependencies: {}", dependenciesMatched ? "MATCHED" : "OUT OF DATE");
}
final ProjectsInputInfo projectsInputInfoType = new ProjectsInputInfo();
projectsInputInfoType.setChecksum(checksum.digest());
projectsInputInfoType.getItems().addAll(items);
final long t2 = System.currentTimeMillis();
for (DigestItem item : projectsInputInfoType.getItems()) {
LOGGER.debug("Hash calculated, item: {}, hash: {}", item.getType(), item.getHash());
}
LOGGER.info(
"Project inputs calculated in {} ms. {} checksum [{}] calculated in {} ms.",
t1 - t0,
config.getHashFactory().getAlgorithm(),
projectsInputInfoType.getChecksum(),
t2 - t1);
return projectsInputInfoType;
}
private void checkEffectivePomMatch(ProjectsInputInfo baselineBuild, DigestItem effectivePomChecksum) {
Optional<DigestItem> pomHolder = Optional.empty();
for (DigestItem it : baselineBuild.getItems()) {
if (it.getType().equals("pom")) {
pomHolder = Optional.of(it);
break;
}
}
if (pomHolder.isPresent()) {
DigestItem pomItem = pomHolder.get();
final boolean matches = StringUtils.equals(pomItem.getHash(), effectivePomChecksum.getHash());
if (!matches) {
LOGGER.info(
"Mismatch in effective poms. Current: {}, remote: {}",
effectivePomChecksum.getHash(),
pomItem.getHash());
}
LOGGER.info("Effective pom: {}", matches ? "MATCHED" : "OUT OF DATE");
}
}
private boolean checkItemMatchesBaseline(ProjectsInputInfo baselineBuild, DigestItem fileDigest) {
Optional<DigestItem> baselineFileDigest = Optional.empty();
for (DigestItem it : baselineBuild.getItems()) {
if (it.getType().equals(fileDigest.getType())
&& fileDigest.getValue().equals(it.getValue().trim())) {
baselineFileDigest = Optional.of(it);
break;
}
}
boolean matched = false;
if (baselineFileDigest.isPresent()) {
String hash = baselineFileDigest.get().getHash();
matched = StringUtils.equals(hash, fileDigest.getHash());
if (!matched) {
LOGGER.info(
"Mismatch in {}: {}. Local hash: {}, remote: {}",
fileDigest.getType(),
fileDigest.getValue(),
fileDigest.getHash(),
hash);
}
} else {
LOGGER.info("Mismatch in {}: {}. Not found in remote cache", fileDigest.getType(), fileDigest.getValue());
}
return matched;
}
/**
* @param prototype effective model fully resolved by maven build. Do not pass here just parsed Model.
*/
private String getEffectivePom(Model prototype) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
Writer writer = null;
try {
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);
}
}
private SortedSet<Path> getInputFiles() {
long start = System.currentTimeMillis();
HashSet<WalkKey> visitedDirs = new HashSet<>();
ArrayList<Path> collectedFiles = new ArrayList<>();
org.apache.maven.model.Build build = project.getBuild();
final boolean recursive = true;
startWalk(Paths.get(build.getSourceDirectory()), dirGlob, recursive, collectedFiles, visitedDirs);
for (Resource resource : build.getResources()) {
startWalk(Paths.get(resource.getDirectory()), dirGlob, recursive, collectedFiles, visitedDirs);
}
startWalk(Paths.get(build.getTestSourceDirectory()), dirGlob, recursive, collectedFiles, visitedDirs);
for (Resource testResource : build.getTestResources()) {
startWalk(Paths.get(testResource.getDirectory()), dirGlob, recursive, collectedFiles, visitedDirs);
}
Properties properties = project.getProperties();
for (String name : properties.stringPropertyNames()) {
if (name.startsWith(CACHE_INPUT_NAME)) {
String path = properties.getProperty(name);
startWalk(Paths.get(path), dirGlob, recursive, collectedFiles, visitedDirs);
}
}
List<Include> includes = config.getGlobalIncludePaths();
for (Include include : includes) {
final String path = include.getValue();
final String glob = defaultIfEmpty(include.getGlob(), dirGlob);
startWalk(Paths.get(path), glob, include.isRecursive(), collectedFiles, visitedDirs);
}
long walkKnownPathsFinished = System.currentTimeMillis() - start;
LOGGER.info(
"Scanning plugins configurations to find input files. Probing is {}",
processPlugins
? "enabled, values will be checked for presence in file system"
: "disabled, only tags with attribute " + CACHE_INPUT_NAME + "=\"true\" will be added");
if (processPlugins) {
collectFromPlugins(collectedFiles, visitedDirs);
} else {
LOGGER.info("Skipping check plugins scan (probing is disabled by config)");
}
long pluginsFinished = System.currentTimeMillis() - start - walkKnownPathsFinished;
TreeSet<Path> sorted = new TreeSet<>(fileComparator);
for (Path collectedFile : collectedFiles) {
sorted.add(collectedFile.normalize().toAbsolutePath());
}
LOGGER.info(
"Found {} input files. Project dir processing: {}, plugins: {} millis",
sorted.size(),
walkKnownPathsFinished,
pluginsFinished);
LOGGER.debug("Src input: {}", sorted);
return sorted;
}
/**
* entry point for directory walk
*/
private void startWalk(
Path candidate, String glob, boolean recursive, List<Path> collectedFiles, Set<WalkKey> visitedDirs) {
Path normalized = candidate.isAbsolute() ? candidate : baseDirPath.resolve(candidate);
normalized = normalized.toAbsolutePath().normalize();
WalkKey key = new WalkKey(normalized, glob, recursive);
if (visitedDirs.contains(key) || !Files.exists(normalized)) {
return;
}
if (Files.isDirectory(normalized)) {
if (baseDirPath.startsWith(normalized)) { // requested to walk parent, can do only non recursive
key = new WalkKey(normalized, glob, false);
}
try {
walkDir(key, collectedFiles, visitedDirs);
visitedDirs.add(key);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
if (!isFilteredOutSubpath(normalized)) {
LOGGER.debug("Adding: {}", normalized);
collectedFiles.add(normalized);
}
}
}
private Path normalizedPath(String directory) {
return Paths.get(directory).normalize();
}
private void collectFromPlugins(List<Path> files, HashSet<WalkKey> visitedDirs) {
List<Plugin> plugins = project.getBuild().getPlugins();
for (Plugin plugin : plugins) {
PluginScanConfig scanConfig = config.getPluginDirScanConfig(plugin);
if (scanConfig.isSkip()) {
LOGGER.debug("Skipping plugin config scan (skip by config): {}", plugin.getArtifactId());
continue;
}
Object configuration = plugin.getConfiguration();
LOGGER.debug("Processing plugin config: {}", plugin.getArtifactId());
if (configuration != null) {
addInputsFromPluginConfigs(Xpp3DomUtils.getChildren(configuration), scanConfig, files, visitedDirs);
}
for (PluginExecution exec : plugin.getExecutions()) {
final PluginScanConfig executionScanConfig = config.getExecutionDirScanConfig(plugin, exec);
PluginScanConfig mergedConfig = scanConfig.mergeWith(executionScanConfig);
if (mergedConfig.isSkip()) {
LOGGER.debug(
"Skipping plugin execution config scan (skip by config): {}, execId: {}",
plugin.getArtifactId(),
exec.getId());
continue;
}
Object execConfiguration = exec.getConfiguration();
LOGGER.debug("Processing plugin: {}, execution: {}", plugin.getArtifactId(), exec.getId());
if (execConfiguration != null) {
addInputsFromPluginConfigs(
Xpp3DomUtils.getChildren(execConfiguration), mergedConfig, files, visitedDirs);
}
}
}
}
private Path walkDir(final WalkKey key, final List<Path> collectedFiles, final Set<WalkKey> visitedDirs)
throws IOException {
return Files.walkFileTree(key.getPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes)
throws IOException {
WalkKey currentDirKey =
new WalkKey(path.toAbsolutePath().normalize(), key.getGlob(), key.isRecursive());
if (isHidden(path)) {
LOGGER.debug("Skipping subtree (hidden): {}", path);
return FileVisitResult.SKIP_SUBTREE;
} else if (!isReadable(path)) {
LOGGER.debug("Skipping subtree (not readable): {}", path);
return FileVisitResult.SKIP_SUBTREE;
} else if (isFilteredOutSubpath(path)) {
LOGGER.debug("Skipping subtree (blacklisted): {}", path);
return FileVisitResult.SKIP_SUBTREE;
} else if (visitedDirs.contains(currentDirKey)) {
LOGGER.debug("Skipping subtree (visited): {}", path);
return FileVisitResult.SKIP_SUBTREE;
}
walkDirectoryFiles(path, collectedFiles, key.getGlob(), entry -> filteredOutPaths.stream()
.anyMatch(it -> it.getFileName().equals(entry.getFileName())));
if (!key.isRecursive()) {
LOGGER.debug("Skipping subtree (non recursive): {}", path);
return FileVisitResult.SKIP_SUBTREE;
}
LOGGER.debug("Visiting subtree: {}", path);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
LOGGER.debug("Skipping subtree (exception: {}): {}", exc, path);
return FileVisitResult.SKIP_SUBTREE;
}
});
}
private void addInputsFromPluginConfigs(
Object[] configurationChildren,
PluginScanConfig scanConfig,
List<Path> files,
HashSet<WalkKey> visitedDirs) {
if (configurationChildren == null) {
return;
}
for (Object configChild : configurationChildren) {
String tagName = Xpp3DomUtils.getName(configChild);
String tagValue = Xpp3DomUtils.getValue(configChild);
if (!scanConfig.accept(tagName)) {
LOGGER.debug("Skipping property (scan config)): {}, value: {}", tagName, stripToEmpty(tagValue));
continue;
}
LOGGER.debug("Checking xml tag. Tag: {}, value: {}", tagName, stripToEmpty(tagValue));
addInputsFromPluginConfigs(Xpp3DomUtils.getChildren(configChild), scanConfig, files, visitedDirs);
final ScanConfigProperties propertyConfig = scanConfig.getTagScanProperties(tagName);
final String glob = defaultIfEmpty(propertyConfig.getGlob(), dirGlob);
if ("true".equals(Xpp3DomUtils.getAttribute(configChild, CACHE_INPUT_NAME))) {
LOGGER.info(
"Found tag marked with {} attribute. Tag: {}, value: {}", CACHE_INPUT_NAME, tagName, tagValue);
startWalk(Paths.get(tagValue), glob, propertyConfig.isRecursive(), files, visitedDirs);
} else {
final Path candidate = getPathOrNull(tagValue);
if (candidate != null) {
startWalk(candidate, glob, propertyConfig.isRecursive(), files, visitedDirs);
if ("descriptorRef"
.equals(tagName)) { // hardcoded logic for assembly plugin which could reference files
// omitting .xml suffix
startWalk(Paths.get(tagValue + ".xml"), glob, propertyConfig.isRecursive(), files, visitedDirs);
}
}
}
}
}
private Path getPathOrNull(String text) {
// small optimization to not probe not-paths
if (isBlank(text)) {
// do not even bother logging about blank/null values
} else if (equalsAnyIgnoreCase(text, "true", "false", "utf-8", "null", "\\") // common values
|| contains(text, "*") // tag value is a glob or regex - unclear how to process
|| (contains(text, ":") && !contains(text, ":\\")) // artifactId
|| startsWithAny(text, "com.", "org.", "io.", "java.", "javax.") // java packages
|| startsWithAny(text, "${env.") // env variables in maven notation
|| startsWithAny(
text,
"http:",
"https:",
"scm:",
"ssh:",
"git:",
"svn:",
"cp:",
"classpath:")) // urls identified by common protocols
{
LOGGER.debug("Skipping directory (blacklisted literal): {}", text);
} else if (startsWithAny(text, tmpDir)) // tmp dir
{
LOGGER.debug("Skipping directory (temp dir): {}", text);
} else {
try {
return Paths.get(text);
} catch (Exception ignore) {
LOGGER.debug("Skipping directory (invalid path): {}", text);
}
}
return null;
}
static void walkDirectoryFiles(Path dir, List<Path> collectedFiles, String glob, Predicate<Path> mustBeSkipped) {
if (!Files.isDirectory(dir)) {
return;
}
try {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, glob)) {
for (Path entry : stream) {
if (mustBeSkipped.test(entry)) {
continue;
}
File file = entry.toFile();
if (file.isFile() && !isHidden(entry) && isReadable(entry)) {
collectedFiles.add(entry);
}
}
}
} catch (IOException e) {
throw new RuntimeException("Cannot process directory: " + dir, e);
}
}
private static boolean isHidden(Path entry) throws IOException {
return Files.isHidden(entry) || entry.toFile().getName().startsWith(".");
}
private static boolean isReadable(Path entry) throws IOException {
return Files.isReadable(entry);
}
private boolean isFilteredOutSubpath(Path path) {
Path normalized = path.normalize();
for (Path filteredOutDir : filteredOutPaths) {
if (normalized.startsWith(filteredOutDir)) {
return true;
}
}
return false;
}
private SortedMap<String, String> getMutableDependencies() throws IOException {
SortedMap<String, String> result = new TreeMap<>();
for (Dependency dependency : project.getDependencies()) {
if (CacheUtils.isPom(dependency)) {
// POM dependency will be resolved by maven system to actual dependencies
// and will contribute to effective pom.
// Effective result will be recorded by #getNormalizedPom
// so pom dependencies must be skipped as meaningless by themselves
continue;
}
// saved to index by the end of dependency build
MavenProject dependencyProject = multiModuleSupport
.tryToResolveProject(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())
.orElse(null);
boolean isSnapshot = isSnapshot(dependency.getVersion());
if (dependencyProject == null && !isSnapshot) {
// external immutable dependency, should skip
continue;
}
String projectHash;
if (dependencyProject != null) // part of multi module
{
projectHash =
projectInputCalculator.calculateInput(dependencyProject).getChecksum();
} else // this is a snapshot dependency
{
DigestItem resolved = resolveArtifact(repoSystem.createDependencyArtifact(dependency), false);
projectHash = resolved.getHash();
}
result.put(
KeyUtils.getVersionlessArtifactKey(repoSystem.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()) {
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.getArtifacts().size() != 1) {
throw new IllegalStateException("Unexpected number of artifacts returned. Requested: " + dependencyArtifact
+ ", expected: 1, actual: " + result.getArtifacts());
}
final Artifact resolved = result.getArtifacts().iterator().next();
final HashAlgorithm algorithm = config.getHashFactory().createAlgorithm();
final String hash = algorithm.hash(resolved.getFile().toPath());
return DtoUtils.createDigestedFile(resolved, hash);
}
/**
* PathIgnoringCaseComparator
*/
public static class PathIgnoringCaseComparator implements Comparator<Path> {
@Override
public int compare(Path f1, Path f2) {
String s1 = f1.toAbsolutePath().toString();
String s2 = f2.toAbsolutePath().toString();
if (File.separator.equals("\\")) {
s1 = s1.replaceAll("\\\\", "/");
s2 = s2.replaceAll("\\\\", "/");
}
return s1.compareToIgnoreCase(s2);
}
}
/**
* Skip lookup on a per-project level via a property to force module rebuild
* e.g.{@code <maven.build.cache.skipCache>true<maven.build.cache.skipCache/>}
* @param project
* @return
*/
public static boolean isSkipCache(MavenProject project) {
return Boolean.parseBoolean(project.getProperties().getProperty(CACHE_SKIP, "false"));
}
/**
* Allow skipping generated sources restoration on a per-project level via a property (which defaults to true)
* e.g. {@code <maven.build.cache.restoreGeneratedSources>false<maven.build.cache.restoreGeneratedSources/>}.
*
* @param project
* @return
*/
public static boolean isRestoreGeneratedSources(MavenProject project) {
return Boolean.parseBoolean(
project.getProperties().getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, "true"));
}
/**
* Allow disabling caching entirely on a per-project level via a property - both artifact lookup and upload
* Defaults to false
* {@code <maven.build.cache.enabled>false<maven.build.cache.enabled/>}
* @param project
* @return
*/
public static boolean isCacheDisabled(MavenProject project) {
return !Boolean.parseBoolean(project.getProperties().getProperty(CACHE_ENABLED_PROPERTY_NAME, "true"));
}
}