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  import org.apache.maven.execution.MavenSession;
23  import org.apache.maven.plugin.MojoExecution;
24  import org.apache.maven.plugin.MojoExecutionException;
25  import org.apache.maven.project.MavenProject;
26  import org.apache.maven.shared.utils.io.DirectoryScanResult;
27  import org.apache.maven.shared.utils.io.DirectoryScanner;
28  import org.apache.maven.shared.utils.io.FileUtils;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.util.Set;
33  
34  /**
35   * Various helper methods to support incremental builds
36   */
37  public class IncrementalBuildHelper
38  {
39      /**
40       * the root directory to store status information about Maven executions in.
41       */
42      private static final String MAVEN_STATUS_ROOT = "maven-status";
43      public static final String CREATED_FILES_LST_FILENAME = "createdFiles.lst";
44      private static final String INPUT_FILES_LST_FILENAME = "inputFiles.lst";
45  
46      private static final String[] EMPTY_ARRAY = new String[0];
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(org.apache.maven.shared.incremental.IncrementalBuildHelperRequest)} got 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()
133         throws MojoExecutionException
134     {
135         if ( mojoExecution == null )
136         {
137             throw new MojoExecutionException( "MojoExecution could not get resolved" );
138         }
139 
140         File buildOutputDirectory = new File( mavenProject.getBuild().getDirectory() );
141 
142         //X TODO the executionId contains -cli and -mojoname
143         //X we should remove those postfixes as it should not make
144         //X any difference whether being run on the cli or via build
145         String mojoStatusPath = MAVEN_STATUS_ROOT + File.separator
146                                 + mojoExecution.getMojoDescriptor().getPluginDescriptor().getArtifactId() + File.separator
147                                 + mojoExecution.getMojoDescriptor().getGoal() + File.separator
148                                 + mojoExecution.getExecutionId();
149 
150         File mojoStatusDir = new File( buildOutputDirectory, mojoStatusPath );
151 
152         if ( !mojoStatusDir.exists() )
153         {
154             mojoStatusDir.mkdirs();
155         }
156 
157         return mojoStatusDir;
158     }
159 
160     /**
161      * Detect whether the list of detected files has changed since the last build.
162      * We simply load the list of files for the previous build from a status file
163      * and compare it with the new list. Afterwards we store the new list in the status file.
164      *
165      * @param incrementalBuildHelperRequest
166      * @return <code>true</code> if the set of inputFiles got changed since the last build.
167      * @throws MojoExecutionException
168      */
169     public boolean inputFileTreeChanged( IncrementalBuildHelperRequest incrementalBuildHelperRequest )
170         throws MojoExecutionException
171     {
172         File mojoConfigBase = getMojoStatusDirectory();
173         File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
174 
175         String[] oldInputFiles = new String[0];
176 
177         if ( mojoConfigFile.exists() )
178         {
179             try
180             {
181                 oldInputFiles = FileUtils.fileReadArray( mojoConfigFile );
182             }
183             catch( IOException e )
184             {
185                 throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
186             }
187         }
188 
189         String[] inputFileNames = new String[ incrementalBuildHelperRequest.getInputFiles().size() ];
190         int i = 0;
191         for ( File inputFile : incrementalBuildHelperRequest.getInputFiles() )
192         {
193             inputFileNames[ i++ ] = inputFile.getAbsolutePath();
194         }
195 
196         DirectoryScanResult dsr = DirectoryScanner.diffFiles( oldInputFiles, inputFileNames );
197 
198         try
199         {
200             FileUtils.fileWriteArray( mojoConfigFile, inputFileNames );
201         }
202         catch( IOException e )
203         {
204             throw new MojoExecutionException( "Error while storing the mojo status", e );
205         }
206 
207         return ( dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0 );
208     }
209 
210     /**
211      * Detect whether the list of detected files picked up by the DirectoryScanner
212      * has changed since the last build.
213      * We simply load the list of files for the previous build from a status file
214      * and compare it with the result of the new DirectoryScanner#scan().
215      * Afterwards we store the new list in the status file.
216      *
217      * @param dirScanner
218      * @return <code>true</code> if the set of inputFiles got changed since the last build.
219      * @throws MojoExecutionException
220      */
221     public boolean inputFileTreeChanged( DirectoryScanner dirScanner )
222         throws MojoExecutionException
223     {
224         File mojoConfigBase = getMojoStatusDirectory();
225         File mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
226 
227         String[] oldInputFiles = new String[0];
228 
229         if ( mojoConfigFile.exists() )
230         {
231             try
232             {
233                 oldInputFiles = FileUtils.fileReadArray( mojoConfigFile );
234             }
235             catch( IOException e )
236             {
237                 throw new MojoExecutionException( "Error reading old mojo status " + mojoConfigFile, e );
238             }
239         }
240 
241         dirScanner.scan();
242 
243         try
244         {
245             // store away the list of input files
246             FileUtils.fileWriteArray( mojoConfigFile, dirScanner.getIncludedFiles() );
247         }
248         catch( IOException e )
249         {
250             throw new MojoExecutionException( "Error while storing new mojo status" + mojoConfigFile, e );
251         }
252 
253         DirectoryScanResult dsr = dirScanner.diffIncludedFiles( oldInputFiles );
254 
255         return ( dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0 );
256     }
257 
258     /**
259      * <p>This method shall get invoked before the actual Mojo task gets triggered,
260      * e.g. the actual compile in maven-compiler-plugin.</p>
261      *
262      * <p><b>Attention:</b> This method shall only get invoked if the plugin re-creates <b>all</b> the output.</p>
263      *
264      * <p>It first picks up the list of files created in the previous build and delete them.
265      * This step is necessary to prevent left-overs. After that we take a 'directory snapshot'
266      * (list of all files which exist in the outputDirectory after the clean). </p>
267      *
268      * <p>After the actual Mojo task got executed you should invoke the method
269      * {@link #afterRebuildExecution(org.apache.maven.shared.incremental.IncrementalBuildHelperRequest)} to collect the list of files which got changed
270      * by this task.</p>
271      *
272      *
273      * @param incrementalBuildHelperRequest
274      * @return all files which got created in the previous build and have been deleted now.
275      * @throws MojoExecutionException
276      */
277     public String[] beforeRebuildExecution( IncrementalBuildHelperRequest incrementalBuildHelperRequest )
278         throws MojoExecutionException
279     {
280         File mojoConfigBase = getMojoStatusDirectory();
281         File mojoConfigFile = new File( mojoConfigBase, CREATED_FILES_LST_FILENAME );
282 
283         String[] oldFiles;
284 
285         try
286         {
287             oldFiles = FileUtils.fileReadArray( mojoConfigFile );
288             for ( String oldFileName : oldFiles )
289             {
290                 File oldFile = new File( incrementalBuildHelperRequest.getOutputDirectory(), oldFileName );
291                 oldFile.delete();
292             }
293         }
294         catch( IOException e )
295         {
296             throw new MojoExecutionException( "Error reading old mojo status", e );
297         }
298 
299         // we remember all files which currently exist in the output directory
300         DirectoryScanner diffScanner = getDirectoryScanner();
301         diffScanner.setBasedir( incrementalBuildHelperRequest.getOutputDirectory() );
302         if ( incrementalBuildHelperRequest.getOutputDirectory().exists() )
303         {
304             diffScanner.scan();
305             filesBeforeAction = diffScanner.getIncludedFiles();
306         }
307 
308         return oldFiles;
309     }
310 
311     /**
312      * <p>This method collects and stores all information about files changed since
313      * the call to {@link #beforeRebuildExecution(org.apache.maven.shared.incremental.IncrementalBuildHelperRequest)}.</p>
314      *
315      * <p><b>Attention:</b> This method shall only get invoked if the plugin re-creates <b>all</b> the output.</p>
316      *
317      * @param incrementalBuildHelperRequest will contains file sources to store if create files are not yet stored
318      *
319      * @throws MojoExecutionException
320      */
321     public void afterRebuildExecution( IncrementalBuildHelperRequest incrementalBuildHelperRequest )
322         throws MojoExecutionException
323     {
324         DirectoryScanner diffScanner = getDirectoryScanner();
325         // now scan the same directory again and create a diff
326         diffScanner.scan();
327         DirectoryScanResult scanResult = diffScanner.diffIncludedFiles( filesBeforeAction );
328 
329         File mojoConfigBase = getMojoStatusDirectory();
330         File mojoConfigFile = new File( mojoConfigBase, CREATED_FILES_LST_FILENAME );
331 
332         try
333         {
334             FileUtils.fileWriteArray( mojoConfigFile, scanResult.getFilesAdded() );
335         }
336         catch ( IOException e )
337         {
338             throw new MojoExecutionException( "Error while storing the mojo status", e );
339         }
340 
341         // in case of clean compile the file is not created so next compile won't see it
342         // we mus create it here
343         mojoConfigFile = new File( mojoConfigBase, INPUT_FILES_LST_FILENAME );
344         if ( !mojoConfigFile.exists() )
345         {
346             try
347             {
348                 FileUtils.fileWriteArray( mojoConfigFile, toArrayOfPath( incrementalBuildHelperRequest.getInputFiles() ));
349             }
350             catch ( IOException e )
351             {
352                 throw new MojoExecutionException( "Error while storing the mojo status", e );
353             }
354         }
355 
356     }
357 
358     private String[] toArrayOfPath( Set<File> files )
359     {
360         if (files == null || files.isEmpty())
361         {
362             return EMPTY_ARRAY;
363         }
364         String[] paths = new String[files.size()];
365 
366         int i = 0;
367 
368         for ( File file : files )
369         {
370             paths[i] = file.getPath();
371             i++;
372         }
373 
374         return paths;
375     }
376 }