View Javadoc

1   package org.apache.maven.shared.incremental;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  
23  import org.apache.maven.execution.MavenSession;
24  import org.apache.maven.plugin.MojoExecution;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.project.MavenProject;
27  import org.apache.maven.shared.utils.io.DirectoryScanResult;
28  import org.apache.maven.shared.utils.io.DirectoryScanner;
29  import org.apache.maven.shared.utils.io.FileUtils;
30  
31  import java.io.File;
32  import java.io.IOException;
33  import java.util.Set;
34  
35  /**
36   * Various helper methods to support incremental builds
37   *
38   */
39  public class IncrementalBuildHelper
40  {
41      /**
42       * the root directory to store status information about maven executions in.
43       */
44      private static final String MAVEN_STATUS_ROOT = "maven-status";
45      public static final String CREATED_FILES_LST_FILENAME = "createdFiles.lst";
46      private static final String INPUT_FILES_LST_FILENAME = "inputFiles.lst";
47  
48      /**
49       * Needed for storing the status for the incremental build support.
50       */
51      private MojoExecution mojoExecution;
52  
53      /**
54       * Needed for storing the status for the incremental build support.
55       */
56      private MavenProject mavenProject;
57  
58      /**
59       * Used for detecting changes between the Mojo execution.
60       * @see #getDirectoryScanner();
61       */
62      private DirectoryScanner directoryScanner;
63  
64      /**
65       * Once the {@link #beforeRebuildExecution(java.io.File)} gots called
66       * this will contain the list of files in the build directory.
67       */
68      private String[] filesBeforeAction = new String[0];
69  
70      public IncrementalBuildHelper( MojoExecution mojoExecution, MavenSession mavenSession )
71      {
72          this( mojoExecution, getMavenProject( mavenSession ) );
73      }
74  
75      public IncrementalBuildHelper( MojoExecution mojoExecution, MavenProject mavenProject )
76      {
77          if ( mavenProject == null )
78          {
79              throw new IllegalArgumentException( "MavenProject must not be null!" );
80          }
81          if ( mojoExecution == null )
82          {
83              throw new IllegalArgumentException( "MojoExecution must not be null!" );
84          }
85  
86          this.mavenProject = mavenProject;
87          this.mojoExecution = mojoExecution;
88      }
89  
90      /**
91       * small helper method to allow for the nullcheck in the ct invocation
92       */
93      private static MavenProject getMavenProject( MavenSession mavenSession )
94      {
95          if ( mavenSession == null )
96          {
97              throw new IllegalArgumentException( "MavenSession must not be null!" );
98          }
99  
100         return mavenSession.getCurrentProject();
101     }
102 
103     /**
104      * Get the existing DirectoryScanner used by this helper,
105      * or create new a DirectoryScanner if none is yet set.
106      * The DirectoryScanner is used for detecting changes in a directory
107      */
108     public DirectoryScanner getDirectoryScanner()
109     {
110         if ( directoryScanner == null )
111         {
112             directoryScanner = new DirectoryScanner();
113         }
114 
115         return directoryScanner;
116     }
117 
118     /**
119      * Set the DirectoryScanner which shall get used by this build helper.
120      * @param directoryScanner
121      */
122     public void setDirectoryScanner( DirectoryScanner directoryScanner )
123     {
124         this.directoryScanner = directoryScanner;
125     }
126 
127     /**
128      * We use a specific status directory for each mojo execution to store state
129      * which is needed during the next build invocation run.
130      * @return the directory for storing status information of the current mojo execution.
131      */
132     public File getMojoStatusDirectory() throws MojoExecutionException
133     {
134         if ( mojoExecution == null )
135         {
136             throw new MojoExecutionException( "MojoExecution could not get resolved" );
137         }
138 
139         File buildOutputDirectory = new File( mavenProject.getBuild().getDirectory() );
140 
141         //X TODO the executionId contains -cli and -mojoname
142         //X we should remove those postfixes as it should not make
143         //X any difference whether being run on the cli or via build
144         String mojoStatusPath = MAVEN_STATUS_ROOT + File.separator
145                                 + mojoExecution.getMojoDescriptor().getPluginDescriptor().getArtifactId() + File.separator
146                                 + mojoExecution.getMojoDescriptor().getGoal() + File.separator
147                                 + mojoExecution.getExecutionId();
148 
149         File mojoStatusDir = new File( buildOutputDirectory, mojoStatusPath );
150 
151         if ( !mojoStatusDir.exists() )
152         {
153             mojoStatusDir.mkdirs();
154         }
155 
156         return mojoStatusDir;
157     }
158 
159     /**
160      * Detect whether the list of detected files has changed since the last build.
161      * We simply load the list of files for the previous build from a status file
162      * and compare it with the new list. Afterwards we store the new list in the status file.
163      *
164      * @param inputFiles
165      * @return <code>true</code> if the set of inputFiles got changed since the last build.
166      * @throws MojoExecutionException
167      */
168     public boolean inputFileTreeChanged( Set<File> inputFiles ) throws MojoExecutionException
169     {
170         File mojoConfigBase = getMojoStatusDirectory();
171         File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
172 
173         String[] oldInputFiles = new String[0];
174 
175         if ( mojoConfigFile.exists() )
176         {
177             try
178             {
179                 oldInputFiles = FileUtils.fileReadArray( mojoConfigFile );
180             }
181             catch( IOException e )
182             {
183                 throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
184             }
185         }
186 
187         String[] inputFileNames = new String[ inputFiles.size() ];
188         int i = 0;
189         for ( File inputFile : inputFiles )
190         {
191             inputFileNames[ i++ ] = inputFile.getAbsolutePath();
192         }
193 
194         DirectoryScanResult dsr = DirectoryScanner.diffFiles( oldInputFiles, inputFileNames );
195 
196 
197         try
198         {
199             FileUtils.fileWriteArray( mojoConfigFile, inputFileNames );
200         }
201         catch( IOException e )
202         {
203             throw new MojoExecutionException( "Error while storing the mojo status", e );
204         }
205 
206         return ( dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0 );
207     }
208 
209     /**
210      * Detect whether the list of detected files picked up by the DirectoryScanner
211      * has changed since the last build.
212      * We simply load the list of files for the previous build from a status file
213      * and compare it with the result of the new DirectoryScanner#scan().
214      * Afterwards we store the new list in the status file.
215      *
216      * @param dirScanner
217      * @return <code>true</code> if the set of inputFiles got changed since the last build.
218      * @throws MojoExecutionException
219      */
220     public boolean inputFileTreeChanged( DirectoryScanner dirScanner ) throws MojoExecutionException
221     {
222         File mojoConfigBase = getMojoStatusDirectory();
223         File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
224 
225         String[] oldInputFiles = new String[0];
226 
227         if ( mojoConfigFile.exists() )
228         {
229             try
230             {
231                 oldInputFiles = FileUtils.fileReadArray( mojoConfigFile );
232             }
233             catch( IOException e )
234             {
235                 throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
236             }
237         }
238 
239         dirScanner.scan();
240 
241         try
242         {
243             // store away the list of input files
244             FileUtils.fileWriteArray( mojoConfigFile, dirScanner.getIncludedFiles() );
245         }
246         catch( IOException e )
247         {
248             throw new MojoExecutionException( "Error while storing new mojo status" + mojoConfigFile, e );
249         }
250 
251         DirectoryScanResult dsr = dirScanner.diffIncludedFiles( oldInputFiles );
252 
253         return ( dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0 );
254     }
255 
256     /**
257      * <p>This method shall get invoked before the actual mojo task gets triggered,
258      * e.g. the actual compile in maven-compiler-plugin.</p>
259      *
260      * <p><b>Attention:</b> This method shall only get invoked if the plugin re-creates <b>all</b> the output.</p>
261      *
262      * <p>It first picks up the list of files created in the previous build and delete them.
263      * This step is necessary to prevent left-overs. After that we take a 'directory snapshot'
264      * (list of all files which exist in the outputDirectory after the clean). </p>
265      *
266      * <p>After the actual mojo task got executed you should invoke the method
267      * {@link #afterRebuildExecution()} to collect the list of files which got changed
268      * by this task.</p>
269      *
270      *
271      * @param outputDirectory
272      * @return all files which got created in the previous build and have been deleted now.
273      * @throws MojoExecutionException
274      */
275     public String[] beforeRebuildExecution( File outputDirectory ) throws MojoExecutionException
276     {
277         File mojoConfigBase = getMojoStatusDirectory();
278         File mojoConfigFile = new File( mojoConfigBase, CREATED_FILES_LST_FILENAME );
279 
280         String[] oldFiles;
281 
282         try
283         {
284             oldFiles = FileUtils.fileReadArray( mojoConfigFile );
285             for ( String oldFileName : oldFiles )
286             {
287                 File oldFile = new File( outputDirectory, oldFileName );
288                 oldFile.delete();
289             }
290         }
291         catch( IOException e )
292         {
293             throw new MojoExecutionException( "Error reading old mojo status", e );
294         }
295 
296         // we remember all files which currently exist in the output directory
297         DirectoryScanner diffScanner = getDirectoryScanner();
298         diffScanner.setBasedir( outputDirectory );
299         if ( outputDirectory.exists() )
300         {
301             diffScanner.scan();
302             filesBeforeAction = diffScanner.getIncludedFiles();
303         }
304 
305         return oldFiles;
306     }
307 
308     /**
309      * <p>This method collects and stores all information about files changed since
310      * the call to {@link #beforeRebuildExecution(java.io.File)}.</p>
311      *
312      * <p><b>Attention:</b> This method shall only get invoked if the plugin re-creates <b>all</b> the output.</p>
313      *
314      * @throws MojoExecutionException
315      */
316     public void afterRebuildExecution() throws MojoExecutionException
317     {
318         DirectoryScanner diffScanner = getDirectoryScanner();
319         // now scan the same directory again and create a diff
320         diffScanner.scan();
321         DirectoryScanResult scanResult = diffScanner.diffIncludedFiles( filesBeforeAction );
322 
323         File mojoConfigBase = getMojoStatusDirectory();
324         File mojoConfigFile = new File( mojoConfigBase, CREATED_FILES_LST_FILENAME );
325 
326         try
327         {
328             FileUtils.fileWriteArray( mojoConfigFile, scanResult.getFilesAdded() );
329         }
330         catch( IOException e )
331         {
332             throw new MojoExecutionException( "Error while storing the mojo status", e );
333         }
334 
335     }
336 }