View Javadoc

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