[MINVOKER-174] add a property to configure build order (#6)

Signed-off-by: olivier lamy <olamy@apache.org>
diff --git a/src/it/project-setup/pom.xml b/src/it/project-setup/pom.xml
index b68981b..0922bba 100644
--- a/src/it/project-setup/pom.xml
+++ b/src/it/project-setup/pom.xml
@@ -43,6 +43,7 @@
           <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
           <setupIncludes>
             <setupInclude>project3</setupInclude>
+            <setupInclude>project6</setupInclude>
           </setupIncludes>
           <pomIncludes>
             <pomInclude>*/pom.xml</pomInclude>
diff --git a/src/it/project-setup/src/it/project3/invoker.properties b/src/it/project-setup/src/it/project3/invoker.properties
new file mode 100644
index 0000000..5cf17e6
--- /dev/null
+++ b/src/it/project-setup/src/it/project3/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.ordinal=1
diff --git a/src/it/project-setup/src/it/project6/invoker.properties b/src/it/project-setup/src/it/project6/invoker.properties
new file mode 100644
index 0000000..081215b
--- /dev/null
+++ b/src/it/project-setup/src/it/project6/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.ordinal=2
diff --git a/src/it/project-setup/src/it/project6/pom.xml b/src/it/project-setup/src/it/project6/pom.xml
new file mode 100644
index 0000000..4be5874
--- /dev/null
+++ b/src/it/project-setup/src/it/project6/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>test</groupId>
+  <artifactId>project6</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+</project>
diff --git a/src/it/project-setup/verify.bsh b/src/it/project-setup/verify.bsh
index ff4a005..59a985d 100644
--- a/src/it/project-setup/verify.bsh
+++ b/src/it/project-setup/verify.bsh
@@ -51,6 +51,14 @@
         return false;
     }
 
+    int indexProject3 = log.indexOf( "Building: project3" );
+    int indexProject6 = log.indexOf( "Building: project6" );
+    if(indexProject3 > indexProject6)
+    {
+        System.out.println( "FAILED! project3 should be build before project6" );
+        return false;
+    }
+
     File reportFile = new File( basedir, "target/invoker-reports/BUILD-project1.xml" );
     if ( !reportFile.exists() )
     {
diff --git a/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java b/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java
index 4197c86..da8d390 100644
--- a/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java
+++ b/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java
@@ -93,6 +93,7 @@
 import java.util.Arrays;

 import java.util.Collection;

 import java.util.Collections;

+import java.util.Comparator;

 import java.util.HashMap;

 import java.util.HashSet;

 import java.util.LinkedHashMap;

@@ -566,6 +567,12 @@
      * # Path to an alternate <code>settings.xml</code> to use for Maven invocation with this IT.

      * # Since plugin version 3.0.1

      * invoker.settingsFile = ../

+     *

+     * # An integer value to control run order of projects. sorted in the ascending order of the ordinal.

+     * In other words, the BuildJobs with the slowest numbers will be executed first

+     * # Since plugin version 3.2.1

+     * invoker.ordinal = 3

+     * invoker.ordinal = 1

      * </pre>

      *

      * @since 1.2

@@ -691,7 +698,7 @@
             setupReportsFolder();

         }

 

-        BuildJob[] buildJobs;

+        List<BuildJob> buildJobs;

         if ( pom == null )

         {

             try

@@ -716,10 +723,10 @@
                     + "pom File parameter. Reason: " + e.getMessage(), e );

             }

 

-            buildJobs = new BuildJob[] { new BuildJob( pom.getName(), BuildJob.Type.NORMAL ) };

+            buildJobs = Collections.singletonList( new BuildJob( pom.getName(), BuildJob.Type.NORMAL ) );

         }

 

-        if ( ( buildJobs == null ) || ( buildJobs.length < 1 ) )

+        if ( buildJobs.isEmpty() )

         {

             doFailIfNoProjects();

 

@@ -754,7 +761,7 @@
         }

 

         // First run setup jobs.

-        BuildJob[] setupBuildJobs = null;

