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