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