+        List<BuildJob> setupBuildJobs = null;

         try

         {

             setupBuildJobs = getSetupBuildJobsFromFolders();

@@ -765,20 +772,20 @@
             // TODO: Check shouldn't we fail in case of problems?

         }

 

-        if ( ( setupBuildJobs != null ) && ( setupBuildJobs.length > 0 ) )

+        if ( !setupBuildJobs.isEmpty() )

         {

             // Run setup jobs in single thread

             // mode.

             //

             // Some Idea about ordering?

-            getLog().info( "Running " + setupBuildJobs.length + " setup job"

-                + ( ( setupBuildJobs.length < 2 ) ? "" : "s" ) + ":" );

+            getLog().info( "Running " + setupBuildJobs.size() + " setup job"

+                + ( ( setupBuildJobs.size() < 2 ) ? "" : "s" ) + ":" );

             runBuilds( projectsDir, setupBuildJobs, 1 );

             getLog().info( "Setup done." );

         }

 

         // Afterwards run all other jobs.

-        BuildJob[] nonSetupBuildJobs = getNonSetupJobs( buildJobs );

+        List<BuildJob> nonSetupBuildJobs = getNonSetupJobs( buildJobs );

         // We will run the non setup jobs with the configured

         // parallelThreads number.

         runBuilds( projectsDir, nonSetupBuildJobs, parallelThreads );

@@ -816,7 +823,7 @@
         }

     }

 

-    private BuildJob[] getNonSetupJobs( BuildJob[] buildJobs )

+    private List<BuildJob> getNonSetupJobs( List<BuildJob> buildJobs )

     {

         List<BuildJob> result = new LinkedList<>();

         for ( BuildJob buildJob : buildJobs )

@@ -826,8 +833,7 @@
                 result.add( buildJob );

             }

         }

-        BuildJob[] buildNonSetupJobs = result.toArray( new BuildJob[result.size()] );

-        return buildNonSetupJobs;

+        return result;

     }

 

     private void handleScriptRunnerWithScriptClassPath()

@@ -858,7 +864,7 @@
         scriptRunner.setClassPath( scriptClassPath );

     }

 

-    private void writeSummaryFile( BuildJob[] buildJobs )

+    private void writeSummaryFile( List<BuildJob> buildJobs )

         throws MojoExecutionException

     {

 

@@ -1216,7 +1222,7 @@
      * @param buildJobs The build jobs to run must not be <code>null</code> nor contain <code>null</code> elements.

      * @throws org.apache.maven.plugin.MojoExecutionException If any build could not be launched.

      */

-    private void runBuilds( final File projectsDir, BuildJob[] buildJobs, int runWithParallelThreads )

+    private void runBuilds( final File projectsDir, List<BuildJob> buildJobs, int runWithParallelThreads )

         throws MojoExecutionException

     {

         if ( !localRepositoryPath.exists() )

@@ -2381,42 +2387,53 @@
      * @throws IOException

      * @see {@link #setupIncludes}

      */

-    private BuildJob[] getSetupBuildJobsFromFolders()

-        throws IOException

+    private List<BuildJob> getSetupBuildJobsFromFolders()

+        throws IOException, MojoExecutionException

     {

         List<String> excludes = calculateExcludes();

 

-        BuildJob[] setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP );

+        List<BuildJob> setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP );

         if ( getLog().isDebugEnabled() )

         {

-            getLog().debug( "Setup projects: " + Arrays.asList( setupPoms ) );

+            getLog().debug( "Setup projects: " + setupPoms );

         }

 

         return setupPoms;

     }

 

+    private static class OrdinalComparator implements Comparator

+    {

+        private static final OrdinalComparator INSTANCE = new OrdinalComparator();

+

+        @Override

+        public int compare( Object o1, Object o2 )

+        {

+            return Integer.compare( ( ( BuildJob ) o1 ).getOrdinal(), ( ( BuildJob ) o2 ).getOrdinal() );

+        }

+    }

+

     /**

      * Gets the build jobs that should be processed. Note that the order of the returned build jobs is significant.

      *

      * @return The build jobs to process, may be empty but never <code>null</code>.

      * @throws java.io.IOException If the projects directory could not be scanned.

      */

-    BuildJob[] getBuildJobs()

-        throws IOException

+    List<BuildJob> getBuildJobs()

+        throws IOException, MojoExecutionException

     {

-        BuildJob[] buildJobs;

+        List<BuildJob> buildJobs;

 

         if ( invokerTest == null )

         {

             List<String> excludes = calculateExcludes();

 

-            BuildJob[] setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP );

+            List<BuildJob> setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP );

             if ( getLog().isDebugEnabled() )

             {

                 getLog().debug( "Setup projects: " + Arrays.asList( setupPoms ) );

             }

 

-            BuildJob[] normalPoms = scanProjectsDirectory( pomIncludes, excludes, BuildJob.Type.NORMAL );

+            List<BuildJob> normalPoms = scanProjectsDirectory( pomIncludes, excludes, BuildJob.Type.NORMAL );

 

             Map<String, BuildJob> uniquePoms = new LinkedHashMap<>();

             for ( BuildJob setupPom : setupPoms )

@@ -2431,7 +2448,7 @@
                 }

             }

 

