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