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