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