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