View Javadoc
1   package org.apache.maven.plugins.shade;
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.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.lang.reflect.Field;
26  import java.net.URL;
27  import java.net.URLClassLoader;
28  import java.nio.charset.StandardCharsets;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.LinkedHashSet;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.jar.JarEntry;
36  import java.util.jar.JarOutputStream;
37  
38  import junit.framework.TestCase;
39  
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugins.shade.filter.Filter;
42  import org.apache.maven.plugins.shade.relocation.Relocator;
43  import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
44  import org.apache.maven.plugins.shade.resource.AppendingTransformer;
45  import org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer;
46  import org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;
47  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
48  import org.codehaus.plexus.logging.AbstractLogger;
49  import org.codehaus.plexus.logging.Logger;
50  import org.codehaus.plexus.logging.console.ConsoleLogger;
51  import org.junit.Rule;
52  import org.junit.rules.TemporaryFolder;
53  import org.objectweb.asm.ClassReader;
54  import org.objectweb.asm.ClassVisitor;
55  import org.objectweb.asm.Opcodes;
56  
57  /**
58   * @author Jason van Zyl
59   * @author Mauro Talevi
60   */
61  public class DefaultShaderTest
62      extends TestCase
63  {
64      private static final String[] EXCLUDES = new String[] { "org/codehaus/plexus/util/xml/Xpp3Dom",
65          "org/codehaus/plexus/util/xml/pull.*" };
66  
67      public void testOverlappingResourcesAreLogged() throws IOException, MojoExecutionException {
68          final DefaultShader shader = new DefaultShader();
69          final MockLogger logs = new MockLogger();
70          shader.enableLogging(logs);
71  
72          // we will shade two jars and expect to see META-INF/MANIFEST.MF overlaps, this will always be true
73          // but this can lead to a broken deployment if intended for OSGi or so, so even this should be logged
74          final Set<File> set = new LinkedHashSet<>();
75          set.add( new File( "src/test/jars/test-project-1.0-SNAPSHOT.jar" ) );
76          set.add( new File( "src/test/jars/plexus-utils-1.4.1.jar" ) );
77  
78          final ShadeRequest shadeRequest = new ShadeRequest();
79          shadeRequest.setJars( set );
80          shadeRequest.setRelocators( Collections.<Relocator>emptyList() );
81          shadeRequest.setResourceTransformers( Collections.<ResourceTransformer>emptyList() );
82          shadeRequest.setFilters( Collections.<Filter>emptyList() );
83          shadeRequest.setUberJar( new File( "target/foo-custom_testOverlappingResourcesAreLogged.jar" ) );
84          shader.shade( shadeRequest );
85  
86          final String failureWarnMessage = logs.warnMessages.toString();
87          assertTrue(failureWarnMessage, logs.warnMessages.contains(
88                  "plexus-utils-1.4.1.jar, test-project-1.0-SNAPSHOT.jar define 1 overlapping resource:"));
89          assertTrue(failureWarnMessage, logs.warnMessages.contains("- META-INF/MANIFEST.MF"));
90  
91          final String failureDebugMessage = logs.debugMessages.toString();
92          assertTrue(failureDebugMessage, logs.debugMessages.contains(
93                  "We have a duplicate META-INF/MANIFEST.MF in src/test/jars/plexus-utils-1.4.1.jar" ));
94      }
95  
96      public void testOverlappingResourcesAreLoggedExceptATransformerHandlesIt() throws Exception {
97          TemporaryFolder temporaryFolder = new TemporaryFolder();
98          Set<File> set = new LinkedHashSet<>();
99          temporaryFolder.create();
100         File j1 = temporaryFolder.newFile("j1.jar");
101         try ( JarOutputStream jos = new JarOutputStream(new FileOutputStream( j1 ) ) )
102         {
103             jos.putNextEntry(new JarEntry( "foo.txt" ));
104             jos.write("c1".getBytes(StandardCharsets.UTF_8));
105             jos.closeEntry();
106         }
107         File j2 = temporaryFolder.newFile("j2.jar");
108         try ( JarOutputStream jos = new JarOutputStream(new FileOutputStream( j2 ) ) )
109         {
110             jos.putNextEntry(new JarEntry( "foo.txt" ));
111             jos.write("c2".getBytes(StandardCharsets.UTF_8));
112             jos.closeEntry();
113         }
114         set.add( j1 );
115         set.add( j2 );
116 
117         AppendingTransformer transformer = new AppendingTransformer();
118         Field resource = AppendingTransformer.class.getDeclaredField( "resource" );
119         resource.setAccessible( true );
120         resource.set( transformer, "foo.txt" );
121 
122         ShadeRequest shadeRequest = new ShadeRequest();
123         shadeRequest.setJars( set );
124         shadeRequest.setRelocators( Collections.<Relocator>emptyList() );
125         shadeRequest.setResourceTransformers( Collections.<ResourceTransformer>singletonList( transformer) );
126         shadeRequest.setFilters( Collections.<Filter>emptyList() );
127         shadeRequest.setUberJar( new File( "target/foo-custom_testOverlappingResourcesAreLogged.jar" ) );
128 
129         DefaultShader shaderWithTransformer = new DefaultShader();
130         final MockLogger logWithTransformer = new MockLogger();
131         shaderWithTransformer.enableLogging( logWithTransformer );
132         shaderWithTransformer.shade( shadeRequest );
133 
134         DefaultShader shaderWithoutTransformer = new DefaultShader();
135         MockLogger logWithoutTransformer = new MockLogger();
136         shaderWithoutTransformer.enableLogging( logWithoutTransformer );
137         shadeRequest.setResourceTransformers( Collections.<ResourceTransformer>emptyList() );
138         shaderWithoutTransformer.shade( shadeRequest );
139 
140         temporaryFolder.delete();
141 
142         assertTrue(logWithTransformer.warnMessages.toString(), logWithTransformer.warnMessages.isEmpty());
143         assertTrue(logWithoutTransformer.warnMessages.toString(), logWithoutTransformer.warnMessages.containsAll(
144                Arrays.<String>asList( "j1.jar, j2.jar define 1 overlapping resource:", "- foo.txt" ) ) );
145     }
146 
147     public void testShaderWithDefaultShadedPattern()
148         throws Exception
149     {
150         shaderWithPattern( null, new File( "target/foo-default.jar" ), EXCLUDES );
151     }
152 
153     public void testShaderWithStaticInitializedClass()
154         throws Exception
155     {
156         Shader s = newShader();
157 
158         Set<File> set = new LinkedHashSet<>();
159 
160         set.add( new File( "src/test/jars/test-artifact-1.0-SNAPSHOT.jar" ) );
161 
162         List<Relocator> relocators = new ArrayList<>();
163 
164         relocators.add( new SimpleRelocator( "org.apache.maven.plugins.shade", null, null, null ) );
165 
166         List<ResourceTransformer> resourceTransformers = new ArrayList<>();
167 
168         List<Filter> filters = new ArrayList<>();
169 
170         File file = new File( "target/testShaderWithStaticInitializedClass.jar" );
171 
172         ShadeRequest shadeRequest = new ShadeRequest();
173         shadeRequest.setJars( set );
174         shadeRequest.setUberJar( file );
175         shadeRequest.setFilters( filters );
176         shadeRequest.setRelocators( relocators );
177         shadeRequest.setResourceTransformers( resourceTransformers );
178 
179         s.shade( shadeRequest );
180 
181         try ( URLClassLoader cl = new URLClassLoader( new URL[] { file.toURI().toURL() } ) ) {
182           Class<?> c = cl.loadClass( "hidden.org.apache.maven.plugins.shade.Lib" );
183           Object o = c.newInstance();
184           assertEquals( "foo.bar/baz", c.getDeclaredField( "CONSTANT" ).get( o ) );
185         }
186     }
187 
188     public void testShaderWithCustomShadedPattern()
189         throws Exception
190     {
191         shaderWithPattern( "org/shaded/plexus/util", new File( "target/foo-custom.jar" ), EXCLUDES );
192     }
193 
194     public void testShaderWithoutExcludesShouldRemoveReferencesOfOriginalPattern()
195         throws Exception
196     {
197         // FIXME: shaded jar should not include references to org/codehaus/* (empty dirs) or org.codehaus.* META-INF
198         // files.
199         shaderWithPattern( "org/shaded/plexus/util", new File( "target/foo-custom-without-excludes.jar" ),
200                            new String[] {} );
201     }
202 
203     public void testShaderWithRelocatedClassname()
204         throws Exception
205     {
206         DefaultShader s = newShader();
207 
208         Set<File> set = new LinkedHashSet<>();
209 
210         set.add( new File( "src/test/jars/test-project-1.0-SNAPSHOT.jar" ) );
211 
212         set.add( new File( "src/test/jars/plexus-utils-1.4.1.jar" ) );
213 
214         List<Relocator> relocators = new ArrayList<>();
215 
216         relocators.add( new SimpleRelocator( "org/codehaus/plexus/util/", "_plexus/util/__", null,
217                                              Arrays.<String> asList() ) );
218 
219         List<ResourceTransformer> resourceTransformers = new ArrayList<>();
220 
221         resourceTransformers.add( new ComponentsXmlResourceTransformer() );
222 
223         List<Filter> filters = new ArrayList<>();
224 
225         File file = new File( "target/foo-relocate-class.jar" );
226 
227         ShadeRequest shadeRequest = new ShadeRequest();
228         shadeRequest.setJars( set );
229         shadeRequest.setUberJar( file );
230         shadeRequest.setFilters( filters );
231         shadeRequest.setRelocators( relocators );
232         shadeRequest.setResourceTransformers( resourceTransformers );
233 
234         s.shade( shadeRequest );
235 
236         try ( URLClassLoader cl = new URLClassLoader( new URL[] { file.toURI().toURL() } ) ) {
237           Class<?> c = cl.loadClass( "_plexus.util.__StringUtils" );
238           // first, ensure it works:
239           Object o = c.newInstance();
240           assertEquals( "", c.getMethod( "clean", String.class ).invoke( o, (String) null ) );
241 
242           // now, check that its source file was rewritten:
243           final String[] source = { null };
244           final ClassReader classReader = new ClassReader( cl.getResourceAsStream( "_plexus/util/__StringUtils.class" ) );
245           classReader.accept( new ClassVisitor( Opcodes.ASM4 )
246           {
247             @Override
248             public void visitSource( String arg0, String arg1 )
249             {
250                 super.visitSource( arg0, arg1 );
251                 source[0] = arg0;
252             }
253           }, ClassReader.SKIP_CODE );
254           assertEquals( "__StringUtils.java", source[0] );
255         }
256     }
257 
258     private void shaderWithPattern( String shadedPattern, File jar, String[] excludes )
259         throws Exception
260     {
261         DefaultShader s = newShader();
262 
263         Set<File> set = new LinkedHashSet<>();
264 
265         set.add( new File( "src/test/jars/test-project-1.0-SNAPSHOT.jar" ) );
266 
267         set.add( new File( "src/test/jars/plexus-utils-1.4.1.jar" ) );
268 
269         List<Relocator> relocators = new ArrayList<>();
270 
271         relocators.add( new SimpleRelocator( "org/codehaus/plexus/util", shadedPattern, null, Arrays.asList( excludes ) ) );
272 
273         List<ResourceTransformer> resourceTransformers = new ArrayList<>();
274 
275         resourceTransformers.add( new ComponentsXmlResourceTransformer() );
276 
277         List<Filter> filters = new ArrayList<>();
278 
279         ShadeRequest shadeRequest = new ShadeRequest();
280         shadeRequest.setJars( set );
281         shadeRequest.setUberJar( jar );
282         shadeRequest.setFilters( filters );
283         shadeRequest.setRelocators( relocators );
284         shadeRequest.setResourceTransformers( resourceTransformers );
285 
286         s.shade( shadeRequest );
287     }
288 
289     private static DefaultShader newShader()
290     {
291         DefaultShader s = new DefaultShader();
292 
293         s.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "TEST" ) );
294 
295         return s;
296     }
297 
298     private static class MockLogger extends AbstractLogger
299     {
300         private final List<String> debugMessages = new ArrayList<>();
301         private final List<String> warnMessages = new ArrayList<>();
302 
303         private MockLogger()
304         {
305             super( Logger.LEVEL_INFO, "test" );
306         }
307 
308         @Override
309         public void debug( String s, Throwable throwable )
310         {
311             debugMessages.add( s.replace( '\\', '/' ).trim() );
312         }
313 
314         @Override
315         public void info( String s, Throwable throwable )
316         {
317             // no-op
318         }
319 
320         @Override
321         public void warn( String s, Throwable throwable )
322         {
323             warnMessages.add( s.replace( '\\', '/' ).trim() );
324         }
325 
326         @Override
327         public void error( String s, Throwable throwable )
328         {
329             // no-op
330         }
331 
332         @Override
333         public void fatalError( String s, Throwable throwable )
334         {
335             // no-op
336         }
337 
338         @Override
339         public Logger getChildLogger( String s )
340         {
341             return this;
342         }
343     }
344 }