View Javadoc

1   package org.apache.maven.plugin.dependency;
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.artifact.Artifact;
23  import org.apache.maven.plugin.MojoExecutionException;
24  import org.apache.maven.plugin.dependency.utils.DependencyUtil;
25  import org.apache.maven.plugins.annotations.Component;
26  import org.apache.maven.plugins.annotations.LifecyclePhase;
27  import org.apache.maven.plugins.annotations.Mojo;
28  import org.apache.maven.plugins.annotations.Parameter;
29  import org.apache.maven.plugins.annotations.ResolutionScope;
30  import org.apache.maven.project.MavenProjectHelper;
31  import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
32  import org.codehaus.plexus.util.IOUtil;
33  import org.codehaus.plexus.util.StringUtils;
34  
35  import java.io.BufferedReader;
36  import java.io.BufferedWriter;
37  import java.io.File;
38  import java.io.FileReader;
39  import java.io.FileWriter;
40  import java.io.IOException;
41  import java.io.Writer;
42  import java.util.ArrayList;
43  import java.util.Comparator;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Set;
47  import java.util.regex.Matcher;
48  import java.util.regex.Pattern;
49  
50  /**
51   * This goal will output a classpath string of dependencies from the local repository to a file or log.
52   *
53   * @author ankostis
54   * @version $Id: BuildClasspathMojo.java 1367274 2012-07-30 20:32:05Z hboutemy $
55   * @since 2.0-alpha-2
56   */
57  @Mojo( name = "build-classpath", requiresDependencyResolution = ResolutionScope.TEST,
58         defaultPhase = LifecyclePhase.GENERATE_SOURCES )
59  public class BuildClasspathMojo
60      extends AbstractDependencyFilterMojo
61      implements Comparator<Artifact>
62  {
63  
64      /**
65       * Strip artifact version during copy (only works if prefix is set)
66       */
67      @Parameter( property = "mdep.stripVersion", defaultValue = "false" )
68      private boolean stripVersion = false;
69  
70      /**
71       * The prefix to prepend on each dependent artifact. If undefined, the paths refer to the actual files store in the
72       * local repository (the stipVersion parameter does nothing then).
73       */
74      @Parameter( property = "mdep.prefix" )
75      private String prefix;
76  
77      /**
78       * The file to write the classpath string. If undefined, it just prints the classpath as [INFO].
79       * This parameter is deprecated. Use outputFile instead.
80       *
81       * @since 2.0
82       * @deprecated use outputFile instead
83       */
84      @Parameter( property = "mdep.cpFile" )
85      private File cpFile;
86  
87      /**
88       * The file to write the classpath string. If undefined, it just prints the classpath as [INFO].
89       */
90      @Parameter( property = "mdep.outputFile" )
91      private File outputFile;
92  
93      /**
94       * If 'true', it skips the up-to-date-check, and always regenerates the classpath file.
95       */
96      @Parameter( property = "mdep.regenerateFile", defaultValue = "false" )
97      private boolean regenerateFile;
98  
99      /**
100      * Override the char used between the paths. This field is initialized to contain the first character of the value
101      * of the system property file.separator. On UNIX systems the value of this field is '/'; on Microsoft Windows
102      * systems it is '\'. The default is File.separator
103      *
104      * @since 2.0
105      */
106     @Parameter( property = "mdep.fileSeparator", defaultValue = "" )
107     private String fileSeparator;
108 
109     /**
110      * Override the char used between path folders. The system-dependent path-separator character. This field is
111      * initialized to contain the first character of the value of the system property path.separator. This character is
112      * used to separate filenames in a sequence of files given as a path list. On UNIX systems, this character is ':';
113      * on Microsoft Windows systems it is ';'.
114      *
115      * @since 2.0
116      */
117     @Parameter( property = "mdep.pathSeparator", defaultValue = "" )
118     private String pathSeparator;
119 
120     /**
121      * Replace the absolute path to the local repo with this property. This field is ignored it prefix is declared. The
122      * value will be forced to "${M2_REPO}" if no value is provided AND the attach flag is true.
123      *
124      * @since 2.0
125      */
126     @Parameter( property = "mdep.localRepoProperty", defaultValue = "" )
127     private String localRepoProperty;
128 
129     /**
130      * Attach the classpath file to the main artifact so it can be installed and deployed.
131      *
132      * @since 2.0
133      */
134     @Parameter( defaultValue = "false" )
135     boolean attach;
136 
137     /**
138      * Write out the classpath in a format compatible with filtering (classpath=xxxxx)
139      *
140      * @since 2.0
141      */
142     @Parameter( property = "mdep.outputFilterFile", defaultValue = "false" )
143     boolean outputFilterFile;
144 
145     /**
146      * Maven ProjectHelper
147      */
148     @Component
149     private MavenProjectHelper projectHelper;
150 
151     boolean isFileSepSet = true;
152 
153     boolean isPathSepSet = true;
154 
155     /**
156      * Main entry into mojo. Gets the list of dependencies and iterates through calling copyArtifact.
157      *
158      * @throws MojoExecutionException with a message if an error occurs.
159      * @see #getDependencies
160      * @see #copyArtifact(Artifact, boolean)
161      */
162     public void execute()
163         throws MojoExecutionException
164     {
165 
166         if ( cpFile != null )
167         {
168             getLog().warn( "The parameter cpFile is deprecated. Use outputFile instead." );
169             this.outputFile = cpFile;
170         }
171 
172         // initialize the separators.
173         isFileSepSet = StringUtils.isNotEmpty( fileSeparator );
174         isPathSepSet = StringUtils.isNotEmpty( pathSeparator );
175 
176         //don't allow them to have absolute paths when they attach.
177         if ( attach && StringUtils.isEmpty( localRepoProperty ) )
178         {
179             localRepoProperty = "${M2_REPO}";
180         }
181 
182         Set<Artifact> artifacts = getResolvedDependencies( true );
183 
184         if ( artifacts == null || artifacts.isEmpty() )
185         {
186             getLog().info( "No dependencies found." );
187         }
188 
189         List<Artifact> artList = new ArrayList<Artifact>( artifacts );
190 
191         StringBuffer sb = new StringBuffer();
192         Iterator<Artifact> i = artList.iterator();
193 
194         if ( i.hasNext() )
195         {
196             appendArtifactPath( i.next(), sb );
197 
198             while ( i.hasNext() )
199             {
200                 sb.append( isPathSepSet ? this.pathSeparator : File.pathSeparator );
201                 appendArtifactPath( (Artifact) i.next(), sb );
202             }
203         }
204 
205         String cpString = sb.toString();
206 
207         // if file separator is set, I need to replace the default one from all
208         // the file paths that were pulled from the artifacts
209         if ( isFileSepSet )
210         {
211             // Escape file separators to be used as literal strings
212             final String pattern = Pattern.quote( File.separator );
213             final String replacement = Matcher.quoteReplacement( fileSeparator );
214             cpString = cpString.replaceAll( pattern, replacement );
215         }
216 
217         //make the string valid for filtering
218         if ( outputFilterFile )
219         {
220             cpString = "classpath=" + cpString;
221         }
222 
223         if ( outputFile == null )
224         {
225             getLog().info( "Dependencies classpath:\n" + cpString );
226         }
227         else
228         {
229             if ( regenerateFile || !isUpdToDate( cpString ) )
230             {
231                 storeClasspathFile( cpString, outputFile );
232             }
233             else
234             {
235                 this.getLog().info( "Skipped writing classpath file '" + outputFile + "'.  No changes found." );
236             }
237         }
238         if ( attach )
239         {
240             attachFile( cpString );
241         }
242     }
243 
244     protected void attachFile( String cpString )
245         throws MojoExecutionException
246     {
247         File attachedFile = new File( project.getBuild().getDirectory(), "classpath" );
248         storeClasspathFile( cpString, attachedFile );
249 
250         projectHelper.attachArtifact( project, attachedFile, "classpath" );
251     }
252 
253     /**
254      * Appends the artifact path into the specified stringBuffer.
255      *
256      * @param art
257      * @param sb
258      */
259     protected void appendArtifactPath( Artifact art, StringBuffer sb )
260     {
261         if ( prefix == null )
262         {
263             String file = art.getFile().getPath();
264             // substitute the property for the local repo path to make the classpath file portable.
265             if ( StringUtils.isNotEmpty( localRepoProperty ) )
266             {
267                 file = StringUtils.replace( file, getLocal().getBasedir(), localRepoProperty );
268             }
269             sb.append( file );
270         }
271         else
272         {
273             // TODO: add param for prepending groupId and version.
274             sb.append( prefix );
275             sb.append( File.separator );
276             sb.append( DependencyUtil.getFormattedFileName( art, this.stripVersion, this.prependGroupId ) );
277         }
278     }
279 
280     /**
281      * Checks that new classpath differs from that found inside the old classpathFile.
282      *
283      * @param cpString
284      * @return true if the specified classpath equals to that found inside the file, false otherwise (including when
285      *         file does not exists but new classpath does).
286      */
287     private boolean isUpdToDate( String cpString )
288     {
289         try
290         {
291             String oldCp = readClasspathFile();
292             return ( cpString == oldCp || ( cpString != null && cpString.equals( oldCp ) ) );
293         }
294         catch ( Exception ex )
295         {
296             this.getLog().warn(
297                 "Error while reading old classpath file '" + outputFile + "' for up-to-date check: " + ex );
298 
299             return false;
300         }
301     }
302 
303     /**
304      * It stores the specified string into that file.
305      *
306      * @param cpString the string to be written into the file.
307      * @throws MojoExecutionException
308      */
309     private void storeClasspathFile( String cpString, File out )
310         throws MojoExecutionException
311     {
312         //make sure the parent path exists.
313         out.getParentFile().mkdirs();
314 
315         Writer w = null;
316         try
317         {
318             w = new BufferedWriter( new FileWriter( out ) );
319             w.write( cpString );
320             getLog().info( "Wrote classpath file '" + out + "'." );
321         }
322         catch ( IOException ex )
323         {
324             throw new MojoExecutionException( "Error while writting to classpath file '" + out + "': " + ex.toString(),
325                                               ex );
326         }
327         finally
328         {
329             IOUtil.close( w );
330         }
331     }
332 
333     /**
334      * Reads into a string the file specified by the mojo param 'outputFile'. Assumes, the instance variable
335      * 'outputFile' is not null.
336      * 
337      * @return the string contained in the classpathFile, if exists, or null otherwise.
338      * @throws MojoExecutionException
339      */
340     protected String readClasspathFile()
341         throws IOException
342     {
343         if ( outputFile == null )
344         {
345             throw new IllegalArgumentException(
346                 "The outputFile parameter cannot be null if the file is intended to be read." );
347         }
348 
349         if ( !outputFile.isFile() )
350         {
351             return null;
352         }
353         StringBuffer sb = new StringBuffer();
354         BufferedReader r = null;
355 
356         try
357         {
358             r = new BufferedReader( new FileReader( outputFile ) );
359             String l;
360             while ( ( l = r.readLine() ) != null )
361             {
362                 sb.append( l );
363             }
364 
365             return sb.toString();
366         }
367         finally
368         {
369             IOUtil.close( r );
370         }
371     }
372 
373     /**
374      * Compares artifacts lexicographically, using pattern [group_id][artifact_id][version].
375      *
376      * @param art1 first object
377      * @param art2 second object
378      * @return the value <code>0</code> if the argument string is equal to this string; a value less than
379      *         <code>0</code> if this string is lexicographically less than the string argument; and a value greater
380      *         than <code>0</code> if this string is lexicographically greater than the string argument.
381      */
382     public int compare( Artifact art1, Artifact art2 )
383     {
384         if ( art1 == art2 )
385         {
386             return 0;
387         }
388         else if ( art1 == null )
389         {
390             return -1;
391         }
392         else if ( art2 == null )
393         {
394             return +1;
395         }
396 
397         String s1 = art1.getGroupId() + art1.getArtifactId() + art1.getVersion();
398         String s2 = art2.getGroupId() + art2.getArtifactId() + art2.getVersion();
399 
400         return s1.compareTo( s2 );
401     }
402 
403     protected ArtifactsFilter getMarkedArtifactFilter()
404     {
405         return null;
406     }
407 
408     /**
409      * @return the outputFile
410      */
411     public File getCpFile()
412     {
413         return this.outputFile;
414     }
415 
416     /**
417      * @param theCpFile the outputFile to set
418      */
419     public void setCpFile( File theCpFile )
420     {
421         this.outputFile = theCpFile;
422     }
423 
424     /**
425      * @return the fileSeparator
426      */
427     public String getFileSeparator()
428     {
429         return this.fileSeparator;
430     }
431 
432     /**
433      * @param theFileSeparator the fileSeparator to set
434      */
435     public void setFileSeparator( String theFileSeparator )
436     {
437         this.fileSeparator = theFileSeparator;
438     }
439 
440     /**
441      * @return the pathSeparator
442      */
443     public String getPathSeparator()
444     {
445         return this.pathSeparator;
446     }
447 
448     /**
449      * @param thePathSeparator the pathSeparator to set
450      */
451     public void setPathSeparator( String thePathSeparator )
452     {
453         this.pathSeparator = thePathSeparator;
454     }
455 
456     /**
457      * @return the prefix
458      */
459     public String getPrefix()
460     {
461         return this.prefix;
462     }
463 
464     /**
465      * @param thePrefix the prefix to set
466      */
467     public void setPrefix( String thePrefix )
468     {
469         this.prefix = thePrefix;
470     }
471 
472     /**
473      * @return the regenerateFile
474      */
475     public boolean isRegenerateFile()
476     {
477         return this.regenerateFile;
478     }
479 
480     /**
481      * @param theRegenerateFile the regenerateFile to set
482      */
483     public void setRegenerateFile( boolean theRegenerateFile )
484     {
485         this.regenerateFile = theRegenerateFile;
486     }
487 
488     /**
489      * @return the stripVersion
490      */
491     public boolean isStripVersion()
492     {
493         return this.stripVersion;
494     }
495 
496     /**
497      * @param theStripVersion the stripVersion to set
498      */
499     public void setStripVersion( boolean theStripVersion )
500     {
501         this.stripVersion = theStripVersion;
502     }
503 
504     public String getLocalRepoProperty()
505     {
506         return localRepoProperty;
507     }
508 
509     public void setLocalRepoProperty( String localRepoProperty )
510     {
511         this.localRepoProperty = localRepoProperty;
512     }
513 
514     public boolean isFileSepSet()
515     {
516         return isFileSepSet;
517     }
518 
519     public void setFileSepSet( boolean isFileSepSet )
520     {
521         this.isFileSepSet = isFileSepSet;
522     }
523 
524     public boolean isPathSepSet()
525     {
526         return isPathSepSet;
527     }
528 
529     public void setPathSepSet( boolean isPathSepSet )
530     {
531         this.isPathSepSet = isPathSepSet;
532     }
533 }