1 package org.apache.maven.plugins.shade.mojo;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.LinkedHashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 import org.apache.maven.artifact.Artifact;
39 import org.apache.maven.artifact.factory.ArtifactFactory;
40 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
41 import org.apache.maven.artifact.repository.ArtifactRepository;
42 import org.apache.maven.artifact.resolver.ArtifactCollector;
43 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
44 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
45 import org.apache.maven.artifact.resolver.ArtifactResolver;
46 import org.apache.maven.model.Dependency;
47 import org.apache.maven.model.Exclusion;
48 import org.apache.maven.model.Model;
49 import org.apache.maven.plugin.AbstractMojo;
50 import org.apache.maven.plugin.MojoExecutionException;
51 import org.apache.maven.plugins.shade.Shader;
52 import org.apache.maven.plugins.shade.filter.SimpleFilter;
53 import org.apache.maven.plugins.shade.pom.PomWriter;
54 import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
55 import org.apache.maven.plugins.shade.resource.ResourceTransformer;
56 import org.apache.maven.project.MavenProject;
57 import org.apache.maven.project.MavenProjectBuilder;
58 import org.apache.maven.project.MavenProjectHelper;
59 import org.apache.maven.project.ProjectBuildingException;
60 import org.apache.maven.shared.dependency.tree.DependencyNode;
61 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
62 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
63 import org.codehaus.plexus.util.FileUtils;
64 import org.codehaus.plexus.util.IOUtil;
65 import org.codehaus.plexus.util.WriterFactory;
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public class ShadeMojo
80 extends AbstractMojo
81 {
82
83
84
85
86
87 private MavenProject project;
88
89
90
91
92
93
94 private MavenProjectHelper projectHelper;
95
96
97
98
99
100
101 private Shader shader;
102
103
104
105
106
107
108
109
110 private DependencyTreeBuilder dependencyTreeBuilder;
111
112
113
114
115
116
117
118
119 private MavenProjectBuilder mavenProjectBuilder;
120
121
122
123
124
125
126
127
128 private ArtifactMetadataSource artifactMetadataSource;
129
130
131
132
133
134
135
136
137 private ArtifactCollector artifactCollector;
138
139
140
141
142
143
144
145
146 protected List remoteArtifactRepositories;
147
148
149
150
151
152
153
154
155 protected ArtifactRepository localRepository;
156
157
158
159
160
161
162
163
164 protected ArtifactFactory artifactFactory;
165
166
167
168
169
170
171
172
173 protected ArtifactResolver artifactResolver;
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 private ArtifactSet artifactSet;
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 private PackageRelocation[] relocations;
214
215
216
217
218
219
220
221 private ResourceTransformer[] transformers;
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246 private ArchiveFilter[] filters;
247
248
249
250
251
252
253 private File outputDirectory;
254
255
256
257
258
259
260
261
262
263
264 private String finalName;
265
266
267
268
269
270
271
272
273
274 private String shadedArtifactId;
275
276
277
278
279
280
281
282 private String shadedGroupFilter;
283
284
285
286
287
288
289
290
291 private boolean shadedArtifactAttached;
292
293
294
295
296
297
298
299
300
301 private boolean createDependencyReducedPom;
302
303
304
305
306
307
308
309 private boolean keepDependenciesWithProvidedScope;
310
311
312
313
314
315
316
317
318 private boolean promoteTransitiveDependencies;
319
320
321
322
323
324
325 private String shadedClassifierName;
326
327
328
329
330
331
332 private boolean createSourcesJar;
333
334
335
336
337
338
339
340
341
342
343 private File outputFile;
344
345
346 public void execute()
347 throws MojoExecutionException
348 {
349 Set artifacts = new LinkedHashSet();
350 Set artifactIds = new LinkedHashSet();
351 Set sourceArtifacts = new LinkedHashSet();
352
353 ArtifactSelector artifactSelector =
354 new ArtifactSelector( project.getArtifact(), artifactSet, shadedGroupFilter );
355
356 if ( artifactSelector.isSelected( project.getArtifact() ) && !"pom".equals( project.getArtifact().getType() ) )
357 {
358 if ( project.getArtifact().getFile() == null )
359 {
360 getLog().error( "The project main artifact does not exist. This could have the following" );
361 getLog().error( "reasons:" );
362 getLog().error( "- You have invoked the goal directly from the command line. This is not" );
363 getLog().error( " supported. Please add the goal to the default lifecycle via an" );
364 getLog().error( " <execution> element in your POM and use \"mvn package\" to have it run." );
365 getLog().error( "- You have bound the goal to a lifecycle phase before \"package\". Please" );
366 getLog().error( " remove this binding from your POM such that the goal will be run in" );
367 getLog().error( " the proper phase." );
368 throw new MojoExecutionException( "Failed to create shaded artifact, "
369 + "project main artifact does not exist." );
370 }
371
372 artifacts.add( project.getArtifact().getFile() );
373
374 if ( createSourcesJar )
375 {
376 File file = shadedSourcesArtifactFile();
377 if ( file.isFile() )
378 {
379 sourceArtifacts.add( file );
380 }
381 }
382 }
383
384 for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
385 {
386 Artifact artifact = (Artifact) it.next();
387
388 if ( !artifactSelector.isSelected( artifact ) )
389 {
390 getLog().info( "Excluding " + artifact.getId() + " from the shaded jar." );
391
392 continue;
393 }
394
395 if ( "pom".equals( artifact.getType() ) )
396 {
397 getLog().info( "Skipping pom dependency " + artifact.getId() + " in the shaded jar." );
398 continue;
399 }
400
401 getLog().info( "Including " + artifact.getId() + " in the shaded jar." );
402
403 artifacts.add( artifact.getFile() );
404
405 artifactIds.add( getId( artifact ) );
406
407 if ( createSourcesJar )
408 {
409 File file = resolveArtifactSources( artifact );
410 if ( file != null )
411 {
412 sourceArtifacts.add( file );
413 }
414 }
415 }
416
417
418 File outputJar = ( outputFile != null ) ? outputFile : shadedArtifactFileWithClassifier();
419 File sourcesJar = shadedSourceArtifactFileWithClassifier();
420
421
422 try
423 {
424 List filters = getFilters();
425
426 List relocators = getRelocators();
427
428 List resourceTransformers = getResourceTransformers();
429
430 shader.shade( artifacts, outputJar, filters, relocators, resourceTransformers );
431
432 if ( createSourcesJar )
433 {
434 shader.shade( sourceArtifacts, sourcesJar, filters, relocators, resourceTransformers );
435 }
436
437 if ( outputFile == null )
438 {
439 boolean renamed = false;
440
441
442
443
444 if ( finalName != null && finalName.length() > 0
445 && !finalName.equals( project.getBuild().getFinalName() ) )
446 {
447 String finalFileName = finalName + "." + project.getArtifact().getArtifactHandler().getExtension();
448 File finalFile = new File( outputDirectory, finalFileName );
449 replaceFile( finalFile, outputJar );
450 outputJar = finalFile;
451
452 renamed = true;
453 }
454
455 if ( shadedArtifactAttached )
456 {
457 getLog().info( "Attaching shaded artifact." );
458 projectHelper.attachArtifact( project, project.getArtifact().getType(), shadedClassifierName,
459 outputJar );
460 if ( createSourcesJar )
461 {
462 projectHelper.attachArtifact( project, "jar", shadedClassifierName + "-sources", sourcesJar );
463 }
464 }
465 else if ( !renamed )
466 {
467 getLog().info( "Replacing original artifact with shaded artifact." );
468 File originalArtifact = project.getArtifact().getFile();
469 replaceFile( originalArtifact, outputJar );
470
471 if ( createSourcesJar )
472 {
473 File shadedSources = shadedSourcesArtifactFile();
474
475 replaceFile( shadedSources, sourcesJar );
476
477 projectHelper.attachArtifact( project, "jar", "sources", shadedSources );
478 }
479
480 if ( createDependencyReducedPom )
481 {
482 createDependencyReducedPom( artifactIds );
483 }
484 }
485 }
486 }
487 catch ( Exception e )
488 {
489 throw new MojoExecutionException( "Error creating shaded jar: " + e.getMessage(), e );
490 }
491 }
492
493 private void replaceFile( File oldFile, File newFile ) throws MojoExecutionException
494 {
495 getLog().info( "Replacing " + oldFile + " with " + newFile );
496
497 File origFile = new File( outputDirectory, "original-" + oldFile.getName() );
498 if ( oldFile.exists() && !oldFile.renameTo( origFile ) )
499 {
500
501 System.gc();
502 System.gc();
503
504 if ( !oldFile.renameTo( origFile ) )
505 {
506
507 try
508 {
509 FileOutputStream fout = new FileOutputStream( origFile );
510 FileInputStream fin = new FileInputStream( oldFile );
511 try
512 {
513 IOUtil.copy( fin, fout );
514 }
515 finally
516 {
517 IOUtil.close( fin );
518 IOUtil.close( fout );
519 }
520 }
521 catch ( IOException ex )
522 {
523
524 getLog().warn( ex );
525 }
526 }
527 }
528 if ( !newFile.renameTo( oldFile ) )
529 {
530
531 System.gc();
532 System.gc();
533
534 if ( !newFile.renameTo( oldFile ) )
535 {
536
537 try
538 {
539 FileOutputStream fout = new FileOutputStream( oldFile );
540 FileInputStream fin = new FileInputStream( newFile );
541 try
542 {
543 IOUtil.copy( fin, fout );
544 }
545 finally
546 {
547 IOUtil.close( fin );
548 IOUtil.close( fout );
549 }
550 }
551 catch ( IOException ex )
552 {
553 throw new MojoExecutionException( "Could not replace original artifact with shaded artifact!", ex );
554 }
555 }
556 }
557 }
558
559 private File resolveArtifactSources( Artifact artifact )
560 {
561
562 Artifact resolvedArtifact =
563 artifactFactory.createArtifactWithClassifier( artifact.getGroupId(),
564 artifact.getArtifactId(),
565 artifact.getVersion(),
566 "java-source",
567 "sources" );
568
569 try
570 {
571 artifactResolver.resolve( resolvedArtifact, remoteArtifactRepositories, localRepository );
572 }
573 catch ( ArtifactNotFoundException e )
574 {
575
576 }
577 catch ( ArtifactResolutionException e )
578 {
579 getLog().warn( "Could not get sources for " + artifact );
580 }
581
582 if ( resolvedArtifact.isResolved() )
583 {
584 return resolvedArtifact.getFile();
585 }
586 return null;
587 }
588
589 private List getRelocators()
590 {
591 List relocators = new ArrayList();
592
593 if ( relocations == null )
594 {
595 return relocators;
596 }
597
598 for ( int i = 0; i < relocations.length; i++ )
599 {
600 PackageRelocation r = relocations[i];
601
602 relocators.add( new SimpleRelocator( r.getPattern(), r.getShadedPattern(), r.getExcludes() ) );
603 }
604
605 return relocators;
606 }
607
608 private List getResourceTransformers()
609 {
610 if ( transformers == null )
611 {
612 return Collections.EMPTY_LIST;
613 }
614
615 return Arrays.asList( transformers );
616 }
617
618 private List getFilters()
619 {
620 List filters = new ArrayList();
621
622 if ( this.filters == null || this.filters.length <= 0 )
623 {
624 return filters;
625 }
626
627 Map artifacts = new HashMap();
628
629 artifacts.put( project.getArtifact(), new ArtifactId( project.getArtifact() ) );
630
631 for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
632 {
633 Artifact artifact = (Artifact) it.next();
634
635 artifacts.put( artifact, new ArtifactId( artifact ) );
636 }
637
638 for ( int i = 0; i < this.filters.length; i++ )
639 {
640 ArchiveFilter filter = this.filters[i];
641
642 ArtifactId pattern = new ArtifactId( filter.getArtifact() );
643
644 Set jars = new HashSet();
645
646 for ( Iterator it = artifacts.entrySet().iterator(); it.hasNext(); )
647 {
648 Map.Entry entry = (Map.Entry) it.next();
649
650 if ( ( (ArtifactId) entry.getValue() ).matches( pattern ) )
651 {
652 jars.add( ( (Artifact) entry.getKey() ).getFile() );
653 }
654 }
655
656 if ( jars.isEmpty() )
657 {
658 getLog().info( "No artifact matching filter " + filter.getArtifact() );
659
660 continue;
661 }
662
663 filters.add( new SimpleFilter( jars, filter.getIncludes(), filter.getExcludes() ) );
664
665 }
666
667 return filters;
668 }
669
670 private File shadedArtifactFileWithClassifier()
671 {
672 Artifact artifact = project.getArtifact();
673 final String shadedName =
674 shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "."
675 + artifact.getArtifactHandler().getExtension();
676 return new File( outputDirectory, shadedName );
677 }
678
679 private File shadedSourceArtifactFileWithClassifier()
680 {
681 Artifact artifact = project.getArtifact();
682 final String shadedName =
683 shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "-sources."
684 + artifact.getArtifactHandler().getExtension();
685 return new File( outputDirectory, shadedName );
686 }
687
688 private File shadedSourcesArtifactFile()
689 {
690 Artifact artifact = project.getArtifact();
691
692 String shadedName;
693
694 if ( project.getBuild().getFinalName() != null )
695 {
696 shadedName = project.getBuild().getFinalName() + "-sources." + artifact.getArtifactHandler().getExtension();
697 }
698 else
699 {
700 shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-sources."
701 + artifact.getArtifactHandler().getExtension();
702 }
703
704 return new File( outputDirectory, shadedName );
705 }
706
707
708
709 private void createDependencyReducedPom( Set artifactsToRemove )
710 throws IOException, DependencyTreeBuilderException, ProjectBuildingException
711 {
712 Model model = project.getOriginalModel();
713 List dependencies = new ArrayList();
714
715 boolean modified = false;
716
717 List transitiveDeps = new ArrayList();
718
719 for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
720 {
721 Artifact artifact = (Artifact) it.next();
722
723
724 Dependency dep = new Dependency();
725 dep.setArtifactId( artifact.getArtifactId() );
726 if ( artifact.hasClassifier() )
727 {
728 dep.setClassifier( artifact.getClassifier() );
729 }
730 dep.setGroupId( artifact.getGroupId() );
731 dep.setOptional( artifact.isOptional() );
732 dep.setScope( artifact.getScope() );
733 dep.setType( artifact.getType() );
734 dep.setVersion( artifact.getVersion() );
735
736
737
738 transitiveDeps.add( dep );
739 }
740 List origDeps = project.getDependencies();
741
742 if ( promoteTransitiveDependencies )
743 {
744 origDeps = transitiveDeps;
745 }
746
747 for ( Iterator i = origDeps.iterator(); i.hasNext(); )
748 {
749 Dependency d = (Dependency) i.next();
750
751 dependencies.add( d );
752
753 String id = getId( d );
754
755 if ( artifactsToRemove.contains( id ) )
756 {
757 modified = true;
758
759 if ( keepDependenciesWithProvidedScope )
760 {
761 d.setScope( "provided" );
762 }
763 else
764 {
765 dependencies.remove( d );
766 }
767 }
768 }
769
770
771 if ( modified )
772 {
773 while ( modified )
774 {
775
776 model.setDependencies( dependencies );
777
778
779
780
781
782 File f = new File( project.getBasedir(), "dependency-reduced-pom.xml" );
783 if ( f.exists() )
784 {
785 f.delete();
786 }
787
788 Writer w = WriterFactory.newXmlWriter( f );
789
790 try
791 {
792 PomWriter.write( w, model, true );
793 }
794 finally
795 {
796 w.close();
797 }
798
799 MavenProject p2 = mavenProjectBuilder.build( f, localRepository, null );
800 modified = updateExcludesInDeps( p2, dependencies, transitiveDeps );
801
802 }
803
804
805
806
807
808
809
810 File f = new File( project.getBasedir(), "dependency-reduced-pom.xml" );
811 File f2 = new File( outputDirectory, "dependency-reduced-pom.xml" );
812 FileUtils.copyFile( f, f2 );
813 FileUtils.forceDeleteOnExit( f );
814 project.setFile( f );
815 }
816 }
817
818 private String getId( Artifact artifact )
819 {
820 return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier() );
821 }
822
823 private String getId( Dependency dependency )
824 {
825 return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(),
826 dependency.getClassifier() );
827 }
828
829 private String getId( String groupId, String artifactId, String type, String classifier )
830 {
831 return groupId + ":" + artifactId + ":" + type + ":" + ( ( classifier != null ) ? classifier : "" );
832 }
833
834 public boolean updateExcludesInDeps( MavenProject project,
835 List dependencies,
836 List transitiveDeps )
837 throws DependencyTreeBuilderException
838 {
839 DependencyNode node = dependencyTreeBuilder.buildDependencyTree(
840 project,
841 localRepository,
842 artifactFactory,
843 artifactMetadataSource,
844 null,
845 artifactCollector );
846 boolean modified = false;
847 Iterator it = node.getChildren().listIterator();
848 while ( it.hasNext() )
849 {
850 DependencyNode n2 = (DependencyNode) it.next();
851 Iterator it2 = n2.getChildren().listIterator();
852 while ( it2.hasNext() )
853 {
854 DependencyNode n3 = (DependencyNode) it2.next();
855
856
857
858 if ( n3.getState() == DependencyNode.INCLUDED )
859 {
860
861
862
863
864
865
866 boolean found = false;
867 for ( int x = 0; x < transitiveDeps.size(); x++ )
868 {
869 Dependency dep = (Dependency) transitiveDeps.get( x );
870 if ( dep.getArtifactId().equals( n3.getArtifact().getArtifactId() )
871 && dep.getGroupId().equals( n3.getArtifact().getGroupId() ) )
872 {
873 found = true;
874 }
875
876 }
877
878 if ( !found )
879 {
880 for ( int x = 0; x < dependencies.size(); x++ )
881 {
882 Dependency dep = (Dependency) dependencies.get( x );
883 if ( dep.getArtifactId().equals( n2.getArtifact().getArtifactId() )
884 && dep.getGroupId().equals( n2.getArtifact().getGroupId() ) )
885 {
886 Exclusion exclusion = new Exclusion();
887 exclusion.setArtifactId( n3.getArtifact().getArtifactId() );
888 exclusion.setGroupId( n3.getArtifact().getGroupId() );
889 dep.addExclusion( exclusion );
890 modified = true;
891 break;
892 }
893 }
894 }
895 }
896 }
897 }
898 return modified;
899 }
900 }