package com.vackosar.gitflowincrementalbuild.boundary; import com.vackosar.gitflowincrementalbuild.control.Property; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; import java.nio.file.Path; import java.nio.file.Paths; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; public class Configuration { public final boolean disableBranchComparison; public final String referenceBranch; public final boolean fetchReferenceBranch; public final String baseBranch; public final boolean fetchBaseBranch; public final boolean useJschAgentProxy; public final boolean compareToMergeBase; public final boolean uncommited; public final boolean untracked; public final Predicate<String> excludePathRegex; public final Predicate<String> includePathRegex; public final boolean buildAll; public final boolean buildAllIfNoChanges; public final boolean buildDownstream; public final BuildUpstreamMode buildUpstreamMode; public final boolean skipTestsForUpstreamModules; public final Map<String, String> argsForUpstreamModules; public final List<Pattern> forceBuildModules; public final List<String> excludeDownstreamModulesPackagedAs; public final boolean failOnMissingGitDir; public final boolean failOnError; public final Optional<Path> logImpactedTo; private Configuration(MavenSession session) { Properties projectProperties = getProjectProperties(session); checkProperties(projectProperties); // change detection config disableBranchComparison = Boolean.valueOf(Property.disableBranchComparison.getValue(projectProperties)); referenceBranch = Property.referenceBranch.getValue(projectProperties); fetchReferenceBranch = Boolean.valueOf(Property.fetchReferenceBranch.getValue(projectProperties)); baseBranch = Property.baseBranch.getValue(projectProperties); fetchBaseBranch = Boolean.valueOf(Property.fetchBaseBranch.getValue(projectProperties)); useJschAgentProxy = Boolean.valueOf(Property.useJschAgentProxy.getValue(projectProperties)); compareToMergeBase = Boolean.valueOf(Property.compareToMergeBase.getValue(projectProperties)); uncommited = Boolean.valueOf(Property.uncommited.getValue(projectProperties)); untracked = Boolean.valueOf(Property.untracked.getValue(projectProperties)); excludePathRegex = compilePattern(Property.excludePathRegex, projectProperties).asPredicate(); includePathRegex = compilePattern(Property.includePathRegex, projectProperties).asPredicate(); // build config buildAll = Boolean.valueOf(Property.buildAll.getValue(projectProperties)); buildAllIfNoChanges = Boolean.valueOf(Property.buildAllIfNoChanges.getValue(projectProperties)); buildDownstream = isBuildStreamActive(Property.buildDownstream, projectProperties, session, MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM); buildUpstreamMode = parseBuildUpstreamMode(session, projectProperties); skipTestsForUpstreamModules = Boolean.valueOf(Property.skipTestsForUpstreamModules.getValue(projectProperties)); argsForUpstreamModules = parseDelimited(Property.argsForUpstreamModules.getValue(projectProperties), " ") .map(Configuration::keyValueStringToEntry) .collect(collectingAndThen(toLinkedMap(), Collections::unmodifiableMap)); forceBuildModules = parseDelimited(Property.forceBuildModules.getValue(projectProperties), ",") .map(str -> compilePattern(str, Property.forceBuildModules)) .collect(collectingAndThen(toList(), Collections::unmodifiableList)); excludeDownstreamModulesPackagedAs = parseDelimited(Property.excludeDownstreamModulesPackagedAs.getValue(projectProperties), ",") .collect(collectingAndThen(toList(), Collections::unmodifiableList)); // error handling config failOnMissingGitDir = Boolean.valueOf(Property.failOnMissingGitDir.getValue(projectProperties)); failOnError = Boolean.valueOf(Property.failOnError.getValue(projectProperties)); logImpactedTo = Optional.ofNullable(Property.logImpactedTo.getValue(projectProperties)).map(Paths::get); } /** * Returns the value for {@link Property#enabled} without initializing all the other configuration fields to abort quickly without any additional overhead. * * @param session the current session * @return whether or not GIB is enabled or not */ public static boolean isEnabled(MavenSession session) { return Boolean.valueOf(Property.enabled.getValue(getProjectProperties(session))); } /** * Returns the value for {@link Property#help} without initializing all the other configuration fields (help can be requested even if * {@link #isEnabled(MavenSession)} returns {@code false}). * * @param session the current session * @return whether or not to print GIB help */ public static boolean isHelpRequested(MavenSession session) { return Boolean.valueOf(Property.help.getValue(getProjectProperties(session))); } /** * Returns whether or not the given make behaviour is active for the given session. * * @param expectedMakeBehavior one of {@link MavenExecutionRequest#REACTOR_MAKE_DOWNSTREAM} or {@link MavenExecutionRequest#REACTOR_MAKE_UPSTREAM} * @param session the session providing the request * @return whether the given behaviour (or {@link MavenExecutionRequest#REACTOR_MAKE_BOTH}) is active or not */ public static boolean isMakeBehaviourActive(String expectedMakeBehavior, MavenSession session) { String actualMakeBehavior = session.getRequest().getMakeBehavior(); return expectedMakeBehavior.equals(actualMakeBehavior) || MavenExecutionRequest.REACTOR_MAKE_BOTH.equals(actualMakeBehavior); } private static Properties getProjectProperties(MavenSession session) { return session.getTopLevelProject().getProperties(); } private static void checkProperties(Properties projectProperties) { Set<String> availablePropertyNames = Arrays.stream(Property.values()) .flatMap(p -> p.allNames().stream()) .collect(Collectors.toSet()); String invalidPropertyNames = Stream.concat(System.getProperties().keySet().stream(), projectProperties.keySet().stream()) .distinct() .map(k -> (String) k) .filter(k -> k.startsWith(Property.PREFIX) && !availablePropertyNames.contains(k)) .collect(Collectors.joining("\n\t")); if (!invalidPropertyNames.isEmpty()) { throw new IllegalArgumentException( String.format("Invalid GIB properties found:%n\t%s%nAllowed properties:%n%s", invalidPropertyNames, Property.exemplifyAll())); } } private static BuildUpstreamMode parseBuildUpstreamMode(MavenSession session, Properties projectProperties) { if (!isBuildStreamActive(Property.buildUpstream, projectProperties, session, MavenExecutionRequest.REACTOR_MAKE_UPSTREAM)) { return BuildUpstreamMode.NONE; } try { String propertyValue = Optional.ofNullable(Property.buildUpstreamMode.getValue(projectProperties)).orElse(""); return BuildUpstreamMode.valueOf(propertyValue.toUpperCase()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("GIB property " + Property.buildUpstreamMode.fullOrShortName() + " defines an invalid mode", e); } } private static boolean isBuildStreamActive(Property property, Properties projectProperties, MavenSession session, String expectedMakeBehavior) { switch (property.getValue(projectProperties)) { case "derived": return isMakeBehaviourActive(expectedMakeBehavior, session); case "always": case "true": return true; case "never": case "false": return false; default: throw new IllegalArgumentException( "GIB property " + property.fullOrShortName() + " defines an invalid value: " + property.getValue(projectProperties)); } } private static Stream<String> parseDelimited(String value, String delimiter) { return value.isEmpty() ? Stream.empty() : Arrays.stream(value.split(delimiter)) .map(String::trim) .filter(s -> !s.isEmpty()); } private static Map.Entry<String, String> keyValueStringToEntry(String pair) { int indexOfDelim = pair.indexOf('='); return indexOfDelim > 0 ? new AbstractMap.SimpleEntry<>(pair.substring(0, indexOfDelim), pair.substring(indexOfDelim + 1)) : new AbstractMap.SimpleEntry<>(pair, ""); } private static Collector<Entry<String, String>, ?, LinkedHashMap<String, String>> toLinkedMap() { return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new); } private static Pattern compilePattern(String patternString, Property property) { try { return Pattern.compile(patternString); } catch (PatternSyntaxException e) { throw new IllegalArgumentException("GIB property " + property.fullOrShortName() + " defines an invalid pattern string", e); } } private static Pattern compilePattern(Property property, Properties projectProperties) { return compilePattern(property.getValue(projectProperties), property); } public static enum BuildUpstreamMode { NONE, CHANGED, IMPACTED; } @Singleton @Named public static class Provider implements javax.inject.Provider<Configuration> { private final MavenSession mavenSession; private Configuration configuration; @Inject public Provider(MavenSession mavenSession) { this.mavenSession = mavenSession; } /** * Returns a {@link Configuration} instance which is constructed when first called. Subsequent calls will return the same instance. * * @return a {@link Configuration} instance */ @Override public Configuration get() { if (configuration == null) { configuration = new Configuration(mavenSession); } return configuration; } } }