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 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36
37 import org.apache.maven.artifact.Artifact;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.dependency.utils.DependencyUtil;
40 import org.apache.maven.project.MavenProjectHelper;
41 import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
42 import org.codehaus.plexus.util.IOUtil;
43 import org.codehaus.plexus.util.StringUtils;
44
45 /**
46 * This goal will output a classpath string of dependencies from the local repository to a file or log.
47 *
48 * @goal build-classpath
49 * @requiresDependencyResolution test
50 * @phase generate-sources
51 * @author ankostis
52 * @version $Id: BuildClasspathMojo.java 1085777 2011-03-26 18:13:19Z hboutemy $
53 * @since 2.0-alpha-2
54 */
55 public class BuildClasspathMojo
56 extends AbstractDependencyFilterMojo
57 implements Comparator<Artifact>
58 {
59
60 /**
61 * Strip artifact version during copy (only works if prefix is set)
62 *
63 * @parameter expression="${mdep.stripVersion}" default-value="false"
64 * @parameter
65 */
66 private boolean stripVersion = false;
67
68 /**
69 * The prefix to prepend on each dependent artifact. If undefined, the paths refer to the actual files store in the
70 * local repository (the stipVersion parameter does nothing then).
71 *
72 * @parameter expression="${mdep.prefix}"
73 */
74 private String prefix;
75
76 /**
77 * The file to write the classpath string. If undefined, it just prints the classpath as [INFO].
78 * This parameter is deprecated. Use outputFile instead.
79 * @parameter expression="${mdep.cpFile}"
80 * @deprecated use outputFile instead
81 * @since 2.0
82 */
83 private File cpFile;
84
85 /**
86 * The file to write the classpath string. If undefined, it just prints the classpath as [INFO].
87 * @parameter expression="${mdep.outputFile}"
88 */
89 private File outputFile;
90
91 /**
92 * If 'true', it skips the up-to-date-check, and always regenerates the classpath file.
93 *
94 * @parameter default-value="false" expression="${mdep.regenerateFile}"
95 */
96 private boolean regenerateFile;
97
98 /**
99 * Override the char used between the paths. This field is initialized to contain the first character of the value
100 * of the system property file.separator. On UNIX systems the value of this field is '/'; on Microsoft Windows
101 * systems it is '\'. The default is File.separator
102 *
103 * @since 2.0
104 * @parameter default-value="" expression="${mdep.fileSeparator}"
105 */
106 private String fileSeparator;
107
108 /**
109 * Override the char used between path folders. The system-dependent path-separator character. This field is
110 * initialized to contain the first character of the value of the system property path.separator. This character is
111 * used to separate filenames in a sequence of files given as a path list. On UNIX systems, this character is ':';
112 * on Microsoft Windows systems it is ';'.
113 *
114 * @since 2.0
115 * @parameter default-value="" expression="${mdep.pathSeparator}"
116 */
117 private String pathSeparator;
118
119 /**
120 * Replace the absolute path to the local repo with this property. This field is ignored it prefix is declared. The
121 * value will be forced to "${M2_REPO}" if no value is provided AND the attach flag is true.
122 *
123 * @since 2.0
124 * @parameter default-value="" expression="${mdep.localRepoProperty}"
125 */
126 private String localRepoProperty;
127
128 /**
129 * Attach the classpath file to the main artifact so it can be installed and deployed.
130 *
131 * @since 2.0
132 * @parameter default-value=false
133 */
134 boolean attach;
135
136 /**
137 * Write out the classpath in a format compatible with filtering (classpath=xxxxx)
138 *
139 * @since 2.0
140 * @parameter default-value=false expression="${mdep.outputFilterFile}"
141 */
142 boolean outputFilterFile;
143
144 /**
145 * Maven ProjectHelper
146 *
147 * @component
148 * @readonly
149 */
150 private MavenProjectHelper projectHelper;
151
152 boolean isFileSepSet = true;
153
154 boolean isPathSepSet = true;
155
156 /**
157 * Main entry into mojo. Gets the list of dependencies and iterates through calling copyArtifact.
158 *
159 * @throws MojoExecutionException with a message if an error occurs.
160 * @see #getDependencies
161 * @see #copyArtifact(Artifact, boolean)
162 */
163 public void execute()
164 throws MojoExecutionException
165 {
166
167 if ( cpFile != null )
168 {
169 getLog().warn( "The parameter cpFile is deprecated. Use outputFile instead." );
170 this.outputFile = cpFile;
171 }
172
173 // initialize the separators.
174 isFileSepSet = StringUtils.isNotEmpty( fileSeparator );
175 isPathSepSet = StringUtils.isNotEmpty( pathSeparator );
176
177 //don't allow them to have absolute paths when they attach.
178 if ( attach && StringUtils.isEmpty( localRepoProperty ) )
179 {
180 localRepoProperty = "${M2_REPO}";
181 }
182
183 Set<Artifact> artifacts = getResolvedDependencies( true );
184
185 if ( artifacts == null || artifacts.isEmpty() )
186 {
187 getLog().info( "No dependencies found." );
188 }
189
190 List<Artifact> artList = new ArrayList<Artifact>( artifacts );
191
192 StringBuffer sb = new StringBuffer();
193 Iterator<Artifact> i = artList.iterator();
194
195 if ( i.hasNext() )
196 {
197 appendArtifactPath( i.next(), sb );
198
199 while ( i.hasNext() )
200 {
201 sb.append( isPathSepSet ? this.pathSeparator : File.pathSeparator );
202 appendArtifactPath( (Artifact) i.next(), sb );
203 }
204 }
205
206 String cpString = sb.toString();
207
208 // if file separator is set, I need to replace the default one from all
209 // the file paths that were pulled from the artifacts
210 if ( isFileSepSet )
211 {
212 // Escape file separators to be used as literal strings
213 final String pattern = Pattern.quote( File.separator );
214 final String replacement = Matcher.quoteReplacement( fileSeparator );
215 cpString = cpString.replaceAll( pattern, replacement );
216 }
217
218 //make the string valid for filtering
219 if ( outputFilterFile )
220 {
221 cpString = "classpath=" + cpString;
222 }
223
224 if ( outputFile == null )
225 {
226 getLog().info( "Dependencies classpath:\n" + cpString );
227 }
228 else
229 {
230 if ( regenerateFile || !isUpdToDate( cpString ) )
231 {
232 storeClasspathFile( cpString, outputFile );
233 }
234 else
235 {
236 this.getLog().info( "Skipped writing classpath file '" + outputFile + "'. No changes found." );
237 }
238 }
239 if ( attach )
240 {
241 attachFile( cpString );
242 }
243 }
244
245 protected void attachFile( String cpString )
246 throws MojoExecutionException
247 {
248 File attachedFile = new File( project.getBuild().getDirectory(), "classpath" );
249 storeClasspathFile( cpString, attachedFile );
250
251 projectHelper.attachArtifact( project, attachedFile, "classpath" );
252 }
253
254 /**
255 * Appends the artifact path into the specified stringBuffer.
256 *
257 * @param art
258 * @param sb
259 */
260 protected void appendArtifactPath( Artifact art, StringBuffer sb )
261 {
262 if ( prefix == null )
263 {
264 String file = art.getFile().getPath();
265 // substitute the property for the local repo path to make the classpath file portable.
266 if ( StringUtils.isNotEmpty( localRepoProperty ) )
267 {
268 file = StringUtils.replace( file, getLocal().getBasedir(), localRepoProperty );
269 }
270 sb.append( file );
271 }
272 else
273 {
274 // TODO: add param for prepending groupId and version.
275 sb.append( prefix );
276 sb.append( File.separator );
277 sb.append( DependencyUtil.getFormattedFileName( art, this.stripVersion, this.prependGroupId) );
278 }
279 }
280
281 /**
282 * Checks that new classpath differs from that found inside the old classpathFile.
283 *
284 * @param cpString
285 * @return true if the specified classpath equals to that found inside the file, false otherwise (including when
286 * file does not exists but new classpath does).
287 */
288 private boolean isUpdToDate( String cpString )
289 {
290 try
291 {
292 String oldCp = readClasspathFile();
293 return ( cpString == oldCp || ( cpString != null && cpString.equals( oldCp ) ) );
294 }
295 catch ( Exception ex )
296 {
297 this.getLog().warn( "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 + "': "
325 + ex.toString(), 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 'outputFile' is not
335 * 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 }