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