-            buildJobs = uniquePoms.values().toArray( new BuildJob[uniquePoms.size()] );

+            buildJobs = new ArrayList<>( uniquePoms.values() );

         }

         else

         {

@@ -2474,12 +2491,12 @@
      * @return The build jobs matching the patterns, never <code>null</code>.

      * @throws java.io.IOException If the project directory could not be scanned.

      */

-    private BuildJob[] scanProjectsDirectory( List<String> includes, List<String> excludes, String type )

-        throws IOException

+    private List<BuildJob> scanProjectsDirectory( List<String> includes, List<String> excludes, String type )

+        throws IOException, MojoExecutionException

     {

         if ( !projectsDirectory.isDirectory() )

         {

-            return new BuildJob[0];

+            return Collections.emptyList();

         }

 

         DirectoryScanner scanner = new DirectoryScanner();

@@ -2516,7 +2533,19 @@
             }

         }

 

-        return matches.values().toArray( new BuildJob[matches.size()] );

+        List<BuildJob> projects = new ArrayList<>( matches.size() );

+

+        // setup ordinal values to have an order here

+        for ( BuildJob buildJob : matches.values() )

+        {

+            InvokerProperties invokerProperties =

+                    getInvokerProperties( new File( projectsDirectory, buildJob.getProject() ).getParentFile(),

+                            null );

+            buildJob.setOrdinal( invokerProperties.getOrdinal() );

+            projects.add( buildJob );

+        }

+        Collections.sort( projects, OrdinalComparator.INSTANCE );

+        return projects;

     }

 

     /**

@@ -2528,7 +2557,7 @@
      *            contain <code>null</code> elements.

      * @throws java.io.IOException If any path could not be relativized.

      */

-    private void relativizeProjectPaths( BuildJob[] buildJobs )

+    private void relativizeProjectPaths( List<BuildJob> buildJobs )

         throws IOException

     {

         String projectsDirPath = projectsDirectory.getCanonicalPath();

@@ -2778,47 +2807,6 @@
             props = new Properties();

         }

         

-//        Path projectsSourceFolder = this.projectsDirectory.toPath();

-//        Path projectsTargetFolder;

-//        if ( cloneProjectsTo != null )

-//        {

-//            projectsTargetFolder = cloneProjectsTo.toPath();

-//        }

-//        else

-//        {

-//            projectsTargetFolder = projectsSourceFolder;

-//        }

-//        

-//        Path projectDir = projectsTargetFolder.relativize( projectDirectory.toPath() );

-//        

-//        for ( int i = 0; i < projectDir.getNameCount(); i++ )

-//        {

-//            Path subInvokerProperties;

-//            if ( i == 0 )

-//            {

-//                subInvokerProperties = projectsSourceFolder.resolve( invokerPropertiesFile );

-//            }

-//            else

-//            {

-//                subInvokerProperties =

