Repository: vackosar/gitflow-incremental-builder
Pull request #46 (2018-07-15 15:47:37)

Fix issue #45: Introduce gib.forceBuildModules


Patch:
diff --git a/README.md b/README.md
index ffe6d2d..cae0d65 100644
--- a/README.md
+++ b/README.md
@@ -219,6 +219,7 @@ Maven pom properties configuration with default values is below:
 	<gib.skipTestsForNotImpactedModules>false</gib.skipTestsForNotImpactedModules>
 	<gib.argsForNotImpactedModules>false</gib.argsForNotImpactedModules>
 	<gib.buildAll>false</gib.buildAll>
+	<gib.forceBuildModules></gib.forceBuildModules>
 	<gib.compareToMergeBase>true</gib.compareToMergeBase>
 	<gib.fetchBaseBranch>false</gib.fetchBaseBranch>
 	<gib.fetchReferenceBranch>false</gib.fetchReferenceBranch>
@@ -261,6 +262,16 @@ mvn clean install -am -Dgib.argsForNotImpactedModules='-Denforcer.skip -Dcheckst
 
 Can be combined with `gib.skipTestsForNotImpactedModules`.
 
+### gib.forceBuildModules
+
+Defines artifact ids of modules to build forcibly, even if these modules have not been changed and/or do not depend on changed modules. Example:
+```
+mvn clean install -Dgib.forceBuildModules=unchanged-module-1,unchanged-module-2
+```
+Each of these modules is subject to `argsForNotImpactedModules` and `skipTestsForNotImpactedModules`.
+
+This property has no effect in case `buildAll` is enabled.
+
 ## Requirements
 
 - Maven version 3+.
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
index cf58c23..ea6a7e2 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
@@ -2,7 +2,6 @@ package com.vackosar.gitflowincrementalbuild.boundary;
 
 import com.vackosar.gitflowincrementalbuild.control.Property;
 
-import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.maven.MavenExecutionException;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
@@ -39,6 +38,7 @@ public class Configuration {
     public final boolean skipTestsForNotImpactedModules;
     public final Map<String, String> argsForNotImpactedModules;
     public final boolean buildAll;
+    public final List<String> forceBuildModules;
     public final boolean compareToMergeBase;
     public final boolean fetchBaseBranch;
     public final boolean fetchReferenceBranch;
@@ -61,12 +61,14 @@ public class Configuration {
             argsForNotImpactedModules = Collections.unmodifiableMap(
                     parseSpaceDelimitedArgs(Property.argsForNotImpactedModules.getValue()));
             buildAll = Boolean.valueOf(Property.buildAll.getValue());
+            forceBuildModules = Collections.unmodifiableList(
+                    parseDelimited(Property.forceBuildModules.getValue(), ","));
             compareToMergeBase = Boolean.valueOf(Property.compareToMergeBase.getValue());
             fetchReferenceBranch = Boolean.valueOf(Property.fetchReferenceBranch.getValue());
             fetchBaseBranch = Boolean.valueOf(Property.fetchBaseBranch.getValue());
             excludePathRegex = Pattern.compile(Property.excludePathRegex.getValue()).asPredicate();
             failOnMissingGitDir = Boolean.valueOf(Property.failOnMissingGitDir.getValue());
-        } catch (Exception e) {
+        } catch (MavenExecutionException | IOException e) {
             throw new RuntimeException(e);
         }
     }
@@ -81,14 +83,19 @@ public class Configuration {
         }
     }
 
