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.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Optional;
43 import java.util.Properties;
44 import java.util.Set;
45 import java.util.SortedMap;
46 import java.util.SortedSet;
47 import java.util.TreeMap;
48 import java.util.TreeSet;
49 import java.util.concurrent.atomic.AtomicInteger;
50 import java.util.function.Predicate;
51
52 import org.apache.commons.lang3.Strings;
53 import org.apache.maven.artifact.Artifact;
54 import org.apache.maven.artifact.DefaultArtifact;
55 import org.apache.maven.artifact.handler.ArtifactHandler;
56 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
57 import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
58 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
59 import org.apache.maven.artifact.versioning.VersionRange;
60 import org.apache.maven.buildcache.CacheUtils;
61 import org.apache.maven.buildcache.MultiModuleSupport;
62 import org.apache.maven.buildcache.NormalizedModelProvider;
63 import org.apache.maven.buildcache.PluginScanConfig;
64 import org.apache.maven.buildcache.ProjectInputCalculator;
65 import org.apache.maven.buildcache.RemoteCacheRepository;
66 import org.apache.maven.buildcache.ScanConfigProperties;
67 import org.apache.maven.buildcache.Xpp3DomUtils;
68 import org.apache.maven.buildcache.checksum.exclude.ExclusionResolver;
69 import org.apache.maven.buildcache.hash.HashAlgorithm;
70 import org.apache.maven.buildcache.hash.HashChecksum;
71 import org.apache.maven.buildcache.xml.CacheConfig;
72 import org.apache.maven.buildcache.xml.DtoUtils;
73 import org.apache.maven.buildcache.xml.build.DigestItem;
74 import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
75 import org.apache.maven.buildcache.xml.config.Include;
76 import org.apache.maven.execution.MavenSession;
77 import org.apache.maven.model.Dependency;
78 import org.apache.maven.model.Exclusion;
79 import org.apache.maven.model.Model;
80 import org.apache.maven.model.Plugin;
81 import org.apache.maven.model.PluginExecution;
82 import org.apache.maven.model.Resource;
83 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
84 import org.apache.maven.project.MavenProject;
85 import org.codehaus.plexus.util.WriterFactory;
86 import org.eclipse.aether.RepositorySystem;
87 import org.eclipse.aether.artifact.DefaultArtifactType;
88 import org.eclipse.aether.resolution.ArtifactRequest;
89 import org.eclipse.aether.resolution.ArtifactResolutionException;
90 import org.eclipse.aether.resolution.ArtifactResult;
91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
93
94 import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
95 import static org.apache.commons.lang3.StringUtils.isBlank;
96 import static org.apache.commons.lang3.StringUtils.replaceEachRepeatedly;
97 import static org.apache.commons.lang3.StringUtils.stripToEmpty;
98 import static org.apache.maven.buildcache.CacheUtils.isPom;
99 import static org.apache.maven.buildcache.CacheUtils.isSnapshot;
100 import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME;
101 import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_SKIP;
102 import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_GENERATED_SOURCES_PROPERTY_NAME;
103 import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME;
104
105
106
107
108 public class MavenProjectInput {
109
110
111
112
113 public static final String CACHE_IMPLEMENTATION_VERSION = "v1.1";
114
115
116
117
118 private static final String CACHE_INPUT_GLOB_NAME = "maven.build.cache.input.glob";
119
120
121
122
123 private static final String CACHE_INPUT_NAME = "maven.build.cache.input";
124
125
126
127 private static final String CACHE_PROCESS_PLUGINS = "maven.build.cache.processPlugins";
128
129 private static final Logger LOGGER = LoggerFactory.getLogger(MavenProjectInput.class);
130
131 private final MavenProject project;
132 private final MavenSession session;
133 private final RemoteCacheRepository remoteCache;
134 private final RepositorySystem repoSystem;
135 private final CacheConfig config;
136 private final PathIgnoringCaseComparator fileComparator;
137 private final NormalizedModelProvider normalizedModelProvider;
138 private final MultiModuleSupport multiModuleSupport;
139 private final ProjectInputCalculator projectInputCalculator;
140 private final Path baseDirPath;
141 private final ArtifactHandlerManager artifactHandlerManager;
142
143
144
145
146 private final String projectGlob;
147
148 private final ExclusionResolver exclusionResolver;
149
150 private final boolean processPlugins;
151 private final String tmpDir;
152
153 @SuppressWarnings("checkstyle:parameternumber")
154 public MavenProjectInput(
155 MavenProject project,
156 NormalizedModelProvider normalizedModelProvider,
157 MultiModuleSupport multiModuleSupport,
158 ProjectInputCalculator projectInputCalculator,
159 MavenSession session,
160 CacheConfig config,
161 RepositorySystem repoSystem,
162 RemoteCacheRepository remoteCache,
163 ArtifactHandlerManager artifactHandlerManager) {
164 this.project = project;
165 this.normalizedModelProvider = normalizedModelProvider;
166 this.multiModuleSupport = multiModuleSupport;
167 this.projectInputCalculator = projectInputCalculator;
168 this.session = session;
169 this.config = config;
170 this.baseDirPath = project.getBasedir().toPath().toAbsolutePath();
171 this.repoSystem = repoSystem;
172 this.remoteCache = remoteCache;
173 Properties properties = project.getProperties();
174 this.projectGlob = properties.getProperty(CACHE_INPUT_GLOB_NAME, config.getDefaultGlob());
175 this.processPlugins =
176 Boolean.parseBoolean(properties.getProperty(CACHE_PROCESS_PLUGINS, config.isProcessPlugins()));
177 this.tmpDir = System.getProperty("java.io.tmpdir");
178
179 this.exclusionResolver = new ExclusionResolver(project, config);
180
181 this.fileComparator = new PathIgnoringCaseComparator();
182 this.artifactHandlerManager = artifactHandlerManager;
183 }
184
185 public ProjectsInputInfo calculateChecksum() throws IOException {
186 final long t0 = System.currentTimeMillis();
187
188 final String effectivePom = getEffectivePom(normalizedModelProvider.normalizedModel(project));
189 final SortedSet<Path> inputFiles = isPom(project) ? Collections.emptySortedSet() : getInputFiles();
190 final SortedMap<String, String> dependenciesChecksum = getMutableDependencies();
191 final SortedMap<String, String> pluginDependenciesChecksum = getMutablePluginDependencies();
192
193 final long t1 = System.currentTimeMillis();
194
195
196 final int count = 1
197 + (config.calculateProjectVersionChecksum() ? 1 : 0)
198 + 2 * inputFiles.size()
199 + dependenciesChecksum.size()
200 + pluginDependenciesChecksum.size();
201
202 final List<DigestItem> items = new ArrayList<>(count);
203 final HashChecksum checksum = config.getHashFactory().createChecksum(count);
204
205 Optional<ProjectsInputInfo> baselineHolder = Optional.empty();
206 if (config.isBaselineDiffEnabled()) {
207 baselineHolder =
208 remoteCache.findBaselineBuild(project).map(b -> b.getDto().getProjectsInputInfo());
209 }
210
211 if (config.calculateProjectVersionChecksum()) {
212 DigestItem projectVersion = new DigestItem();
213 projectVersion.setType("version");
214 projectVersion.setIsText("yes");
215 projectVersion.setValue(project.getVersion());
216 items.add(projectVersion);
217
218 checksum.update(project.getVersion().getBytes(StandardCharsets.UTF_8));
219 }
220
221 DigestItem effectivePomChecksum = DigestUtils.pom(checksum, effectivePom);
222 items.add(effectivePomChecksum);
223 final boolean compareWithBaseline = config.isBaselineDiffEnabled() && baselineHolder.isPresent();
224 if (compareWithBaseline) {
225 checkEffectivePomMatch(baselineHolder.get(), effectivePomChecksum);
226 }
227
228 boolean sourcesMatched = true;
229 for (Path file : inputFiles) {
230 DigestItem fileDigest = DigestUtils.file(checksum, baseDirPath, file);
231 items.add(fileDigest);
232 if (compareWithBaseline) {
233 sourcesMatched &= checkItemMatchesBaseline(baselineHolder.get(), fileDigest);
234 }
235 }
236 if (compareWithBaseline) {
237 LOGGER.info("Source code: {}", sourcesMatched ? "MATCHED" : "OUT OF DATE");
238 }
239
240 boolean dependenciesMatched = true;
241 for (Map.Entry<String, String> entry : dependenciesChecksum.entrySet()) {
242 DigestItem dependencyDigest = DigestUtils.dependency(checksum, entry.getKey(), entry.getValue());
243 items.add(dependencyDigest);
244 if (compareWithBaseline) {
245 dependenciesMatched &= checkItemMatchesBaseline(baselineHolder.get(), dependencyDigest);
246 }
247 }
248
249 if (compareWithBaseline) {
250 LOGGER.info("Dependencies: {}", dependenciesMatched ? "MATCHED" : "OUT OF DATE");
251 }
252
253 boolean pluginDependenciesMatched = true;
254 for (Map.Entry<String, String> entry : pluginDependenciesChecksum.entrySet()) {
255 DigestItem dependencyDigest = DigestUtils.pluginDependency(checksum, entry.getKey(), entry.getValue());
256 items.add(dependencyDigest);
257 if (compareWithBaseline) {
258 pluginDependenciesMatched &= checkItemMatchesBaseline(baselineHolder.get(), dependencyDigest);
259 }
260 }
261
262 if (compareWithBaseline) {
263 LOGGER.info("Plugin dependencies: {}", pluginDependenciesMatched ? "MATCHED" : "OUT OF DATE");
264 }
265
266 final ProjectsInputInfo projectsInputInfoType = new ProjectsInputInfo();
267 projectsInputInfoType.setChecksum(checksum.digest());
268 projectsInputInfoType.getItems().addAll(items);
269
270 final long t2 = System.currentTimeMillis();
271
272 if (LOGGER.isDebugEnabled()) {
273 for (DigestItem item : projectsInputInfoType.getItems()) {
274 LOGGER.debug("Hash calculated, item: {}, hash: {}", item.getType(), item.getHash());
275 }
276 }
277
278 LOGGER.info(
279 "Project inputs calculated in {} ms. {} checksum [{}] calculated in {} ms.",
280 t1 - t0,
281 config.getHashFactory().getAlgorithm(),
282 projectsInputInfoType.getChecksum(),
283 t2 - t1);
284 return projectsInputInfoType;
285 }
286
287 private void checkEffectivePomMatch(ProjectsInputInfo baselineBuild, DigestItem effectivePomChecksum) {
288 Optional<DigestItem> pomHolder = Optional.empty();
289 for (DigestItem it : baselineBuild.getItems()) {
290 if (it.getType().equals("pom")) {
291 pomHolder = Optional.of(it);
292 break;
293 }
294 }
295
296 if (pomHolder.isPresent()) {
297 DigestItem pomItem = pomHolder.get();
298 final boolean matches = Strings.CS.equals(pomItem.getHash(), effectivePomChecksum.getHash());
299 if (!matches) {
300 LOGGER.info(
301 "Mismatch in effective poms. Current: {}, remote: {}",
302 effectivePomChecksum.getHash(),
303 pomItem.getHash());
304 }
305 LOGGER.info("Effective pom: {}", matches ? "MATCHED" : "OUT OF DATE");
306 }
307 }
308
309 private boolean checkItemMatchesBaseline(ProjectsInputInfo baselineBuild, DigestItem fileDigest) {
310 Optional<DigestItem> baselineFileDigest = Optional.empty();
311 for (DigestItem it : baselineBuild.getItems()) {
312 if (it.getType().equals(fileDigest.getType())
313 && fileDigest.getValue().equals(it.getValue().trim())) {
314 baselineFileDigest = Optional.of(it);
315 break;
316 }
317 }
318
319 boolean matched = false;
320 if (baselineFileDigest.isPresent()) {
321 String hash = baselineFileDigest.get().getHash();
322 matched = Strings.CS.equals(hash, fileDigest.getHash());
323 if (!matched) {
324 LOGGER.info(
325 "Mismatch in {}: {}. Local hash: {}, remote: {}",
326 fileDigest.getType(),
327 fileDigest.getValue(),
328 fileDigest.getHash(),
329 hash);
330 }
331 } else {
332 LOGGER.info("Mismatch in {}: {}. Not found in remote cache", fileDigest.getType(), fileDigest.getValue());
333 }
334 return matched;
335 }
336
337
338
339
340 private String getEffectivePom(Model prototype) throws IOException {
341 ByteArrayOutputStream output = new ByteArrayOutputStream();
342
343 try (Writer writer = WriterFactory.newXmlWriter(output)) {
344 new MavenXpp3Writer().write(writer, prototype);
345
346
347 final String[] searchList = {baseDirPath.toString(), "\\", "windows", "linux"};
348 final String[] replacementList = {"", "/", "os.classifier", "os.classifier"};
349 return replaceEachRepeatedly(output.toString(), searchList, replacementList);
350 }
351 }
352
353 private SortedSet<Path> getInputFiles() {
354 long start = System.currentTimeMillis();
355 HashSet<WalkKey> visitedDirs = new HashSet<>();
356 ArrayList<Path> collectedFiles = new ArrayList<>();
357
358 org.apache.maven.model.Build build = project.getBuild();
359
360 final boolean recursive = true;
361 startWalk(Paths.get(build.getSourceDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
362 for (Resource resource : build.getResources()) {
363 startWalk(Paths.get(resource.getDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
364 }
365
366 startWalk(Paths.get(build.getTestSourceDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
367 for (Resource testResource : build.getTestResources()) {
368 startWalk(Paths.get(testResource.getDirectory()), projectGlob, recursive, collectedFiles, visitedDirs);
369 }
370
371 Properties properties = project.getProperties();
372 for (String name : properties.stringPropertyNames()) {
373 if (name.startsWith(CACHE_INPUT_NAME) && !CACHE_INPUT_GLOB_NAME.equals(name)) {
374 String path = properties.getProperty(name);
375 startWalk(Paths.get(path), projectGlob, recursive, collectedFiles, visitedDirs);
376 }
377 }
378
379 List<Include> includes = config.getGlobalIncludePaths();
380 for (Include include : includes) {
381 final String path = include.getValue();
382 final String glob = defaultIfEmpty(include.getGlob(), projectGlob);
383 startWalk(Paths.get(path), glob, include.isRecursive(), collectedFiles, visitedDirs);
384 }
385
386 long walkKnownPathsFinished = System.currentTimeMillis() - start;
387
388 LOGGER.info(
389 "Scanning plugins configurations to find input files. Probing is {}",
390 processPlugins
391 ? "enabled, values will be checked for presence in file system"
392 : "disabled, only tags with attribute " + CACHE_INPUT_NAME + "=\"true\" will be added");
393
394 if (processPlugins) {
395 collectFromPlugins(collectedFiles, visitedDirs);
396 } else {
397 LOGGER.info("Skipping check plugins scan (probing is disabled by config)");
398 }
399
400 long pluginsFinished = System.currentTimeMillis() - start - walkKnownPathsFinished;
401
402 TreeSet<Path> sorted = new TreeSet<>(fileComparator);
403 for (Path collectedFile : collectedFiles) {
404 sorted.add(collectedFile.normalize().toAbsolutePath());
405 }
406
407 LOGGER.info(
408 "Found {} input files. Project dir processing: {}, plugins: {} millis",
409 sorted.size(),
410 walkKnownPathsFinished,
411 pluginsFinished);
412 LOGGER.debug("Src input: {}", sorted);
413
414 return sorted;
415 }
416
417 private Path convertToAbsolutePath(Path path) {
418 Path resolvedPath = path.isAbsolute() ? path : baseDirPath.resolve(path);
419 return resolvedPath.toAbsolutePath().normalize();
420 }
421
422
423
424
425 private void startWalk(
426 Path candidate, String glob, boolean recursive, List<Path> collectedFiles, Set<WalkKey> visitedDirs) {
427 Path normalized = convertToAbsolutePath(candidate);
428 WalkKey key = new WalkKey(normalized, glob, recursive);
429 if (visitedDirs.contains(key) || !Files.exists(normalized)) {
430 return;
431 }
432
433 if (Files.isDirectory(normalized)) {
434 if (baseDirPath.startsWith(normalized)) {
435 key = new WalkKey(normalized, glob, false);
436 }
437 try {
438 walkDir(key, collectedFiles, visitedDirs);
439 visitedDirs.add(key);
440 } catch (IOException e) {
441 throw new RuntimeException(e);
442 }
443 } else {
444 if (!exclusionResolver.excludesPath(normalized)) {
445 LOGGER.debug("Adding: {}", normalized);
446 collectedFiles.add(normalized);
447 }
448 }
449 }
450
451 private void collectFromPlugins(List<Path> files, HashSet<WalkKey> visitedDirs) {
452 List<Plugin> plugins = project.getBuild().getPlugins();
453 for (Plugin plugin : plugins) {
454 PluginScanConfig scanConfig = config.getPluginDirScanConfig(plugin);
455
456 if (scanConfig.isSkip()) {
457 LOGGER.debug("Skipping plugin config scan (skip by config): {}", plugin.getArtifactId());
458 continue;
459 }
460
461 Object configuration = plugin.getConfiguration();
462 LOGGER.debug("Processing plugin config: {}", plugin.getArtifactId());
463 if (configuration != null) {
464 addInputsFromPluginConfigs(Xpp3DomUtils.getChildren(configuration), scanConfig, files, visitedDirs);
465 }
466
467 for (PluginExecution exec : plugin.getExecutions()) {
468 final PluginScanConfig executionScanConfig = config.getExecutionDirScanConfig(plugin, exec);
469 PluginScanConfig mergedConfig = scanConfig.mergeWith(executionScanConfig);
470
471 if (mergedConfig.isSkip()) {
472 LOGGER.debug(
473 "Skipping plugin execution config scan (skip by config): {}, execId: {}",
474 plugin.getArtifactId(),
475 exec.getId());
476 continue;
477 }
478
479 Object execConfiguration = exec.getConfiguration();
480 LOGGER.debug("Processing plugin: {}, execution: {}", plugin.getArtifactId(), exec.getId());
481
482 if (execConfiguration != null) {
483 addInputsFromPluginConfigs(
484 Xpp3DomUtils.getChildren(execConfiguration), mergedConfig, files, visitedDirs);
485 }
486 }
487 }
488 }
489
490 private Path walkDir(final WalkKey key, final List<Path> collectedFiles, final Set<WalkKey> visitedDirs)
491 throws IOException {
492 return Files.walkFileTree(key.getPath(), new SimpleFileVisitor<Path>() {
493
494 @Override
495 public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes)
496 throws IOException {
497 WalkKey currentDirKey =
498 new WalkKey(path.toAbsolutePath().normalize(), key.getGlob(), key.isRecursive());
499 if (isHidden(path)) {
500 LOGGER.debug("Skipping subtree (hidden): {}", path);
501 return FileVisitResult.SKIP_SUBTREE;
502 } else if (!isReadable(path)) {
503 LOGGER.debug("Skipping subtree (not readable): {}", path);
504 return FileVisitResult.SKIP_SUBTREE;
505 } else if (exclusionResolver.excludesPath(path)) {
506 LOGGER.debug("Skipping subtree (blacklisted): {}", path);
507 return FileVisitResult.SKIP_SUBTREE;
508 } else if (visitedDirs.contains(currentDirKey)) {
509 LOGGER.debug("Skipping subtree (visited): {}", path);
510 return FileVisitResult.SKIP_SUBTREE;
511 }
512
513 walkDirectoryFiles(path, collectedFiles, key.getGlob(), exclusionResolver::excludesPath);
514
515 if (!key.isRecursive()) {
516 LOGGER.debug("Skipping subtree (non recursive): {}", path);
517 return FileVisitResult.SKIP_SUBTREE;
518 }
519
520 LOGGER.debug("Visiting subtree: {}", path);
521 return FileVisitResult.CONTINUE;
522 }
523
524 @Override
525 public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
526 LOGGER.debug("Skipping subtree (exception: {}): {}", exc, path);
527 return FileVisitResult.SKIP_SUBTREE;
528 }
529 });
530 }
531
532 private void addInputsFromPluginConfigs(
533 Object[] configurationChildren,
534 PluginScanConfig scanConfig,
535 List<Path> files,
536 HashSet<WalkKey> visitedDirs) {
537 if (configurationChildren == null) {
538 return;
539 }
540
541 for (Object configChild : configurationChildren) {
542 String tagName = Xpp3DomUtils.getName(configChild);
543 String tagValue = Xpp3DomUtils.getValue(configChild);
544
545 if (!scanConfig.accept(tagName)) {
546 LOGGER.debug("Skipping property (scan config)): {}, value: {}", tagName, stripToEmpty(tagValue));
547 continue;
548 }
549
550 LOGGER.debug("Checking xml tag. Tag: {}, value: {}", tagName, stripToEmpty(tagValue));
551
552 addInputsFromPluginConfigs(Xpp3DomUtils.getChildren(configChild), scanConfig, files, visitedDirs);
553
554 final ScanConfigProperties propertyConfig = scanConfig.getTagScanProperties(tagName);
555 final String glob = defaultIfEmpty(propertyConfig.getGlob(), projectGlob);
556 if ("true".equals(Xpp3DomUtils.getAttribute(configChild, CACHE_INPUT_NAME))) {
557 LOGGER.info(
558 "Found tag marked with {} attribute. Tag: {}, value: {}", CACHE_INPUT_NAME, tagName, tagValue);
559 startWalk(Paths.get(tagValue), glob, propertyConfig.isRecursive(), files, visitedDirs);
560 } else {
561 final Path candidate = getPathOrNull(tagValue);
562 if (candidate != null) {
563 startWalk(candidate, glob, propertyConfig.isRecursive(), files, visitedDirs);
564 if ("descriptorRef"
565 .equals(tagName)) {
566
567 startWalk(Paths.get(tagValue + ".xml"), glob, propertyConfig.isRecursive(), files, visitedDirs);
568 }
569 }
570 }
571 }
572 }
573
574 private Path getPathOrNull(String text) {
575
576 if (isBlank(text)) {
577
578 } else if (Strings.CI.equalsAny(text, "true", "false", "utf-8", "null", "\\")
579 || Strings.CS.contains(text, "*")
580 || (Strings.CS.contains(text, ":") && !Strings.CS.contains(text, ":\\"))
581 || Strings.CS.startsWithAny(text, "com.", "org.", "io.", "java.", "javax.")
582 || Strings.CS.startsWithAny(text, "${env.")
583 || Strings.CS.startsWithAny(
584 text,
585 "http:",
586 "https:",
587 "scm:",
588 "ssh:",
589 "git:",
590 "svn:",
591 "cp:",
592 "classpath:"))
593 {
594 LOGGER.debug("Skipping directory (blacklisted literal): {}", text);
595 } else if (Strings.CS.startsWithAny(text, tmpDir))
596 {
597 LOGGER.debug("Skipping directory (temp dir): {}", text);
598 } else {
599 try {
600 return Paths.get(text);
601 } catch (Exception ignore) {
602 LOGGER.debug("Skipping directory (invalid path): {}", text);
603 }
604 }
605 return null;
606 }
607
608 static void walkDirectoryFiles(Path dir, List<Path> collectedFiles, String glob, Predicate<Path> mustBeSkipped) {
609 if (!Files.isDirectory(dir)) {
610 return;
611 }
612
613 try {
614 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, glob)) {
615 for (Path entry : stream) {
616 if (mustBeSkipped.test(entry)) {
617 continue;
618 }
619 File file = entry.toFile();
620 if (file.isFile() && !isHidden(entry) && isReadable(entry)) {
621 collectedFiles.add(entry);
622 }
623 }
624 }
625 } catch (IOException e) {
626 throw new RuntimeException("Cannot process directory: " + dir, e);
627 }
628 }
629
630 private static boolean isHidden(Path entry) throws IOException {
631 return Files.isHidden(entry) || entry.toFile().getName().startsWith(".");
632 }
633
634 private static boolean isReadable(Path entry) throws IOException {
635 return Files.isReadable(entry);
636 }
637
638 private SortedMap<String, String> getMutableDependencies() throws IOException {
639 return getMutableDependenciesHashes("", project.getDependencies());
640 }
641
642 private SortedMap<String, String> getMutablePluginDependencies() throws IOException {
643 Map<String, AtomicInteger> keyPrefixOccurrenceIndex = new HashMap<>();
644 SortedMap<String, String> fullMap = new TreeMap<>();
645 for (Plugin plugin : project.getBuildPlugins()) {
646 if (config.isPluginDependenciesExcluded(plugin)) {
647 continue;
648 }
649
650 String rawKeyPrefix = KeyUtils.getVersionlessArtifactKey(createPluginArtifact(plugin));
651 int occurrenceIndex = keyPrefixOccurrenceIndex
652 .computeIfAbsent(rawKeyPrefix, k -> new AtomicInteger())
653 .getAndIncrement();
654 fullMap.putAll(
655 getMutableDependenciesHashes(rawKeyPrefix + "|" + occurrenceIndex + "|", plugin.getDependencies()));
656 }
657 return fullMap;
658 }
659
660 public Artifact createPluginArtifact(Plugin plugin) {
661
662 VersionRange versionRange;
663 try {
664 versionRange = VersionRange.createFromVersionSpec(plugin.getVersion());
665 } catch (InvalidVersionSpecificationException e) {
666 LOGGER.error(
667 String.format(
668 "Invalid version specification '%s' creating plugin artifact '%s'.",
669 plugin.getVersion(), plugin),
670 e);
671
672 throw new RuntimeException(e);
673 }
674
675 return createArtifact(
676 plugin.getGroupId(),
677 plugin.getArtifactId(),
678 versionRange,
679 "maven-plugin",
680 null,
681 Artifact.SCOPE_RUNTIME,
682 null,
683 false);
684 }
685
686 private Artifact createArtifact(
687 String groupId,
688 String artifactId,
689 VersionRange versionRange,
690 String type,
691 String classifier,
692 String scope,
693 String inheritedScope,
694 boolean optional) {
695
696 String desiredScope = Artifact.SCOPE_RUNTIME;
697
698 if (inheritedScope == null) {
699 desiredScope = scope;
700 } else if (Artifact.SCOPE_TEST.equals(scope) || Artifact.SCOPE_PROVIDED.equals(scope)) {
701 return null;
702 } else if (Artifact.SCOPE_COMPILE.equals(scope) && Artifact.SCOPE_COMPILE.equals(inheritedScope)) {
703
704 desiredScope = Artifact.SCOPE_COMPILE;
705 }
706
707 if (Artifact.SCOPE_TEST.equals(inheritedScope)) {
708 desiredScope = Artifact.SCOPE_TEST;
709 }
710
711 if (Artifact.SCOPE_PROVIDED.equals(inheritedScope)) {
712 desiredScope = Artifact.SCOPE_PROVIDED;
713 }
714
715 if (Artifact.SCOPE_SYSTEM.equals(scope)) {
716
717 desiredScope = Artifact.SCOPE_SYSTEM;
718 }
719 ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(type);
720
721 return new DefaultArtifact(
722 groupId, artifactId, versionRange, desiredScope, type, classifier, handler, optional);
723 }
724
725 public Artifact createDependencyArtifact(Dependency d) {
726 VersionRange versionRange;
727 try {
728 versionRange = VersionRange.createFromVersionSpec(d.getVersion());
729 } catch (InvalidVersionSpecificationException e) {
730 LOGGER.error(
731 String.format(
732 "Invalid version specification '%s' creating dependency artifact '%s'.", d.getVersion(), d),
733 e);
734
735 throw new RuntimeException(e);
736 }
737
738 Artifact artifact = createArtifact(
739 d.getGroupId(),
740 d.getArtifactId(),
741 versionRange,
742 d.getType(),
743 d.getClassifier(),
744 d.getScope(),
745 null,
746 d.isOptional());
747
748 if (Artifact.SCOPE_SYSTEM.equals(d.getScope()) && d.getSystemPath() != null) {
749 artifact.setFile(new File(d.getSystemPath()));
750 }
751
752 if (!d.getExclusions().isEmpty()) {
753 List<String> exclusions = new ArrayList<>();
754
755 for (Exclusion exclusion : d.getExclusions()) {
756 exclusions.add(exclusion.getGroupId() + ':' + exclusion.getArtifactId());
757 }
758
759 artifact.setDependencyFilter(new ExcludesArtifactFilter(exclusions));
760 }
761
762 return artifact;
763 }
764
765 private SortedMap<String, String> getMutableDependenciesHashes(String keyPrefix, List<Dependency> dependencies)
766 throws IOException {
767 SortedMap<String, String> result = new TreeMap<>();
768
769 for (Dependency dependency : dependencies) {
770
771 if (CacheUtils.isPom(dependency)) {
772
773
774
775
776 continue;
777 }
778
779
780 MavenProject dependencyProject = multiModuleSupport
781 .tryToResolveProject(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())
782 .orElse(null);
783 boolean isSnapshot = isSnapshot(dependency.getVersion());
784 if (dependencyProject == null && !isSnapshot) {
785
786 continue;
787 }
788 String projectHash;
789 if (dependencyProject != null)
790 {
791 projectHash =
792 projectInputCalculator.calculateInput(dependencyProject).getChecksum();
793 } else
794 {
795 DigestItem resolved = null;
796 try {
797 resolved = resolveArtifact(dependency);
798 } catch (ArtifactResolutionException | InvalidVersionSpecificationException e) {
799 throw new IOException(e);
800 }
801 projectHash = resolved.getHash();
802 }
803 result.put(
804 keyPrefix + KeyUtils.getVersionlessArtifactKey(createDependencyArtifact(dependency)), projectHash);
805 }
806 return result;
807 }
808
809 @Nonnull
810 private DigestItem resolveArtifact(final Dependency dependency)
811 throws IOException, ArtifactResolutionException, InvalidVersionSpecificationException {
812
813 org.eclipse.aether.artifact.Artifact dependencyArtifact = new org.eclipse.aether.artifact.DefaultArtifact(
814 dependency.getGroupId(),
815 dependency.getArtifactId(),
816 dependency.getClassifier(),
817 null,
818 dependency.getVersion(),
819 new DefaultArtifactType(dependency.getType()));
820 ArtifactRequest artifactRequest = new ArtifactRequest().setArtifact(dependencyArtifact);
821 artifactRequest.setRepositories(project.getRemoteProjectRepositories());
822
823 ArtifactResult result = repoSystem.resolveArtifact(session.getRepositorySession(), artifactRequest);
824
825 if (!result.isResolved()) {
826 throw new DependencyNotResolvedException("Cannot resolve in-project dependency: " + dependencyArtifact);
827 }
828
829 if (result.isMissing()) {
830 throw new DependencyNotResolvedException("Cannot resolve missing artifact: " + dependencyArtifact);
831 }
832
833 org.eclipse.aether.artifact.Artifact resolved = result.getArtifact();
834
835 Artifact artifact = createArtifact(
836 resolved.getGroupId(),
837 resolved.getArtifactId(),
838 VersionRange.createFromVersionSpec(resolved.getVersion()),
839 dependency.getType(),
840 resolved.getClassifier(),
841 dependency.getType(),
842 dependency.getScope(),
843 false);
844
845 final HashAlgorithm algorithm = config.getHashFactory().createAlgorithm();
846 final String hash = algorithm.hash(resolved.getFile().toPath());
847 return DtoUtils.createDigestedFile(artifact, hash);
848 }
849
850
851
852
853 public static class PathIgnoringCaseComparator implements Comparator<Path> {
854
855 @Override
856 public int compare(Path f1, Path f2) {
857 String s1 = f1.toAbsolutePath().toString();
858 String s2 = f2.toAbsolutePath().toString();
859 if (File.separator.equals("\\")) {
860 s1 = s1.replaceAll("\\\\", "/");
861 s2 = s2.replaceAll("\\\\", "/");
862 }
863 return s1.compareToIgnoreCase(s2);
864 }
865 }
866
867
868
869
870
871
872
873 public static boolean isSkipCache(MavenProject project) {
874 return Boolean.parseBoolean(project.getProperties().getProperty(CACHE_SKIP, "false"));
875 }
876
877
878
879
880
881
882
883
884 public static boolean isRestoreGeneratedSources(MavenProject project) {
885 return Boolean.parseBoolean(
886 project.getProperties().getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, "true"));
887 }
888
889
890
891
892
893
894
895
896 public static boolean isRestoreOnDiskArtifacts(MavenProject project) {
897 return Boolean.parseBoolean(
898 project.getProperties().getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, "true"));
899 }
900
901
902
903
904
905
906
907
908 public static boolean isCacheDisabled(MavenProject project) {
909 return !Boolean.parseBoolean(project.getProperties().getProperty(CACHE_ENABLED_PROPERTY_NAME, "true"));
910 }
911 }