-//                    projectsSourceFolder.resolve( projectDir.subpath( 0, i ) ).resolve( invokerPropertiesFile );

-//            }

-//            

-//            getLog().debug( "Looking for " + subInvokerProperties );

-//                

-//            if ( Files.isRegularFile( subInvokerProperties ) )

-//            {

-//                try ( InputStream in = new FileInputStream( subInvokerProperties.toFile() ) )

-//                {

-//                    props.load( in );

-//                }

-//                catch ( IOException e )

-//                {

-//                    throw new MojoExecutionException( "Failed to read invoker properties: " + subInvokerProperties );

-//                }

-//            }

-//        }

-        

         File propertiesFile = new File( projectDirectory, invokerPropertiesFile );

         if ( propertiesFile.isFile() )

         {

diff --git a/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java b/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java
index e8ba20e..a3b8f3c 100644
--- a/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java
+++ b/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java
@@ -54,7 +54,8 @@
         SYSTEM_PROPERTIES_FILE( "invoker.systemPropertiesFile" ),

         DEBUG( "invoker.debug" ),

         SETTINGS_FILE ( "invoker.settingsFile" ),

-        TIMEOUT_IN_SECONDS ( "invoker.timeoutInSeconds" );

+        TIMEOUT_IN_SECONDS ( "invoker.timeoutInSeconds" ),

+        ORDINAL ( "invoker.ordinal" );

 

         private final String key;

 

@@ -137,6 +138,15 @@
     }

 

     /**

+     * Get the corresponding ordinal value

+     * @return The ordinal value

+     */

+    public int getOrdinal()

+    {

+        return Integer.parseInt( this.properties.getProperty( "invoker.ordinal", "0" ) );

+    }

+

+    /**

      * Gets the specification of JRE versions on which this build job should be run.

      *

      * @return The specification of JRE versions or an empty string if not set.

@@ -302,14 +312,14 @@
         String goals = get( InvocationProperty.GOALS, index );

         if ( goals != null )

         {

-            request.setGoals( new ArrayList<String>( Arrays.asList( StringUtils.split( goals, ", \t\n\r\f" ) ) ) );

+            request.setGoals( new ArrayList<>( Arrays.asList( StringUtils.split( goals, ", \t\n\r\f" ) ) ) );

         }

 

         String profiles = get( InvocationProperty.PROFILES, index );

         if ( profiles != null )

         {

             // CHECKSTYLE_OFF: LineLength

-            request.setProfiles( new ArrayList<String>( Arrays.asList( StringUtils.split( profiles,

+            request.setProfiles( new ArrayList<>( Arrays.asList( StringUtils.split( profiles,

                                                                                           ", \t\n\r\f" ) ) ) );

             // CHECKSTYLE_ON: LineLength

         }

diff --git a/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java b/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java
index 7c11479..43ed9bb 100644
--- a/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java
+++ b/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java
@@ -22,7 +22,6 @@
 import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;

 

 import java.util.ArrayList;

-import java.util.Arrays;

 import java.util.List;

 

 import org.apache.maven.plugin.MojoFailureException;

@@ -60,9 +59,9 @@
      *

      * @param buildJobs The build jobs to set, must not be <code>null</code>.

      */

-    InvokerSession( BuildJob[] buildJobs )

