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 }