| package org.apache.maven.archetype.mojos; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileReader; |
| 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.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.shared.artifact.DefaultArtifactCoordinate; |
| import org.apache.maven.shared.artifact.resolve.ArtifactResolver; |
| import org.apache.maven.shared.artifact.resolve.ArtifactResolverException; |
| 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.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/> |
| * <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. |
| * An example structure for such an integration test looks like this |
| * <table> |
| * <tr> |
| * <th>File/Directory</code></td> |
| * <th>Description</td> |
| * </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. |
| * |
| * @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<String, String>(); |
| |
| @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 ( !StringUtils.isEmpty( errors ) ) |
| { |
| 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: " + referenceFiles ); |
| |
| 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 ( !StringUtils.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 ( StringUtils.isNotEmpty( goals ) ) |
| { |
| 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 ( StringUtils.isEmpty( groupId ) ) |
| { |
| throw new MojoExecutionException( "Property " + Constants.GROUP_ID + " not set in " + archetypePomPropertiesFile ); |
| } |
| String artifactId = archetypePomProperties.getProperty( Constants.ARTIFACT_ID ); |
| if ( StringUtils.isEmpty( artifactId ) ) |
| { |
| throw new MojoExecutionException( "Property " + Constants.ARTIFACT_ID + " not set in " + archetypePomPropertiesFile ); |
| } |
| String version = archetypePomProperties.getProperty( Constants.VERSION ); |
| if ( StringUtils.isEmpty( version ) ) |
| { |
| 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 ).setInteractive( false ).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 interpolatedSettingsFile = null; |
| if ( settingsFile != null ) |
| { |
| File interpolatedSettingsDirectory = |
| new File( project.getBuild().getOutputDirectory(), "archetype-it" ); |
| if ( interpolatedSettingsDirectory.exists() ) |
| { |
| FileUtils.deleteDirectory( interpolatedSettingsDirectory ); |
| } |
| interpolatedSettingsDirectory.mkdir(); |
| interpolatedSettingsFile = |
| new File( interpolatedSettingsDirectory, "interpolated-" + settingsFile.getName() ); |
| |
| buildInterpolatedFile( settingsFile, interpolatedSettingsFile ); |
| |
| request.setUserSettingsFile( interpolatedSettingsFile ); |
| } |
| |
| 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<String, Object>(); |
| 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<String, Object>(); |
| 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 ); |
| writer.flush(); |
| } |
| } |
| 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; |
| } |
| } |