All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.invoker.AbstractInvokerMojo Maven / Gradle / Ivy

Go to download

The Maven Invoker Plugin is used to run a set of Maven projects. The plugin can determine whether each project execution is successful, and optionally can verify the output generated from a given project execution.

The newest version!
/*
 * 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.
 */
package org.apache.maven.plugins.invoker;

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.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Model;
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.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.invoker.model.BuildJob;
import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Writer;
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.ScriptException;
import org.apache.maven.shared.scriptinterpreter.ScriptReturnException;
import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
import org.apache.maven.shared.utils.logging.MessageBuilder;
import org.apache.maven.toolchain.MisconfiguredToolchainException;
import org.apache.maven.toolchain.ToolchainManagerPrivate;
import org.apache.maven.toolchain.ToolchainPrivate;
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.NioFiles;
import org.codehaus.plexus.util.ReflectionUtils;
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 org.codehaus.plexus.util.xml.XmlStreamReader;
import org.codehaus.plexus.util.xml.XmlStreamWriter;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;

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 {
    private static final float ONE_SECOND = 1000.0f;

    /**
     * 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 true, 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.
     *
     * @since 1.0
     */
    @Parameter(defaultValue = "false")
    protected boolean suppressSummaries;

    /**
     * Flag used to determine whether the build logs should be output to the normal mojo log.
     *
     * @since 1.0
     */
    @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 ${project.build.directory}/it-repo. Otherwise, your ordinary local repository will
     * be used, potentially soiling it with broken artifacts.
     *
     * @since 1.0
     */
    @Parameter(property = "invoker.localRepositoryPath", defaultValue = "${settings.localRepository}")
    private File localRepositoryPath;

    /**
     * Directory to search for integration tests.
     *
     * @since 1.0
     */
    @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 build-job 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 set to {@code null}, 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 target and build.log in the test's base directory.
     * (Exception when project using invoker plugin is of maven-plugin packaging:
     * In such case IT projects will be cloned to and executed in target/its by default.)
     *
     * @since 1.1
     */
    @Parameter(property = "invoker.cloneProjectsTo")
    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. .svn, CVS,
     * *~, etc: see 
     * reference for full list). Setting this parameter to true will cause all files to be copied to
     * the cloneProjectsTo 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 = "true")
    private boolean cloneClean;

    /**
     * A single POM to build, skipping any scanning parameters and behavior.
     *
     * @since 1.0
     */
    @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 pom.xml files one
     * directory below {@link #projectsDirectory} (i.e. */pom.xml).
*
* Starting with version 1.3, mere directories can also be matched by these patterns. For example, the include * pattern * will run Maven builds on all immediate sub directories of {@link #projectsDirectory}, * regardless if they contain a pom.xml. This allows to perform builds that need/should not depend on * the existence of a POM. * * @since 1.0 */ @Parameter private List 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 *, the * custom settings file specified by the parameter {@link #settingsFile} will always be excluded automatically. * * @since 1.0 */ @Parameter private List 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: setup*/pom.xml. * * @since 1.3 */ @Parameter private List setupIncludes = Collections.singletonList("setup*/pom.xml"); /** * The list of goals to execute on each project. Default value is: package. * * @since 1.0 */ @Parameter private List goals = Collections.singletonList("package"); /** * 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. selector), * the plugin searches for the file by trying out the well-known extensions .bsh and * .groovy. If this script exists for a particular project but returns any non-null value different * from true, 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. prebuild), the plugin * searches for the file by trying out the well-known extensions .bsh and .groovy. If this * script exists for a particular project but returns any non-null value different from true 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. * * @since 1.0 */ @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. verify), the * plugin searches for the file by trying out the well-known extensions .bsh and .groovy. * If this script exists for a particular project but returns any non-null value different from true or * throws an exception, the corresponding build is flagged as a failure. * * @since 1.0 */ @Parameter(property = "invoker.postBuildHookScript", defaultValue = "postbuild") private String postBuildHookScript; /** * Location of a properties file that defines CLI properties for the test. * * @since 1.0 */ @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 properties; /** * Whether to show errors in the build output. * * @since 1.0 */ @Parameter(property = "invoker.showErrors", defaultValue = "false") private boolean showErrors; /** * Whether to show debug statements in the build output. * * @since 1.0 */ @Parameter(property = "invoker.debug", defaultValue = "false") private boolean debug; /** * Whether to execute Maven in quiet mode. * * @since 3.3.0 */ @Parameter(property = "invoker.quiet", defaultValue = "false") private boolean quiet; /** * Suppress logging to the build.log file. * * @since 1.0 */ @Parameter(property = "invoker.noLog", defaultValue = "false") private boolean noLog; /** * By default a {@code build.log} is created in the root of the project. By setting this folder * files are written to a different folder, respecting the structure of the projectsDirectory. * * @since 3.2.0 */ @Parameter private File logDirectory; /** * List of profile identifiers to explicitly trigger in the build. * * @since 1.1 */ @Parameter private List profiles; /** * A list of additional properties which will be used to filter tokens in POMs and goal files. * * @since 1.3 */ @Parameter private Map filterProperties; /** * 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 * ${projectsDirectory}/pattern. To exclude a test, prefix the pattern with a '!'. * So you can just type -Dinvoker.test=SimpleTest,Comp*Test,!Compare* to run builds in * ${projectsDirectory}/SimpleTest and ${projectsDirectory}/ComplexTest, but not * ${projectsDirectory}/CompareTest * * @since 1.1 (exclusion since 1.8) */ @Parameter(property = "invoker.test") private String invokerTest; /** * Path to an alternate settings.xml to use for Maven invocation with all ITs. Note that the * <localRepository> 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 MAVEN_OPTS environment variable to use when invoking Maven. This value can be overridden for * individual integration tests by using {@link #invokerPropertiesFile}. *
* Since the version 3.7.0 using an alternate syntax for mavenOpts, @{...} * allows late replacement of properties when the plugin is executed, * so properties that have been modified by other plugins will be picked up correctly. * * @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 ${maven.home}/bin/, test project workspace * or an absolute file. * * @since 1.8 */ @Parameter(property = "invoker.mavenExecutable") private String mavenExecutable; /** * The JAVA_HOME 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; /** * 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 false, the class path of script interpreter consists only of the * runtime dependencies of the Maven Invoker Plugin. If set the true, * 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 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 * ${project.version} to reference project properties or values from the parameter * {@link #filterProperties}.

* *

* As of 3.2.0 it is possible to put this folder in any of the ancestor folders, where properties will be inherited. * This way you can provide a single properties file for a group of projects *

* * The snippet below describes the supported properties: *
     * # 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
     * # can be indexed
     * 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
     * # can be indexed
     * invoker.project = sub-module
     *
     * # The maven executable can either be a file relative to ${maven.home}/bin/, test project workspace
     * # or an absolute file.
     * # Since plugin version 3.3.0
     * # can be indexed
     * invoker.mavenExecutable = mvnw
     *
     * # The value for the environment variable MAVEN_OPTS
     * # can be indexed
     * invoker.mavenOpts = -Dfile.encoding=UTF-16 -Xms32m -Xmx256m
     *
     * # Possible values are "fail-fast" (default), "fail-at-end" and "fail-never"
     * # can be indexed
     * invoker.failureBehavior = fail-never
     *
     * # The expected result of the build, possible values are "success" (default) and "failure"
     * # can be indexed
     * invoker.buildResult = failure
     *
     * # A boolean value controlling the aggregator mode of Maven, defaults to "false"
     *
     * # can be indexed
     * invoker.nonRecursive = true
     *
     * # A boolean value controlling the network behavior of Maven, defaults to "false"
     * # Since plugin version 1.4
     * # can be indexed
     * invoker.offline = true
     *
     * # The path to the properties file from which to load user properties, defaults to the
     * # filename given by the plugin parameter testPropertiesFile
     * # Since plugin version 1.4
     * # can be indexed
     * invoker.userPropertiesFile = test.properties
     *
     * # An optional human friendly name and description for this build job.
     * # Both name and description have to be set to be included in the build reports.
     * # Since plugin version 1.4
     * invoker.name = Test Build 01
     * 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
     *
     * # A mapping for toolchain to ensure it exists
     * # Since plugin version 3.2.0
     * invoker.toolchain.<type>.<provides> = value
     * invoker.toolchain.jdk.version = 11
     *
     * # For java.version, maven.version, os.family and toolchain 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 "false"
     * # Since plugin version 1.8
     * # can be indexed
     * invoker.debug = true
     *
     * # Whether to execute Maven in quiet mode
     * # Since plugin version 3.3.0
     * # can be indexed
     * invoker.quiet = true
     *
     * The execution timeout in seconds.
     * # Since plugin version 3.0.2
     * # can be indexed
     * invoker.timeoutInSeconds = 5
     *
     * # Path to an alternate settings.xml to use for Maven invocation with this IT.
     * # Since plugin version 3.0.1
     * # can be indexed
     * invoker.settingsFile = ../
     *
     * # An integer value to control run order of projects. sorted in the descending order of the ordinal.
     * # In other words, the BuildJobs with the highest numbers will be executed first
     * # Default value is 0 (zero)
     * # Since plugin version 3.2.1
     * invoker.ordinal = 3
     *
     * # The additional value for the environment variable.
     * # Since plugin version 3.2.2
     * invoker.environmentVariables.<variableName> = variableValue
     * invoker.environmentVariables.MY_ENV_NAME = myEnvValue
     *
     * # A boolean value indicating a check for missing releases and updated snapshots on remote repositories to be done
     * # Passed to the invoker. Same as passing -U, --update-snapshots flag on the command line
     * # Since plugin version 3.4.0
     * invoker.updateSnapshots = true
     *
     * 
* * @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. * When terminated with "C", the number part is multiplied by the number of processors (cores) available * to the Java virtual machine. Floating point value are only accepted together with "C".

* *

Example values: "1.5C", "4"

* * @since 1.6 */ @Parameter(property = "invoker.parallelThreads", defaultValue = "1") private String parallelThreads; /** * 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 environmentVariables; /** * Additional variables for use in the hook scripts. * * @since 1.9 */ @Parameter private Map scriptVariables; /** * * @since 3.0.2 */ @Parameter(defaultValue = "0", property = "invoker.timeoutInSeconds") private int timeoutInSeconds; /** * Write test result in junit format. * @since 3.1.2 */ @Parameter(defaultValue = "false", property = "invoker.writeJunitReport") private boolean writeJunitReport; /** * The package name use in junit report * @since 3.1.2 */ @Parameter(defaultValue = "maven.invoker.it", property = "invoker.junitPackageName") private String junitPackageName = "maven.invoker.it"; /** * Only invoke maven projects if their sources have been modified since * they were last built. Only works in conjunction with cloneProjectsTo. * * @since 3.2.2 */ @Parameter(defaultValue = "false", property = "invoker.updateOnly") private boolean updateOnly = false; /** * Force a check for missing releases and updated snapshots on remote repositories. This is passed to the invoked * maven projects (it is the same as if you were to use the -U, --update-snapshots flag on the command line). * * @since 3.4.0 */ @Parameter(defaultValue = "false", property = "invoker.updateSnapshots") private boolean updateSnapshots; /** * Projects that are cloned undergo some filtering. In order to grab all projects and make sure * they are all filtered, projects are read and parsed. In some cases, this may not be a desired * behavior (especially when some pom.xml cannot be parsed by Maven directly). In such cases, * the exact list of projects can be set using this field, avoiding the parsing of all pom.xml * files found. * * @since 3.9.0 */ @Parameter private List collectedProjects; // internal state variables /** * 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 null. This will be set to * null if the POMs have already been filtered during cloning. */ private String filteredPomPrefix = "interpolated-"; /** * The version of Maven which is used to run the builds */ private String actualMavenVersion; // used components - readonly parameters @Parameter(property = "plugin.artifacts", required = true, readonly = true) private List pluginArtifacts; @Parameter(defaultValue = "${project.testClasspathElements}", readonly = true) private List testClassPath; @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true) private MojoExecution mojoExecution; @Parameter(defaultValue = "${project}", readonly = true, required = true) private MavenProject project; @Parameter(defaultValue = "${session}", readonly = true, required = true) private MavenSession session; @Parameter(defaultValue = "${settings}", readonly = true, required = true) private Settings settings; @Component private Invoker invoker; @Component private SettingsBuilder settingsBuilder; @Component private ToolchainManagerPrivate toolchainManagerPrivate; @Component private InterpolatorUtils interpolatorUtils; /** * 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 (encoding == null || encoding.isEmpty()) { getLog().warn("File encoding has not been set, using platform encoding " + Charset.defaultCharset().displayName() + ", i.e. build is platform dependent!"); } // done it here to prevent issues with concurrent access in case of parallel run if (!disableReports) { setupReportsFolder(); } List 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 = Collections.singletonList(new BuildJob(pom.getName())); } if (buildJobs.isEmpty()) { doFailIfNoProjects(); getLog().info("No projects were selected for execution."); return; } setupActualMavenVersion(); handleScriptRunnerWithScriptClassPath(); File projectsDir = projectsDirectory; if (cloneProjectsTo == null && "maven-plugin".equals(project.getPackaging())) { cloneProjectsTo = new File(project.getBuild().getDirectory(), "its"); } if (updateOnly) { if (cloneProjectsTo == null) { getLog().warn("updateOnly functionality is not supported without cloning the projects"); } else if (lastModifiedRecursive(projectsDirectory) <= lastModifiedRecursive(cloneProjectsTo)) { getLog().debug("Skipping invocation as cloned projects are up-to-date " + "and updateOnly parameter is set to true."); return; } else { getLog().debug("Cloned projects are out of date"); } } if (cloneProjectsTo != null) { Collection collectedProjects = this.collectedProjects; if (collectedProjects == null) { collectedProjects = new LinkedHashSet<>(); for (BuildJob buildJob : buildJobs) { collectProjects(projectsDirectory, buildJob.getProject(), collectedProjects, true); } } cloneProjects(collectedProjects); addMissingDotMvnDirectory(cloneProjectsTo, buildJobs); projectsDir = cloneProjectsTo; } else { getLog().warn("Filtering of parent/child POMs is not supported without cloning the projects"); } // First run setup jobs. List setupBuildJobs = getSetupJobs(buildJobs); if (!setupBuildJobs.isEmpty()) { // Run setup jobs in single thread mode. // // Jobs are ordered according to ordinal value from invoker.properties getLog().info("Running " + setupBuildJobs.size() + " setup job" + ((setupBuildJobs.size() < 2) ? "" : "s") + ":"); runBuilds(projectsDir, setupBuildJobs, 1); getLog().info("Setup done."); } List nonSetupBuildJobs = getNonSetupJobs(buildJobs); if (setupBuildJobs.isEmpty() || setupBuildJobs.stream().allMatch(BuildJob::isNotError)) { // We will run the non setup jobs with the configured // parallelThreads number. runBuilds(projectsDir, nonSetupBuildJobs, getParallelThreadsCount()); } else { for (BuildJob buildJob : nonSetupBuildJobs) { buildJob.setResult(BuildJob.Result.SKIPPED); buildJob.setFailureMessage("Skipped due to setup job(s) failure"); writeBuildReport(buildJob); } } writeSummaryFile(buildJobs); processResults(new InvokerSession(buildJobs)); } /** * We need add missing {@code .mnvn} directories for executing projects * * @param projectsDir base of projects * @param buildJobs list of discovered jobs */ private void addMissingDotMvnDirectory(File projectsDir, List buildJobs) throws MojoExecutionException { for (BuildJob buildJob : buildJobs) { Path projectPath = projectsDir.toPath().resolve(buildJob.getProject()); if (Files.isRegularFile(projectPath)) { projectPath = projectPath.getParent(); } Path mvnDotPath = projectPath.resolve(".mvn"); if (!Files.exists(mvnDotPath)) { try { Files.createDirectories(mvnDotPath); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } } } private void setupActualMavenVersion() throws MojoExecutionException { if (mavenHome != null) { try { actualMavenVersion = SelectorUtils.getMavenVersion(mavenHome); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } else { actualMavenVersion = SelectorUtils.getMavenVersion(); } } /** * Find the latest lastModified recursively within a directory structure. * * @param file the root file to check. * @return the latest lastModified time found. */ private long lastModifiedRecursive(File file) { long lastModified = file.lastModified(); final File[] entries = file.listFiles(); if (entries != null) { for (File entry : entries) { lastModified = Math.max(lastModified, lastModifiedRecursive(entry)); } } return lastModified; } /** * 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 List getSetupJobs(List buildJobs) { return buildJobs.stream() .filter(buildJob -> buildJob.getType().equals(BuildJob.Type.SETUP)) .collect(Collectors.toList()); } private List getNonSetupJobs(List buildJobs) { return buildJobs.stream() .filter(buildJob -> !buildJob.getType().equals(BuildJob.Type.SETUP)) .collect(Collectors.toList()); } private void handleScriptRunnerWithScriptClassPath() { final List scriptClassPath; if (addTestClassPath) { scriptClassPath = new ArrayList<>(testClassPath); for (Artifact pluginArtifact : pluginArtifacts) { scriptClassPath.remove(pluginArtifact.getFile().getAbsolutePath()); } } else { scriptClassPath = null; } scriptRunner = new ScriptRunner(); scriptRunner.setScriptEncoding(encoding); scriptRunner.setGlobalVariable("localRepositoryPath", localRepositoryPath); scriptRunner.setGlobalVariable("mavenVersion", actualMavenVersion); if (scriptVariables != null) { scriptVariables.forEach((key, value) -> scriptRunner.setGlobalVariable(key, value)); } scriptRunner.setClassPath(scriptClassPath); } private void writeSummaryFile(List buildJobs) throws MojoExecutionException { File summaryReportFile = new File(reportsDirectory, "invoker-summary.txt"); try (Writer writer = new BufferedWriter(new FileWriter(summaryReportFile))) { for (BuildJob buildJob : buildJobs) { if (!BuildJob.Result.SUCCESS.equals(buildJob.getResult())) { 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"); } } } 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 null. * @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; /** * 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 null. * @param projectPath The relative path of the current project, can denote either the POM or its base directory, * must not be null. * @param projectPaths The set of already collected projects to add new projects to, must not be null. * 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 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 && 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 modulePaths = new LinkedHashSet<>(model.getModules()); model.getProfiles().forEach(profile -> 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); } } private boolean isNotEmpty(String s) { return Objects.nonNull(s) && !s.isEmpty(); } /** * 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 * null nor contain null elements. * @throws org.apache.maven.plugin.MojoExecutionException If the projects could not be copied/filtered. */ private void cloneProjects(Collection 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 dirs = new LinkedHashSet<>(); 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 clonedSubpaths = new ArrayList<>(); 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 = Files.createTempDirectory("pre-invocation-clone.") .toFile(); 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 null. * @return The parent path or "." if the specified path has no parent, never null. */ 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 null. * @param destDir The target directory to copy to, must not be null. * @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. */ Files.createDirectories(destDir.toPath()); // 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); if (NioFiles.isSymbolicLink(sourceFile)) { NioFiles.createSymbolicLink(destFile, NioFiles.readSymbolicLink(sourceFile)); } else { 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 null. * @param clonedSubpaths The list of already cloned paths, must not be null nor contain * null elements. * @return true if the specified path has already been cloned, false otherwise. */ static boolean alreadyCloned(String subpath, List 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 null. * @param buildJobs The build jobs to run must not be null nor contain null elements. * @throws org.apache.maven.plugin.MojoExecutionException If any build could not be launched. */ private void runBuilds(final File projectsDir, List buildJobs, int runWithParallelThreads) throws MojoExecutionException { if (!localRepositoryPath.exists()) { localRepositoryPath.mkdirs(); } // ----------------------------------------------- // interpolate settings file // ----------------------------------------------- File interpolatedSettingsFile = interpolateSettings(settingsFile); final File mergedSettingsFile = mergeSettings(interpolatedSettingsFile); final CharSequence actualJreVersion; // @todo if ( javaVersions ) ... to be picked up from toolchains if (javaHome != null) { actualJreVersion = resolveExternalJreVersion(); } else { actualJreVersion = SelectorUtils.getJreVersion(); } final Path projectsPath = this.projectsDirectory.toPath(); Set folderGroupSet = new HashSet<>(); folderGroupSet.add(Paths.get(".")); for (BuildJob buildJob : buildJobs) { Path p = Paths.get(buildJob.getProject()); if (Files.isRegularFile(projectsPath.resolve(p))) { p = p.getParent(); } if (p != null) { p = p.getParent(); } while (p != null && folderGroupSet.add(p)) { p = p.getParent(); } } List folderGroup = new ArrayList<>(folderGroupSet); Collections.sort(folderGroup); final Map globalInvokerProperties = new HashMap<>(); for (Path path : folderGroup) { Properties ancestorProperties = globalInvokerProperties.get(projectsPath.resolve(path).getParent()); Path currentInvokerProperties = projectsPath.resolve(path).resolve(invokerPropertiesFile); Properties currentProperties; if (Files.isRegularFile(currentInvokerProperties)) { if (ancestorProperties != null) { currentProperties = new Properties(ancestorProperties); } else { currentProperties = new Properties(); } } else { currentProperties = ancestorProperties; } if (Files.isRegularFile(currentInvokerProperties)) { try (InputStream in = new FileInputStream(currentInvokerProperties.toFile())) { currentProperties.load(in); } catch (IOException e) { throw new MojoExecutionException("Failed to read invoker properties: " + currentInvokerProperties); } } if (currentProperties != null) { globalInvokerProperties.put(projectsPath.resolve(path).normalize(), currentProperties); } } try { if (runWithParallelThreads > 1) { getLog().info("use parallelThreads " + runWithParallelThreads); } JobExecutor jobExecutor = new JobExecutor(buildJobs, runWithParallelThreads); jobExecutor.forEach(job -> { Path ancestorFolder = getAncestorFolder(projectsPath.resolve(job.getProject())); runBuild( projectsDir, job, mergedSettingsFile, javaHome, actualJreVersion, globalInvokerProperties.get(ancestorFolder)); }); } finally { if (interpolatedSettingsFile != null && cloneProjectsTo == null) { interpolatedSettingsFile.delete(); } if (mergedSettingsFile != null && mergedSettingsFile.exists()) { mergedSettingsFile.delete(); } } } private Path getAncestorFolder(Path p) { Path ancestor = p; if (Files.isRegularFile(ancestor)) { ancestor = ancestor.getParent(); } if (ancestor != null) { ancestor = ancestor.getParent(); } return ancestor; } /** * Interpolate settings.xml file. * @param settingsFile a settings file * * @return The interpolated settings.xml file. * @throws MojoExecutionException in case of a problem. */ private File interpolateSettings(File settingsFile) 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 = Files.createTempFile("invoker-settings", ".xml").toFile(); SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer(); try (FileWriter fileWriter = new FileWriter(mergedSettingsFile)) { settingsWriter.write(fileWriter, mergedSettings); } 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 = actualJreVersion::append; 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 (filteredPomPrefix != null && !filteredPomPrefix.isEmpty()) { 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 null. * @param buildJob The build job to run, must not be null. * @param settingsFile The (already interpolated) user settings file for the build, may be null to use * the current user settings. * @param globalInvokerProperties * @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, Properties globalInvokerProperties) 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()).build()); InvokerProperties invokerProperties = getInvokerProperties(basedir, globalInvokerProperties); // 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 startTime = System.currentTimeMillis(); boolean executed; FileLogger buildLogger = setupBuildLogFile(basedir); if (buildLogger != null) { buildJob.setBuildlog(buildLogger.getOutputFile().getAbsolutePath()); } try { executed = runBuild( basedir, interpolatedPomFile, settingsFile, actualJavaHome, invokerProperties, buildLogger); } finally { long elapsedTime = System.currentTimeMillis() - startTime; buildJob.setTime(elapsedTime / ONE_SECOND); if (buildLogger != null) { buildLogger.close(); } } if (executed) { buildJob.setResult(BuildJob.Result.SUCCESS); if (!suppressSummaries) { getLog().info(pad(buildJob).success("SUCCESS").a(' ') + "(" + formatElapsedTime(buildJob.getTime()) + ")"); } } else { buildJob.setResult(BuildJob.Result.SKIPPED); if (!suppressSummaries) { getLog().info(pad(buildJob).warning("SKIPPED").a(' ') + "(" + formatElapsedTime(buildJob.getTime()) + ")"); } } } else { buildJob.setResult(BuildJob.Result.SKIPPED); List messages = new ArrayList<>(); if (selection == Selector.SELECTOR_MULTI) { messages.add("non-matching selectors"); } else { if ((selection & Selector.SELECTOR_MAVENVERSION) != 0) { messages.add("Maven version"); } if ((selection & Selector.SELECTOR_JREVERSION) != 0) { messages.add("JRE version"); } if ((selection & Selector.SELECTOR_OSFAMILY) != 0) { messages.add("OS"); } if ((selection & Selector.SELECTOR_TOOLCHAIN) != 0) { messages.add("Toolchain"); } } String message = String.join(", ", messages); if (!suppressSummaries) { getLog().info(pad(buildJob).warning("SKIPPED") + " due to " + message); } // Abuse failureMessage, the field in the report which should contain the reason for skipping // Consider skipCode + I18N buildJob.setFailureMessage("Skipped due to " + message); } } catch (RunFailureException e) { buildJob.setResult(e.getType()); buildJob.setFailureMessage(e.getMessage()); if (!suppressSummaries) { getLog().info(" " + e.getMessage()); getLog().info(pad(buildJob).failure("FAILED").a(' ') + "(" + formatElapsedTime(buildJob.getTime()) + ")"); } } finally { deleteInterpolatedPomFile(interpolatedPomFile); writeBuildReport(buildJob); } } private MessageBuilder pad(BuildJob buildJob) { MessageBuilder buffer = buffer(128); buffer.a(" "); buffer.a(buildJob.getProject()); int l = 10 + buildJob.getProject().length(); if (l < RESULT_COLUMN) { buffer.a(' '); l++; 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 && (filteredPomPrefix != null && !filteredPomPrefix.isEmpty())) { 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 null. * @return 0 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(), getToolchainPrivateManager()) .getSelection(invokerProperties); } private ToolchainPrivateManager getToolchainPrivateManager() { return new ToolchainPrivateManager(toolchainManagerPrivate, session); } /** * 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 null. * @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); Writer osw = new OutputStreamWriter(fos, buildJob.getModelEncoding())) { BuildJobXpp3Writer writer = new BuildJobXpp3Writer(); writer.write(osw, buildJob); } catch (IOException e) { throw new MojoExecutionException("Failed to write build report " + reportFile, e); } if (writeJunitReport) { writeJunitReport(buildJob, safeFileName); } } private void writeJunitReport(BuildJob buildJob, String safeFileName) throws MojoExecutionException { File reportFile = new File(reportsDirectory, "TEST-" + safeFileName + ".xml"); Xpp3Dom testsuite = new Xpp3Dom("testsuite"); testsuite.setAttribute("name", junitPackageName + "." + safeFileName); testsuite.setAttribute("time", Float.toString(buildJob.getTime())); // set default value for required attributes testsuite.setAttribute("tests", "1"); testsuite.setAttribute("errors", "0"); testsuite.setAttribute("skipped", "0"); testsuite.setAttribute("failures", "0"); Xpp3Dom testcase = new Xpp3Dom("testcase"); testsuite.addChild(testcase); switch (buildJob.getResult()) { case BuildJob.Result.SUCCESS: break; case BuildJob.Result.SKIPPED: testsuite.setAttribute("skipped", "1"); // adding the failure element Xpp3Dom skipped = new Xpp3Dom("skipped"); testcase.addChild(skipped); skipped.setValue(buildJob.getFailureMessage()); break; case BuildJob.Result.ERROR: testsuite.setAttribute("errors", "1"); break; default: testsuite.setAttribute("failures", "1"); // adding the failure element Xpp3Dom failure = new Xpp3Dom("failure"); testcase.addChild(failure); failure.setAttribute("message", buildJob.getFailureMessage()); } testcase.setAttribute("classname", junitPackageName + "." + safeFileName); testcase.setAttribute("name", safeFileName); testcase.setAttribute("time", Float.toString(buildJob.getTime())); Xpp3Dom systemOut = new Xpp3Dom("system-out"); testcase.addChild(systemOut); File buildLogFile = buildJob.getBuildlog() != null ? new File(buildJob.getBuildlog()) : null; if (buildLogFile != null && buildLogFile.exists()) { getLog().debug("fileLogger:" + buildLogFile); try { systemOut.setValue(FileUtils.fileRead(buildLogFile)); } catch (IOException e) { throw new MojoExecutionException("Failed to read logfile " + buildLogFile, e); } } else { getLog().debug(safeFileName + "not exists buildLogFile = " + buildLogFile); } try (FileOutputStream fos = new FileOutputStream(reportFile); Writer osw = new OutputStreamWriter(fos, buildJob.getModelEncoding())) { Xpp3DomWriter.write(osw, testsuite); } catch (IOException e) { throw new MojoExecutionException("Failed to write JUnit build report " + reportFile, e); } } /** * Formats the specified elapsed time. * * @param time The eapsed time of the build. * @return The formatted time, never null. */ private String formatElapsedTime(float time) { /* * Rationale: The idea is to always display four digits for visually consistent output * Important: Keep in sync with src/main/resources/invoker-report.properties */ final MessageFormat elapsedTimeFormat = new MessageFormat( "{0,choice,0#0|0.0<{0,number,0.000}|10#{0,number,0.00}|100#{0,number,0.0}|1000#{0,number,0}} s", Locale.ROOT); return elapsedTimeFormat.format(new Object[] {time}); } /** * Runs the specified project. * * @param basedir The base directory of the project, must not be null. * @param pomFile The (already interpolated) POM file, may be null for a POM-less Maven invocation. * @param settingsFile The (already interpolated) user settings file for the build, may be null. Will * be merged with the settings file of the invoking Maven process. * @param invokerProperties The properties to use. * @param logger file logger to write execution build.log * @return true if the project was launched or false 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 RunFailureException If either a hook script or the build itself * failed. */ private boolean runBuild( File basedir, File pomFile, File settingsFile, File actualJavaHome, InvokerProperties invokerProperties, FileLogger logger) throws MojoExecutionException, RunFailureException { if (getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty()) { Properties props = invokerProperties.getProperties(); getLog().debug("Using invoker properties:"); for (String key : new TreeSet<>(props.stringPropertyNames())) { String value = props.getProperty(key); getLog().debug(" " + key + " = " + value); } } Map context = new LinkedHashMap<>(); Properties scriptUserProperties = new Properties(); context.put("userProperties", scriptUserProperties); if (!runSelectorHook(basedir, context, logger)) { return false; } try { runPreBuildHook(basedir, context, logger); for (int invocationIndex = 1; ; invocationIndex++) { if (invocationIndex > 1 && !invokerProperties.isInvocationDefined(invocationIndex)) { break; } final InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); // values only from Mojo configurations request.setLocalRepositoryDirectory(localRepositoryPath); request.setShowErrors(showErrors); request.setShowVersion(showVersion); request.setJavaHome(actualJavaHome); request.setMavenHome(mavenHome); setupLoggerForBuildJob(logger, request); request.setBaseDirectory(basedir); request.setPomFile(pomFile); String customSettingsFile = invokerProperties.getSettingsFile(invocationIndex); if (customSettingsFile != null) { File interpolateSettingsFile = interpolateSettings(new File(customSettingsFile)); File mergeSettingsFile = mergeSettings(interpolateSettingsFile); request.setUserSettingsFile(mergeSettingsFile); } else { request.setUserSettingsFile(settingsFile); } Properties userProperties = getUserProperties(basedir, invokerProperties.getUserPropertiesFile(invocationIndex)); userProperties.putAll(scriptUserProperties); request.setProperties(userProperties); 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()); } } try { InvocationResult result = invoker.execute(request); verify(result, invocationIndex, invokerProperties, logger); } catch (final MavenInvocationException e) { getLog().debug("Error invoking Maven: " + e.getMessage(), e); throw new RunFailureException( "Maven invocation failed. " + e.getMessage(), BuildJob.Result.FAILURE_BUILD); } } } finally { runPostBuildHook(basedir, context, logger); } return true; } int getParallelThreadsCount() { if (parallelThreads.endsWith("C")) { float parallelThreadsMultiple = Float.parseFloat(parallelThreads.substring(0, parallelThreads.length() - 1)); return (int) (parallelThreadsMultiple * Runtime.getRuntime().availableProcessors()); } else { return Integer.parseInt(parallelThreads); } } private boolean runSelectorHook(File basedir, Map context, FileLogger logger) throws MojoExecutionException, RunFailureException { try { scriptRunner.run("selector script", basedir, selectorScript, context, logger); } catch (ScriptReturnException e) { return false; } catch (ScriptException e) { throw new RunFailureException(BuildJob.Result.ERROR, e); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } return true; } private void runPreBuildHook(File basedir, Map context, FileLogger logger) throws MojoExecutionException, RunFailureException { try { scriptRunner.run("pre-build script", basedir, preBuildHookScript, context, logger); } catch (ScriptException e) { throw new RunFailureException(BuildJob.Result.FAILURE_PRE_HOOK, e); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } private void runPostBuildHook(File basedir, Map context, FileLogger logger) throws MojoExecutionException, RunFailureException { try { scriptRunner.run("post-build script", basedir, postBuildHookScript, context, logger); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (ScriptException e) { throw new RunFailureException(e.getMessage(), BuildJob.Result.FAILURE_POST_HOOK, e); } } private void setupLoggerForBuildJob(final 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 null. * @return The build logger or null 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) { Path projectLogDirectory; if (logDirectory == null) { projectLogDirectory = basedir.toPath(); } else if (cloneProjectsTo != null) { projectLogDirectory = logDirectory.toPath().resolve(cloneProjectsTo.toPath().relativize(basedir.toPath())); } else { projectLogDirectory = logDirectory.toPath().resolve(projectsDirectory.toPath().relativize(basedir.toPath())); } try { if (streamLogs) { logger = new FileLogger( projectLogDirectory.resolve("build.log").toFile(), getLog()); } else { logger = new FileLogger( projectLogDirectory.resolve("build.log").toFile()); } getLog().debug("Build log initialized in: " + projectLogDirectory); } catch (IOException e) { throw new MojoExecutionException("Error initializing build logfile in: " + projectLogDirectory, e); } } return logger; } /** * Gets the system properties to use for the specified project. * * @param basedir The base directory of the project, must not be null. * @param filename The filename to the properties file to load, may be null to use the default path * given by {@link #testPropertiesFile}. * @return The system properties to use, may be empty but never null. * @throws org.apache.maven.plugin.MojoExecutionException If the properties file exists but could not be read. */ private Properties getUserProperties(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 entry : properties.entrySet()) { if (entry.getValue() != null) { collectedTestProperties.put(entry.getKey(), entry.getValue()); } } } File propertiesFile = null; if (filename != null) { propertiesFile = new File(basedir, filename); } if (propertiesFile != null && propertiesFile.isFile()) { try (InputStream fin = Files.newInputStream(propertiesFile.toPath())) { Properties loadedProperties = new Properties(); loadedProperties.load(fin); collectedTestProperties.putAll(loadedProperties); } catch (IOException e) { throw new MojoExecutionException("Error reading user properties from " + propertiesFile); } } return collectedTestProperties; } /** * Verifies the invocation result. * * @param result The invocation result to check, must not be null. * @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 null. * @param logger The build logger, may be null if logging is disabled. */ 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); } } private List calculateIncludes() { if (invokerTest != null) { String[] testRegexes = invokerTest.split(",+"); return Arrays.stream(testRegexes) .map(String::trim) .filter(s -> !s.isEmpty()) .filter(s -> !s.startsWith("!")) .collect(Collectors.toList()); } else { Set uniqueIncludes = new HashSet<>(); uniqueIncludes.addAll(pomIncludes); uniqueIncludes.addAll(setupIncludes); return new ArrayList<>(uniqueIncludes); } } private List calculateExcludes() throws IOException { List excludes; if (invokerTest != null) { String[] testRegexes = invokerTest.split(",+"); excludes = Arrays.stream(testRegexes) .map(String::trim) .filter(s -> !s.isEmpty()) .filter(s -> s.startsWith("!")) .map(s -> s.substring(1)) .collect(Collectors.toList()); } else { excludes = pomExcludes != null ? new ArrayList<>(pomExcludes) : new ArrayList<>(); } 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; } /** * 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 null. * @throws java.io.IOException If the projects directory could not be scanned. */ List getBuildJobs() throws IOException, MojoExecutionException { List includes = calculateIncludes(); List excludes = calculateExcludes(); List buildJobsAll = scanProjectsDirectory(includes, excludes); List buildJobsSetup = scanProjectsDirectory(setupIncludes, excludes); List setupProjects = buildJobsSetup.stream().map(BuildJob::getProject).collect(Collectors.toList()); for (BuildJob job : buildJobsAll) { if (setupProjects.contains(job.getProject())) { job.setType(BuildJob.Type.SETUP); } InvokerProperties invokerProperties = getInvokerProperties(new File(projectsDirectory, job.getProject()).getParentFile(), null); job.setOrdinal(invokerProperties.getOrdinal()); } relativizeProjectPaths(buildJobsAll); return buildJobsAll; } /** * 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 null. * @param excludes The exclude patterns for the scanner, may be null to exclude nothing. * @return The build jobs matching the patterns, never null. * @throws java.io.IOException If the project directory could not be scanned. */ private List scanProjectsDirectory(List includes, List excludes) throws IOException { if (!projectsDirectory.isDirectory()) { return Collections.emptyList(); } DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(projectsDirectory.getCanonicalFile()); scanner.setFollowSymlinks(false); if (includes != null) { scanner.setIncludes(includes.toArray(new String[0])); } if (excludes != null) { if ((includes == null || includes.isEmpty()) && !excludes.isEmpty()) { scanner.setIncludes(new String[] {"*"}); } scanner.setExcludes(excludes.toArray(new String[0])); } scanner.addDefaultExcludes(); scanner.scan(); Map matches = new LinkedHashMap<>(); for (String includedFile : scanner.getIncludedFiles()) { matches.put(includedFile, new BuildJob(includedFile)); } 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)); } else { matches.put(includedDir, new BuildJob(includedDir)); } } return new ArrayList<>(matches.values()); } /** * 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 null nor * contain null elements. * @throws java.io.IOException If any path could not be relativized. */ private void relativizeProjectPaths(List buildJobs) throws IOException { String projectsDirPath = projectsDirectory.getCanonicalPath(); for (BuildJob buildJob : buildJobs) { String projectPath = buildJob.getProject(); File file = new File(projectPath); if (!file.isAbsolute()) { file = new File(projectsDirectory, projectPath); } String relativizedPath = relativizePath(file, projectsDirPath); if (relativizedPath == null) { relativizedPath = projectPath; } buildJob.setProject(relativizedPath); } } /** * Relativizes the specified path against the given base directory. Besides relativization, the returned path will * also be normalized, e.g. directory references like ".." will be removed. * * @param path The path to relativize, must not be null. * @param basedir The (canonical path of the) base directory to relativize against, must not be null. * @return The relative path in normal form or null if the input path does not denote a sub path of the * base directory. * @throws java.io.IOException If the path could not be relativized. */ private String relativizePath(File path, String basedir) throws IOException { String relativizedPath = path.getCanonicalPath(); if (relativizedPath.startsWith(basedir)) { relativizedPath = relativizedPath.substring(basedir.length()); if (relativizedPath.startsWith(File.separator)) { relativizedPath = relativizedPath.substring(File.separator.length()); } return relativizedPath; } else { return null; } } /** * Returns the map-based value source used to interpolate POMs and other stuff. * * @param escapeXml {@code true}, to escape any XML special characters in the property values; {@code false}, to not * escape any property values. * * @return The map-based value source for interpolation, never null. */ private Map getInterpolationValueSource(final boolean escapeXml) { Map props = new HashMap<>(); if (filterProperties != null) { props.putAll(filterProperties); } props.put("basedir", this.project.getBasedir().getAbsolutePath()); props.put("baseurl", toUrl(this.project.getBasedir().getAbsolutePath())); if (settings.getLocalRepository() != null) { props.put("localRepository", settings.getLocalRepository()); props.put("localRepositoryUrl", toUrl(settings.getLocalRepository())); } return new CompositeMap(this.project, props, escapeXml); } /** * Converts the specified filesystem path to a URL. The resulting URL has no trailing slash regardless whether the * path denotes a file or a directory. * * @param filename The filesystem path to convert, must not be null. * @return The file: URL for the specified path, never null. */ private static String toUrl(String filename) { /* * NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI() here * as-is but use the decoded path component in the URL. */ String url = "file://" + new File(filename).toURI().getPath(); if (url.endsWith("/")) { url = url.substring(0, url.length() - 1); } return url; } /** * Interpolates the specified POM/settings file to a temporary file. The destination file may be same as the input * file, i.e. interpolation can be performed in-place. *

* Note:This methods expects the file to be a XML file and applies special XML escaping during interpolation. *

* * @param originalFile The XML file to interpolate, must not be null. * @param interpolatedFile The target file to write the interpolated contents of the original file to, must not be * null. * * @throws org.apache.maven.plugin.MojoExecutionException If the target file could not be created. */ void buildInterpolatedFile(File originalFile, File interpolatedFile) throws MojoExecutionException { getLog().debug("Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath()); try { String xml; Map composite = getInterpolationValueSource(true); // interpolation with token @...@ try (Reader reader = new InterpolationFilterReader(new XmlStreamReader(originalFile), composite, "@", "@")) { xml = IOUtil.toString(reader); } try (Writer writer = new XmlStreamWriter(interpolatedFile)) { interpolatedFile.getParentFile().mkdirs(); writer.write(xml); } } catch (IOException e) { throw new MojoExecutionException("Failed to interpolate file " + originalFile.getPath(), e); } } /** * Gets the (interpolated) invoker properties for an integration test. * * @param projectDirectory The base directory of the IT project, must not be null. * @return The invoker properties, may be empty but never null. * @throws org.apache.maven.plugin.MojoExecutionException If an I/O error occurred during reading the properties. */ private InvokerProperties getInvokerProperties(final File projectDirectory, Properties globalInvokerProperties) throws MojoExecutionException { Properties props; if (globalInvokerProperties != null) { props = new Properties(globalInvokerProperties); } else { props = new Properties(); } File propertiesFile = new File(projectDirectory, invokerPropertiesFile); if (propertiesFile.isFile()) { try (InputStream in = new FileInputStream(propertiesFile)) { props.load(in); } catch (IOException e) { throw new MojoExecutionException("Failed to read invoker properties: " + propertiesFile, e); } } Interpolator interpolator = new RegexBasedInterpolator(); interpolator.addValueSource(new MapBasedValueSource(getInterpolationValueSource(false))); // CHECKSTYLE_OFF: LineLength for (String key : props.stringPropertyNames()) { String value = props.getProperty(key); try { value = interpolator.interpolate(value, ""); } catch (InterpolationException e) { throw new MojoExecutionException("Failed to interpolate invoker properties: " + propertiesFile, e); } props.setProperty(key, value); } InvokerProperties invokerProperties = new InvokerProperties(props); // set default value for Invoker - it will be used if not present in properties invokerProperties.setDefaultDebug(debug); invokerProperties.setDefaultQuiet(quiet); invokerProperties.setDefaultGoals(goals); invokerProperties.setDefaultProfiles(profiles); invokerProperties.setDefaultMavenExecutable(mavenExecutable); invokerProperties.setDefaultMavenOpts(interpolatorUtils.interpolateAtPattern(mavenOpts)); invokerProperties.setDefaultTimeoutInSeconds(timeoutInSeconds); invokerProperties.setDefaultEnvironmentVariables(environmentVariables); invokerProperties.setDefaultUpdateSnapshots(updateSnapshots); invokerProperties.setDefaultUserPropertiesFiles(testPropertiesFile); return invokerProperties; } static class ToolchainPrivateManager { private ToolchainManagerPrivate manager; private MavenSession session; ToolchainPrivateManager(ToolchainManagerPrivate manager, MavenSession session) { this.manager = manager; this.session = session; } ToolchainPrivate[] getToolchainPrivates(String type) throws MisconfiguredToolchainException { return manager.getToolchainsForType(type, session); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy