1 package org.apache.maven.plugins.shade;
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.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
59
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
73
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
198
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
239 Object o = c.newInstance();
240 assertEquals( "", c.getMethod( "clean", String.class ).invoke( o, (String) null ) );
241
242
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
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
330 }
331
332 @Override
333 public void fatalError( String s, Throwable throwable )
334 {
335
336 }
337
338 @Override
339 public Logger getChildLogger( String s )
340 {
341 return this;
342 }
343 }
344 }