1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.buildcache.xml;
20
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23 import javax.inject.Inject;
24 import javax.inject.Named;
25 import javax.inject.Provider;
26
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Optional;
34 import java.util.regex.Pattern;
35
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.lang3.Strings;
38 import org.apache.maven.SessionScoped;
39 import org.apache.maven.buildcache.DefaultPluginScanConfig;
40 import org.apache.maven.buildcache.PluginScanConfig;
41 import org.apache.maven.buildcache.PluginScanConfigImpl;
42 import org.apache.maven.buildcache.hash.HashFactory;
43 import org.apache.maven.buildcache.xml.config.AttachedOutputs;
44 import org.apache.maven.buildcache.xml.config.CacheConfig;
45 import org.apache.maven.buildcache.xml.config.Configuration;
46 import org.apache.maven.buildcache.xml.config.CoordinatesBase;
47 import org.apache.maven.buildcache.xml.config.DirName;
48 import org.apache.maven.buildcache.xml.config.Exclude;
49 import org.apache.maven.buildcache.xml.config.Executables;
50 import org.apache.maven.buildcache.xml.config.ExecutionConfigurationScan;
51 import org.apache.maven.buildcache.xml.config.ExecutionControl;
52 import org.apache.maven.buildcache.xml.config.ExecutionIdsList;
53 import org.apache.maven.buildcache.xml.config.GoalReconciliation;
54 import org.apache.maven.buildcache.xml.config.GoalsList;
55 import org.apache.maven.buildcache.xml.config.Include;
56 import org.apache.maven.buildcache.xml.config.Input;
57 import org.apache.maven.buildcache.xml.config.Local;
58 import org.apache.maven.buildcache.xml.config.MultiModule;
59 import org.apache.maven.buildcache.xml.config.PathSet;
60 import org.apache.maven.buildcache.xml.config.PluginConfigurationScan;
61 import org.apache.maven.buildcache.xml.config.PluginSet;
62 import org.apache.maven.buildcache.xml.config.ProjectVersioning;
63 import org.apache.maven.buildcache.xml.config.PropertyName;
64 import org.apache.maven.buildcache.xml.config.Remote;
65 import org.apache.maven.buildcache.xml.config.TrackedProperty;
66 import org.apache.maven.execution.MavenSession;
67 import org.apache.maven.model.Plugin;
68 import org.apache.maven.model.PluginExecution;
69 import org.apache.maven.plugin.MojoExecution;
70 import org.apache.maven.rtinfo.RuntimeInformation;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 import static java.lang.Boolean.TRUE;
75 import static org.apache.maven.buildcache.CacheUtils.getMultimoduleRoot;
76
77
78
79
80 @SessionScoped
81 @Named
82 @SuppressWarnings("unused")
83 public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheConfig {
84
85 public static final String CONFIG_PATH_PROPERTY_NAME = "maven.build.cache.configPath";
86 public static final String CACHE_ENABLED_PROPERTY_NAME = "maven.build.cache.enabled";
87 public static final String CACHE_LOCATION_PROPERTY_NAME = "maven.build.cache.location";
88 public static final String REMOTE_ENABLED_PROPERTY_NAME = "maven.build.cache.remote.enabled";
89 public static final String REMOTE_URL_PROPERTY_NAME = "maven.build.cache.remote.url";
90 public static final String REMOTE_SERVER_ID_PROPERTY_NAME = "maven.build.cache.remote.server.id";
91 public static final String SAVE_TO_REMOTE_PROPERTY_NAME = "maven.build.cache.remote.save.enabled";
92 public static final String SAVE_NON_OVERRIDEABLE_NAME = "maven.build.cache.remote.save.final";
93 public static final String FAIL_FAST_PROPERTY_NAME = "maven.build.cache.failFast";
94 public static final String BASELINE_BUILD_URL_PROPERTY_NAME = "maven.build.cache.baselineUrl";
95 public static final String LAZY_RESTORE_PROPERTY_NAME = "maven.build.cache.lazyRestore";
96 public static final String RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME = "maven.build.cache.restoreOnDiskArtifacts";
97 public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = "maven.build.cache.restoreGeneratedSources";
98 public static final String ALWAYS_RUN_PLUGINS = "maven.build.cache.alwaysRunPlugins";
99 public static final String MANDATORY_CLEAN = "maven.build.cache.mandatoryClean";
100 public static final String CACHE_COMPILE = "maven.build.cache.cacheCompile";
101
102
103
104
105
106
107
108
109
110 public static final String CACHE_SKIP = "maven.build.cache.skipCache";
111
112
113
114
115 public static final String SKIP_SAVE = "maven.build.cache.skipSave";
116
117 private static final Logger LOGGER = LoggerFactory.getLogger(CacheConfigImpl.class);
118
119 private final XmlService xmlService;
120 private final Provider<MavenSession> providerSession;
121 private final RuntimeInformation rtInfo;
122
123 private volatile CacheState state;
124 private CacheConfig cacheConfig;
125 private HashFactory hashFactory;
126 private List<Pattern> excludePatterns;
127
128 @Inject
129 public CacheConfigImpl(XmlService xmlService, Provider<MavenSession> providerSession, RuntimeInformation rtInfo) {
130 this.xmlService = xmlService;
131 this.providerSession = providerSession;
132 this.rtInfo = rtInfo;
133 }
134
135 @Nonnull
136 @Override
137 public CacheState initialize() {
138 if (state == null) {
139 synchronized (this) {
140 if (state == null) {
141 final boolean enabled = getProperty(CACHE_ENABLED_PROPERTY_NAME, true);
142
143 if (!rtInfo.isMavenVersion("[3.9.0,)")) {
144 LOGGER.warn(
145 "Cache requires Maven >= 3.9, but version is {}. Disabling cache.",
146 rtInfo.getMavenVersion());
147 state = CacheState.DISABLED;
148 } else if (!enabled) {
149 LOGGER.info("Cache disabled by command line flag, project will be built fully and not cached");
150 state = CacheState.DISABLED;
151 } else {
152 Path configPath;
153
154 String configPathText = getProperty(CONFIG_PATH_PROPERTY_NAME, null);
155 if (StringUtils.isNotBlank(configPathText)) {
156 configPath = Paths.get(configPathText);
157 } else {
158 final MavenSession session = providerSession.get();
159 configPath =
160 getMultimoduleRoot(session).resolve(".mvn").resolve("maven-build-cache-config.xml");
161 }
162
163 if (!Files.exists(configPath)) {
164 LOGGER.info(
165 "Cache configuration is not available at configured path {}, "
166 + "cache is enabled with defaults",
167 configPath);
168 cacheConfig = new CacheConfig();
169 } else {
170 try {
171 LOGGER.info("Loading cache configuration from {}", configPath);
172 cacheConfig = xmlService.loadCacheConfig(configPath.toFile());
173 } catch (Exception e) {
174 throw new IllegalArgumentException(
175 "Cannot initialize cache because xml config is not valid or not available", e);
176 }
177 }
178 fillWithDefaults(cacheConfig);
179
180
181
182 boolean cacheEnabled = getProperty(
183 CACHE_ENABLED_PROPERTY_NAME, getConfiguration().isEnabled());
184
185 if (!cacheEnabled) {
186 state = CacheState.DISABLED;
187 } else {
188 String hashAlgorithm = null;
189 try {
190 hashAlgorithm = getConfiguration().getHashAlgorithm();
191 hashFactory = HashFactory.of(hashAlgorithm);
192 LOGGER.info("Using {} hash algorithm for cache", hashAlgorithm);
193 } catch (Exception e) {
194 throw new IllegalArgumentException(
195 "Unsupported hashing algorithm: " + hashAlgorithm, e);
196 }
197
198 excludePatterns = compileExcludePatterns();
199 state = CacheState.INITIALIZED;
200 }
201 }
202 }
203 }
204 }
205 return state;
206 }
207
208 private void fillWithDefaults(CacheConfig cacheConfig) {
209 if (cacheConfig.getConfiguration() == null) {
210 cacheConfig.setConfiguration(new Configuration());
211 }
212 Configuration configuration = cacheConfig.getConfiguration();
213 if (configuration.getLocal() == null) {
214 configuration.setLocal(new Local());
215 }
216 if (configuration.getRemote() == null) {
217 configuration.setRemote(new Remote());
218 }
219 if (cacheConfig.getInput() == null) {
220 cacheConfig.setInput(new Input());
221 }
222 Input input = cacheConfig.getInput();
223 if (input.getGlobal() == null) {
224 input.setGlobal(new PathSet());
225 }
226 }
227
228 @Nonnull
229 @Override
230 public List<TrackedProperty> getTrackedProperties(MojoExecution mojoExecution) {
231 checkInitializedState();
232 final GoalReconciliation reconciliationConfig = findReconciliationConfig(mojoExecution);
233 if (reconciliationConfig != null) {
234 return reconciliationConfig.getReconciles();
235 } else {
236 return Collections.emptyList();
237 }
238 }
239
240 @Override
241 public boolean isLogAllProperties(MojoExecution mojoExecution) {
242 final GoalReconciliation reconciliationConfig = findReconciliationConfig(mojoExecution);
243 if (reconciliationConfig != null && reconciliationConfig.isLogAll()) {
244 return true;
245 }
246 return cacheConfig.getExecutionControl() != null
247 && cacheConfig.getExecutionControl().getReconcile() != null
248 && cacheConfig.getExecutionControl().getReconcile().isLogAllProperties();
249 }
250
251 private GoalReconciliation findReconciliationConfig(MojoExecution mojoExecution) {
252 if (cacheConfig.getExecutionControl() == null) {
253 return null;
254 }
255
256 final ExecutionControl executionControl = cacheConfig.getExecutionControl();
257 if (executionControl.getReconcile() == null) {
258 return null;
259 }
260
261 final List<GoalReconciliation> reconciliation =
262 executionControl.getReconcile().getPlugins();
263
264 for (GoalReconciliation goalReconciliationConfig : reconciliation) {
265 final String goal = mojoExecution.getGoal();
266
267 if (isPluginMatch(mojoExecution.getPlugin(), goalReconciliationConfig)
268 && Strings.CS.equals(goal, goalReconciliationConfig.getGoal())) {
269 return goalReconciliationConfig;
270 }
271 }
272 return null;
273 }
274
275 @Nonnull
276 @Override
277 public List<PropertyName> getLoggedProperties(MojoExecution mojoExecution) {
278 checkInitializedState();
279
280 final GoalReconciliation reconciliationConfig = findReconciliationConfig(mojoExecution);
281 if (reconciliationConfig != null) {
282 return reconciliationConfig.getLogs();
283 } else {
284 return Collections.emptyList();
285 }
286 }
287
288 @Nonnull
289 @Override
290 public List<PropertyName> getNologProperties(MojoExecution mojoExecution) {
291 checkInitializedState();
292 final GoalReconciliation reconciliationConfig = findReconciliationConfig(mojoExecution);
293 if (reconciliationConfig != null) {
294 return reconciliationConfig.getNologs();
295 } else {
296 return Collections.emptyList();
297 }
298 }
299
300 @Nonnull
301 @Override
302 public List<String> getEffectivePomExcludeProperties(Plugin plugin) {
303 checkInitializedState();
304 final PluginConfigurationScan pluginConfig = findPluginScanConfig(plugin);
305
306 if (pluginConfig != null && pluginConfig.getEffectivePom() != null) {
307 return pluginConfig.getEffectivePom().getExcludeProperties();
308 }
309 return Collections.emptyList();
310 }
311
312 @Override
313 public boolean isPluginDependenciesExcluded(Plugin plugin) {
314 checkInitializedState();
315 final PluginConfigurationScan pluginConfig = findPluginScanConfig(plugin);
316
317 if (pluginConfig != null) {
318 return pluginConfig.isExcludeDependencies();
319 }
320 return false;
321 }
322
323 @Nullable
324 @Override
325 public MultiModule getMultiModule() {
326 checkInitializedState();
327 return cacheConfig.getConfiguration().getMultiModule();
328 }
329
330 private PluginConfigurationScan findPluginScanConfig(Plugin plugin) {
331 if (cacheConfig.getInput() == null) {
332 return null;
333 }
334
335 final List<PluginConfigurationScan> pluginConfigs =
336 cacheConfig.getInput().getPlugins();
337 for (PluginConfigurationScan pluginConfig : pluginConfigs) {
338 if (isPluginMatch(plugin, pluginConfig)) {
339 return pluginConfig;
340 }
341 }
342 return null;
343 }
344
345 private boolean isPluginMatch(Plugin plugin, CoordinatesBase pluginConfig) {
346 return Strings.CS.equals(pluginConfig.getArtifactId(), plugin.getArtifactId())
347 && (pluginConfig.getGroupId() == null
348 || Strings.CS.equals(pluginConfig.getGroupId(), plugin.getGroupId()));
349 }
350
351 @Nonnull
352 @Override
353 public PluginScanConfig getPluginDirScanConfig(Plugin plugin) {
354 checkInitializedState();
355 final PluginConfigurationScan pluginConfig = findPluginScanConfig(plugin);
356 if (pluginConfig == null || pluginConfig.getDirScan() == null) {
357 return new DefaultPluginScanConfig();
358 }
359
360 return new PluginScanConfigImpl(pluginConfig.getDirScan());
361 }
362
363 @Nonnull
364 @Override
365 public PluginScanConfig getExecutionDirScanConfig(Plugin plugin, PluginExecution exec) {
366 checkInitializedState();
367 final PluginConfigurationScan pluginScanConfig = findPluginScanConfig(plugin);
368
369 if (pluginScanConfig != null) {
370 final ExecutionConfigurationScan executionScanConfig =
371 findExecutionScanConfig(exec, pluginScanConfig.getExecutions());
372 if (executionScanConfig != null && executionScanConfig.getDirScan() != null) {
373 return new PluginScanConfigImpl(executionScanConfig.getDirScan());
374 }
375 }
376
377 return new DefaultPluginScanConfig();
378 }
379
380 private ExecutionConfigurationScan findExecutionScanConfig(
381 PluginExecution execution, List<ExecutionConfigurationScan> scanConfigs) {
382 for (ExecutionConfigurationScan executionScanConfig : scanConfigs) {
383 if (executionScanConfig.getExecIds().contains(execution.getId())) {
384 return executionScanConfig;
385 }
386 }
387 return null;
388 }
389
390 @Override
391 public String isProcessPlugins() {
392 checkInitializedState();
393 return TRUE.toString();
394 }
395
396 @Override
397 public String getDefaultGlob() {
398 checkInitializedState();
399 return StringUtils.trim(cacheConfig.getInput().getGlobal().getGlob());
400 }
401
402 @Nonnull
403 @Override
404 public List<Include> getGlobalIncludePaths() {
405 checkInitializedState();
406 return cacheConfig.getInput().getGlobal().getIncludes();
407 }
408
409 @Nonnull
410 @Override
411 public List<Exclude> getGlobalExcludePaths() {
412 checkInitializedState();
413 return cacheConfig.getInput().getGlobal().getExcludes();
414 }
415
416 @Nonnull
417 @Override
418 public HashFactory getHashFactory() {
419 checkInitializedState();
420 return hashFactory;
421 }
422
423 @Override
424 public boolean canIgnore(MojoExecution mojoExecution) {
425 checkInitializedState();
426 if (cacheConfig.getExecutionControl() == null
427 || cacheConfig.getExecutionControl().getIgnoreMissing() == null) {
428 return false;
429 }
430
431 return executionMatches(mojoExecution, cacheConfig.getExecutionControl().getIgnoreMissing());
432 }
433
434 @Override
435 public boolean isForcedExecution(MojoExecution execution) {
436 checkInitializedState();
437 if (cacheConfig.getExecutionControl() == null
438 || cacheConfig.getExecutionControl().getRunAlways() == null) {
439 return false;
440 }
441
442 return executionMatches(execution, cacheConfig.getExecutionControl().getRunAlways());
443 }
444
445 private boolean executionMatches(MojoExecution execution, Executables executablesType) {
446 final List<PluginSet> pluginConfigs = executablesType.getPlugins();
447 for (PluginSet pluginConfig : pluginConfigs) {
448 if (isPluginMatch(execution.getPlugin(), pluginConfig)) {
449 return true;
450 }
451 }
452
453 final List<ExecutionIdsList> executionIds = executablesType.getExecutions();
454 for (ExecutionIdsList executionConfig : executionIds) {
455 if (isPluginMatch(execution.getPlugin(), executionConfig)
456 && executionConfig.getExecIds().contains(execution.getExecutionId())) {
457 return true;
458 }
459 }
460
461 final List<GoalsList> pluginsGoalsList = executablesType.getGoalsLists();
462 for (GoalsList pluginGoals : pluginsGoalsList) {
463 if (isPluginMatch(execution.getPlugin(), pluginGoals)
464 && pluginGoals.getGoals().contains(execution.getGoal())) {
465 return true;
466 }
467 }
468
469 return false;
470 }
471
472 @Override
473 public boolean isEnabled() {
474 return state == CacheState.INITIALIZED;
475 }
476
477 @Override
478 public boolean isRemoteCacheEnabled() {
479 checkInitializedState();
480 return getUrl() != null
481 && getProperty(REMOTE_ENABLED_PROPERTY_NAME, getRemote().isEnabled());
482 }
483
484 @Override
485 public boolean isSaveToRemote() {
486 return isRemoteCacheEnabled()
487 && getProperty(SAVE_TO_REMOTE_PROPERTY_NAME, getRemote().isSaveToRemote());
488 }
489
490 @Override
491 public boolean isSaveToRemoteFinal() {
492 return isSaveToRemote() && getProperty(SAVE_NON_OVERRIDEABLE_NAME, false);
493 }
494
495 @Override
496 public boolean isSkipCache() {
497 return getProperty(CACHE_SKIP, false);
498 }
499
500 @Override
501 public boolean isFailFast() {
502 return getProperty(FAIL_FAST_PROPERTY_NAME, false);
503 }
504
505 @Override
506 public boolean isBaselineDiffEnabled() {
507 return getProperty(BASELINE_BUILD_URL_PROPERTY_NAME, null) != null;
508 }
509
510 @Override
511 public String getBaselineCacheUrl() {
512 return getProperty(BASELINE_BUILD_URL_PROPERTY_NAME, null);
513 }
514
515 @Override
516 public boolean isLazyRestore() {
517 return getProperty(LAZY_RESTORE_PROPERTY_NAME, false);
518 }
519
520 @Override
521 public boolean isRestoreGeneratedSources() {
522 return getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, true);
523 }
524
525 @Override
526 public boolean isRestoreOnDiskArtifacts() {
527 return getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, true);
528 }
529
530 @Override
531 public String getAlwaysRunPlugins() {
532 return getProperty(ALWAYS_RUN_PLUGINS, null);
533 }
534
535 @Override
536 public boolean isSkipSave() {
537 return getProperty(SKIP_SAVE, false);
538 }
539
540 @Override
541 public boolean isMandatoryClean() {
542 return getProperty(MANDATORY_CLEAN, getConfiguration().isMandatoryClean());
543 }
544
545 @Override
546 public boolean isCacheCompile() {
547 return getProperty(CACHE_COMPILE, true);
548 }
549
550 @Override
551 public String getId() {
552 checkInitializedState();
553 return getProperty(REMOTE_SERVER_ID_PROPERTY_NAME, getRemote().getId());
554 }
555
556 @Override
557 public String getUrl() {
558 checkInitializedState();
559 return getProperty(REMOTE_URL_PROPERTY_NAME, getRemote().getUrl());
560 }
561
562 @Override
563 public String getTransport() {
564 checkInitializedState();
565 return getRemote().getTransport();
566 }
567
568 @Override
569 public int getMaxLocalBuildsCached() {
570 checkInitializedState();
571 return getLocal().getMaxBuildsCached();
572 }
573
574 @Override
575 public String getLocalRepositoryLocation() {
576 checkInitializedState();
577 return getProperty(CACHE_LOCATION_PROPERTY_NAME, getLocal().getLocation());
578 }
579
580 @Override
581 public List<DirName> getAttachedOutputs() {
582 checkInitializedState();
583 final AttachedOutputs attachedOutputs = getConfiguration().getAttachedOutputs();
584 return attachedOutputs == null ? Collections.emptyList() : attachedOutputs.getDirNames();
585 }
586
587 @Override
588 public boolean isPreservePermissions() {
589 checkInitializedState();
590 final AttachedOutputs attachedOutputs = getConfiguration().getAttachedOutputs();
591 return attachedOutputs == null || attachedOutputs.isPreservePermissions();
592 }
593
594 @Override
595 public boolean adjustMetaInfVersion() {
596 if (isEnabled()) {
597 return Optional.ofNullable(getConfiguration().getProjectVersioning())
598 .map(ProjectVersioning::isAdjustMetaInf)
599 .orElse(false);
600 } else {
601 return false;
602 }
603 }
604
605 @Override
606 public boolean calculateProjectVersionChecksum() {
607 if (isEnabled()) {
608 return Optional.ofNullable(getConfiguration().getProjectVersioning())
609 .map(ProjectVersioning::isCalculateProjectVersionChecksum)
610 .orElse(false);
611 } else {
612 return false;
613 }
614 }
615
616 @Nonnull
617 @Override
618 public List<Pattern> getExcludePatterns() {
619 checkInitializedState();
620 return excludePatterns;
621 }
622
623 private List<Pattern> compileExcludePatterns() {
624 if (cacheConfig.getOutput() != null && cacheConfig.getOutput().getExclude() != null) {
625 List<Pattern> patterns = new ArrayList<>();
626 for (String pattern : cacheConfig.getOutput().getExclude().getPatterns()) {
627 patterns.add(Pattern.compile(pattern));
628 }
629 return patterns;
630 }
631 return Collections.emptyList();
632 }
633
634 private Remote getRemote() {
635 return getConfiguration().getRemote();
636 }
637
638 private Local getLocal() {
639 return getConfiguration().getLocal();
640 }
641
642 private Configuration getConfiguration() {
643 return cacheConfig.getConfiguration();
644 }
645
646 private void checkInitializedState() {
647 if (state != CacheState.INITIALIZED) {
648 throw new IllegalStateException("Cache is not initialized. Actual state: " + state);
649 }
650 }
651
652 private String getProperty(String key, String defaultValue) {
653 MavenSession session = providerSession.get();
654 String value = session.getUserProperties().getProperty(key);
655 if (value == null) {
656 value = session.getSystemProperties().getProperty(key);
657 if (value == null) {
658 value = defaultValue;
659 }
660 }
661 return value;
662 }
663
664 private boolean getProperty(String key, boolean defaultValue) {
665 MavenSession session = providerSession.get();
666 String value = session.getUserProperties().getProperty(key);
667 if (value == null) {
668 value = session.getSystemProperties().getProperty(key);
669 if (value == null) {
670 return defaultValue;
671 }
672 }
673 return Boolean.parseBoolean(value);
674 }
675 }