| /* |
| * 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.archetype.mojos; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.apache.commons.collections.CollectionUtils; |
| import org.apache.maven.archetype.ArchetypeGenerationRequest; |
| import org.apache.maven.archetype.ArchetypeGenerationResult; |
| import org.apache.maven.archetype.common.Constants; |
| import org.apache.maven.archetype.exception.ArchetypeNotConfigured; |
| import org.apache.maven.archetype.generator.ArchetypeGenerator; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.plugin.AbstractMojo; |
| 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.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.DefaultProjectBuildingRequest; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.project.ProjectBuildingRequest; |
| import org.apache.maven.settings.Settings; |
| import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer; |
| 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.MavenInvocationException; |
| import org.apache.maven.shared.scriptinterpreter.RunFailureException; |
| import org.apache.maven.shared.scriptinterpreter.ScriptRunner; |
| import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate; |
| import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver; |
| import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException; |
| 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.StringUtils; |
| import org.codehaus.plexus.util.WriterFactory; |
| import org.codehaus.plexus.util.introspection.ReflectionValueExtractor; |
| |
| /** |
| * <p>Execute the archetype integration tests, consisting in generating projects from the current archetype and optionally |
| * comparing generated projects with reference copy.</p> |
| * |
| * <p>Each IT consists of a sub-directory in <code>src/test/resources/projects</code> containing:</p> |
| * |
| * <ul> |
| * <li>a <code>goal.txt</code> file, containing a list of goals to run against the generated project (can be empty, |
| * content ignored before maven-archetype-plugin 2.1),</li> |
| * <li>an <code>archetype.properties</code> file, containing properties for project generation,</li> |
| * <li>an optional <code>reference/</code> directory containing a reference copy of the expected project created from |
| * the IT.</li> |
| * </ul> |
| * <p>To let the IT create a Maven module below some other Maven project (being generated from another archetype) |
| * one can additionally specify an optional <code>archetype.pom.properties</code> file in the parent directory, |
| * specifying the archetype's <code>groupId</code>, <code>artifactId</code> and <code>version</code> along with its |
| * <code>archetype.properties</code> file, containing properties for project generation. Both files are leveraged |
| * to create the parent project for this IT. Parent projects can be nested.</p> |
| * |
| * <p>An example structure for such an integration test looks like this</p> |
| * <table> |
| * <caption>integration test folder structure</caption> |
| * <tr> |
| * <th>File/Directory</th> |
| * <th>Description</th> |
| * </tr> |
| * <tr> |
| * <td><code>src/test/resources/projects/it1</code></td> |
| * <td>Directory for integration test 1</td> |
| * </tr> |
| * <tr> |
| * <td><code>src/test/resources/projects/it1/archetype.pom.properties</code></td> |
| * <td>GAV for the archetype from which to generate the parent</td> |
| * </tr> |
| * <tr> |
| * <td><code>src/test/resources/projects/it1/archetype.properties</code></td> |
| * <td>All required properties for the archetype being specified by <code>archetype.pom.properties</code> on this level</td> |
| * </tr> |
| * <tr> |
| * <td><code>src/test/resources/projects/it1/child</code></td> |
| * <td>Directory for maven module within integration test 1 (this folder's name is not relevant)</td> |
| * </tr> |
| * <tr> |
| * <td><code>src/test/resources/projects/it1/child/goal.txt</code></td> |
| * <td>The file containing the list of goals to be executed against the generated project</td> |
| * </tr> |
| * <tr> |
| * <td><code>src/test/resources/projects/it1/child/archetype.properties</code></td> |
| * <td>All required properties for this project's archetype</td> |
| * </tr> |
| * </table> |
| * |
| * <p>Notice that it is expected to be run as part as of a build after the <code>package</code> phase and not directly as a |
| * goal from CLI.</p> |
| * |
| * @author rafale |
| */ |
| @Mojo(name = "integration-test", requiresProject = true) |
| public class IntegrationTestMojo extends AbstractMojo { |
| |
| @Component |
| private ArchetypeGenerator archetypeGenerator; |
| |
| @Component |
| private Invoker invoker; |
| |
| @Component |
| private ArtifactResolver artifactResolver; |
| |
| @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true) |
| protected List<ArtifactRepository> remoteRepositories; |
| |
| @Parameter(defaultValue = "${localRepository}", readonly = true, required = true) |
| protected ArtifactRepository localRepository; |
| |
| /** |
| * The archetype project to execute the integration tests on. |
| */ |
| @Parameter(defaultValue = "${project}", readonly = true, required = true) |
| private MavenProject project; |
| |
| @Parameter(defaultValue = "${session}", readonly = true, required = true) |
| private MavenSession session; |
| |
| /** |
| * Skip the integration test. |
| */ |
| @Parameter(property = "archetype.test.skip") |
| private boolean skip = false; |
| |
| /** |
| * Directory of test projects |
| * |
| * @since 2.2 |
| */ |
| @Parameter( |
| property = "archetype.test.projectsDirectory", |
| defaultValue = "${project.build.testOutputDirectory}/projects", |
| required = true) |
| private File testProjectsDirectory; |
| |
| /** |
| * Relative path of a cleanup/verification hook script to run after executing the build. This script may be written |
| * with either BeanShell or Groovy. 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. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "archetype.test.verifyScript", defaultValue = "verify") |
| private String postBuildHookScript; |
| |
| /** |
| * Suppress logging to the <code>build.log</code> file. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "archetype.test.noLog", defaultValue = "false") |
| private boolean noLog; |
| |
| /** |
| * Flag used to determine whether the build logs should be output to the normal mojo log. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "archetype.test.streamLogs", defaultValue = "true") |
| private boolean streamLogs; |
| |
| /** |
| * The file encoding for the post-build script. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}") |
| private String encoding; |
| |
| /** |
| * The local repository to run maven instance. |
| * |
| * @since 2.2 |
| */ |
| @Parameter( |
| property = "archetype.test.localRepositoryPath", |
| defaultValue = "${settings.localRepository}", |
| required = true) |
| private File localRepositoryPath; |
| |
| /** |
| * flag to enable show mvn version used for running its (cli option : -V,--show-version ) |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "archetype.test.showVersion", defaultValue = "false") |
| private boolean showVersion; |
| |
| /** |
| * Ignores the EOL encoding for comparing files (default and original behaviour is false). |
| * |
| * @since 2.3 |
| */ |
| @Parameter(property = "archetype.test.ignoreEOLStyle", defaultValue = "false") |
| private boolean ignoreEOLStyle; |
| |
| /** |
| * Whether to show debug statements in the build output. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "archetype.test.debug", defaultValue = "false") |
| private boolean debug; |
| |
| /** |
| * A list of additional properties which will be used to filter tokens in settings.xml |
| * |
| * @since 2.2 |
| */ |
| @Parameter |
| private Map<String, String> filterProperties; |
| |
| /** |
| * The current user system settings for use in Maven. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(defaultValue = "${settings}", required = true, readonly = true) |
| private Settings settings; |
| |
| /** |
| * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the |
| * <code><localRepository></code> element of this settings file is always ignored, i.e. the path given by the |
| * parameter {@link #localRepositoryPath} is dominant. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(property = "archetype.test.settingsFile") |
| private File settingsFile; |
| |
| /** |
| * Common set of properties to pass in on each project's command line, via -D parameters. |
| * |
| * @since 3.0.2 |
| */ |
| @Parameter |
| private Map<String, String> properties = new HashMap<>(); |
| |
| @Override |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| if (skip) { |
| return; |
| } |
| |
| if (!testProjectsDirectory.exists()) { |
| getLog().warn("No Archetype IT projects: root 'projects' directory not found."); |
| |
| return; |
| } |
| |
| File archetypeFile = project.getArtifact().getFile(); |
| |
| if (archetypeFile == null) { |
| throw new MojoFailureException("Unable to get the archetypes' artifact which should have just been built:" |
| + " you probably launched 'mvn archetype:integration-test' instead of" |
| + " 'mvn integration-test'."); |
| } |
| |
| try { |
| List<File> projectsGoalFiles = FileUtils.getFiles(testProjectsDirectory, "**/goal.txt", ""); |
| |
| if (projectsGoalFiles.size() == 0) { |
| getLog().warn("No Archetype IT projects: no directory with goal.txt found."); |
| |
| return; |
| } |
| |
| StringWriter errorWriter = new StringWriter(); |
| for (File goalFile : projectsGoalFiles) { |
| try { |
| processIntegrationTest(goalFile, archetypeFile); |
| } catch (IntegrationTestFailure ex) { |
| errorWriter.write( |
| "\nArchetype IT '" + goalFile.getParentFile().getName() + "' failed: "); |
| errorWriter.write(ex.getMessage()); |
| } |
| } |
| |
| String errors = errorWriter.toString(); |
| if (!(errors == null || errors.isEmpty())) { |
| throw new MojoExecutionException(errors); |
| } |
| } catch (IOException ex) { |
| throw new MojoFailureException(ex, ex.getMessage(), ex.getMessage()); |
| } |
| } |
| |
| /** |
| * Checks that actual directory content is the same as reference. |
| * |
| * @param reference the reference directory |
| * @param actual the actual directory to compare with the reference |
| * @throws IntegrationTestFailure if content differs |
| */ |
| private void assertDirectoryEquals(File reference, File actual) throws IntegrationTestFailure, IOException { |
| List<String> referenceFiles = |
| FileUtils.getFileAndDirectoryNames(reference, "**", null, false, true, true, true); |
| getLog().debug("reference content: " + referenceFiles); |
| |
| List<String> actualFiles = FileUtils.getFileAndDirectoryNames(actual, "**", null, false, true, true, true); |
| getLog().debug("actual content: " + actualFiles); |
| |
| boolean fileNamesEquals = CollectionUtils.isEqualCollection(referenceFiles, actualFiles); |
| |
| if (!fileNamesEquals) { |
| getLog().debug("Actual list of files is not the same as reference:"); |
| int missing = 0; |
| for (String ref : referenceFiles) { |
| if (actualFiles.contains(ref)) { |
| actualFiles.remove(ref); |
| getLog().debug("Contained " + ref); |
| } else { |
| missing++; |
| getLog().error("Not contained " + ref); |
| } |
| } |
| getLog().error("Remains " + actualFiles); |
| |
| throw new IntegrationTestFailure("Reference and generated project differs (missing: " + missing |
| + ", unexpected: " + actualFiles.size() + ")"); |
| } |
| |
| boolean contentEquals = true; |
| |
| for (String file : referenceFiles) { |
| File referenceFile = new File(reference, file); |
| File actualFile = new File(actual, file); |
| |
| if (referenceFile.isDirectory()) { |
| if (actualFile.isFile()) { |
| getLog().warn("File " + file + " is a directory in the reference but a file in actual"); |
| contentEquals = false; |
| } |
| } else if (actualFile.isDirectory()) { |
| if (referenceFile.isFile()) { |
| getLog().warn("File " + file + " is a file in the reference but a directory in actual"); |
| contentEquals = false; |
| } |
| } else if (!contentEquals(referenceFile, actualFile)) { |
| getLog().warn("Contents of file " + file + " are not equal"); |
| contentEquals = false; |
| } |
| } |
| if (!contentEquals) { |
| throw new IntegrationTestFailure("Some content are not equals"); |
| } |
| } |
| |
| /** |
| * Uses the {@link #ignoreEOLStyle} attribute to compare the two files. If {@link #ignoreEOLStyle} is true, |
| * then the comparison does not take care about the EOL (aka newline) character. |
| */ |
| private boolean contentEquals(File referenceFile, File actualFile) throws IOException { |
| // Original behaviour |
| if (!ignoreEOLStyle) { |
| getLog().warn("Property ignoreEOLStyle was not set - files will be compared considering their EOL style!"); |
| return FileUtils.contentEquals(referenceFile, actualFile); |
| } |
| |
| getLog().debug("Comparing files with EOL style ignored."); |
| |
| try (BufferedReader referenceFileReader = new BufferedReader(new FileReader(referenceFile)); |
| BufferedReader actualFileReader = new BufferedReader(new FileReader(actualFile))) { |
| String refLine = null; |
| String actualLine = null; |
| |
| do { |
| refLine = referenceFileReader.readLine(); |
| actualLine = actualFileReader.readLine(); |
| if (!Objects.equals(refLine, actualLine)) { |
| return false; |
| } |
| } while (refLine != null || actualLine != null); |
| |
| return true; |
| } |
| } |
| |
| private Properties loadProperties(final File propertiesFile) throws IOException { |
| Properties properties = new Properties(); |
| |
| try (InputStream in = new FileInputStream(propertiesFile)) { |
| properties.load(in); |
| } |
| |
| return properties; |
| } |
| |
| private void processIntegrationTest(File goalFile, File archetypeFile) |
| throws IntegrationTestFailure, MojoExecutionException { |
| getLog().info("Processing Archetype IT project: " |
| + goalFile.getParentFile().getName()); |
| |
| try { |
| Properties properties = getProperties(goalFile); |
| |
| File basedir = new File(goalFile.getParentFile(), "project"); |
| |
| FileUtils.deleteDirectory(basedir); |
| |
| FileUtils.mkdir(basedir.toString()); |
| |
| basedir = setupParentProjects(goalFile.getParentFile().getParentFile(), basedir); |
| |
| ArchetypeGenerationRequest request = generate( |
| project.getGroupId(), |
| project.getArtifactId(), |
| project.getVersion(), |
| archetypeFile, |
| properties, |
| basedir.toString()); |
| |
| File reference = new File(goalFile.getParentFile(), "reference"); |
| |
| if (reference.exists()) { |
| // compare generated project with reference |
| getLog().info("Comparing generated project with reference content: " + reference); |
| |
| assertDirectoryEquals(reference, new File(basedir, request.getArtifactId())); |
| } |
| |
| String goals = FileUtils.fileRead(goalFile); |
| |
| if (goals != null && !goals.isEmpty()) { |
| invokePostArchetypeGenerationGoals(goals.trim(), new File(basedir, request.getArtifactId()), goalFile); |
| } |
| } catch (IOException ioe) { |
| throw new IntegrationTestFailure(ioe); |
| } |
| } |
| |
| private ArchetypeGenerationRequest generate( |
| String archetypeGroupId, |
| String archetypeArtifactId, |
| String archetypeVersion, |
| File archetypeFile, |
| Properties properties, |
| String basedir) |
| throws IntegrationTestFailure { |
| // @formatter:off |
| ArchetypeGenerationRequest request = new ArchetypeGenerationRequest() |
| .setArchetypeGroupId(archetypeGroupId) |
| .setArchetypeArtifactId(archetypeArtifactId) |
| .setArchetypeVersion(archetypeVersion) |
| .setGroupId(properties.getProperty(Constants.GROUP_ID)) |
| .setArtifactId(properties.getProperty(Constants.ARTIFACT_ID)) |
| .setVersion(properties.getProperty(Constants.VERSION)) |
| .setPackage(properties.getProperty(Constants.PACKAGE)) |
| .setOutputDirectory(basedir) |
| .setProperties(properties); |
| // @formatter:on |
| |
| ArchetypeGenerationResult result = new ArchetypeGenerationResult(); |
| |
| archetypeGenerator.generateArchetype(request, archetypeFile, result); |
| |
| if (result.getCause() != null) { |
| if (result.getCause() instanceof ArchetypeNotConfigured) { |
| ArchetypeNotConfigured anc = (ArchetypeNotConfigured) result.getCause(); |
| |
| throw new IntegrationTestFailure( |
| "Missing required properties in archetype.properties: " |
| + StringUtils.join(anc.getMissingProperties().iterator(), ", "), |
| anc); |
| } |
| |
| throw new IntegrationTestFailure(result.getCause().getMessage(), result.getCause()); |
| } |
| return request; |
| } |
| |
| private File setupParentProjects(File configFolder, File buildFolder) |
| throws IOException, MojoExecutionException, IntegrationTestFailure { |
| // look for 'archetype.pom.properties' |
| File archetypePomPropertiesFile = new File(configFolder, "archetype.pom.properties"); |
| if (!archetypePomPropertiesFile.exists()) { |
| getLog().debug("No 'archetype.pom.properties' file found in " + configFolder); |
| return buildFolder; |
| } |
| |
| // go up to the parent configuration folder |
| buildFolder = setupParentProjects(configFolder.getParentFile(), buildFolder); |
| |
| Properties archetypePomProperties = loadProperties(archetypePomPropertiesFile); |
| String groupId = archetypePomProperties.getProperty(Constants.GROUP_ID); |
| if (groupId == null || groupId.isEmpty()) { |
| throw new MojoExecutionException( |
| "Property " + Constants.GROUP_ID + " not set in " + archetypePomPropertiesFile); |
| } |
| String artifactId = archetypePomProperties.getProperty(Constants.ARTIFACT_ID); |
| if (artifactId == null || artifactId.isEmpty()) { |
| throw new MojoExecutionException( |
| "Property " + Constants.ARTIFACT_ID + " not set in " + archetypePomPropertiesFile); |
| } |
| String version = archetypePomProperties.getProperty(Constants.VERSION); |
| if (version == null || version.isEmpty()) { |
| throw new MojoExecutionException( |
| "Property " + Constants.VERSION + " not set in " + archetypePomPropertiesFile); |
| } |
| |
| File archetypeFile; |
| try { |
| archetypeFile = getArchetypeFile(groupId, artifactId, version); |
| } catch (ArtifactResolverException e) { |
| throw new MojoExecutionException("Could not resolve archetype artifact ", e); |
| } |
| Properties archetypeProperties = getProperties(archetypePomPropertiesFile); |
| getLog().info("Setting up parent project in " + buildFolder); |
| ArchetypeGenerationRequest request = |
| generate(groupId, artifactId, version, archetypeFile, archetypeProperties, buildFolder.toString()); |
| return new File(buildFolder, request.getArtifactId()); |
| } |
| |
| private File getArchetypeFile(String groupId, String artifactId, String version) throws ArtifactResolverException { |
| ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); |
| if (localRepository != null) { |
| buildingRequest = buildingRequest.setLocalRepository(localRepository); |
| } |
| if (remoteRepositories != null && !remoteRepositories.isEmpty()) { |
| buildingRequest = buildingRequest.setRemoteRepositories(remoteRepositories); |
| } |
| |
| DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate(); |
| coordinate.setGroupId(groupId); |
| coordinate.setArtifactId(artifactId); |
| coordinate.setVersion(version); |
| |
| return artifactResolver |
| .resolveArtifact(buildingRequest, coordinate) |
| .getArtifact() |
| .getFile(); |
| } |
| |
| private Properties getProperties(File goalFile) throws IOException { |
| File propertiesFile = new File(goalFile.getParentFile(), "archetype.properties"); |
| |
| return loadProperties(propertiesFile); |
| } |
| |
| private void invokePostArchetypeGenerationGoals(String goals, File basedir, File goalFile) |
| throws IntegrationTestFailure, IOException, MojoExecutionException { |
| FileLogger logger = setupLogger(basedir); |
| |
| if (!StringUtils.isBlank(goals)) { |
| |
| getLog().info("Invoking post-archetype-generation goals: " + goals); |
| |
| if (!localRepositoryPath.exists()) { |
| localRepositoryPath.mkdirs(); |
| } |
| |
| // @formatter:off |
| InvocationRequest request = new DefaultInvocationRequest() |
| .setBaseDirectory(basedir) |
| .setGoals(Arrays.asList(StringUtils.split(goals, ","))) |
| .setLocalRepositoryDirectory(localRepositoryPath) |
| .setBatchMode(true) |
| .setShowErrors(true); |
| // @formatter:on |
| |
| request.setDebug(debug); |
| |
| request.setShowVersion(showVersion); |
| |
| if (logger != null) { |
| request.setErrorHandler(logger); |
| |
| request.setOutputHandler(logger); |
| } |
| |
| if (!properties.isEmpty()) { |
| Properties props = new Properties(); |
| for (Map.Entry<String, String> entry : properties.entrySet()) { |
| if (entry.getValue() != null) { |
| props.setProperty(entry.getKey(), entry.getValue()); |
| } |
| } |
| request.setProperties(props); |
| } |
| |
| File archetypeItDirectory = new File(project.getBuild().getDirectory(), "archetype-it"); |
| if (archetypeItDirectory.exists()) { |
| FileUtils.deleteDirectory(archetypeItDirectory); |
| } |
| archetypeItDirectory.mkdir(); |
| File userSettings; |
| if (settingsFile != null) { |
| userSettings = new File(archetypeItDirectory, "interpolated-" + settingsFile.getName()); |
| |
| buildInterpolatedFile(settingsFile, userSettings); |
| } else { |
| // Use settings coming from the main Maven build |
| userSettings = new File(archetypeItDirectory, "archetype-settings.xml"); |
| |
| SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer(); |
| |
| try (FileWriter fileWriter = new FileWriter(userSettings)) { |
| settingsWriter.write(fileWriter, settings); |
| } |
| } |
| request.setUserSettingsFile(userSettings); |
| |
| try { |
| InvocationResult result = invoker.execute(request); |
| |
| getLog().info("Post-archetype-generation invoker exit code: " + result.getExitCode()); |
| |
| if (result.getExitCode() != 0) { |
| throw new IntegrationTestFailure( |
| "Execution failure: exit code = " + result.getExitCode(), result.getExecutionException()); |
| } |
| } catch (MavenInvocationException e) { |
| throw new IntegrationTestFailure("Cannot run additions goals.", e); |
| } |
| } else { |
| getLog().info("No post-archetype-generation goals to invoke."); |
| } |
| // verify result |
| ScriptRunner scriptRunner = new ScriptRunner(getLog()); |
| scriptRunner.setScriptEncoding(encoding); |
| |
| Map<String, Object> context = new LinkedHashMap<>(); |
| context.put("projectDir", basedir); |
| |
| try { |
| scriptRunner.run( |
| "post-build script", |
| goalFile.getParentFile(), |
| postBuildHookScript, |
| context, |
| logger, |
| "failure post script", |
| true); |
| } catch (RunFailureException e) { |
| throw new IntegrationTestFailure("post build script failure failure: " + e.getMessage(), e); |
| } |
| } |
| |
| private FileLogger setupLogger(File basedir) throws IOException { |
| FileLogger logger = null; |
| |
| if (!noLog) { |
| File outputLog = new File(basedir, "build.log"); |
| |
| if (streamLogs) { |
| logger = new FileLogger(outputLog, getLog()); |
| } else { |
| logger = new FileLogger(outputLog); |
| } |
| |
| getLog().debug("build log initialized in: " + outputLog); |
| } |
| |
| return logger; |
| } |
| |
| class IntegrationTestFailure extends Exception { |
| IntegrationTestFailure() { |
| super(); |
| } |
| |
| IntegrationTestFailure(String message) { |
| super(message); |
| } |
| |
| IntegrationTestFailure(Throwable cause) { |
| super(cause); |
| } |
| |
| IntegrationTestFailure(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| |
| /** |
| * Returns the map-based value source used to interpolate settings and other stuff. |
| * |
| * @return The map-based value source for interpolation, never <code>null</code>. |
| */ |
| private Map<String, Object> getInterpolationValueSource() { |
| Map<String, Object> props = new HashMap<>(); |
| if (filterProperties != null) { |
| props.putAll(filterProperties); |
| } |
| 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); |
| } |
| |
| protected void buildInterpolatedFile(File originalFile, File interpolatedFile) throws MojoExecutionException { |
| getLog().debug("Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath()); |
| |
| try { |
| String xml; |
| |
| // interpolation with token @...@ |
| Map<String, Object> composite = getInterpolationValueSource(); |
| |
| try (Reader xmlStreamReader = ReaderFactory.newXmlReader(originalFile); |
| Reader reader = new InterpolationFilterReader(xmlStreamReader, composite, "@", "@")) { |
| xml = IOUtil.toString(reader); |
| } |
| |
| try (Writer writer = WriterFactory.newXmlWriter(interpolatedFile)) { |
| interpolatedFile.getParentFile().mkdirs(); |
| |
| writer.write(xml); |
| } |
| } catch (IOException e) { |
| throw new MojoExecutionException("Failed to interpolate file " + originalFile.getPath(), e); |
| } |
| } |
| |
| private static class CompositeMap implements Map<String, Object> { |
| |
| /** |
| * The Maven project from which to extract interpolated values, never <code>null</code>. |
| */ |
| private MavenProject mavenProject; |
| |
| /** |
| * The set of additional properties from which to extract interpolated values, never <code>null</code>. |
| */ |
| private Map<String, Object> properties; |
| |
| /** |
| * Creates a new interpolation source backed by the specified Maven project and some user-specified properties. |
| * |
| * @param mavenProject The Maven project from which to extract interpolated values, must not be |
| * <code>null</code>. |
| * @param properties The set of additional properties from which to extract interpolated values, may be |
| * <code>null</code>. |
| */ |
| protected CompositeMap(MavenProject mavenProject, Map<String, Object> properties) { |
| if (mavenProject == null) { |
| throw new IllegalArgumentException("no project specified"); |
| } |
| this.mavenProject = mavenProject; |
| this.properties = properties == null ? (Map) new Properties() : properties; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#clear() |
| */ |
| @Override |
| public void clear() { |
| // nothing here |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#containsKey(java.lang.Object) |
| */ |
| @Override |
| public boolean containsKey(Object key) { |
| if (!(key instanceof String)) { |
| return false; |
| } |
| |
| String expression = (String) key; |
| if (expression.startsWith("project.") || expression.startsWith("pom.")) { |
| try { |
| Object evaluated = ReflectionValueExtractor.evaluate(expression, this.mavenProject); |
| if (evaluated != null) { |
| return true; |
| } |
| } catch (Exception e) { |
| // uhm do we have to throw a RuntimeException here ? |
| } |
| } |
| |
| return properties.containsKey(key) || mavenProject.getProperties().containsKey(key); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#containsValue(java.lang.Object) |
| */ |
| @Override |
| public boolean containsValue(Object value) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#entrySet() |
| */ |
| @Override |
| public Set<Entry<String, Object>> entrySet() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#get(java.lang.Object) |
| */ |
| @Override |
| public Object get(Object key) { |
| if (!(key instanceof String)) { |
| return null; |
| } |
| |
| String expression = (String) key; |
| if (expression.startsWith("project.") || expression.startsWith("pom.")) { |
| try { |
| Object evaluated = ReflectionValueExtractor.evaluate(expression, this.mavenProject); |
| if (evaluated != null) { |
| return evaluated; |
| } |
| } catch (Exception e) { |
| // uhm do we have to throw a RuntimeException here ? |
| } |
| } |
| |
| Object value = properties.get(key); |
| |
| return value != null ? value : this.mavenProject.getProperties().get(key); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#isEmpty() |
| */ |
| @Override |
| public boolean isEmpty() { |
| return this.mavenProject == null |
| && this.mavenProject.getProperties().isEmpty() |
| && this.properties.isEmpty(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#keySet() |
| */ |
| @Override |
| public Set<String> keySet() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#put(java.lang.Object, java.lang.Object) |
| */ |
| @Override |
| public Object put(String key, Object value) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#putAll(java.util.Map) |
| */ |
| @Override |
| public void putAll(Map<? extends String, ? extends Object> t) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#remove(java.lang.Object) |
| */ |
| @Override |
| public Object remove(Object key) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#size() |
| */ |
| @Override |
| public int size() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.util.Map#values() |
| */ |
| @Override |
| public Collection<Object> values() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** |
| * 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 <code>null</code>. |
| * @return The <code>file:</code> URL for the specified path, never <code>null</code>. |
| */ |
| 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; |
| } |
| } |