-    private static Map<String, String> parseSpaceDelimitedArgs(String value) {
+    private static List<String> parseDelimited(String value, String delimiter) {
         return value.isEmpty()
-                ? Collections.emptyMap()
-                : Arrays.stream(value.split(" "))
+                ? Collections.emptyList()
+                : Arrays.stream(value.split(delimiter))
                     .map(String::trim)
                     .filter(s -> !s.isEmpty())
-                    .map(Configuration::keyValueStringToEntry)
-                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
+                    .collect(Collectors.toList());
+    }
+
+    private static Map<String, String> parseSpaceDelimitedArgs(String value) {
+        return parseDelimited(value, " ").stream()
+                .map(Configuration::keyValueStringToEntry)
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
     }
 
     private static Map.Entry<String, String> keyValueStringToEntry(String pair) {
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
index 3e2218b..0e682ab 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
@@ -49,6 +49,13 @@ class UnchangedProjectsRemover {
                 mavenSession.setProjects(Collections.singletonList(mavenSession.getCurrentProject()));
                 mavenSession.getGoals().clear();
                 mavenSession.getGoals().add("validate");
+            } else if (!configuration.forceBuildModules.isEmpty()) {
+                Stream<MavenProject> forceBuildModules = mavenSession.getProjects().stream()
+                        .filter(p -> configuration.forceBuildModules.contains(p.getArtifactId()))
+                        .filter(p -> !rebuild.contains(p))
+                        .map(this::applyNotImpactedModuleArgs);
+                mavenSession.setProjects(
+                        Stream.concat(forceBuildModules, rebuild.stream()).collect(Collectors.toList()));
             } else {
                 mavenSession.setProjects(new ArrayList<>(rebuild));
             }
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
index 994feef..11fa35a 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
@@ -11,6 +11,7 @@ public enum Property {
     skipTestsForNotImpactedModules("false"),
     argsForNotImpactedModules(""),
     buildAll("false"),
+    forceBuildModules(""),
     compareToMergeBase("true"),
     fetchBaseBranch("false"),
     fetchReferenceBranch("false"),


Test patch:
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
index 225777f..d122ecf 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
@@ -14,8 +14,10 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Properties;
+import java.util.Set;
 
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
@@ -42,7 +44,10 @@ import com.vackosar.gitflowincrementalbuild.control.Property;
 @RunWith(MockitoJUnitRunner.class)
 public class UnchangedProjectsRemoverTest {
 
-    @Mock
+    private static final String ARTIFACT_ID_1 = "first-module";
+    private static final String ARTIFACT_ID_2 = "changed-module";
+
+    @Mock(name = ARTIFACT_ID_1)
     private MavenProject mavenProjectMock;
 
     @Mock
@@ -61,32 +66,33 @@ public class UnchangedProjectsRemoverTest {
     private ChangedProjects changedProjectsMock;
 
     private final List<MavenProject> projects = new ArrayList<>(); 
+    private final Set<MavenProject> changedProjects = new LinkedHashSet<>();
     
     @Before
-    public void setup() {
+    public void setup() throws GitAPIException, IOException {
         addPropertiesToMock(mavenProjectMock);
         when(mavenProjectMock.getBasedir()).thenReturn(new File("."));
+        when(mavenProjectMock.getArtifactId()).thenReturn(ARTIFACT_ID_1);
         when(mavenSessionMock.getCurrentProject()).thenReturn(mavenProjectMock);
         when(mavenSessionMock.getRequest()).thenReturn(mavenExecutionRequestMock);
         projects.add(mavenProjectMock);
         when(mavenSessionMock.getProjects()).thenReturn(projects);
         when(mavenSessionMock.getProjectDependencyGraph()).thenReturn(projectDependencyGraphMock);
+        when(changedProjectsMock.get()).thenReturn(changedProjects);
     }
     
     @Test
     public void singleChanged() throws GitAPIException, IOException {
-        MavenProject changedProjectMock = Mockito.mock(MavenProject.class);
-        when(changedProjectsMock.get()).thenReturn(Collections.singleton(changedProjectMock));
-        projects.add(changedProjectMock);
+        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
 
         buildUnderTest().act();
 
-        Mockito.verify(mavenSessionMock).setProjects(Collections.singletonList(changedProjectMock));
+        Mockito.verify(mavenSessionMock).setProjects(Collections.singletonList(changedModuleMock));
     }
 
     @Test
     public void singleChanged_alsoMake_argsForNotImpactedModules() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = addChangedModuleMock();
+        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
 
         when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);
 
@@ -107,7 +113,7 @@ public class UnchangedProjectsRemoverTest {
 
     @Test
     public void singleChanged_buildAll_argsForNotImpactedModules() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = addChangedModuleMock();
+        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
 
         ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
                 Property.argsForNotImpactedModules, "enforcer.skip=true argWithNoValue",
@@ -125,15 +131,44 @@ public class UnchangedProjectsRemoverTest {
                 new Properties(), changedModuleMock.getProperties());
     }
 
+    @Test
+    public void singleChanged_forceBuildModules() throws GitAPIException, IOException {
+        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.forceBuildModules, mavenProjectMock.getArtifactId());
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(Arrays.asList(mavenProjectMock, changedModuleMock));
+    }
+
+    @Test
+    public void singleChanged_forceBuildModules_two() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addChangedModuleMock(ARTIFACT_ID_2);
+        MavenProject unchangedProjectMock = addChangedModuleMock("unchanged-module");
+        changedProjects.remove(unchangedProjectMock);
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.forceBuildModules,
+                mavenProjectMock.getArtifactId() + "," + unchangedProjectMock.getArtifactId());
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(mavenProjectMock, unchangedProjectMock, changedProjectMock));
+    }
+
     private Properties addPropertiesToMock(MavenProject mavenProjectMock) {
         Properties properties = new Properties();
         when(mavenProjectMock.getProperties()).thenReturn(properties);
         return properties;
     }
 
-    private MavenProject addChangedModuleMock() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = mock(MavenProject.class);
-        when(changedProjectsMock.get()).thenReturn(Collections.singleton(changedModuleMock));
+    private MavenProject addChangedModuleMock(String moduleArtifactId) {
+        MavenProject changedModuleMock = mock(MavenProject.class, moduleArtifactId);
+        when(changedModuleMock.getArtifactId()).thenReturn(moduleArtifactId);
+        changedProjects.add(changedModuleMock);
         projects.add(changedModuleMock);
         addPropertiesToMock(changedModuleMock);