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