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