+    InvokerSession( List<BuildJob> buildJobs )

     {

-        this.buildJobs = new ArrayList<>( Arrays.asList( buildJobs ) );

+        this.buildJobs = new ArrayList<>( buildJobs );

     }

 

     /**

diff --git a/src/main/mdo/invocation.mdo b/src/main/mdo/invocation.mdo
index f402423..a019b2d 100644
--- a/src/main/mdo/invocation.mdo
+++ b/src/main/mdo/invocation.mdo
@@ -96,6 +96,13 @@
           <type>String</type>
           <description>The type of the build job.</description>
         </field>
+        <field xml.attribute="true">
+          <name>ordinal</name>
+          <version>1.0.0</version>
+          <required>false</required>
+          <type>int</type>
+          <description>BuildJobs will be sorted in the ascending order of the ordinal. In other words, the BuildJobs with the slowest numbers will be executed first</description>
+        </field>
       </fields>
       <codeSegments>
         <codeSegment>
@@ -209,4 +216,4 @@
       </codeSegments>
     </class>
   </classes>
-</model>
\ No newline at end of file
+</model>
diff --git a/src/test/java/org/apache/maven/plugins/invoker/InvokerMojoTest.java b/src/test/java/org/apache/maven/plugins/invoker/InvokerMojoTest.java
index b58234c..5d51c40 100644
--- a/src/test/java/org/apache/maven/plugins/invoker/InvokerMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/invoker/InvokerMojoTest.java
@@ -25,6 +25,8 @@
 

 import org.apache.maven.plugin.testing.AbstractMojoTestCase;

 import org.apache.maven.plugins.invoker.model.BuildJob;

+import org.apache.maven.project.MavenProject;

+import org.apache.maven.settings.Settings;

 

 /**

  * @author Olivier Lamy

@@ -34,6 +36,13 @@
     extends AbstractMojoTestCase

 {

 

+    private MavenProject getMavenProject()

+    {

+        MavenProject mavenProject = new MavenProject();

+        mavenProject.setFile(new File("target/foo.txt"));

+        return mavenProject;

+    }

+

     public void testSingleInvokerTest()

         throws Exception

     {

@@ -42,9 +51,12 @@
         List<String> goals = invokerMojo.getGoals( new File( dirPath ) );

         assertEquals( 1, goals.size() );

         setVariableValueToObject( invokerMojo, "projectsDirectory", new File( dirPath ) );

+        setVariableValueToObject( invokerMojo, "invokerPropertiesFile", "invoker.properties");

+        setVariableValueToObject( invokerMojo, "project", getMavenProject() );

         setVariableValueToObject( invokerMojo, "invokerTest", "*dummy*" );

-        BuildJob[] poms = invokerMojo.getBuildJobs();

-        assertEquals( 1, poms.length );

+        setVariableValueToObject( invokerMojo, "settings", new Settings() );

+        List<BuildJob> poms = invokerMojo.getBuildJobs();

+        assertEquals( 1, poms.size() );

     }

 

     public void testMultiInvokerTest()

@@ -55,9 +67,12 @@
         List<String> goals = invokerMojo.getGoals( new File( dirPath ) );

         assertEquals( 1, goals.size() );

         setVariableValueToObject( invokerMojo, "projectsDirectory", new File( dirPath ) );

+        setVariableValueToObject( invokerMojo, "invokerPropertiesFile", "invoker.properties");

+        setVariableValueToObject( invokerMojo, "project", getMavenProject() );

         setVariableValueToObject( invokerMojo, "invokerTest", "*dummy*,*terpolatio*" );

-        BuildJob[] poms = invokerMojo.getBuildJobs();

-        assertEquals( 2, poms.length );

+        setVariableValueToObject( invokerMojo, "settings", new Settings() );

+        List<BuildJob> poms = invokerMojo.getBuildJobs();

+        assertEquals( 2, poms.size() );

     }

 

     public void testFullPatternInvokerTest()

@@ -68,9 +83,12 @@
         List<String> goals = invokerMojo.getGoals( new File( dirPath ) );

         assertEquals( 1, goals.size() );

         setVariableValueToObject( invokerMojo, "projectsDirectory", new File( dirPath ) );

+        setVariableValueToObject( invokerMojo, "invokerPropertiesFile", "invoker.properties");

+        setVariableValueToObject( invokerMojo, "project", getMavenProject() );

         setVariableValueToObject( invokerMojo, "invokerTest", "*" );

-        BuildJob[] poms = invokerMojo.getBuildJobs();

-        assertEquals( 4, poms.length );

+        setVariableValueToObject( invokerMojo, "settings", new Settings() );

+        List<BuildJob> poms = invokerMojo.getBuildJobs();

+        assertEquals( 4, poms.size() );

     }

 

     public void testAlreadyCloned()