blob: 35dd1055f266a5db3b3940d3dc808e1b628af5a2 [file]
package org.apache.maven.shared.incremental;
/*
* 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 org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.ArrayUtils;
import org.apache.maven.shared.utils.io.DirectoryScanResult;
import org.apache.maven.shared.utils.io.DirectoryScanner;
import org.apache.maven.shared.utils.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.Set;
/**
* Various helper methods to support incremental builds
*
*/
public class IncrementalBuildHelper
{
/**
* the root directory to store status information about maven executions in.
*/
private static final String MAVEN_STATUS_ROOT = "maven-status";
public static final String CREATED_FILES_LST_FILENAME = "createdFiles.lst";
private static final String INPUT_FILES_LST_FILENAME = "inputFiles.lst";
/**
* Needed for storing the status for the incremental build support.
*/
private MojoExecution mojoExecution;
/**
* Needed for storing the status for the incremental build support.
*/
private MavenProject mavenProject;
/**
* Used for detecting changes between the Mojo execution.
* @see #getDirectoryScanner();
*/
private DirectoryScanner directoryScanner;
/**
* Once the {@link #beforeRebuildExecution(java.io.File)} gots called
* this will contain the list of files in the build directory.
*/
private String[] filesBeforeAction = ArrayUtils.EMPTY_STRING_ARRAY;
public IncrementalBuildHelper( MojoExecution mojoExecution, MavenSession mavenSession )
{
this( mojoExecution, getMavenProject( mavenSession ) );
}
public IncrementalBuildHelper( MojoExecution mojoExecution, MavenProject mavenProject )
{
if ( mavenProject == null )
{
throw new IllegalArgumentException( "MavenProject must not be null!" );
}
if ( mojoExecution == null )
{
throw new IllegalArgumentException( "MojoExecution must not be null!" );
}
this.mavenProject = mavenProject;
this.mojoExecution = mojoExecution;
}
/**
* small helper method to allow for the nullcheck in the ct invocation
*/
private static MavenProject getMavenProject( MavenSession mavenSession )
{
if ( mavenSession == null )
{
throw new IllegalArgumentException( "MavenSession must not be null!" );
}
return mavenSession.getCurrentProject();
}
/**
* Get the existing DirectoryScanner used by this helper,
* or create new a DirectoryScanner if none is yet set.
* The DirectoryScanner is used for detecting changes in a directory
*/
public DirectoryScanner getDirectoryScanner()
{
if ( directoryScanner == null )
{
directoryScanner = new DirectoryScanner();
}
return directoryScanner;
}
/**
* Set the DirectoryScanner which shall get used by this build helper.
* @param directoryScanner
*/
public void setDirectoryScanner( DirectoryScanner directoryScanner )
{
this.directoryScanner = directoryScanner;
}
/**
* We use a specific status directory for each mojo execution to store state
* which is needed during the next build invocation run.
* @return the directory for storing status information of the current mojo execution.
*/
public File getMojoStatusDirectory() throws MojoExecutionException
{
if ( mojoExecution == null )
{
throw new MojoExecutionException( "MojoExecution could not get resolved" );
}
File buildOutputDirectory = new File( mavenProject.getBuild().getDirectory() );
//X TODO the executionId contains -cli and -mojoname
//X we should remove those postfixes as it should not make
//X any difference whether being run on the cli or via build
String mojoStatusPath = MAVEN_STATUS_ROOT + File.separator
+ mojoExecution.getMojoDescriptor().getPluginDescriptor().getArtifactId() + File.separator
+ mojoExecution.getMojoDescriptor().getGoal() + File.separator
+ mojoExecution.getExecutionId();
File mojoStatusDir = new File( buildOutputDirectory, mojoStatusPath );
if ( !mojoStatusDir.exists() )
{
mojoStatusDir.mkdirs();
}
return mojoStatusDir;
}
/**
* Detect whether the list of detected files has changed since the last build.
* We simply load the list of files for the previous build from a status file
* and compare it with the new list. Afterwards we store the new list in the status file.
*
* @param inputFiles
* @return <code>true</code> if the set of inputFiles got changed since the last build.
* @throws MojoExecutionException
*/
public boolean inputFileTreeChanged( Set<File> inputFiles ) throws MojoExecutionException
{
File mojoConfigBase = getMojoStatusDirectory();
File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
String[] oldInputFiles = ArrayUtils.EMPTY_STRING_ARRAY;
if ( mojoConfigFile.exists() )
{
try
{
oldInputFiles = FileUtils.fileReadArray( mojoConfigFile );
}
catch( IOException e )
{
throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
}
}
String[] inputFileNames = new String[ inputFiles.size() ];
int i = 0;
for ( File inputFile : inputFiles )
{
inputFileNames[ i++ ] = inputFile.getAbsolutePath();
}
DirectoryScanResult dsr = DirectoryScanner.diffFiles( oldInputFiles, inputFileNames );
try
{
FileUtils.fileWriteArray( mojoConfigFile, inputFileNames );
}
catch( IOException e )
{
throw new MojoExecutionException( "Error while storing the mojo status", e );
}
return ( dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0 );
}
/**
* Detect whether the list of detected files picked up by the DirectoryScanner
* has changed since the last build.
* We simply load the list of files for the previous build from a status file
* and compare it with the result of the new DirectoryScanner#scan().
* Afterwards we store the new list in the status file.
*
* @param dirScanner
* @return <code>true</code> if the set of inputFiles got changed since the last build.
* @throws MojoExecutionException
*/
public boolean inputFileTreeChanged( DirectoryScanner dirScanner ) throws MojoExecutionException
{
File mojoConfigBase = getMojoStatusDirectory();
File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
String[] oldInputFiles = ArrayUtils.EMPTY_STRING_ARRAY;
if ( mojoConfigFile.exists() )
{
try
{
oldInputFiles = FileUtils.fileReadArray( mojoConfigFile );
}
catch( IOException e )
{
throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
}
}
dirScanner.scan();
try
{
// store away the list of input files
FileUtils.fileWriteArray( mojoConfigFile, dirScanner.getIncludedFiles() );
}
catch( IOException e )
{
throw new MojoExecutionException( "Error while storing new mojo status" + mojoConfigFile, e );
}
DirectoryScanResult dsr = dirScanner.diffIncludedFiles( oldInputFiles );
return ( dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0 );
}
/**
* <p>This method shall get invoked before the actual mojo task gets triggered,
* e.g. the actual compile in maven-compiler-plugin.</p>
*
* <p><b>Attention:</b> This method shall only get invoked if the plugin re-creates <b>all</b> the output.</p>
*
* <p>It first picks up the list of files created in the previous build and delete them.
* This step is necessary to prevent left-overs. After that we take a 'directory snapshot'
* (list of all files which exist in the outputDirectory after the clean). </p>
*
* <p>After the actual mojo task got executed you should invoke the method
* {@link #afterRebuildExecution()} to collect the list of files which got changed
* by this task.</p>
*
*
* @param outputDirectory
* @return all files which got created in the previous build and have been deleted now.
* @throws MojoExecutionException
*/
public String[] beforeRebuildExecution( File outputDirectory ) throws MojoExecutionException
{
File mojoConfigBase = getMojoStatusDirectory();
File mojoConfigFile = new File( mojoConfigBase, CREATED_FILES_LST_FILENAME );
String[] oldFiles;
try
{
oldFiles = FileUtils.fileReadArray( mojoConfigFile );
for ( String oldFileName : oldFiles )
{
File oldFile = new File( outputDirectory, oldFileName );
oldFile.delete();
}
}
catch( IOException e )
{
throw new MojoExecutionException( "Error reading old mojo status", e );
}
// we remember all files which currently exist in the output directory
DirectoryScanner diffScanner = getDirectoryScanner();
diffScanner.setBasedir( outputDirectory );
if ( outputDirectory.exists() )
{
diffScanner.scan();
filesBeforeAction = diffScanner.getIncludedFiles();
}
return oldFiles;
}
/**
* <p>This method collects and stores all information about files changed since
* the call to {@link #beforeRebuildExecution(java.io.File)}.</p>
*
* <p><b>Attention:</b> This method shall only get invoked if the plugin re-creates <b>all</b> the output.</p>
*
* @throws MojoExecutionException
*/
public void afterRebuildExecution() throws MojoExecutionException
{
DirectoryScanner diffScanner = getDirectoryScanner();
// now scan the same directory again and create a diff
diffScanner.scan();
DirectoryScanResult scanResult = diffScanner.diffIncludedFiles( filesBeforeAction );
File mojoConfigBase = getMojoStatusDirectory();
File mojoConfigFile = new File( mojoConfigBase, CREATED_FILES_LST_FILENAME );
try
{
FileUtils.fileWriteArray( mojoConfigFile, scanResult.getFilesAdded() );
}
catch( IOException e )
{
throw new MojoExecutionException( "Error while storing the mojo status", e );
}
}
}