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 1400739 2012-10-21 23:05:22Z hboutemy $
55   * @since 2.0-alpha-2
56   */
57  @Mojo( name = "build-classpath", requiresDependencyResolution = ResolutionScope.TEST,
58         defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true )
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 stripVersion 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      * Either append the artifact's baseVersion or uniqueVersion to the filename.
147      * Will only be used if {@link #isStripVersion()} is {@code false}.
148      * @since 2.6
149      */
150     @Parameter( property = "mdep.useBaseVersion", defaultValue = "true" )
151     protected boolean useBaseVersion = true;
152 
153     /**
154      * Maven ProjectHelper
155      */
156     @Component
157     private MavenProjectHelper projectHelper;
158 
159     boolean isFileSepSet = true;
160 
161     boolean isPathSepSet = true;
162 
163     /**
164      * Main entry into mojo. Gets the list of dependencies and iterates through calling copyArtifact.
165      *
166      * @throws MojoExecutionException with a message if an error occurs.
167      * @see #getDependencies
168      * @see #copyArtifact(Artifact, boolean)
169      */
170     public void execute()
171         throws MojoExecutionException
172     {
173 
174         if ( cpFile != null )
175         {
176             getLog().warn( "The parameter cpFile is deprecated. Use outputFile instead." );
177             this.outputFile = cpFile;
178         }
179 
180         // initialize the separators.
181         isFileSepSet = StringUtils.isNotEmpty( fileSeparator );
182         isPathSepSet = StringUtils.isNotEmpty( pathSeparator );
183 
184         //don't allow them to have absolute paths when they attach.
185         if ( attach && StringUtils.isEmpty( localRepoProperty ) )
186         {
187             localRepoProperty = "${M2_REPO}";
188         }
189 
190         Set<Artifact> artifacts = getResolvedDependencies( true );
191 
192         if ( artifacts == null || artifacts.isEmpty() )
193         {
194             getLog().info( "No dependencies found." );
195         }
196 
197         List<Artifact> artList = new ArrayList<Artifact>( artifacts );
198 
199         StringBuilder sb = new StringBuilder();
200         Iterator<Artifact> i = artList.iterator();
201 
202         if ( i.hasNext() )
203         {
204             appendArtifactPath( i.next(), sb );
205 
206             while ( i.hasNext() )
207             {
208                 sb.append( isPathSepSet ? this.pathSeparator : File.pathSeparator );
209                 appendArtifactPath( (Artifact) i.next(), sb );
210             }
211         }
212 
213         String cpString = sb.toString();
214 
215         // if file separator is set, I need to replace the default one from all
216         // the file paths that were pulled from the artifacts
217         if ( isFileSepSet )
218         {
219             // Escape file separators to be used as literal strings
220             final String pattern = Pattern.quote( File.separator );
221             final String replacement = Matcher.quoteReplacement( fileSeparator );
222             cpString = cpString.replaceAll( pattern, replacement );
223         }
224 
225         //make the string valid for filtering
226         if ( outputFilterFile )
227         {
228             cpString = "classpath=" + cpString;
229         }
230 
231         if ( outputFile == null )
232         {
233             getLog().info( "Dependencies classpath:\n" + cpString );
234         }
235         else
236         {
237             if ( regenerateFile || !isUpdToDate( cpString ) )
238             {
239                 storeClasspathFile( cpString, outputFile );
240             }
241             else
242             {
243                 this.getLog().info( "Skipped writing classpath file '" + outputFile + "'.  No changes found." );
244             }
245         }
246         if ( attach )
247         {
248             attachFile( cpString );
249         }
250     }
251 
252     protected void attachFile( String cpString )
253         throws MojoExecutionException
254     {
255         File attachedFile = new File( project.getBuild().getDirectory(), "classpath" );
256         storeClasspathFile( cpString, attachedFile );
257 
258         projectHelper.attachArtifact( project, attachedFile, "classpath" );
259     }
260 
261     /**
262      * Appends the artifact path into the specified StringBuilder.
263      *
264      * @param art
265      * @param sb
266      */
267     protected void appendArtifactPath( Artifact art, StringBuilder sb )
268     {
269         if ( prefix == null )
270         {
271             String file = art.getFile().getPath();
272             // substitute the property for the local repo path to make the classpath file portable.
273             if ( StringUtils.isNotEmpty( localRepoProperty ) )
274             {
275                 file = StringUtils.replace( file, getLocal().getBasedir(), localRepoProperty );
276             }
277             sb.append( file );
278         }
279         else
280         {
281             // TODO: add param for prepending groupId and version.
282             sb.append( prefix );
283             sb.append( File.separator );
284             sb.append( DependencyUtil.getFormattedFileName( art, this.stripVersion, this.prependGroupId, this.useBaseVersion ) );
285         }
286     }
287 
288     /**
289      * Checks that new classpath differs from that found inside the old classpathFile.
290      *
291      * @param cpString
292      * @return true if the specified classpath equals to that found inside the file, false otherwise (including when
293      *         file does not exists but new classpath does).
294      */
295     private boolean isUpdToDate( String cpString )
296     {
297         try
298         {
299             String oldCp = readClasspathFile();
300             return ( cpString == oldCp || ( cpString != null && cpString.equals( oldCp ) ) );
301         }
302         catch ( Exception ex )
303         {
304             this.getLog().warn(
305                 "Error while reading old classpath file '" + outputFile + "' for up-to-date check: " + ex );
306 
307             return false;
308         }
309     }
310 
311     /**
312      * It stores the specified string into that file.
313      *
314      * @param cpString the string to be written into the file.
315      * @throws MojoExecutionException
316      */
317     private void storeClasspathFile( String cpString, File out )
318         throws MojoExecutionException
319     {
320         //make sure the parent path exists.
321         out.getParentFile().mkdirs();
322 
323         Writer w = null;
324         try
325         {
326             w = new BufferedWriter( new FileWriter( out ) );
327             w.write( cpString );
328             getLog().info( "Wrote classpath file '" + out + "'." );
329         }
330         catch ( IOException ex )
331         {
332             throw new MojoExecutionException( "Error while writting to classpath file '" + out + "': " + ex.toString(),
333                                               ex );
334         }
335         finally
336         {
337             IOUtil.close( w );
338         }
339     }
340 
341     /**
342      * Reads into a string the file specified by the mojo param 'outputFile'. Assumes, the instance variable
343      * 'outputFile' is not null.
344      * 
345      * @return the string contained in the classpathFile, if exists, or null otherwise.
346      * @throws MojoExecutionException
347      */
348     protected String readClasspathFile()
349         throws IOException
350     {
351         if ( outputFile == null )
352         {
353             throw new IllegalArgumentException(
354                 "The outputFile parameter cannot be null if the file is intended to be read." );
355         }
356 
357         if ( !outputFile.isFile() )
358         {
359             return null;
360         }
361         StringBuilder sb = new StringBuilder();
362         BufferedReader r = null;
363 
364         try
365         {
366             r = new BufferedReader( new FileReader( outputFile ) );
367             String l;
368             while ( ( l = r.readLine() ) != null )
369             {
370                 sb.append( l );
371             }
372 
373             return sb.toString();
374         }
375         finally
376         {
377             IOUtil.close( r );
378         }
379     }
380 
381     /**
382      * Compares artifacts lexicographically, using pattern [group_id][artifact_id][version].
383      *
384      * @param art1 first object
385      * @param art2 second object
386      * @return the value <code>0</code> if the argument string is equal to this string; a value less than
387      *         <code>0</code> if this string is lexicographically less than the string argument; and a value greater
388      *         than <code>0</code> if this string is lexicographically greater than the string argument.
389      */
390     public int compare( Artifact art1, Artifact art2 )
391     {
392         if ( art1 == art2 )
393         {
394             return 0;
395         }
396         else if ( art1 == null )
397         {
398             return -1;
399         }
400         else if ( art2 == null )
401         {
402             return +1;
403         }
404 
405         String s1 = art1.getGroupId() + art1.getArtifactId() + art1.getVersion();
406         String s2 = art2.getGroupId() + art2.getArtifactId() + art2.getVersion();
407 
408         return s1.compareTo( s2 );
409     }
410 
411     protected ArtifactsFilter getMarkedArtifactFilter()
412     {
413         return null;
414     }
415 
416     /**
417      * @return the outputFile
418      */
419     public File getCpFile()
420     {
421         return this.outputFile;
422     }
423 
424     /**
425      * @param theCpFile the outputFile to set
426      */
427     public void setCpFile( File theCpFile )
428     {
429         this.outputFile = theCpFile;
430     }
431 
432     /**
433      * @return the fileSeparator
434      */
435     public String getFileSeparator()
436     {
437         return this.fileSeparator;
438     }
439 
440     /**
441      * @param theFileSeparator the fileSeparator to set
442      */
443     public void setFileSeparator( String theFileSeparator )
444     {
445         this.fileSeparator = theFileSeparator;
446     }
447 
448     /**
449      * @return the pathSeparator
450      */
451     public String getPathSeparator()
452     {
453         return this.pathSeparator;
454     }
455 
456     /**
457      * @param thePathSeparator the pathSeparator to set
458      */
459     public void setPathSeparator( String thePathSeparator )
460     {
461         this.pathSeparator = thePathSeparator;
462     }
463 
464     /**
465      * @return the prefix
466      */
467     public String getPrefix()
468     {
469         return this.prefix;
470     }
471 
472     /**
473      * @param thePrefix the prefix to set
474      */
475     public void setPrefix( String thePrefix )
476     {
477         this.prefix = thePrefix;
478     }
479 
480     /**
481      * @return the regenerateFile
482      */
483     public boolean isRegenerateFile()
484     {
485         return this.regenerateFile;
486     }
487 
488     /**
489      * @param theRegenerateFile the regenerateFile to set
490      */
491     public void setRegenerateFile( boolean theRegenerateFile )
492     {
493         this.regenerateFile = theRegenerateFile;
494     }
495 
496     /**
497      * @return the stripVersion
498      */
499     public boolean isStripVersion()
500     {
501         return this.stripVersion;
502     }
503 
504     /**
505      * @param theStripVersion the stripVersion to set
506      */
507     public void setStripVersion( boolean theStripVersion )
508     {
509         this.stripVersion = theStripVersion;
510     }
511 
512     public String getLocalRepoProperty()
513     {
514         return localRepoProperty;
515     }
516 
517     public void setLocalRepoProperty( String localRepoProperty )
518     {
519         this.localRepoProperty = localRepoProperty;
520     }
521 
522     public boolean isFileSepSet()
523     {
524         return isFileSepSet;
525     }
526 
527     public void setFileSepSet( boolean isFileSepSet )
528     {
529         this.isFileSepSet = isFileSepSet;
530     }
531 
532     public boolean isPathSepSet()
533     {
534         return isPathSepSet;
535     }
536 
537     public void setPathSepSet( boolean isPathSepSet )
538     {
539         this.isPathSepSet = isPathSepSet;
540     }
541 }