1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.buildcache.checksum;
20
21 import javax.annotation.Nonnull;
22
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.DirectoryStream;
29 import java.nio.file.FileVisitResult;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.nio.file.SimpleFileVisitor;
34 import java.nio.file.attribute.BasicFileAttributes;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Optional;
42 import java.util.Properties;
43 import java.util.Set;
44 import java.util.SortedMap;
45 import java.util.SortedSet;
46 import java.util.TreeMap;
47 import java.util.TreeSet;
48 import java.util.function.Predicate;
49
50 import org.apache.commons.lang3.StringUtils;
51 import org.apache.maven.artifact.Artifact;
52 import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
53 import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
54 import org.apache.maven.buildcache.CacheUtils;
55 import org.apache.maven.buildcache.MultiModuleSupport;
56 import org.apache.maven.buildcache.NormalizedModelProvider;
57 import org.apache.maven.buildcache.PluginScanConfig;
58 import org.apache.maven.buildcache.ProjectInputCalculator;
59 import org.apache.maven.buildcache.RemoteCacheRepository;
60 import org.apache.maven.buildcache.ScanConfigProperties;
61 import org.apache.maven.buildcache.Xpp3DomUtils;
62 import org.apache.maven.buildcache.checksum.exclude.ExclusionResolver;
63 import org.apache.maven.buildcache.hash.HashAlgorithm;
64 import org.apache.maven.buildcache.hash.HashChecksum;
65 import org.apache.maven.buildcache.xml.CacheConfig;
66 import org.apache.maven.buildcache.xml.DtoUtils;
67 import org.apache.maven.buildcache.xml.build.DigestItem;
68 import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
69 import org.apache.maven.buildcache.xml.config.Include;
70 import org.apache.maven.execution.MavenSession;
71 import org.apache.maven.model.Dependency;
72 import org.apache.maven.model.Model;
73 import org.apache.maven.model.Plugin;
74 import org.apache.maven.model.PluginExecution;
75 import org.apache.maven.model.Resource;
76 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
77 import org.apache.maven.project.MavenProject;
78 import org.apache.maven.repository.RepositorySystem;
79 import org.codehaus.plexus.util.IOUtil;
80 import org.codehaus.plexus.util.WriterFactory;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83
84 import static org.apache.commons.lang3.StringUtils.contains;
85 import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
86 import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase;
87 import static org.apache.commons.lang3.StringUtils.isBlank;
88 import static org.apache.commons.lang3.StringUtils.replaceEachRepeatedly;
89 import static org.apache.commons.lang3.StringUtils.startsWithAny;
90 import static org.apache.commons.lang3.StringUtils.stripToEmpty;
91 import static org.apache.maven.buildcache.CacheUtils.isPom;
92 import static org.apache.maven.buildcache.CacheUtils.isSnapshot;
93 import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME;
94 import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_SKIP;
95 import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_GENERATED_SOURCES_PROPERTY_NAME;
96 import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME;
97
98
99
100
101 public class MavenProjectInput {
102
103
104
105
106 public static final String CACHE_IMPLEMENTATION_VERSION = "v1.1";
107
108
109
110
111 private static final String CACHE_INPUT_GLOB_NAME = "maven.build.cache.input.glob";
112
113
114
115
116 private static final String CACHE_INPUT_NAME = "maven.build.cache.input";
117
118
119
120 private static final String CACHE_PROCESS_PLUGINS = "maven.build.cache.processPlugins";
121
122 private static final Logger LOGGER = LoggerFactory.getLogger(MavenProjectInput.class);
123
124 private final MavenProject project;
125 private final MavenSession session;
126 private final RemoteCacheRepository remoteCache;
127 private final RepositorySystem repoSystem;
128 private final CacheConfig config;
129 private final PathIgnoringCaseComparator fileComparator;
130 private final NormalizedModelProvider normalizedModelProvider;
131 private final MultiModuleSupport multiModuleSupport;
132 private final ProjectInputCalculator projectInputCalculator;
133 private final Path baseDirPath;
134
135
136
137 private final String projectGlob;
138
139 private final ExclusionResolver exclusionResolver;
140
141 private final boolean processPlugins;
142 private final String tmpDir;
143
144 @SuppressWarnings("checkstyle:parameternumber")
145 public MavenProjectInput(
146 MavenProject project,
147 NormalizedModelProvider normalizedModelProvider,
148 MultiModuleSupport multiModuleSupport,
149 ProjectInputCalculator projectInputCalculator,
150 MavenSession session,
151 CacheConfig config,
152 RepositorySystem repoSystem,
153 RemoteCacheRepository remoteCache) {
154 this.project = project;
155 this.normalizedModelProvider = normalizedModelProvider;
156 this.multiModuleSupport = multiModuleSupport;
157 this.projectInputCalculator = projectInputCalculator;
158 this.session = session;
159 this.config = config;
160 this.baseDirPath = project.getBasedir().toPath().toAbsolutePath();
161 this.repoSystem = repoSystem;
162 this.remoteCache = remoteCache;
163 Properties properties = project.getProperties();
164 this.projectGlob = properties.getProperty(CACHE_INPUT_GLOB_NAME, config.getDefaultGlob());
165 this.processPlugins =
166 Boolean.parseBoolean(properties.getProperty(CACHE_PROCESS_PLUGINS, config.isProcessPlugins()));
167 this.tmpDir = System.getProperty("java.io.tmpdir");
168
169 this.exclusionResolver = new ExclusionResolver(project, config);
170
171 this.fileComparator = new PathIgnoringCaseComparator();
172 }
173
174 public ProjectsInputInfo calculateChecksum() throws IOException {
175 final long t0 = System.currentTimeMillis();
176
177 final String effectivePom = getEffectivePom(normalizedModelProvider.normalizedModel(project));
178 final SortedSet<Path> inputFiles = isPom(project) ? Collections.emptySortedSet() : getInputFiles();
179 final SortedMap<String, String> dependenciesChecksum = getMutableDependencies();
180
181 final long t1 = System.currentTimeMillis();
182
183
184 final int count = 1
185 + (config.calculateProjectVersionChecksum() ? 1 : 0)
186 + inputFiles.size()
187 + dependenciesChecksum.size();
188
189 final List<DigestItem> items = new ArrayList<>(count);
190 final HashChecksum checksum = config.getHashFactory().createChecksum(count);
191
192 Optional<ProjectsInputInfo> baselineHolder = Optional.empty();
193 if (config.isBaselineDiffEnabled()) {
194 baselineHolder =
195 remoteCache.findBaselineBuild(project).map(b -> b.getDto().getProjectsInputInfo());
196 }
197
198 if (config.calculateProjectVersionChecksum()) {
199 DigestItem projectVersion = new DigestItem();
200 projectVersion.setType("version");
201 projectVersion.setIsText("yes");
202 projectVersion.setValue(project.getVersion());
203 items.add(projectVersion);
204
205 checksum.update(project.getVersion().getBytes(StandardCharsets.UTF_8));
206 }
207
208 DigestItem effectivePomChecksum = DigestUtils.pom(checksum, effectivePom);
209 items.add(effectivePomChecksum);
210 final boolean compareWithBaseline = config.isBaselineDiffEnabled() && baselineHolder.isPresent();
211 if (compareWithBaseline) {
212 checkEffectivePomMatch(baselineHolder.get(), effectivePomChecksum);
213 }
214
215 boolean sourcesMatched = true;
216 for (Path file : inputFiles) {
217 DigestItem fileDigest = DigestUtils.file(checksum, baseDirPath, file);
218 items.add(fileDigest);
219 if (compareWithBaseline) {
220 sourcesMatched &= checkItemMatchesBaseline(baselineHolder.get(), fileDigest);
221 }
222 }
223 if (compareWithBaseline) {
224 LOGGER.info("Source code: {}", sourcesMatched ? "MATCHED" : "OUT OF DATE");
225 }
226
227 boolean dependenciesMatched = true;
228 for (Map.Entry<String, String> entry : dependenciesChecksum.entrySet()) {
229 DigestItem dependencyDigest = DigestUtils.dependency(checksum, entry.getKey(), entry.getValue());
230 items.add(dependencyDigest);
231 if (compareWithBaseline) {
232 dependenciesMatched &= checkItemMatchesBaseline(baselineHolder.get(), dependencyDigest);
233 }
234 }
235
236 if (compareWithBaseline) {
237 LOGGER.info("Dependencies: {}", dependenciesMatched ? "MATCHED" : "OUT OF DATE");
238 }
239
240 final ProjectsInputInfo projectsInputInfoType = new ProjectsInputInfo();
241 projectsInputInfoType.setChecksum(checksum.digest());
242 projectsInputInfoType.getItems().addAll(items);
243
244 final long t2 = System.currentTimeMillis();
245
246 if (LOGGER.isDebugEnabled()) {
247 for (DigestItem item : projectsInputInfoType.getItems()) {
248 LOGGER.debug("Hash calculated, item: {}, hash: {}", item.getType(), item.getHash());
249 }
250 }
251
252 LOGGER.info(
253 "Project inputs calculated in {} ms. {} checksum [{}] calculated in {} ms.",
254 t1 - t0,
255 config.getHashFactory().getAlgorithm(),
256 projectsInputInfoType.getChecksum(),
257 t2 - t1);
258 return projectsInputInfoType;
259 }
260
261 private void checkEffectivePomMatch(ProjectsInputInfo baselineBuild, DigestItem effectivePomChecksum) {
262 Optional<DigestItem> pomHolder = Optional.empty();
263 for (DigestItem it : baselineBuild.getItems()) {
264 if (it.getType().equals("pom")) {
265 pomHolder = Optional.of(it);
266 break;
267 }
268 }
269
270 if (pomHolder.isPresent()) {
271 DigestItem pomItem = pomHolder.get();
272 final boolean matches = StringUtils.equals(pomItem.getHash(), effectivePomChecksum.getHash());
273 if (!matches) {
274 LOGGER.info(
275 "Mismatch in effective poms. Current: {}, remote: {}",
276 effectivePomChecksum.getHash(),
277 pomItem.getHash());
278 }
279 LOGGER.info("Effective pom: {}", matches ? "MATCHED" : "OUT OF DATE");
280 }
281 }
282
283 private boolean checkItemMatchesBaseline(ProjectsInputInfo baselineBuild, DigestItem fileDigest) {
284 Optional<DigestItem> baselineFileDigest = Optional.empty();
285 for (DigestItem it : baselineBuild.getItems()) {
286 if (it.getType().equals(fileDigest.getType())
287 && fileDigest.getValue().equals(it.getValue().trim())) {
288 baselineFileDigest = Optional.of(it);
289 break;
290 }
291 }
292
293 boolean matched = false;
294 if (baselineFileDigest.isPresent()) {
295 String hash = baselineFileDigest.get().getHash();
296 matched = StringUtils.equals(hash, fileDigest.getHash());
297 if (!matched) {
298 LOGGER.info(
299 "Mismatch in {}: {}. Local hash: {}, remote: {}",
300 fileDigest.getType(),
301 fileDigest.getValue(),
302 fileDigest.getHash(),
303 hash);
304 }
305 } else {
306 LOGGER.info("Mismatch in {}: {}. Not found in remote cache", fileDigest.getType(), fileDigest.getValue());
307 }
308 return matched;
309 }
310
311
312
313
314 private String getEffectivePom(Model prototype) throws IOException {
315 ByteArrayOutputStream output = new ByteArrayOutputStream();
316
317 Writer writer = null;
318 try {
319 writer = WriterFactory.newXmlWriter(output);
320 new MavenXpp3Writer().write(writer, prototype);
321
322
323 final String[] searchList = {baseDirPath.toString(), "\\", "windows", "linux"};
324 final String[] replacementList = {"", "/", "os.classifier", "os.classifier"};
325
326 return replaceEachRepeatedly(output.toString(), searchList, replacementList);
327
328 } finally {
329 IOUtil.close(writer);
330 }
331 }
332
333 private SortedSet<Path> getInputFiles() {
334 long start = System.currentTimeMillis();
335 HashSet<WalkKey> visitedDirs = new HashSet<>();
336 ArrayList<Path> collectedFiles = new ArrayList<>();
337
338 org.apache.maven.model.Build build = project.getBuild();
339
340 final boolean recursive = true;
341 startWalk(Paths.get(build.getSourceDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
342 for (Resource resource : build.getResources()) {
343 startWalk(Paths.get(resource.getDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
344 }
345
346 startWalk(Paths.get(build.getTestSourceDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
347 for (Resource testResource : build.getTestResources()) {
348 startWalk(Paths.get(testResource.getDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
349 }
350
351 Properties properties = project.getProperties();
352 for (String name : properties.stringPropertyNames()) {
353 if (name.startsWith(CACHE_INPUT_NAME)) {
354 String path = properties.getProperty(name);
355 startWalk(Paths.get(path), projectGlob, recursive, collectedFiles, visitedDirs);
356 }
357 }
358
359 List<Include> includes = config.getGlobalIncludePaths();
360 for (Include include : includes) {
361 final String path = include.getValue();
362 final String glob = defaultIfEmpty(include.getGlob(), projectGlob);
363 startWalk(Paths.get(path), glob, include.isRecursive(), collectedFiles, visitedDirs);
364 }
365
366 long walkKnownPathsFinished = System.currentTimeMillis() - start;
367
368 LOGGER.info(
369 "Scanning plugins configurations to find input files. Probing is {}",
370 processPlugins
371 ? "enabled, values will be checked for presence in file system"
372 : "disabled, only tags with attribute " + CACHE_INPUT_NAME + "=\"true\" will be added");
373
374 if (processPlugins) {
375 collectFromPlugins(collectedFiles, visitedDirs);
376 } else {
377 LOGGER.info("Skipping check plugins scan (probing is disabled by config)");
378 }
379
380 long pluginsFinished = System.currentTimeMillis() - start - walkKnownPathsFinished;
381
382 TreeSet<Path> sorted = new TreeSet<>(fileComparator);
383 for (Path collectedFile : collectedFiles) {
384 sorted.add(collectedFile.normalize().toAbsolutePath());
385 }
386
387 LOGGER.info(
388 "Found {} input files. Project dir processing: {}, plugins: {} millis",
389 sorted.size(),
390 walkKnownPathsFinished,
391 pluginsFinished);
392 LOGGER.debug("Src input: {}", sorted);
393
394 return sorted;
395 }
396
397 private Path convertToAbsolutePath(Path path) {
398 Path resolvedPath = path.isAbsolute() ? path : baseDirPath.resolve(path);
399 return resolvedPath.toAbsolutePath().normalize();
400 }
401
402
403
404
405 private void startWalk(
406 Path candidate, String glob, boolean recursive, List<Path> collectedFiles, Set<WalkKey> visitedDirs) {
407 Path normalized = convertToAbsolutePath(candidate);
408 WalkKey key = new WalkKey(normalized, glob, recursive);
409 if (visitedDirs.contains(key) || !Files.exists(normalized)) {
410 return;
411 }
412
413 if (Files.isDirectory(normalized)) {
414 if (baseDirPath.startsWith(normalized)) {
415 key = new WalkKey(normalized, glob, false);
416 }
417 try {
418 walkDir(key, collectedFiles, visitedDirs);
419 visitedDirs.add(key);
420 } catch (IOException e) {
421 throw new RuntimeException(e);
422 }
423 } else {
424 if (!exclusionResolver.excludesPath(normalized)) {
425 LOGGER.debug("Adding: {}", normalized);
426 collectedFiles.add(normalized);
427 }
428 }
429 }
430
431 private void collectFromPlugins(List<Path> files, HashSet<WalkKey> visitedDirs) {
432 List<Plugin> plugins = project.getBuild().getPlugins();
433 for (Plugin plugin : plugins) {
434 PluginScanConfig scanConfig = config.getPluginDirScanConfig(plugin);
435
436 if (scanConfig.isSkip()) {
437 LOGGER.debug("Skipping plugin config scan (skip by config): {}", plugin.getArtifactId());
438 continue;
439 }
440
441 Object configuration = plugin.getConfiguration();
442 LOGGER.debug("Processing plugin config: {}", plugin.getArtifactId());
443 if (configuration != null) {
444 addInputsFromPluginConfigs(Xpp3DomUtils.getChildren(configuration), scanConfig, files, visitedDirs);
445 }
446
447 for (PluginExecution exec : plugin.getExecutions()) {
448 final PluginScanConfig executionScanConfig = config.getExecutionDirScanConfig(plugin, exec);
449 PluginScanConfig mergedConfig = scanConfig.mergeWith(executionScanConfig);
450
451 if (mergedConfig.isSkip()) {
452 LOGGER.debug(
453 "Skipping plugin execution config scan (skip by config): {}, execId: {}",
454 plugin.getArtifactId(),
455 exec.getId());
456 continue;
457 }
458
459 Object execConfiguration = exec.getConfiguration();
460 LOGGER.debug("Processing plugin: {}, execution: {}", plugin.getArtifactId(), exec.getId());
461
462 if (execConfiguration != null) {
463 addInputsFromPluginConfigs(
464 Xpp3DomUtils.getChildren(execConfiguration), mergedConfig, files, visitedDirs);
465 }
466 }
467 }
468 }
469
470 private Path walkDir(final WalkKey key, final List<Path> collectedFiles, final Set<WalkKey> visitedDirs)
471 throws IOException {
472 return Files.walkFileTree(key.getPath(), new SimpleFileVisitor<Path>() {
473
474 @Override
475 public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes)
476 throws IOException {
477 WalkKey currentDirKey =
478 new WalkKey(path.toAbsolutePath().normalize(), key.getGlob(), key.isRecursive());
479 if (isHidden(path)) {
480 LOGGER.debug("Skipping subtree (hidden): {}", path);
481 return FileVisitResult.SKIP_SUBTREE;
482 } else if (!isReadable(path)) {
483 LOGGER.debug("Skipping subtree (not readable): {}", path);
484 return FileVisitResult.SKIP_SUBTREE;
485 } else if (exclusionResolver.excludesPath(path)) {
486 LOGGER.debug("Skipping subtree (blacklisted): {}", path);
487 return FileVisitResult.SKIP_SUBTREE;
488 } else if (visitedDirs.contains(currentDirKey)) {
489 LOGGER.debug("Skipping subtree (visited): {}", path);
490 return FileVisitResult.SKIP_SUBTREE;
491 }
492
493 walkDirectoryFiles(path, collectedFiles, key.getGlob(), entry -> exclusionResolver.excludesPath(entry));
494
495 if (!key.isRecursive()) {
496 LOGGER.debug("Skipping subtree (non recursive): {}", path);
497 return FileVisitResult.SKIP_SUBTREE;
498 }
499
500 LOGGER.debug("Visiting subtree: {}", path);
501 return FileVisitResult.CONTINUE;
502 }
503
504 @Override
505 public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
506 LOGGER.debug("Skipping subtree (exception: {}): {}", exc, path);
507 return FileVisitResult.SKIP_SUBTREE;
508 }
509 });
510 }
511
512 private void addInputsFromPluginConfigs(
513 Object[] configurationChildren,
514 PluginScanConfig scanConfig,
515 List<Path> files,
516 HashSet<WalkKey> visitedDirs) {
517 if (configurationChildren == null) {
518 return;
519 }
520
521 for (Object configChild : configurationChildren) {
522 String tagName = Xpp3DomUtils.getName(configChild);
523 String tagValue = Xpp3DomUtils.getValue(configChild);
524
525 if (!scanConfig.accept(tagName)) {
526 LOGGER.debug("Skipping property (scan config)): {}, value: {}", tagName, stripToEmpty(tagValue));
527 continue;
528 }
529
530 LOGGER.debug("Checking xml tag. Tag: {}, value: {}", tagName, stripToEmpty(tagValue));
531
532 addInputsFromPluginConfigs(Xpp3DomUtils.getChildren(configChild), scanConfig, files, visitedDirs);
533
534 final ScanConfigProperties propertyConfig = scanConfig.getTagScanProperties(tagName);
535 final String glob = defaultIfEmpty(propertyConfig.getGlob(), projectGlob);
536 if ("true".equals(Xpp3DomUtils.getAttribute(configChild, CACHE_INPUT_NAME))) {
537 LOGGER.info(
538 "Found tag marked with {} attribute. Tag: {}, value: {}", CACHE_INPUT_NAME, tagName, tagValue);
539 startWalk(Paths.get(tagValue), glob, propertyConfig.isRecursive(), files, visitedDirs);
540 } else {
541 final Path candidate = getPathOrNull(tagValue);
542 if (candidate != null) {
543 startWalk(candidate, glob, propertyConfig.isRecursive(), files, visitedDirs);
544 if ("descriptorRef"
545 .equals(tagName)) {
546
547 startWalk(Paths.get(tagValue + ".xml"), glob, propertyConfig.isRecursive(), files, visitedDirs);
548 }
549 }
550 }
551 }
552 }
553
554 private Path getPathOrNull(String text) {
555
556 if (isBlank(text)) {
557
558 } else if (equalsAnyIgnoreCase(text, "true", "false", "utf-8", "null", "\\")
559 || contains(text, "*")
560 || (contains(text, ":") && !contains(text, ":\\"))
561 || startsWithAny(text, "com.", "org.", "io.", "java.", "javax.")
562 || startsWithAny(text, "${env.")
563 || startsWithAny(
564 text,
565 "http:",
566 "https:",
567 "scm:",
568 "ssh:",
569 "git:",
570 "svn:",
571 "cp:",
572 "classpath:"))
573 {
574 LOGGER.debug("Skipping directory (blacklisted literal): {}", text);
575 } else if (startsWithAny(text, tmpDir))
576 {
577 LOGGER.debug("Skipping directory (temp dir): {}", text);
578 } else {
579 try {
580 return Paths.get(text);
581 } catch (Exception ignore) {
582 LOGGER.debug("Skipping directory (invalid path): {}", text);
583 }
584 }
585 return null;
586 }
587
588 static void walkDirectoryFiles(Path dir, List<Path> collectedFiles, String glob, Predicate<Path> mustBeSkipped) {
589 if (!Files.isDirectory(dir)) {
590 return;
591 }
592
593 try {
594 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, glob)) {
595 for (Path entry : stream) {
596 if (mustBeSkipped.test(entry)) {
597 continue;
598 }
599 File file = entry.toFile();
600 if (file.isFile() && !isHidden(entry) && isReadable(entry)) {
601 collectedFiles.add(entry);
602 }
603 }
604 }
605 } catch (IOException e) {
606 throw new RuntimeException("Cannot process directory: " + dir, e);
607 }
608 }
609
610 private static boolean isHidden(Path entry) throws IOException {
611 return Files.isHidden(entry) || entry.toFile().getName().startsWith(".");
612 }
613
614 private static boolean isReadable(Path entry) throws IOException {
615 return Files.isReadable(entry);
616 }
617
618 private SortedMap<String, String> getMutableDependencies() throws IOException {
619 SortedMap<String, String> result = new TreeMap<>();
620
621 for (Dependency dependency : project.getDependencies()) {
622
623 if (CacheUtils.isPom(dependency)) {
624
625
626
627
628 continue;
629 }
630
631
632 MavenProject dependencyProject = multiModuleSupport
633 .tryToResolveProject(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())
634 .orElse(null);
635 boolean isSnapshot = isSnapshot(dependency.getVersion());
636 if (dependencyProject == null && !isSnapshot) {
637
638 continue;
639 }
640 String projectHash;
641 if (dependencyProject != null)
642 {
643 projectHash =
644 projectInputCalculator.calculateInput(dependencyProject).getChecksum();
645 } else
646 {
647 DigestItem resolved = resolveArtifact(repoSystem.createDependencyArtifact(dependency), false);
648 projectHash = resolved.getHash();
649 }
650 result.put(
651 KeyUtils.getVersionlessArtifactKey(repoSystem.createDependencyArtifact(dependency)), projectHash);
652 }
653 return result;
654 }
655
656 @Nonnull
657 private DigestItem resolveArtifact(final Artifact dependencyArtifact, boolean isOffline) throws IOException {
658 ArtifactResolutionRequest request = new ArtifactResolutionRequest()
659 .setArtifact(dependencyArtifact)
660 .setResolveRoot(true)
661 .setResolveTransitively(false)
662 .setLocalRepository(session.getLocalRepository())
663 .setRemoteRepositories(project.getRemoteArtifactRepositories())
664 .setOffline(session.isOffline() || isOffline)
665 .setForceUpdate(session.getRequest().isUpdateSnapshots())
666 .setServers(session.getRequest().getServers())
667 .setMirrors(session.getRequest().getMirrors())
668 .setProxies(session.getRequest().getProxies());
669
670 final ArtifactResolutionResult result = repoSystem.resolve(request);
671
672 if (!result.isSuccess()) {
673 throw new DependencyNotResolvedException("Cannot resolve in-project dependency: " + dependencyArtifact);
674 }
675
676 if (!result.getMissingArtifacts().isEmpty()) {
677 throw new DependencyNotResolvedException(
678 "Cannot resolve artifact: " + dependencyArtifact + ", missing: " + result.getMissingArtifacts());
679 }
680
681 if (result.getArtifacts().size() != 1) {
682 throw new IllegalStateException("Unexpected number of artifacts returned. Requested: " + dependencyArtifact
683 + ", expected: 1, actual: " + result.getArtifacts());
684 }
685
686 final Artifact resolved = result.getArtifacts().iterator().next();
687
688 final HashAlgorithm algorithm = config.getHashFactory().createAlgorithm();
689 final String hash = algorithm.hash(resolved.getFile().toPath());
690 return DtoUtils.createDigestedFile(resolved, hash);
691 }
692
693
694
695
696 public static class PathIgnoringCaseComparator implements Comparator<Path> {
697
698 @Override
699 public int compare(Path f1, Path f2) {
700 String s1 = f1.toAbsolutePath().toString();
701 String s2 = f2.toAbsolutePath().toString();
702 if (File.separator.equals("\\")) {
703 s1 = s1.replaceAll("\\\\", "/");
704 s2 = s2.replaceAll("\\\\", "/");
705 }
706 return s1.compareToIgnoreCase(s2);
707 }
708 }
709
710
711
712
713
714
715
716 public static boolean isSkipCache(MavenProject project) {
717 return Boolean.parseBoolean(project.getProperties().getProperty(CACHE_SKIP, "false"));
718 }
719
720
721
722
723
724
725
726
727 public static boolean isRestoreGeneratedSources(MavenProject project) {
728 return Boolean.parseBoolean(
729 project.getProperties().getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, "true"));
730 }
731
732
733
734
735
736
737
738
739 public static boolean isRestoreOnDiskArtifacts(MavenProject project) {
740 return Boolean.parseBoolean(
741 project.getProperties().getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, "true"));
742 }
743
744
745
746
747
748
749
750
751 public static boolean isCacheDisabled(MavenProject project) {
752 return !Boolean.parseBoolean(project.getProperties().getProperty(CACHE_ENABLED_PROPERTY_NAME, "true"));
753 }
754 }