maven-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rfscho...@apache.org
Subject svn commit: r1795243 [2/4] - in /maven/plugins/trunk/maven-invoker-plugin/src: it/local-repo-url/src/it/project/ main/java/org/apache/maven/plugin/ main/java/org/apache/maven/plugins/ main/java/org/apache/maven/plugins/invoker/ main/mdo/ test/java/org/...
Date Mon, 15 May 2017 21:10:28 GMT
Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java
URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java?rev=1795243&view=auto
==============================================================================
--- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java (added)
+++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/AbstractInvokerMojo.java Mon May 15 21:10:27 2017
@@ -0,0 +1,2553 @@
+package org.apache.maven.plugins.invoker;
+
+/*
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Profile;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.invoker.model.BuildJob;
+import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Writer;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.settings.SettingsUtils;
+import org.apache.maven.settings.TrackableBase;
+import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
+import org.apache.maven.settings.building.SettingsBuilder;
+import org.apache.maven.settings.building.SettingsBuildingException;
+import org.apache.maven.settings.building.SettingsBuildingRequest;
+import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
+import org.apache.maven.shared.invoker.CommandLineConfigurationException;
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.InvocationResult;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.MavenCommandLineBuilder;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.apache.maven.shared.scriptinterpreter.RunErrorException;
+import org.apache.maven.shared.scriptinterpreter.RunFailureException;
+import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
+import org.apache.maven.shared.utils.logging.MessageBuilder;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.Interpolator;
+import org.codehaus.plexus.interpolation.MapBasedValueSource;
+import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
+import org.codehaus.plexus.util.DirectoryScanner;
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.InterpolationFilterReader;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.ReflectionUtils;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.codehaus.plexus.util.cli.CommandLineUtils;
+import org.codehaus.plexus.util.cli.Commandline;
+import org.codehaus.plexus.util.cli.StreamConsumer;
+import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
+
+/**
+ * Provides common code for mojos invoking sub builds.
+ *
+ * @author Stephen Connolly
+ * @since 15-Aug-2009 09:09:29
+ */
+public abstract class AbstractInvokerMojo
+    extends AbstractMojo
+{
+    /**
+     * The zero-based column index where to print the invoker result.
+     */
+    private static final int RESULT_COLUMN = 60;
+
+    /**
+     * Flag used to suppress certain invocations. This is useful in tailoring the build using profiles.
+     *
+     * @since 1.1
+     */
+    @Parameter( property = "invoker.skip", defaultValue = "false" )
+    private boolean skipInvocation;
+
+    /**
+     * Flag used to suppress the summary output notifying of successes and failures. If set to <code>true</code>, the
+     * only indication of the build's success or failure will be the effect it has on the main build (if it fails, the
+     * main build should fail as well). If {@link #streamLogs} is enabled, the sub-build summary will also provide an
+     * indication.
+     */
+    @Parameter( defaultValue = "false" )
+    protected boolean suppressSummaries;
+
+    /**
+     * Flag used to determine whether the build logs should be output to the normal mojo log.
+     */
+    @Parameter( property = "invoker.streamLogs", defaultValue = "false" )
+    private boolean streamLogs;
+
+    /**
+     * The local repository for caching artifacts. It is strongly recommended to specify a path to an isolated
+     * repository like <code>${project.build.directory}/it-repo</code>. Otherwise, your ordinary local repository will
+     * be used, potentially soiling it with broken artifacts.
+     */
+    @Parameter( property = "invoker.localRepositoryPath", defaultValue = "${settings.localRepository}" )
+    private File localRepositoryPath;
+
+    /**
+     * Directory to search for integration tests.
+     */
+    @Parameter( property = "invoker.projectsDirectory", defaultValue = "${basedir}/src/it/" )
+    private File projectsDirectory;
+
+    /**
+     * Base directory where all build reports are written to. Every execution of an integration test will produce an XML
+     * file which contains the information about success or failure of that particular build job. The format of the
+     * resulting XML file is documented in the given <a href="./build-job.html">build-job</a> reference.
+     *
+     * @since 1.4
+     */
+    @Parameter( property = "invoker.reportsDirectory", defaultValue = "${project.build.directory}/invoker-reports" )
+    private File reportsDirectory;
+
+    /**
+     * A flag to disable the generation of build reports.
+     *
+     * @since 1.4
+     */
+    @Parameter( property = "invoker.disableReports", defaultValue = "false" )
+    private boolean disableReports;
+
+    /**
+     * Directory to which projects should be cloned prior to execution. If not specified, each integration test will be
+     * run in the directory in which the corresponding IT POM was found. In this case, you most likely want to configure
+     * your SCM to ignore <code>target</code> and <code>build.log</code> in the test's base directory.
+     *
+     * @since 1.1
+     */
+    @Parameter
+    private File cloneProjectsTo;
+
+    // CHECKSTYLE_OFF: LineLength
+    /**
+     * Some files are normally excluded when copying the IT projects from the directory specified by the parameter
+     * projectsDirectory to the directory given by cloneProjectsTo (e.g. <code>.svn</code>, <code>CVS</code>,
+     * <code>*~</code>, etc: see <a href=
+     * "https://codehaus-plexus.github.io/plexus-utils/apidocs/org/codehaus/plexus/util/AbstractScanner.html#DEFAULTEXCLUDES">
+     * reference</a> for full list). Setting this parameter to <code>true</code> will cause all files to be copied to
+     * the <code>cloneProjectsTo</code> directory.
+     *
+     * @since 1.2
+     */
+    @Parameter( defaultValue = "false" )
+    private boolean cloneAllFiles;
+    // CHECKSTYLE_ON: LineLength
+
+    /**
+     * Ensure the {@link #cloneProjectsTo} directory is not polluted with files from earlier invoker runs.
+     *
+     * @since 1.6
+     */
+    @Parameter( defaultValue = "false" )
+    private boolean cloneClean;
+
+    /**
+     * A single POM to build, skipping any scanning parameters and behavior.
+     */
+    @Parameter( property = "invoker.pom" )
+    private File pom;
+
+    /**
+     * Include patterns for searching the integration test directory for projects. This parameter is meant to be set
+     * from the POM. If this parameter is not set, the plugin will search for all <code>pom.xml</code> files one
+     * directory below {@link #projectsDirectory} (i.e. <code>*&#47;pom.xml</code>).<br>
+     * <br>
+     * Starting with version 1.3, mere directories can also be matched by these patterns. For example, the include
+     * pattern <code>*</code> will run Maven builds on all immediate sub directories of {@link #projectsDirectory},
+     * regardless if they contain a <code>pom.xml</code>. This allows to perform builds that need/should not depend on
+     * the existence of a POM.
+     */
+    @Parameter
+    private List<String> pomIncludes = Collections.singletonList( "*/pom.xml" );
+
+    /**
+     * Exclude patterns for searching the integration test directory. This parameter is meant to be set from the POM. By
+     * default, no POM files are excluded. For the convenience of using an include pattern like <code>*</code>, the
+     * custom settings file specified by the parameter {@link #settingsFile} will always be excluded automatically.
+     */
+    @Parameter
+    private List<String> pomExcludes = Collections.emptyList();
+
+    /**
+     * Include patterns for searching the projects directory for projects that need to be run before the other projects.
+     * This parameter allows to declare projects that perform setup tasks like installing utility artifacts into the
+     * local repository. Projects matched by these patterns are implicitly excluded from the scan for ordinary projects.
+     * Also, the exclusions defined by the parameter {@link #pomExcludes} apply to the setup projects, too. Default
+     * value is: <code>setup*&#47;pom.xml</code>.
+     *
+     * @since 1.3
+     */
+    @Parameter
+    private List<String> setupIncludes = Collections.singletonList( "setup*/pom.xml" );
+
+    /**
+     * The list of goals to execute on each project. Default value is: <code>package</code>.
+     */
+    @Parameter
+    private List<String> goals = Collections.singletonList( "package" );
+
+    /**
+     */
+    @Component
+    private Invoker invoker;
+
+    @Component
+    private SettingsBuilder settingsBuilder;
+
+    /**
+     * Relative path of a selector script to run prior in order to decide if the build should be executed. This script
+     * may be written with either BeanShell or Groovy. If the file extension is omitted (e.g. <code>selector</code>),
+     * the plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and
+     * <code>.groovy</code>. If this script exists for a particular project but returns any non-null value different
+     * from <code>true</code>, the corresponding build is flagged as skipped. In this case, none of the pre-build hook
+     * script, Maven nor the post-build hook script will be invoked. If this script throws an exception, the
+     * corresponding build is flagged as in error, and none of the pre-build hook script, Maven not the post-build hook
+     * script will be invoked.
+     *
+     * @since 1.5
+     */
+    @Parameter( property = "invoker.selectorScript", defaultValue = "selector" )
+    private String selectorScript;
+
+    /**
+     * Relative path of a pre-build hook script to run prior to executing the build. This script may be written with
+     * either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>prebuild</code>), the plugin
+     * searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. If this
+     * script exists for a particular project but returns any non-null value different from <code>true</code> or throws
+     * an exception, the corresponding build is flagged as a failure. In this case, neither Maven nor the post-build
+     * hook script will be invoked.
+     */
+    @Parameter( property = "invoker.preBuildHookScript", defaultValue = "prebuild" )
+    private String preBuildHookScript;
+
+    /**
+     * Relative path of a cleanup/verification hook script to run after executing the build. This script may be written
+     * with either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>verify</code>), the
+     * plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>.
+     * If this script exists for a particular project but returns any non-null value different from <code>true</code> or
+     * throws an exception, the corresponding build is flagged as a failure.
+     */
+    @Parameter( property = "invoker.postBuildHookScript", defaultValue = "postbuild" )
+    private String postBuildHookScript;
+
+    /**
+     * Location of a properties file that defines CLI properties for the test.
+     */
+    @Parameter( property = "invoker.testPropertiesFile", defaultValue = "test.properties" )
+    private String testPropertiesFile;
+
+    /**
+     * Common set of properties to pass in on each project's command line, via -D parameters.
+     *
+     * @since 1.1
+     */
+    @Parameter
+    private Map<String, String> properties;
+
+    /**
+     * Whether to show errors in the build output.
+     */
+    @Parameter( property = "invoker.showErrors", defaultValue = "false" )
+    private boolean showErrors;
+
+    /**
+     * Whether to show debug statements in the build output.
+     */
+    @Parameter( property = "invoker.debug", defaultValue = "false" )
+    private boolean debug;
+
+    /**
+     * Suppress logging to the <code>build.log</code> file.
+     */
+    @Parameter( property = "invoker.noLog", defaultValue = "false" )
+    private boolean noLog;
+
+    /**
+     * List of profile identifiers to explicitly trigger in the build.
+     *
+     * @since 1.1
+     */
+    @Parameter
+    private List<String> profiles;
+
+    /**
+     * A list of additional properties which will be used to filter tokens in POMs and goal files.
+     *
+     * @since 1.3
+     */
+    @Parameter
+    private Map<String, String> filterProperties;
+
+    /**
+     * The Maven Project Object
+     *
+     * @since 1.1
+     */
+    @Parameter( defaultValue = "${project}", readonly = true, required = true )
+    private MavenProject project;
+
+    @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
+    private MojoExecution mojoExecution;
+
+    /**
+     * A comma separated list of projectname patterns to run. Specify this parameter to run individual tests by file
+     * name, overriding the {@link #setupIncludes}, {@link #pomIncludes} and {@link #pomExcludes} parameters. Each
+     * pattern you specify here will be used to create an include/exclude pattern formatted like
+     * <code>${projectsDirectory}/<i>pattern</i></code>. To exclude a test, prefix the pattern with a '<code>!</code>'.
+     * So you can just type <nobr><code>-Dinvoker.test=SimpleTest,Comp*Test,!Compare*</code></nobr> to run builds in
+     * <code>${projectsDirectory}/SimpleTest</code> and <code>${projectsDirectory}/ComplexTest</code>, but not
+     * <code>${projectsDirectory}/CompareTest</code>
+     *
+     * @since 1.1 (exclusion since 1.8)
+     */
+    @Parameter( property = "invoker.test" )
+    private String invokerTest;
+
+    /**
+     * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the
+     * <code>&lt;localRepository&gt;</code> element of this settings file is always ignored, i.e. the path given by the
+     * parameter {@link #localRepositoryPath} is dominant.
+     *
+     * @since 1.2
+     */
+    @Parameter( property = "invoker.settingsFile" )
+    private File settingsFile;
+
+    /**
+     * The <code>MAVEN_OPTS</code> environment variable to use when invoking Maven. This value can be overridden for
+     * individual integration tests by using {@link #invokerPropertiesFile}.
+     *
+     * @since 1.2
+     */
+    @Parameter( property = "invoker.mavenOpts" )
+    private String mavenOpts;
+
+    /**
+     * The home directory of the Maven installation to use for the forked builds. Defaults to the current Maven
+     * installation.
+     *
+     * @since 1.3
+     */
+    @Parameter( property = "invoker.mavenHome" )
+    private File mavenHome;
+
+    /**
+     * mavenExecutable can either be a file relative to <code>${maven.home}/bin/</code> or an absolute file.
+     *
+     * @since 1.8
+     * @see Invoker#setMavenExecutable(File)
+     */
+    @Parameter( property = "invoker.mavenExecutable" )
+    private String mavenExecutable;
+
+    /**
+     * The <code>JAVA_HOME</code> environment variable to use for forked Maven invocations. Defaults to the current Java
+     * home directory.
+     *
+     * @since 1.3
+     */
+    @Parameter( property = "invoker.javaHome" )
+    private File javaHome;
+
+    /**
+     * The file encoding for the pre-/post-build scripts and the list files for goals and profiles.
+     *
+     * @since 1.2
+     */
+    @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
+    private String encoding;
+
+    /**
+     * The current user system settings for use in Maven.
+     *
+     * @since 1.2
+     */
+    @Parameter( defaultValue = "${settings}", readonly = true, required = true )
+    private Settings settings;
+
+    /**
+     * A flag whether the test class path of the project under test should be included in the class path of the
+     * pre-/post-build scripts. If set to <code>false</code>, the class path of script interpreter consists only of the
+     * <a href="dependencies.html">runtime dependencies</a> of the Maven Invoker Plugin. If set the <code>true</code>,
+     * the project's test class path will be prepended to the interpreter class path. Among others, this feature allows
+     * the scripts to access utility classes from the test sources of your project.
+     *
+     * @since 1.2
+     */
+    @Parameter( property = "invoker.addTestClassPath", defaultValue = "false" )
+    private boolean addTestClassPath;
+
+    /**
+     * The test class path of the project under test.
+     */
+    @Parameter( defaultValue = "${project.testClasspathElements}", readonly = true )
+    private List<String> testClassPath;
+
+    /**
+     * The name of an optional project-specific file that contains properties used to specify settings for an individual
+     * Maven invocation. Any property present in the file will override the corresponding setting from the plugin
+     * configuration. The values of the properties are filtered and may use expressions like
+     * <code>${project.version}</code> to reference project properties or values from the parameter
+     * {@link #filterProperties}. The snippet below describes the supported properties:
+     * <p/>
+     *
+     * <pre>
+     * # A comma or space separated list of goals/phases to execute, may
+     * # specify an empty list to execute the default goal of the IT project.
+     * # Environment variables used by maven plugins can be added here
+     * invoker.goals = clean install -Dplugin.variable=value
+     *
+     * # Or you can give things like this if you need.
+     * invoker.goals = -T2 clean verify
+     *
+     * # Optionally, a list of goals to run during further invocations of Maven
+     * invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:run
+     *
+     * # A comma or space separated list of profiles to activate
+     * invoker.profiles = its,jdk15
+     *
+     * # The path to an alternative POM or base directory to invoke Maven on, defaults to the
+     * # project that was originally specified in the plugin configuration
+     * # Since plugin version 1.4
+     * invoker.project = sub-module
+     *
+     * # The value for the environment variable MAVEN_OPTS
+     * invoker.mavenOpts = -Dfile.encoding=UTF-16 -Xms32m -Xmx256m
+     *
+     * # Possible values are &quot;fail-fast&quot; (default), &quot;fail-at-end&quot; and &quot;fail-never&quot;
+     * invoker.failureBehavior = fail-never
+     *
+     * # The expected result of the build, possible values are &quot;success&quot; (default) and &quot;failure&quot;
+     * invoker.buildResult = failure
+     *
+     * # A boolean value controlling the aggregator mode of Maven, defaults to &quot;false&quot;
+     * invoker.nonRecursive = true
+     *
+     * # A boolean value controlling the network behavior of Maven, defaults to &quot;false&quot;
+     * # Since plugin version 1.4
+     * invoker.offline = true
+     *
+     * # The path to the properties file from which to load system properties, defaults to the
+     * # filename given by the plugin parameter testPropertiesFile
+     * # Since plugin version 1.4
+     * invoker.systemPropertiesFile = test.properties
+     *
+     * # An optional human friendly name for this build job to be included in the build reports.
+     * # Since plugin version 1.4
+     * invoker.name = Test Build 01
+     *
+     * # An optional description for this build job to be included in the build reports.
+     * # Since plugin version 1.4
+     * invoker.description = Checks the support for build reports.
+     *
+     * # A comma separated list of JRE versions on which this build job should be run.
+     * # Since plugin version 1.4
+     * invoker.java.version = 1.4+, !1.4.1, 1.7-
+     * 
+     * # A comma separated list of OS families on which this build job should be run.
+     * # Since plugin version 1.4
+     * invoker.os.family = !windows, unix, mac
+     *
+     * # A comma separated list of Maven versions on which this build should be run.
+     * # Since plugin version 1.5
+     * invoker.maven.version = 2.0.10+, !2.1.0, !2.2.0
+     * 
+     * # For java.version, maven.version and os.family it is possible to define multiple selectors.
+     * # If one of the indexed selectors matches, the test is executed.
+     * # With the invoker.x.y equivalents you can specify global matchers.  
+     * selector.1.java.version = 1.8+
+     * selector.1.maven.version = 3.2.5+
+     * selector.1.os.family = !windows
+     * selector.2.maven.version = 3.0+
+     * selector.3.java.version = 9+
+     * 
+     * # A boolean value controlling the debug logging level of Maven, , defaults to &quot;false&quot;
+     * # Since plugin version 1.8
+     * invoker.debug = true
+     * </pre>
+     *
+     * @since 1.2
+     */
+    @Parameter( property = "invoker.invokerPropertiesFile", defaultValue = "invoker.properties" )
+    private String invokerPropertiesFile;
+
+    /**
+     * flag to enable show mvn version used for running its (cli option : -V,--show-version )
+     *
+     * @since 1.4
+     */
+    @Parameter( property = "invoker.showVersion", defaultValue = "false" )
+    private boolean showVersion;
+
+    /**
+     * number of threads for running tests in parallel. This will be the number of maven forked process in parallel.
+     *
+     * @since 1.6
+     */
+    @Parameter( property = "invoker.parallelThreads", defaultValue = "1" )
+    private int parallelThreads;
+
+    /**
+     * @since 1.6
+     */
+    @Parameter( property = "plugin.artifacts", required = true, readonly = true )
+    private List<Artifact> pluginArtifacts;
+
+    /**
+     * If enable and if you have a settings file configured for the execution, it will be merged with your user
+     * settings.
+     *
+     * @since 1.6
+     */
+    @Parameter( property = "invoker.mergeUserSettings", defaultValue = "false" )
+    private boolean mergeUserSettings;
+
+    /**
+     * Additional environment variables to set on the command line.
+     *
+     * @since 1.8
+     */
+    @Parameter
+    private Map<String, String> environmentVariables;
+
+    /**
+     * Additional variables for use in the hook scripts.
+     *
+     * @since 1.9
+     */
+    @Parameter
+    private Map<String, String> scriptVariables;
+
+    /**
+     * The scripter runner that is responsible to execute hook scripts.
+     */
+    private ScriptRunner scriptRunner;
+
+    /**
+     * A string used to prefix the file name of the filtered POMs in case the POMs couldn't be filtered in-place (i.e.
+     * the projects were not cloned to a temporary directory), can be <code>null</code>. This will be set to
+     * <code>null</code> if the POMs have already been filtered during cloning.
+     */
+    private String filteredPomPrefix = "interpolated-";
+
+    /**
+     * The format for elapsed build time.
+     */
+    private final DecimalFormat secFormat = new DecimalFormat( "(0.0 s)", new DecimalFormatSymbols( Locale.ENGLISH ) );
+
+    /**
+     * The version of Maven which is used to run the builds
+     */
+    private String actualMavenVersion;
+
+    /**
+     * Invokes Maven on the configured test projects.
+     *
+     * @throws org.apache.maven.plugin.MojoExecutionException If the goal encountered severe errors.
+     * @throws org.apache.maven.plugin.MojoFailureException If any of the Maven builds failed.
+     */
+    public void execute()
+        throws MojoExecutionException, MojoFailureException
+    {
+        if ( skipInvocation )
+        {
+            getLog().info( "Skipping invocation per configuration."
+                + " If this is incorrect, ensure the skipInvocation parameter is not set to true." );
+            return;
+        }
+
+        if ( StringUtils.isEmpty( encoding ) )
+        {
+            getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
+                + ", i.e. build is platform dependent!" );
+        }
+
+        // done it here to prevent issues with concurrent access in case of parallel run
+        if ( !disableReports )
+        {
+            setupReportsFolder();
+        }
+
+        BuildJob[] buildJobs;
+        if ( pom == null )
+        {
+            try
+            {
+                buildJobs = getBuildJobs();
+            }
+            catch ( final IOException e )
+            {
+                throw new MojoExecutionException( "Error retrieving POM list from includes, "
+                    + "excludes, and projects directory. Reason: " + e.getMessage(), e );
+            }
+        }
+        else
+        {
+            try
+            {
+                projectsDirectory = pom.getCanonicalFile().getParentFile();
+            }
+            catch ( IOException e )
+            {
+                throw new MojoExecutionException( "Failed to discover projectsDirectory from "
+                    + "pom File parameter. Reason: " + e.getMessage(), e );
+            }
+
+            buildJobs = new BuildJob[] { new BuildJob( pom.getName(), BuildJob.Type.NORMAL ) };
+        }
+
+        if ( ( buildJobs == null ) || ( buildJobs.length < 1 ) )
+        {
+            doFailIfNoProjects();
+
+            getLog().info( "No projects were selected for execution." );
+            return;
+        }
+
+        handleScriptRunnerWithScriptClassPath();
+
+        Collection<String> collectedProjects = new LinkedHashSet<String>();
+        for ( BuildJob buildJob : buildJobs )
+        {
+            collectProjects( projectsDirectory, buildJob.getProject(), collectedProjects, true );
+        }
+
+        File projectsDir = projectsDirectory;
+
+        if ( cloneProjectsTo != null )
+        {
+            cloneProjects( collectedProjects );
+            projectsDir = cloneProjectsTo;
+        }
+        else
+        {
+            getLog().warn( "Filtering of parent/child POMs is not supported without cloning the projects" );
+        }
+
+        // First run setup jobs.
+        BuildJob[] setupBuildJobs = null;
+        try
+        {
+            setupBuildJobs = getSetupBuildJobsFromFolders();
+        }
+        catch ( IOException e )
+        {
+            getLog().error( "Failure during scanning of folders.", e );
+            // TODO: Check shouldn't we fail in case of problems?
+        }
+
+        if ( setupBuildJobs != null )
+        {
+            // Run setup jobs in single thread
+            // mode.
+            //
+            // Some Idea about ordering?
+            getLog().info( "Running Setup Jobs" );
+            runBuilds( projectsDir, setupBuildJobs, 1 );
+        }
+
+        // Afterwards run all other jobs.
+        BuildJob[] nonSetupBuildJobs = getNonSetupJobs( buildJobs );
+        // We will run the non setup jobs with the configured
+        // parallelThreads number.
+        runBuilds( projectsDir, nonSetupBuildJobs, parallelThreads );
+
+        writeSummaryFile( nonSetupBuildJobs );
+
+        processResults( new InvokerSession( nonSetupBuildJobs ) );
+
+    }
+
+    /**
+     * This will create the necessary folders for the reports.
+     * 
+     * @throws MojoExecutionException in case of failure during creation of the reports folder.
+     */
+    private void setupReportsFolder()
+        throws MojoExecutionException
+    {
+        // If it exists from previous run...
+        if ( reportsDirectory.exists() )
+        {
+            try
+            {
+                FileUtils.deleteDirectory( reportsDirectory );
+            }
+            catch ( IOException e )
+            {
+                throw new MojoExecutionException( "Failure while trying to delete "
+                    + reportsDirectory.getAbsolutePath(), e );
+            }
+        }
+        if ( !reportsDirectory.mkdirs() )
+        {
+            throw new MojoExecutionException( "Failure while creating the " + reportsDirectory.getAbsolutePath() );
+        }
+    }
+
+    private BuildJob[] getNonSetupJobs( BuildJob[] buildJobs )
+    {
+        List<BuildJob> result = new LinkedList<BuildJob>();
+        for ( int i = 0; i < buildJobs.length; i++ )
+        {
+            if ( !buildJobs[i].getType().equals( BuildJob.Type.SETUP ) )
+            {
+                result.add( buildJobs[i] );
+            }
+        }
+        BuildJob[] buildNonSetupJobs = result.toArray( new BuildJob[result.size()] );
+        return buildNonSetupJobs;
+    }
+
+    private void handleScriptRunnerWithScriptClassPath()
+    {
+        final List<String> scriptClassPath;
+        if ( addTestClassPath )
+        {
+            scriptClassPath = new ArrayList<String>( testClassPath );
+            for ( Artifact pluginArtifact : pluginArtifacts )
+            {
+                scriptClassPath.remove( pluginArtifact.getFile().getAbsolutePath() );
+            }
+        }
+        else
+        {
+            scriptClassPath = null;
+        }
+        scriptRunner = new ScriptRunner( getLog() );
+        scriptRunner.setScriptEncoding( encoding );
+        scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath );
+        if ( scriptVariables != null )
+        {
+            for ( Entry<String, String> entry : scriptVariables.entrySet() )
+            {
+                scriptRunner.setGlobalVariable( entry.getKey(), entry.getValue() );
+            }
+        }
+        scriptRunner.setClassPath( scriptClassPath );
+    }
+
+    private void writeSummaryFile( BuildJob[] buildJobs )
+        throws MojoExecutionException
+    {
+
+        File summaryReportFile = new File( reportsDirectory, "invoker-summary.txt" );
+
+        try
+        {
+            Writer writer = new BufferedWriter( new FileWriter( summaryReportFile ) );
+
+            for ( int i = 0; i < buildJobs.length; i++ )
+            {
+                BuildJob buildJob = buildJobs[i];
+                if ( !buildJob.getResult().equals( BuildJob.Result.SUCCESS ) )
+                {
+                    writer.append( buildJob.getResult() );
+                    writer.append( " [" );
+                    writer.append( buildJob.getProject() );
+                    writer.append( "] " );
+                    if ( buildJob.getFailureMessage() != null )
+                    {
+                        writer.append( " " );
+                        writer.append( buildJob.getFailureMessage() );
+                    }
+                    writer.append( "\n" );
+                }
+            }
+
+            writer.close();
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "Failed to write summary report " + summaryReportFile, e );
+        }
+    }
+
+    protected void doFailIfNoProjects()
+        throws MojoFailureException
+    {
+        // should only be used during run and verify
+    }
+
+    /**
+     * Processes the results of invoking the build jobs.
+     *
+     * @param invokerSession The session with the build jobs, must not be <code>null</code>.
+     * @throws MojoFailureException If the mojo had failed as a result of invoking the build jobs.
+     * @since 1.4
+     */
+    abstract void processResults( InvokerSession invokerSession )
+        throws MojoFailureException;
+
+    /**
+     * Creates a new reader for the specified file, using the plugin's {@link #encoding} parameter.
+     *
+     * @param file The file to create a reader for, must not be <code>null</code>.
+     * @return The reader for the file, never <code>null</code>.
+     * @throws java.io.IOException If the specified file was not found or the configured encoding is not supported.
+     */
+    private Reader newReader( File file )
+        throws IOException
+    {
+        if ( StringUtils.isNotEmpty( encoding ) )
+        {
+            return ReaderFactory.newReader( file, encoding );
+        }
+        else
+        {
+            return ReaderFactory.newPlatformReader( file );
+        }
+    }
+
+    /**
+     * Collects all projects locally reachable from the specified project. The method will as such try to read the POM
+     * and recursively follow its parent/module elements.
+     *
+     * @param projectsDir The base directory of all projects, must not be <code>null</code>.
+     * @param projectPath The relative path of the current project, can denote either the POM or its base directory,
+     *            must not be <code>null</code>.
+     * @param projectPaths The set of already collected projects to add new projects to, must not be <code>null</code>.
+     *            This set will hold the relative paths to either a POM file or a project base directory.
+     * @param included A flag indicating whether the specified project has been explicitly included via the parameter
+     *            {@link #pomIncludes}. Such projects will always be added to the result set even if there is no
+     *            corresponding POM.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the project tree could not be traversed.
+     */
+    private void collectProjects( File projectsDir, String projectPath, Collection<String> projectPaths,
+                                  boolean included )
+        throws MojoExecutionException
+    {
+        projectPath = projectPath.replace( '\\', '/' );
+        File pomFile = new File( projectsDir, projectPath );
+        if ( pomFile.isDirectory() )
+        {
+            pomFile = new File( pomFile, "pom.xml" );
+            if ( !pomFile.exists() )
+            {
+                if ( included )
+                {
+                    projectPaths.add( projectPath );
+                }
+                return;
+            }
+            if ( !projectPath.endsWith( "/" ) )
+            {
+                projectPath += '/';
+            }
+            projectPath += "pom.xml";
+        }
+        else if ( !pomFile.isFile() )
+        {
+            return;
+        }
+        if ( !projectPaths.add( projectPath ) )
+        {
+            return;
+        }
+        getLog().debug( "Collecting parent/child projects of " + projectPath );
+
+        Model model = PomUtils.loadPom( pomFile );
+
+        try
+        {
+            String projectsRoot = projectsDir.getCanonicalPath();
+            String projectDir = pomFile.getParent();
+
+            String parentPath = "../pom.xml";
+            if ( model.getParent() != null && StringUtils.isNotEmpty( model.getParent().getRelativePath() ) )
+            {
+                parentPath = model.getParent().getRelativePath();
+            }
+            String parent = relativizePath( new File( projectDir, parentPath ), projectsRoot );
+            if ( parent != null )
+            {
+                collectProjects( projectsDir, parent, projectPaths, false );
+            }
+
+            Collection<String> modulePaths = new LinkedHashSet<String>();
+
+            modulePaths.addAll( model.getModules() );
+
+            for ( Profile profile : model.getProfiles() )
+            {
+                modulePaths.addAll( profile.getModules() );
+            }
+
+            for ( String modulePath : modulePaths )
+            {
+                String module = relativizePath( new File( projectDir, modulePath ), projectsRoot );
+                if ( module != null )
+                {
+                    collectProjects( projectsDir, module, projectPaths, false );
+                }
+            }
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "Failed to analyze POM: " + pomFile, e );
+        }
+    }
+
+    /**
+     * Copies the specified projects to the directory given by {@link #cloneProjectsTo}. A project may either be denoted
+     * by a path to a POM file or merely by a path to a base directory. During cloning, the POM files will be filtered.
+     *
+     * @param projectPaths The paths to the projects to clone, relative to the projects directory, must not be
+     *            <code>null</code> nor contain <code>null</code> elements.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the the projects could not be copied/filtered.
+     */
+    private void cloneProjects( Collection<String> projectPaths )
+        throws MojoExecutionException
+    {
+        if ( !cloneProjectsTo.mkdirs() && cloneClean )
+        {
+            try
+            {
+                FileUtils.cleanDirectory( cloneProjectsTo );
+            }
+            catch ( IOException e )
+            {
+                throw new MojoExecutionException( "Could not clean the cloneProjectsTo directory. Reason: "
+                    + e.getMessage(), e );
+            }
+        }
+
+        // determine project directories to clone
+        Collection<String> dirs = new LinkedHashSet<String>();
+        for ( String projectPath : projectPaths )
+        {
+            if ( !new File( projectsDirectory, projectPath ).isDirectory() )
+            {
+                projectPath = getParentPath( projectPath );
+            }
+            dirs.add( projectPath );
+        }
+
+        boolean filter;
+
+        // clone project directories
+        try
+        {
+            filter = !cloneProjectsTo.getCanonicalFile().equals( projectsDirectory.getCanonicalFile() );
+
+            List<String> clonedSubpaths = new ArrayList<String>();
+
+            for ( String subpath : dirs )
+            {
+                // skip this project if its parent directory is also scheduled for cloning
+                if ( !".".equals( subpath ) && dirs.contains( getParentPath( subpath ) ) )
+                {
+                    continue;
+                }
+
+                // avoid copying subdirs that are already cloned.
+                if ( !alreadyCloned( subpath, clonedSubpaths ) )
+                {
+                    // avoid creating new files that point to dir/.
+                    if ( ".".equals( subpath ) )
+                    {
+                        String cloneSubdir = relativizePath( cloneProjectsTo, projectsDirectory.getCanonicalPath() );
+
+                        // avoid infinite recursion if the cloneTo path is a subdirectory.
+                        if ( cloneSubdir != null )
+                        {
+                            File temp = File.createTempFile( "pre-invocation-clone.", "" );
+                            temp.delete();
+                            temp.mkdirs();
+
+                            copyDirectoryStructure( projectsDirectory, temp );
+
+                            FileUtils.deleteDirectory( new File( temp, cloneSubdir ) );
+
+                            copyDirectoryStructure( temp, cloneProjectsTo );
+                        }
+                        else
+                        {
+                            copyDirectoryStructure( projectsDirectory, cloneProjectsTo );
+                        }
+                    }
+                    else
+                    {
+                        File srcDir = new File( projectsDirectory, subpath );
+                        File dstDir = new File( cloneProjectsTo, subpath );
+                        copyDirectoryStructure( srcDir, dstDir );
+                    }
+
+                    clonedSubpaths.add( subpath );
+                }
+            }
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "Failed to clone projects from: " + projectsDirectory + " to: "
+                + cloneProjectsTo + ". Reason: " + e.getMessage(), e );
+        }
+
+        // filter cloned POMs
+        if ( filter )
+        {
+            for ( String projectPath : projectPaths )
+            {
+                File pomFile = new File( cloneProjectsTo, projectPath );
+                if ( pomFile.isFile() )
+                {
+                    buildInterpolatedFile( pomFile, pomFile );
+                }
+
+                // MINVOKER-186
+                // The following is a temporary solution to support Maven 3.3.1 (.mvn/extensions.xml) filtering
+                // Will be replaced by MINVOKER-117 with general filtering mechanism
+                File baseDir = pomFile.getParentFile();
+                File mvnDir = new File( baseDir, ".mvn" );
+                if ( mvnDir.isDirectory() )
+                {
+                    File extensionsFile = new File( mvnDir, "extensions.xml" );
+                    if ( extensionsFile.isFile() )
+                    {
+                        buildInterpolatedFile( extensionsFile, extensionsFile );
+                    }
+                }
+                // END MINVOKER-186
+            }
+            filteredPomPrefix = null;
+        }
+    }
+
+    /**
+     * Gets the parent path of the specified relative path.
+     *
+     * @param path The relative path whose parent should be retrieved, must not be <code>null</code>.
+     * @return The parent path or "." if the specified path has no parent, never <code>null</code>.
+     */
+    private String getParentPath( String path )
+    {
+        int lastSep = Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ) );
+        return ( lastSep < 0 ) ? "." : path.substring( 0, lastSep );
+    }
+
+    /**
+     * Copied a directory structure with default exclusions (.svn, CVS, etc)
+     *
+     * @param sourceDir The source directory to copy, must not be <code>null</code>.
+     * @param destDir The target directory to copy to, must not be <code>null</code>.
+     * @throws java.io.IOException If the directory structure could not be copied.
+     */
+    private void copyDirectoryStructure( File sourceDir, File destDir )
+        throws IOException
+    {
+        DirectoryScanner scanner = new DirectoryScanner();
+        scanner.setBasedir( sourceDir );
+        if ( !cloneAllFiles )
+        {
+            scanner.addDefaultExcludes();
+        }
+        scanner.scan();
+
+        /*
+         * NOTE: Make sure the destination directory is always there (even if empty) to support POM-less ITs.
+         */
+        destDir.mkdirs();
+        // Create all the directories, including any symlinks present in source
+        FileUtils.mkDirs( sourceDir, scanner.getIncludedDirectories(), destDir );
+
+        for ( String includedFile : scanner.getIncludedFiles() )
+        {
+            File sourceFile = new File( sourceDir, includedFile );
+            File destFile = new File( destDir, includedFile );
+            FileUtils.copyFile( sourceFile, destFile );
+
+            // ensure clone project must be writable for additional changes
+            destFile.setWritable( true );
+        }
+    }
+
+    /**
+     * Determines whether the specified sub path has already been cloned, i.e. whether one of its ancestor directories
+     * was already cloned.
+     *
+     * @param subpath The sub path to check, must not be <code>null</code>.
+     * @param clonedSubpaths The list of already cloned paths, must not be <code>null</code> nor contain
+     *            <code>null</code> elements.
+     * @return <code>true</code> if the specified path has already been cloned, <code>false</code> otherwise.
+     */
+    static boolean alreadyCloned( String subpath, List<String> clonedSubpaths )
+    {
+        for ( String path : clonedSubpaths )
+        {
+            if ( ".".equals( path ) || subpath.equals( path ) || subpath.startsWith( path + File.separator ) )
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Runs the specified build jobs.
+     *
+     * @param projectsDir The base directory of all projects, must not be <code>null</code>.
+     * @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 )
+        throws MojoExecutionException
+    {
+        if ( !localRepositoryPath.exists() )
+        {
+            localRepositoryPath.mkdirs();
+        }
+
+        // -----------------------------------------------
+        // interpolate settings file
+        // -----------------------------------------------
+
+        File interpolatedSettingsFile = interpolateSettings();
+
+        final File mergedSettingsFile = mergeSettings( interpolatedSettingsFile );
+
+        if ( mavenHome != null )
+        {
+            actualMavenVersion = SelectorUtils.getMavenVersion( mavenHome );
+        }
+        else
+        {
+            actualMavenVersion = SelectorUtils.getMavenVersion();
+        }
+        scriptRunner.setGlobalVariable( "mavenVersion", actualMavenVersion );
+
+        final CharSequence actualJreVersion;
+        // @todo if ( javaVersions ) ... to be picked up from toolchains
+        if ( javaHome != null )
+        {
+            actualJreVersion = resolveExternalJreVersion();
+        }
+        else
+        {
+            actualJreVersion = SelectorUtils.getJreVersion();
+        }
+
+        try
+        {
+            if ( runWithParallelThreads > 1 )
+            {
+                getLog().info( "use parallelThreads " + runWithParallelThreads );
+
+                ExecutorService executorService = Executors.newFixedThreadPool( runWithParallelThreads );
+                for ( final BuildJob job : buildJobs )
+                {
+                    executorService.execute( new Runnable()
+                    {
+                        public void run()
+                        {
+                            try
+                            {
+                                runBuild( projectsDir, job, mergedSettingsFile, javaHome, actualJreVersion );
+                            }
+                            catch ( MojoExecutionException e )
+                            {
+                                throw new RuntimeException( e.getMessage(), e );
+                            }
+                        }
+                    } );
+                }
+
+                try
+                {
+                    executorService.shutdown();
+                    // TODO add a configurable time out
+                    executorService.awaitTermination( Long.MAX_VALUE, TimeUnit.MILLISECONDS );
+                }
+                catch ( InterruptedException e )
+                {
+                    throw new MojoExecutionException( e.getMessage(), e );
+                }
+            }
+            else
+            {
+                for ( BuildJob job : buildJobs )
+                {
+                    runBuild( projectsDir, job, mergedSettingsFile, javaHome, actualJreVersion );
+                }
+            }
+        }
+        finally
+        {
+            if ( interpolatedSettingsFile != null && cloneProjectsTo == null )
+            {
+                interpolatedSettingsFile.delete();
+            }
+            if ( mergedSettingsFile != null && mergedSettingsFile.exists() )
+            {
+                mergedSettingsFile.delete();
+            }
+        }
+    }
+
+    /**
+     * Interpolate settings.xml file.
+     * 
+     * @return The interpolated settings.xml file.
+     * @throws MojoExecutionException in case of a problem.
+     */
+    private File interpolateSettings()
+        throws MojoExecutionException
+    {
+        File interpolatedSettingsFile = null;
+        if ( settingsFile != null )
+        {
+            if ( cloneProjectsTo != null )
+            {
+                interpolatedSettingsFile = new File( cloneProjectsTo, "interpolated-" + settingsFile.getName() );
+            }
+            else
+            {
+                interpolatedSettingsFile =
+                    new File( settingsFile.getParentFile(), "interpolated-" + settingsFile.getName() );
+            }
+            buildInterpolatedFile( settingsFile, interpolatedSettingsFile );
+        }
+        return interpolatedSettingsFile;
+    }
+
+    /**
+     * Merge the settings file
+     * 
+     * @param interpolatedSettingsFile The interpolated settings file.
+     * @return The merged settings file.
+     * @throws MojoExecutionException Fail the build in case the merged settings file can't be created.
+     */
+    private File mergeSettings( File interpolatedSettingsFile )
+        throws MojoExecutionException
+    {
+        File mergedSettingsFile;
+        Settings mergedSettings = this.settings;
+        if ( mergeUserSettings )
+        {
+            if ( interpolatedSettingsFile != null )
+            {
+                // Have to merge the specified settings file (dominant) and the one of the invoking Maven process
+                try
+                {
+                    SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
+                    request.setGlobalSettingsFile( interpolatedSettingsFile );
+
+                    Settings dominantSettings = settingsBuilder.build( request ).getEffectiveSettings();
+                    Settings recessiveSettings = cloneSettings();
+                    SettingsUtils.merge( dominantSettings, recessiveSettings, TrackableBase.USER_LEVEL );
+
+                    mergedSettings = dominantSettings;
+                    getLog().debug( "Merged specified settings file with settings of invoking process" );
+                }
+                catch ( SettingsBuildingException e )
+                {
+                    throw new MojoExecutionException( "Could not read specified settings file", e );
+                }
+            }
+        }
+
+        if ( this.settingsFile != null && !mergeUserSettings )
+        {
+            mergedSettingsFile = interpolatedSettingsFile;
+        }
+        else
+        {
+            try
+            {
+                mergedSettingsFile = writeMergedSettingsFile( mergedSettings );
+            }
+            catch ( IOException e )
+            {
+                throw new MojoExecutionException( "Could not create temporary file for invoker settings.xml", e );
+            }
+        }
+        return mergedSettingsFile;
+    }
+
+    private File writeMergedSettingsFile( Settings mergedSettings )
+        throws IOException
+    {
+        File mergedSettingsFile;
+        mergedSettingsFile = File.createTempFile( "invoker-settings", ".xml" );
+
+        SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer();
+
+        FileWriter fileWriter = null;
+        try
+        {
+            fileWriter = new FileWriter( mergedSettingsFile );
+            settingsWriter.write( fileWriter, mergedSettings );
+            fileWriter.close();
+            fileWriter = null;
+        }
+        finally
+        {
+            IOUtil.close( fileWriter );
+        }
+
+        if ( getLog().isDebugEnabled() )
+        {
+            getLog().debug( "Created temporary file for invoker settings.xml: "
+                + mergedSettingsFile.getAbsolutePath() );
+        }
+        return mergedSettingsFile;
+    }
+
+    private Settings cloneSettings()
+    {
+        Settings recessiveSettings = SettingsUtils.copySettings( this.settings );
+
+        // MINVOKER-133: reset sourceLevelSet
+        resetSourceLevelSet( recessiveSettings );
+        for ( org.apache.maven.settings.Mirror mirror : recessiveSettings.getMirrors() )
+        {
+            resetSourceLevelSet( mirror );
+        }
+        for ( org.apache.maven.settings.Server server : recessiveSettings.getServers() )
+        {
+            resetSourceLevelSet( server );
+        }
+        for ( org.apache.maven.settings.Proxy proxy : recessiveSettings.getProxies() )
+        {
+            resetSourceLevelSet( proxy );
+        }
+        for ( org.apache.maven.settings.Profile profile : recessiveSettings.getProfiles() )
+        {
+            resetSourceLevelSet( profile );
+        }
+
+        return recessiveSettings;
+    }
+
+    private void resetSourceLevelSet( org.apache.maven.settings.TrackableBase trackable )
+    {
+        try
+        {
+            ReflectionUtils.setVariableValueInObject( trackable, "sourceLevelSet", Boolean.FALSE );
+            getLog().debug( "sourceLevelSet: "
+                + ReflectionUtils.getValueIncludingSuperclasses( "sourceLevelSet", trackable ) );
+        }
+        catch ( IllegalAccessException e )
+        {
+            // noop
+        }
+    }
+
+    private CharSequence resolveExternalJreVersion()
+    {
+        Artifact pluginArtifact = mojoExecution.getMojoDescriptor().getPluginDescriptor().getPluginArtifact();
+        pluginArtifact.getFile();
+
+        Commandline commandLine = new Commandline();
+        commandLine.setExecutable( new File( javaHome, "bin/java" ).getAbsolutePath() );
+        commandLine.createArg().setValue( "-cp" );
+        commandLine.createArg().setFile( pluginArtifact.getFile() );
+        commandLine.createArg().setValue( SystemPropertyPrinter.class.getName() );
+        commandLine.createArg().setValue( "java.version" );
+
+        final StringBuilder actualJreVersion = new StringBuilder();
+        StreamConsumer consumer = new StreamConsumer()
+        {
+            public void consumeLine( String line )
+            {
+                actualJreVersion.append( line );
+            }
+        };
+        try
+        {
+            CommandLineUtils.executeCommandLine( commandLine, consumer, null );
+        }
+        catch ( CommandLineException e )
+        {
+            getLog().warn( e.getMessage() );
+        }
+        return actualJreVersion;
+    }
+
+    /**
+     * Interpolate the pom file.
+     * 
+     * @param pomFile The pom file.
+     * @param basedir The base directory.
+     * @return interpolated pom file location in case we have interpolated the pom file otherwise the original pom file
+     *         will be returned.
+     * @throws MojoExecutionException
+     */
+    private File interpolatePomFile( File pomFile, File basedir )
+        throws MojoExecutionException
+    {
+        File interpolatedPomFile = null;
+        if ( pomFile != null )
+        {
+            if ( StringUtils.isNotEmpty( filteredPomPrefix ) )
+            {
+                interpolatedPomFile = new File( basedir, filteredPomPrefix + pomFile.getName() );
+                buildInterpolatedFile( pomFile, interpolatedPomFile );
+            }
+            else
+            {
+                interpolatedPomFile = pomFile;
+            }
+        }
+        return interpolatedPomFile;
+    }
+
+    /**
+     * Runs the specified project.
+     *
+     * @param projectsDir The base directory of all projects, must not be <code>null</code>.
+     * @param buildJob The build job to run, must not be <code>null</code>.
+     * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code> to use
+     *            the current user settings.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the project could not be launched.
+     */
+    private void runBuild( File projectsDir, BuildJob buildJob, File settingsFile, File actualJavaHome,
+                           CharSequence actualJreVersion )
+        throws MojoExecutionException
+    {
+        // FIXME: Think about the following code part -- START
+        File pomFile = new File( projectsDir, buildJob.getProject() );
+        File basedir;
+        if ( pomFile.isDirectory() )
+        {
+            basedir = pomFile;
+            pomFile = new File( basedir, "pom.xml" );
+            if ( !pomFile.exists() )
+            {
+                pomFile = null;
+            }
+            else
+            {
+                buildJob.setProject( buildJob.getProject() + File.separator + "pom.xml" );
+            }
+        }
+        else
+        {
+            basedir = pomFile.getParentFile();
+        }
+
+        File interpolatedPomFile = interpolatePomFile( pomFile, basedir );
+        // FIXME: Think about the following code part -- ^^^^^^^ END
+
+        getLog().info( buffer().a( "Building: " ).strong( buildJob.getProject() ).toString() );
+
+        InvokerProperties invokerProperties = getInvokerProperties( basedir );
+
+        // let's set what details we can
+        buildJob.setName( invokerProperties.getJobName() );
+        buildJob.setDescription( invokerProperties.getJobDescription() );
+
+        try
+        {
+            int selection = getSelection( invokerProperties, actualJreVersion );
+            if ( selection == 0 )
+            {
+                long milliseconds = System.currentTimeMillis();
+                boolean executed;
+                try
+                {
+                    // CHECKSTYLE_OFF: LineLength
+                    executed =
+                        runBuild( basedir, interpolatedPomFile, settingsFile, actualJavaHome, invokerProperties );
+                    // CHECKSTYLE_ON: LineLength
+                }
+                finally
+                {
+                    milliseconds = System.currentTimeMillis() - milliseconds;
+                    buildJob.setTime( milliseconds / 1000.0 );
+                }
+
+                if ( executed )
+                {
+                    buildJob.setResult( BuildJob.Result.SUCCESS );
+
+                    if ( !suppressSummaries )
+                    {
+                        getLog().info( pad( buildJob ).success( "SUCCESS" ).a( ' ' )
+                            + formatTime( buildJob.getTime() ) );
+                    }
+                }
+                else
+                {
+                    buildJob.setResult( BuildJob.Result.SKIPPED );
+
+                    if ( !suppressSummaries )
+                    {
+                        getLog().info( pad( buildJob ).warning( "SKIPPED" ).a( ' ' )
+                            + formatTime( buildJob.getTime() ) );
+                    }
+                }
+            }
+            else
+            {
+                buildJob.setResult( BuildJob.Result.SKIPPED );
+
+                StringBuilder message = new StringBuilder();
+                if ( selection == Selector.SELECTOR_MULTI )
+                {
+                    message.append( "non-matching selectors" );
+                }
+                else
+                {
+                    if ( ( selection & Selector.SELECTOR_MAVENVERSION ) != 0 )
+                    {
+                        message.append( "Maven version" );
+                    }
+                    if ( ( selection & Selector.SELECTOR_JREVERSION ) != 0 )
+                    {
+                        if ( message.length() > 0 )
+                        {
+                            message.append( ", " );
+                        }
+                        message.append( "JRE version" );
+                    }
+                    if ( ( selection & Selector.SELECTOR_OSFAMILY ) != 0 )
+                    {
+                        if ( message.length() > 0 )
+                        {
+                            message.append( ", " );
+                        }
+                        message.append( "OS" );
+                    }
+                }
+
+                if ( !suppressSummaries )
+                {
+                    getLog().info( pad( buildJob ).warning( "SKIPPED" ) + " due to " + message.toString() );
+                }
+
+                // Abuse failureMessage, the field in the report which should contain the reason for skipping
+                // Consider skipCode + I18N
+                buildJob.setFailureMessage( "Skipped due to " + message.toString() );
+            }
+        }
+        catch ( RunErrorException e )
+        {
+            buildJob.setResult( BuildJob.Result.ERROR );
+            buildJob.setFailureMessage( e.getMessage() );
+
+            if ( !suppressSummaries )
+            {
+                getLog().info( pad( buildJob ).failure( "ERROR" ).a( ' ' ) + formatTime( buildJob.getTime() ) );
+                getLog().info( "  " + e.getMessage() );
+            }
+        }
+        catch ( RunFailureException e )
+        {
+            buildJob.setResult( e.getType() );
+            buildJob.setFailureMessage( e.getMessage() );
+
+            if ( !suppressSummaries )
+            {
+                getLog().info( pad( buildJob ).failure( "FAILED" ).a( ' ' ) + formatTime( buildJob.getTime() ) );
+                getLog().info( "  " + e.getMessage() );
+            }
+        }
+        finally
+        {
+            deleteInterpolatedPomFile( interpolatedPomFile );
+            writeBuildReport( buildJob );
+        }
+    }
+
+    private MessageBuilder pad( BuildJob buildJob )
+    {
+        MessageBuilder buffer = buffer( 128 );
+
+        buffer.a( "          " );
+        buffer.a( buildJob.getProject() );
+        buffer.a( ' ' );
+
+        int l = 11 + buildJob.getProject().length();
+
+        if ( l < RESULT_COLUMN )
+        {
+            for ( int i = RESULT_COLUMN - l; i > 0; i-- )
+            {
+                buffer.a( '.' );
+            }
+        }
+
+        return buffer.a( ' ' );
+    }
+
+    /**
+     * Delete the interpolated pom file if it has been created before.
+     * 
+     * @param interpolatedPomFile The interpolated pom file.
+     */
+    private void deleteInterpolatedPomFile( File interpolatedPomFile )
+    {
+        if ( interpolatedPomFile != null && StringUtils.isNotEmpty( filteredPomPrefix ) )
+        {
+            interpolatedPomFile.delete();
+        }
+    }
+
+    /**
+     * Determines whether selector conditions of the specified invoker properties match the current environment.
+     *
+     * @param invokerProperties The invoker properties to check, must not be <code>null</code>.
+     * @return <code>0</code> if the job corresponding to the properties should be run, otherwise a bitwise value
+     *         representing the reason why it should be skipped.
+     */
+    private int getSelection( InvokerProperties invokerProperties, CharSequence actualJreVersion )
+    {
+        return new Selector( actualMavenVersion, actualJreVersion.toString() ).getSelection( invokerProperties );
+    }
+
+    /**
+     * Writes the XML report for the specified build job unless report generation has been disabled.
+     *
+     * @param buildJob The build job whose report should be written, must not be <code>null</code>.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the report could not be written.
+     */
+    private void writeBuildReport( BuildJob buildJob )
+        throws MojoExecutionException
+    {
+        if ( disableReports )
+        {
+            return;
+        }
+
+        String safeFileName = buildJob.getProject().replace( '/', '_' ).replace( '\\', '_' ).replace( ' ', '_' );
+        if ( safeFileName.endsWith( "_pom.xml" ) )
+        {
+            safeFileName = safeFileName.substring( 0, safeFileName.length() - "_pom.xml".length() );
+        }
+
+        File reportFile = new File( reportsDirectory, "BUILD-" + safeFileName + ".xml" );
+        try
+        {
+            FileOutputStream fos = new FileOutputStream( reportFile );
+            try
+            {
+                Writer osw = new OutputStreamWriter( fos, buildJob.getModelEncoding() );
+                BuildJobXpp3Writer writer = new BuildJobXpp3Writer();
+                writer.write( osw, buildJob );
+                osw.close();
+            }
+            finally
+            {
+                fos.close();
+            }
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "Failed to write build report " + reportFile, e );
+        }
+    }
+
+    /**
+     * Formats the specified build duration time.
+     *
+     * @param seconds The duration of the build.
+     * @return The formatted time, never <code>null</code>.
+     */
+    private String formatTime( double seconds )
+    {
+        return secFormat.format( seconds );
+    }
+
+    /**
+     * Runs the specified project.
+     *
+     * @param basedir The base directory of the project, must not be <code>null</code>.
+     * @param pomFile The (already interpolated) POM file, may be <code>null</code> for a POM-less Maven invocation.
+     * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code>. Will
+     *            be merged with the settings file of the invoking Maven process.
+     * @param invokerProperties The properties to use.
+     * @return <code>true</code> if the project was launched or <code>false</code> if the selector script indicated that
+     *         the project should be skipped.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the project could not be launched.
+     * @throws org.apache.maven.shared.scriptinterpreter.RunFailureException If either a hook script or the build itself
+     *             failed.
+     */
+    private boolean runBuild( File basedir, File pomFile, File settingsFile, File actualJavaHome,
+                              InvokerProperties invokerProperties )
+        throws MojoExecutionException, RunFailureException
+    {
+        if ( getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty() )
+        {
+            Properties props = invokerProperties.getProperties();
+            getLog().debug( "Using invoker properties:" );
+            for ( String key : new TreeSet<String>( props.stringPropertyNames() ) )
+            {
+                String value = props.getProperty( key );
+                getLog().debug( "  " + key + " = " + value );
+            }
+        }
+
+        List<String> goals = getGoals( basedir );
+
+        List<String> profiles = getProfiles( basedir );
+
+        Map<String, Object> context = new LinkedHashMap<String, Object>();
+
+        FileLogger logger = setupBuildLogFile( basedir );
+        try
+        {
+            try
+            {
+                scriptRunner.run( "selector script", basedir, selectorScript, context, logger, BuildJob.Result.SKIPPED,
+                                  false );
+            }
+            catch ( RunErrorException e )
+            {
+                throw e;
+            }
+            catch ( RunFailureException e )
+            {
+                return false;
+            }
+
+            scriptRunner.run( "pre-build script", basedir, preBuildHookScript, context, logger,
+                              BuildJob.Result.FAILURE_PRE_HOOK, false );
+
+            final InvocationRequest request = new DefaultInvocationRequest();
+
+            request.setLocalRepositoryDirectory( localRepositoryPath );
+
+            request.setBatchMode( true );
+
+            request.setShowErrors( showErrors );
+
+            request.setDebug( debug );
+
+            request.setShowVersion( showVersion );
+
+            setupLoggerForBuildJob( logger, request );
+
+            if ( mavenHome != null )
+            {
+                invoker.setMavenHome( mavenHome );
+                // FIXME: Should we really take care of M2_HOME?
+                request.addShellEnvironment( "M2_HOME", mavenHome.getAbsolutePath() );
+            }
+
+            if ( mavenExecutable != null )
+            {
+                invoker.setMavenExecutable( new File( mavenExecutable ) );
+            }
+
+            if ( actualJavaHome != null )
+            {
+                request.setJavaHome( actualJavaHome );
+            }
+
+            if ( environmentVariables != null )
+            {
+                for ( Map.Entry<String, String> variable : environmentVariables.entrySet() )
+                {
+                    request.addShellEnvironment( variable.getKey(), variable.getValue() );
+                }
+            }
+
+            for ( int invocationIndex = 1;; invocationIndex++ )
+            {
+                if ( invocationIndex > 1 && !invokerProperties.isInvocationDefined( invocationIndex ) )
+                {
+                    break;
+                }
+
+                request.setBaseDirectory( basedir );
+
+                request.setPomFile( pomFile );
+
+                request.setGoals( goals );
+
+                request.setProfiles( profiles );
+
+                request.setMavenOpts( mavenOpts );
+
+                request.setOffline( false );
+
+                request.setUserSettingsFile( settingsFile );
+
+                Properties systemProperties =
+                    getSystemProperties( basedir, invokerProperties.getSystemPropertiesFile( invocationIndex ) );
+                request.setProperties( systemProperties );
+
+                invokerProperties.configureInvocation( request, invocationIndex );
+
+                if ( getLog().isDebugEnabled() )
+                {
+                    try
+                    {
+                        getLog().debug( "Using MAVEN_OPTS: " + request.getMavenOpts() );
+                        getLog().debug( "Executing: " + new MavenCommandLineBuilder().build( request ) );
+                    }
+                    catch ( CommandLineConfigurationException e )
+                    {
+                        getLog().debug( "Failed to display command line: " + e.getMessage() );
+                    }
+                }
+
+                InvocationResult result;
+
+                try
+                {
+                    result = invoker.execute( request );
+                }
+                catch ( final MavenInvocationException e )
+                {
+                    getLog().debug( "Error invoking Maven: " + e.getMessage(), e );
+                    throw new RunFailureException( "Maven invocation failed. " + e.getMessage(),
+                                                   BuildJob.Result.FAILURE_BUILD );
+                }
+
+                verify( result, invocationIndex, invokerProperties, logger );
+            }
+
+            scriptRunner.run( "post-build script", basedir, postBuildHookScript, context, logger,
+                              BuildJob.Result.FAILURE_POST_HOOK, true );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( e.getMessage(), e );
+        }
+        finally
+        {
+            if ( logger != null )
+            {
+                logger.close();
+            }
+        }
+        return true;
+    }
+
+    private void setupLoggerForBuildJob( FileLogger logger, final InvocationRequest request )
+    {
+        if ( logger != null )
+        {
+            request.setErrorHandler( logger );
+
+            request.setOutputHandler( logger );
+        }
+    }
+
+    /**
+     * Initializes the build logger for the specified project. This will write the logging information into
+     * {@code build.log}.
+     *
+     * @param basedir The base directory of the project, must not be <code>null</code>.
+     * @return The build logger or <code>null</code> if logging has been disabled.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the log file could not be created.
+     */
+    private FileLogger setupBuildLogFile( File basedir )
+        throws MojoExecutionException
+    {
+        FileLogger logger = null;
+
+        if ( !noLog )
+        {
+            File outputLog = new File( basedir, "build.log" );
+            try
+            {
+                if ( streamLogs )
+                {
+                    logger = new FileLogger( outputLog, getLog() );
+                }
+                else
+                {
+                    logger = new FileLogger( outputLog );
+                }
+
+                getLog().debug( "Build log initialized in: " + outputLog );
+            }
+            catch ( IOException e )
+            {
+                throw new MojoExecutionException( "Error initializing build logfile in: " + outputLog, e );
+            }
+        }
+
+        return logger;
+    }
+
+    /**
+     * Gets the system properties to use for the specified project.
+     *
+     * @param basedir The base directory of the project, must not be <code>null</code>.
+     * @param filename The filename to the properties file to load, may be <code>null</code> to use the default path
+     *            given by {@link #testPropertiesFile}.
+     * @return The system properties to use, may be empty but never <code>null</code>.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the properties file exists but could not be read.
+     */
+    private Properties getSystemProperties( final File basedir, final String filename )
+        throws MojoExecutionException
+    {
+        Properties collectedTestProperties = new Properties();
+
+        if ( properties != null )
+        {
+            // MINVOKER-118: property can have empty value, which is not accepted by collectedTestProperties
+            for ( Map.Entry<String, String> entry : properties.entrySet() )
+            {
+                if ( entry.getValue() != null )
+                {
+                    collectedTestProperties.put( entry.getKey(), entry.getValue() );
+                }
+            }
+        }
+
+        File propertiesFile = null;
+        if ( filename != null )
+        {
+            propertiesFile = new File( basedir, filename );
+        }
+        else if ( testPropertiesFile != null )
+        {
+            propertiesFile = new File( basedir, testPropertiesFile );
+        }
+
+        if ( propertiesFile != null && propertiesFile.isFile() )
+        {
+            InputStream fin = null;
+            try
+            {
+                fin = new FileInputStream( propertiesFile );
+
+                Properties loadedProperties = new Properties();
+                loadedProperties.load( fin );
+                fin.close();
+                fin = null;
+                collectedTestProperties.putAll( loadedProperties );
+            }
+            catch ( IOException e )
+            {
+                throw new MojoExecutionException( "Error reading system properties from " + propertiesFile );
+            }
+            finally
+            {
+                IOUtil.close( fin );
+            }
+        }
+
+        return collectedTestProperties;
+    }
+
+    /**
+     * Verifies the invocation result.
+     *
+     * @param result The invocation result to check, must not be <code>null</code>.
+     * @param invocationIndex The index of the invocation for which to check the exit code, must not be negative.
+     * @param invokerProperties The invoker properties used to check the exit code, must not be <code>null</code>.
+     * @param logger The build logger, may be <code>null</code> if logging is disabled.
+     * @throws org.apache.maven.shared.scriptinterpreter.RunFailureException If the invocation result indicates a build
+     *             failure.
+     */
+    private void verify( InvocationResult result, int invocationIndex, InvokerProperties invokerProperties,
+                         FileLogger logger )
+        throws RunFailureException
+    {
+        if ( result.getExecutionException() != null )
+        {
+            throw new RunFailureException( "The Maven invocation failed. "
+                + result.getExecutionException().getMessage(), BuildJob.Result.ERROR );
+        }
+        else if ( !invokerProperties.isExpectedResult( result.getExitCode(), invocationIndex ) )
+        {
+            StringBuilder buffer = new StringBuilder( 256 );
+            buffer.append( "The build exited with code " ).append( result.getExitCode() ).append( ". " );
+            if ( logger != null )
+            {
+                buffer.append( "See " );
+                buffer.append( logger.getOutputFile().getAbsolutePath() );
+                buffer.append( " for details." );
+            }
+            else
+            {
+                buffer.append( "See console output for details." );
+            }
+            throw new RunFailureException( buffer.toString(), BuildJob.Result.FAILURE_BUILD );
+        }
+    }
+
+    /**
+     * Gets the goal list for the specified project.
+     *
+     * @param basedir The base directory of the project, must not be <code>null</code>.
+     * @return The list of goals to run when building the project, may be empty but never <code>null</code>.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the profile file could not be read.
+     */
+    List<String> getGoals( final File basedir )
+        throws MojoExecutionException
+    {
+        try
+        {
+            // FIXME: Currently we have null for goalsFile which has been removed.
+            // This might mean we can remove getGoals() at all ? Check this.
+            return getTokens( basedir, null, goals );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "error reading goals", e );
+        }
+    }
+
+    /**
+     * Gets the profile list for the specified project.
+     *
+     * @param basedir The base directory of the project, must not be <code>null</code>.
+     * @return The list of profiles to activate when building the project, may be empty but never <code>null</code>.
+     * @throws org.apache.maven.plugin.MojoExecutionException If the profile file could not be read.
+     */
+    List<String> getProfiles( File basedir )
+        throws MojoExecutionException
+    {
+        try
+        {
+            return getTokens( basedir, null, profiles );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "error reading profiles", e );
+        }
+    }
+
+    private List<String> calculateExcludes()
+        throws IOException
+    {
+        List<String> excludes =
+            ( pomExcludes != null ) ? new ArrayList<String>( pomExcludes ) : new ArrayList<String>();
+        if ( this.settingsFile != null )
+        {
+            String exclude = relativizePath( this.settingsFile, projectsDirectory.getCanonicalPath() );
+            if ( exclude != null )
+            {
+                excludes.add( exclude.replace( '\\', '/' ) );
+                getLog().debug( "Automatically excluded " + exclude + " from project scanning" );
+            }
+        }
+        return excludes;
+
+    }
+
+    /**
+     * @return The list of setupUp jobs.
+     * @throws IOException
+     * @see {@link #setupIncludes}
+     */
+    private BuildJob[] getSetupBuildJobsFromFolders()
+        throws IOException
+    {
+        List<String> excludes = calculateExcludes();
+
+        BuildJob[] setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP );
+        if ( getLog().isDebugEnabled() )
+        {
+            getLog().debug( "Setup projects: " + Arrays.asList( setupPoms ) );
+        }
+
+        return setupPoms;
+    }
+
+    /**
+     * 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
+    {
+        BuildJob[] buildJobs;
+
+        if ( invokerTest == null )
+        {
+            List<String> excludes = calculateExcludes();
+
+            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 );
+
+            Map<String, BuildJob> uniquePoms = new LinkedHashMap<String, BuildJob>();
+            for ( BuildJob setupPom : setupPoms )
+            {
+                uniquePoms.put( setupPom.getProject(), setupPom );
+            }
+            for ( BuildJob normalPom : normalPoms )
+            {
+                if ( !uniquePoms.containsKey( normalPom.getProject() ) )
+                {
+                    uniquePoms.put( normalPom.getProject(), normalPom );
+                }
+            }
+
+            buildJobs = uniquePoms.values().toArray( new BuildJob[uniquePoms.size()] );
+        }
+        else
+        {
+            String[] testRegexes = StringUtils.split( invokerTest, "," );
+            List<String> includes = new ArrayList<String>( testRegexes.length );
+            List<String> excludes = new ArrayList<String>();
+
+            for ( String regex : testRegexes )
+            {
+                // user just use -Dinvoker.test=MWAR191,MNG111 to use a directory thats the end is not pom.xml
+                if ( regex.startsWith( "!" ) )
+                {
+                    excludes.add( regex.substring( 1 ) );
+                }
+                else
+                {
+                    includes.add( regex );
+                }
+            }
+
+            // it would be nice if we could figure out what types these are... but perhaps
+            // not necessary for the -Dinvoker.test=xxx t
+            buildJobs = scanProjectsDirectory( includes, excludes, BuildJob.Type.DIRECT );
+        }
+
+        relativizeProjectPaths( buildJobs );
+
+        return buildJobs;
+    }
+
+    /**
+     * Scans the projects directory for projects to build. Both (POM) files and mere directories will be matched by the
+     * scanner patterns. If the patterns match a directory which contains a file named "pom.xml", the results will
+     * include the path to this file rather than the directory path in order to avoid duplicate invocations of the same
+     * project.
+     *
+     * @param includes The include patterns for the scanner, may be <code>null</code>.
+     * @param excludes The exclude patterns for the scanner, may be <code>null</code> to exclude nothing.
+     * @param type The type to assign to the resulting build jobs, must not be <code>null</code>.
+     * @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
+    {
+        if ( !projectsDirectory.isDirectory() )
+        {
+            return new BuildJob[0];
+        }
+
+        DirectoryScanner scanner = new DirectoryScanner();
+        scanner.setBasedir( projectsDirectory.getCanonicalFile() );
+        scanner.setFollowSymlinks( false );
+        if ( includes != null )
+        {
+            scanner.setIncludes( includes.toArray( new String[includes.size()] ) );
+        }
+        if ( excludes != null )
+        {
+            scanner.setExcludes( excludes.toArray( new String[excludes.size()] ) );
+        }
+        scanner.addDefaultExcludes();
+        scanner.scan();
+
+        Map<String, BuildJob> matches = new LinkedHashMap<String, BuildJob>();
+
+        for ( String includedFile : scanner.getIncludedFiles() )
+        {
+            matches.put( includedFile, new BuildJob( includedFile, type ) );
+        }
+
+        for ( String includedDir : scanner.getIncludedDirectories() )
+        {
+            String includedFile = includedDir + File.separatorChar + "pom.xml";
+            if ( new File( scanner.getBasedir(), includedFile ).isFile() )
+            {
+                matches.put( includedFile, new BuildJob( includedFile, type ) );
+            }
+            else
+            {
+                matches.put( includedDir, new BuildJob( includedDir, type ) );
+            }
+        }
+
+        return matches.values().toArray( new BuildJob[matches.size()] );
+    }
+
+    /**
+     * Relativizes the project paths of the specified build jobs against the directory specified by
+     * {@link #projectsDirectory} (if possible). If a project path does not denote a sub path of the projects directory,
+     * it is returned as is.
+     *
+     * @param buildJobs The build jobs whose project paths should be relativized, must not be <code>null</code> nor
+     *            contain <code>null</code> elements.
+     * @throws java.io.IOException If any path could not be relativized.
+     */

[... 317 lines stripped ...]


Mime
View raw message