diff --git a/src/it/projects/MSHADE-313_minimized-services/invoker.properties b/src/it/projects/MSHADE-313_minimized-services/invoker.properties index c8b7b3cd..31f873fc 100644 --- a/src/it/projects/MSHADE-313_minimized-services/invoker.properties +++ b/src/it/projects/MSHADE-313_minimized-services/invoker.properties @@ -15,4 +15,5 @@ # specific language governing permissions and limitations # under the License. +# jdependency-2.6.0 needs Java 8+ invoker.java.version = 1.8+ diff --git a/src/it/projects/MSHADE-313_minimized-services/verify.bsh b/src/it/projects/MSHADE-313_minimized-services/verify.bsh index 2a58a847..2529359c 100644 --- a/src/it/projects/MSHADE-313_minimized-services/verify.bsh +++ b/src/it/projects/MSHADE-313_minimized-services/verify.bsh @@ -34,6 +34,8 @@ String[] wanted = String[] unwanted = { + // Unused SPI config files are not removed + //"META-INF/services/UnusedServiceInterface", "UnusedServiceInterface.class", "UnusedServiceClass.class", "SomeUnreferencedClass.class", diff --git a/src/it/projects/MSHADE-316/invoker.properties b/src/it/projects/MSHADE-316/invoker.properties index f1e7ddb1..31f873fc 100644 --- a/src/it/projects/MSHADE-316/invoker.properties +++ b/src/it/projects/MSHADE-316/invoker.properties @@ -5,9 +5,9 @@ # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -15,5 +15,5 @@ # specific language governing permissions and limitations # under the License. -#jdependency-2.1.1 is Java8 compatible +# jdependency-2.6.0 needs Java 8+ invoker.java.version = 1.8+ diff --git a/src/it/projects/MSHADE-400_self-minimized-services/invoker.properties b/src/it/projects/MSHADE-400_self-minimized-services/invoker.properties new file mode 100644 index 00000000..31f873fc --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/invoker.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# jdependency-2.6.0 needs Java 8+ +invoker.java.version = 1.8+ diff --git a/src/it/projects/MSHADE-400_self-minimized-services/pom.xml b/src/it/projects/MSHADE-400_self-minimized-services/pom.xml new file mode 100644 index 00000000..b48424d9 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/pom.xml @@ -0,0 +1,60 @@ + + + + + + 4.0.0 + + org.acme + module-with-services + 1.0 + + + 8 + 8 + + + + + + org.apache.maven.plugins + maven-shade-plugin + @project.version@ + + + shade + package + + shade + + + true + + org.acme.Application + + + + + + + + + diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/Application.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/Application.java new file mode 100644 index 00000000..1dfaeda9 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/Application.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +import java.util.ServiceLoader; + +public class Application +{ + private UsedClass usedClass = new UsedClass(); + + public static void main( String[] args ) + { + ServiceLoader.load( UsedService.class ); + } +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedClass.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedClass.java new file mode 100644 index 00000000..500782b9 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedClass.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public class UnusedClass +{ +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedService.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedService.java new file mode 100644 index 00000000..7f6410ac --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedService.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public interface UnusedService +{ + public void doSomething(); +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedServiceImplA.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedServiceImplA.java new file mode 100644 index 00000000..0269a720 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedServiceImplA.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public class UnusedServiceImplA implements UnusedService +{ + @Override + public void doSomething() + { + } +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedServiceImplB.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedServiceImplB.java new file mode 100644 index 00000000..dc1d3d60 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UnusedServiceImplB.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public class UnusedServiceImplB implements UnusedService +{ + @Override + public void doSomething() + { + } +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedClass.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedClass.java new file mode 100644 index 00000000..15315216 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedClass.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public class UsedClass +{ +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedService.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedService.java new file mode 100644 index 00000000..23c28405 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedService.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public interface UsedService +{ + public void doSomething(); +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedServiceUnusedImpl.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedServiceUnusedImpl.java new file mode 100644 index 00000000..800ddf07 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedServiceUnusedImpl.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public class UsedServiceUnusedImpl implements UsedService +{ + @Override + public void doSomething() + { + } +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedServiceUsedImpl.java b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedServiceUsedImpl.java new file mode 100644 index 00000000..345235c3 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/java/org/acme/UsedServiceUsedImpl.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.acme; + +public class UsedServiceUsedImpl implements UsedService +{ + @Override + public void doSomething() + { + } +} diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UnusedService b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UnusedService new file mode 100644 index 00000000..c4f9684d --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UnusedService @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# These services are defined, but not used in the entry point or any of its dependency classes +org.acme.UnusedServiceUsedImplA +org.acme.UnusedServiceUsedImplB diff --git a/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UsedService b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UsedService new file mode 100644 index 00000000..0cf82b09 --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UsedService @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +org.acme.UsedServiceUsedImpl + +# This implementation is *not* used: +# org.acme.UsedServiceUnusedImpl diff --git a/src/it/projects/MSHADE-400_self-minimized-services/verify.bsh b/src/it/projects/MSHADE-400_self-minimized-services/verify.bsh new file mode 100644 index 00000000..2708621c --- /dev/null +++ b/src/it/projects/MSHADE-400_self-minimized-services/verify.bsh @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import java.io.*; +import java.util.jar.*; + +String[] wanted = +{ + "META-INF/services/org.acme.UsedService", + "org/acme/Application.class", + "org/acme/UsedClass.class", + "org/acme/UsedService.class", + "org/acme/UsedServiceUsedImpl.class" +}; + +String[] unwanted = +{ + // Unused SPI config files are not removed + //"META-INF/services/org.acme.UnusedService", + "org/acme/UsedServiceUnusedImpl.class", + "org/acme/UnusedClass.class", + "org/acme/UnusedService.class", + "org/acme/UnusedServiceImplA.class", + "org/acme/UnusedServiceImplB.class" +}; + +JarFile jarFile = new JarFile( new File( basedir, "target/module-with-services-1.0.jar" ) ); + +for ( String path : wanted ) +{ + if ( jarFile.getEntry( path ) == null ) + { + throw new IllegalStateException( "wanted path is missing: " + path ); + } +} + +for ( String path : unwanted ) +{ + if ( jarFile.getEntry( path ) != null ) + { + throw new IllegalStateException( "unwanted path is present: " + path ); + } +} + +jarFile.close(); diff --git a/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java b/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java index 9955db0d..0ff035ae 100644 --- a/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java +++ b/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java @@ -22,6 +22,7 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -41,6 +42,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Callable; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -233,60 +235,142 @@ private void shadeJars( ShadeRequest shadeRequest, Set resources, List jarFilters = getFilters( jar, shadeRequest.getFilters() ); - - try ( JarFile jarFile = newJarFile( jar ) ) + if ( jar.isDirectory() ) + { + shadeDir( shadeRequest, resources, transformers, packageMapper, jos, duplicates, + jar, jar, "", jarFilters ); + } + else { + shadeJar( shadeRequest, resources, transformers, packageMapper, jos, duplicates, + jar, jarFilters ); + } + } + } - for ( Enumeration j = jarFile.entries(); j.hasMoreElements(); ) + private void shadeDir( ShadeRequest shadeRequest, Set resources, + List transformers, DefaultPackageMapper packageMapper, + JarOutputStream jos, MultiValuedMap duplicates, + File jar, File current, String prefix, List jarFilters ) throws IOException + { + final File[] children = current.listFiles(); + if ( children == null ) + { + return; + } + for ( final File file : children ) + { + final String name = prefix + file.getName(); + if ( file.isDirectory() ) + { + try + { + shadeDir( + shadeRequest, resources, transformers, packageMapper, jos, + duplicates, jar, file, + prefix + file.getName() + '/', jarFilters ); + continue; + } + catch ( Exception e ) { - JarEntry entry = j.nextElement(); + throw new IOException( + String.format( "Problem shading JAR %s entry %s: %s", current, name, e ), e ); + } + } + if ( isFiltered( jarFilters, name ) || isExcludedEntry( name ) ) + { + continue; + } - String name = entry.getName(); - - if ( entry.isDirectory() || isFiltered( jarFilters, name ) ) - { - continue; - } + try + { + shadeJarEntry( + shadeRequest, resources, transformers, packageMapper, jos, duplicates, jar, + new Callable() + { + @Override + public InputStream call() throws Exception + { + return new FileInputStream( file ); + } + }, name, file.lastModified(), -1 /*ignore*/ ); + } + catch ( Exception e ) + { + throw new IOException( String.format( "Problem shading JAR %s entry %s: %s", current, name, e ), + e ); + } + } + } + private void shadeJar( ShadeRequest shadeRequest, Set resources, + List transformers, DefaultPackageMapper packageMapper, + JarOutputStream jos, MultiValuedMap duplicates, + File jar, List jarFilters ) throws IOException + { + try ( JarFile jarFile = newJarFile( jar ) ) + { - if ( "META-INF/INDEX.LIST".equals( name ) ) - { - // we cannot allow the jar indexes to be copied over or the - // jar is useless. Ideally, we could create a new one - // later - continue; - } + for ( Enumeration j = jarFile.entries(); j.hasMoreElements(); ) + { + final JarEntry entry = j.nextElement(); - if ( "module-info.class".equals( name ) ) - { - logger.warn( "Discovered module-info.class. " - + "Shading will break its strong encapsulation." ); - continue; - } + String name = entry.getName(); - try - { - shadeJarEntry( shadeRequest, resources, transformers, packageMapper, jos, duplicates, jar, - jarFile, entry, name ); - } - catch ( Exception e ) - { - throw new IOException( String.format( "Problem shading JAR %s entry %s: %s", jar, name, e ), - e ); - } + if ( entry.isDirectory() || isFiltered( jarFilters, name ) || isExcludedEntry( name ) ) + { + continue; } + try + { + shadeJarEntry( + shadeRequest, resources, transformers, packageMapper, jos, duplicates, jar, + new Callable() + { + @Override + public InputStream call() throws Exception + { + return jarFile.getInputStream( entry ); + } + }, name, entry.getTime(), entry.getMethod() ); + } + catch ( Exception e ) + { + throw new IOException( String.format( "Problem shading JAR %s entry %s: %s", jar, name, e ), + e ); + } } + } } + private boolean isExcludedEntry( final String name ) + { + if ( "META-INF/INDEX.LIST".equals( name ) ) + { + // we cannot allow the jar indexes to be copied over or the + // jar is useless. Ideally, we could create a new one + // later + return true; + } + + if ( "module-info.class".equals( name ) ) + { + logger.warn( "Discovered module-info.class. " + + "Shading will break its strong encapsulation." ); + return true; + } + return false; + } + private void shadeJarEntry( ShadeRequest shadeRequest, Set resources, List transformers, DefaultPackageMapper packageMapper, JarOutputStream jos, MultiValuedMap duplicates, File jar, - JarFile jarFile, JarEntry entry, String name ) - throws IOException, MojoExecutionException + Callable inputProvider, String name, long time, int method ) + throws Exception { - try ( InputStream in = jarFile.getInputStream( entry ) ) + try ( InputStream in = inputProvider.call() ) { String mappedName = packageMapper.map( name, true, false ); @@ -297,14 +381,14 @@ private void shadeJarEntry( ShadeRequest shadeRequest, Set resources, String dir = mappedName.substring( 0, idx ); if ( !resources.contains( dir ) ) { - addDirectory( resources, jos, dir, entry.getTime() ); + addDirectory( resources, jos, dir, time ); } } duplicates.put( name, jar ); if ( name.endsWith( ".class" ) ) { - addRemappedClass( jos, jar, name, entry.getTime(), in, packageMapper ); + addRemappedClass( jos, jar, name, time, in, packageMapper ); } else if ( shadeRequest.isShadeSourcesContent() && name.endsWith( ".java" ) ) { @@ -314,12 +398,12 @@ else if ( shadeRequest.isShadeSourcesContent() && name.endsWith( ".java" ) ) return; } - addJavaSource( resources, jos, mappedName, entry.getTime(), in, shadeRequest.getRelocators() ); + addJavaSource( resources, jos, mappedName, time, in, shadeRequest.getRelocators() ); } else { if ( !resourceTransformed( transformers, mappedName, in, shadeRequest.getRelocators(), - entry.getTime() ) ) + time ) ) { // Avoid duplicates that aren't accounted for by the resource transformers if ( resources.contains( mappedName ) ) @@ -328,7 +412,7 @@ else if ( shadeRequest.isShadeSourcesContent() && name.endsWith( ".java" ) ) return; } - addResource( resources, jos, mappedName, entry, jarFile ); + addResource( resources, jos, mappedName, inputProvider, time, method ); } else { @@ -534,12 +618,12 @@ private void addRemappedClass( JarOutputStream jos, File jar, String name, return; } - - // Keep the original class in, in case nothing was relocated by RelocatorRemapper. This avoids binary + + // Keep the original class, in case nothing was relocated by ShadeClassRemapper. This avoids binary // differences between classes, simply because they were rewritten and only details like constant pool or // stack map frames are slightly different. byte[] originalClass = IOUtil.toByteArray( is ); - + ClassReader cr = new ClassReader( new ByteArrayInputStream( originalClass ) ); // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool. @@ -561,7 +645,7 @@ private void addRemappedClass( JarOutputStream jos, File jar, String name, throw new MojoExecutionException( "Error in ASM processing class " + name, ise ); } - // If nothing was relocated by RelocatorRemapper, write the original class, otherwise the transformed one + // If nothing was relocated by ShadeClassRemapper, write the original class, otherwise the transformed one final byte[] renamedClass; if ( cv.remapped ) { @@ -656,24 +740,24 @@ private void addJavaSource( Set resources, JarOutputStream jos, String n resources.add( name ); } - private void addResource( Set resources, JarOutputStream jos, String name, JarEntry originalEntry, - JarFile jarFile ) throws IOException + private void addResource( Set resources, JarOutputStream jos, String name, Callable input, + long time, int method ) throws Exception { - ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream( jarFile.getInputStream( originalEntry ) ); + ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream( input.call() ); try { final JarEntry entry = new JarEntry( name ); // We should not change compressed level of uncompressed entries, otherwise JVM can't load these nested jars - if ( inputStream.hasZipHeader() && originalEntry.getMethod() == ZipEntry.STORED ) + if ( inputStream.hasZipHeader() && method == ZipEntry.STORED ) { new CrcAndSize( inputStream ).setupStoredEntry( entry ); inputStream.close(); - inputStream = new ZipHeaderPeekInputStream( jarFile.getInputStream( originalEntry ) ); + inputStream = new ZipHeaderPeekInputStream( input.call() ); } - entry.setTime( originalEntry.getTime() ); + entry.setTime( time ); jos.putNextEntry( entry ); @@ -691,7 +775,7 @@ private interface PackageMapper { /** * Map an entity name according to the mapping rules known to this package mapper - * + * * @param entityName entity name to be mapped * @param mapPaths map "slashy" names like paths or internal Java class names, e.g. {@code com/acme/Foo}? * @param mapPackages map "dotty" names like qualified Java class or package names, e.g. {@code com.acme.Foo}? diff --git a/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java b/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java index 818339ed..84c88090 100644 --- a/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java +++ b/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java @@ -77,17 +77,30 @@ public class MinijarFilter public MinijarFilter( MavenProject project, Log log ) throws IOException { - this( project, log, Collections.emptyList() ); + this( project, log, Collections.emptyList(), Collections.emptySet() ); + } + + /** + * @param project {@link MavenProject} + * @param log {@link Log} + * @param entryPoints + * @throws IOException in case of error. + */ + public MinijarFilter( MavenProject project, Log log, Set entryPoints ) + throws IOException + { + this( project, log, Collections.emptyList(), entryPoints ); } /** * @param project {@link MavenProject} * @param log {@link Log} * @param simpleFilters {@link SimpleFilter} + * @param entryPoints * @throws IOException in case of errors. * @since 1.6 */ - public MinijarFilter( MavenProject project, Log log, List simpleFilters ) + public MinijarFilter( MavenProject project, Log log, List simpleFilters, Set entryPoints ) throws IOException { this.log = log; @@ -111,8 +124,44 @@ public MinijarFilter( MavenProject project, Log log, List simpleFi log.warn( "Removing module-info from " + artifactFile.getName() ); } removePackages( artifactUnit ); - removable.removeAll( artifactUnit.getClazzes() ); - removable.removeAll( artifactUnit.getTransitiveDependencies() ); + if ( entryPoints.isEmpty() ) + { + removable.removeAll( artifactUnit.getClazzes() ); + removable.removeAll( artifactUnit.getTransitiveDependencies() ); + } + else + { + Set artifactUnitClazzes = artifactUnit.getClazzes(); + Set entryPointsToKeep = new HashSet<>(); + for ( String entryPoint : entryPoints ) + { + Clazz entryPointFound = null; + for ( Clazz clazz : artifactUnitClazzes ) + { + if ( clazz.getName().equals( entryPoint ) ) + { + entryPointFound = clazz; + break; + } + } + if ( entryPointFound != null ) + { + entryPointsToKeep.add( entryPointFound ); + } + } + removable.removeAll( entryPointsToKeep ); + if ( entryPointsToKeep.isEmpty() ) + { + removable.removeAll( artifactUnit.getTransitiveDependencies() ); + } + else + { + for ( Clazz entryPoint : entryPointsToKeep ) + { + removable.removeAll( entryPoint.getTransitiveDependencies() ); + } + } + } removeSpecificallyIncludedClasses( project, simpleFilters == null ? Collections.emptyList() : simpleFilters ); removeServices( project, cp ); @@ -131,56 +180,13 @@ private void removeServices( final MavenProject project, final Clazzpath cp ) { for ( final String fileName : project.getRuntimeClasspathElements() ) { - try ( final JarFile jar = new JarFile( fileName ) ) + if ( new File( fileName ).isDirectory() ) { - for ( final Enumeration entries = jar.entries(); entries.hasMoreElements(); ) - { - final JarEntry jarEntry = entries.nextElement(); - if ( jarEntry.isDirectory() || !jarEntry.getName().startsWith( "META-INF/services/" ) ) - { - continue; - } - - final String serviceClassName = - jarEntry.getName().substring( "META-INF/services/".length() ); - final boolean isNeededClass = neededClasses.contains( cp.getClazz( serviceClassName ) ); - if ( !isNeededClass ) - { - continue; - } - - try ( final BufferedReader bufferedReader = - new BufferedReader( new InputStreamReader( jar.getInputStream( jarEntry ), UTF_8 ) ) ) - { - for ( String line = bufferedReader.readLine(); line != null; - line = bufferedReader.readLine() ) - { - final String className = line.split( "#", 2 )[0].trim(); - if ( className.isEmpty() ) - { - continue; - } - - final Clazz clazz = cp.getClazz( className ); - if ( clazz == null || !removable.contains( clazz ) ) - { - continue; - } - - log.debug( className + " was not removed because it is a service" ); - removeClass( clazz ); - repeatScan = true; // check whether the found classes use services in turn - } - } - catch ( final IOException e ) - { - log.warn( e.getMessage() ); - } - } + repeatScan |= removeServicesFromDir( cp, neededClasses, fileName ); } - catch ( final IOException e ) + else { - log.warn( e.getMessage() ); + repeatScan |= removeServicesFromJar( cp, neededClasses, fileName ); } } } @@ -192,6 +198,106 @@ private void removeServices( final MavenProject project, final Clazzpath cp ) while ( repeatScan ); } + private boolean removeServicesFromDir( Clazzpath cp, Set neededClasses, String fileName ) + { + final File servicesDir = new File( fileName, "META-INF/services/" ); + if ( !servicesDir.isDirectory() ) + { + return false; + } + final File[] serviceProviderConfigFiles = servicesDir.listFiles(); + if ( serviceProviderConfigFiles == null || serviceProviderConfigFiles.length == 0 ) + { + return false; + } + + boolean repeatScan = false; + for ( File serviceProviderConfigFile : serviceProviderConfigFiles ) + { + final String serviceClassName = serviceProviderConfigFile.getName(); + final boolean isNeededClass = neededClasses.contains( cp.getClazz( serviceClassName ) ); + if ( !isNeededClass ) + { + continue; + } + + try ( final BufferedReader configFileReader = new BufferedReader( + new InputStreamReader( new FileInputStream( serviceProviderConfigFile ), UTF_8 ) ) ) + { + // check whether the found classes use services in turn + repeatScan |= scanServiceProviderConfigFile( cp, configFileReader ); + } + catch ( final IOException e ) + { + log.warn( e.getMessage() ); + } + } + return repeatScan; + } + + private boolean removeServicesFromJar( Clazzpath cp, Set neededClasses, String fileName ) + { + boolean repeatScan = false; + try ( final JarFile jar = new JarFile( fileName ) ) + { + for ( final Enumeration entries = jar.entries(); entries.hasMoreElements(); ) + { + final JarEntry jarEntry = entries.nextElement(); + if ( jarEntry.isDirectory() || !jarEntry.getName().startsWith( "META-INF/services/" ) ) + { + continue; + } + + final String serviceClassName = jarEntry.getName().substring( "META-INF/services/".length() ); + final boolean isNeededClass = neededClasses.contains( cp.getClazz( serviceClassName ) ); + if ( !isNeededClass ) + { + continue; + } + + try ( final BufferedReader configFileReader = new BufferedReader( + new InputStreamReader( jar.getInputStream( jarEntry ), UTF_8 ) ) ) + { + // check whether the found classes use services in turn + repeatScan = scanServiceProviderConfigFile( cp, configFileReader ); + } + catch ( final IOException e ) + { + log.warn( e.getMessage() ); + } + } + } + catch ( final IOException e ) + { + log.warn( e.getMessage() ); + } + return repeatScan; + } + + private boolean scanServiceProviderConfigFile( Clazzpath cp, BufferedReader configFileReader ) throws IOException + { + boolean serviceClassFound = false; + for ( String line = configFileReader.readLine(); line != null; line = configFileReader.readLine() ) + { + final String className = line.split( "#", 2 )[0].trim(); + if ( className.isEmpty() ) + { + continue; + } + + final Clazz clazz = cp.getClazz( className ); + if ( clazz == null || !removable.contains( clazz ) ) + { + continue; + } + + log.debug( className + " was not removed because it is a service" ); + removeClass( clazz ); + serviceClassFound = true; + } + return serviceClassFound; + } + private void removeClass( final Clazz clazz ) { removable.remove( clazz ); diff --git a/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java b/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java index 6cc019cd..89c2b96f 100644 --- a/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java +++ b/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java @@ -147,7 +147,7 @@ public class ShadeMojo * syntax groupId is equivalent to groupId:*:*:*, groupId:artifactId is * equivalent to groupId:artifactId:*:* and groupId:artifactId:classifier is equivalent to * groupId:artifactId:*:classifier. For example: - * + * *
      * <artifactSet>
      *   <includes>
@@ -164,7 +164,7 @@ public class ShadeMojo
 
     /**
      * Packages to be relocated. For example:
-     * 
+     *
      * 
      * <relocations>
      *   <relocation>
@@ -179,7 +179,7 @@ public class ShadeMojo
      *   </relocation>
      * </relocations>
      * 
- * + * * Note: Support for includes exists only since version 1.4. */ @SuppressWarnings( "MismatchedReadAndWriteOfArray" ) @@ -200,7 +200,7 @@ public class ShadeMojo * to use an include to collect a set of files from the archive then use excludes to further reduce the set. By * default, all files are included and no files are excluded. If multiple filters apply to an artifact, the * intersection of the matched files will be included in the final JAR. For example: - * + * *
      * <filters>
      *   <filter>
@@ -336,13 +336,41 @@ public class ShadeMojo
 
     /**
      * When true, dependencies will be stripped down on the class level to only the transitive hull required for the
-     * artifact. Note: Usage of this feature requires Java 1.5 or higher.
+     * artifact. See also {@link #entryPoints}, if you wish to further optimize JAR minimization.
+     * 

+ * Note: This feature requires Java 1.8 or higher due to its use of + * jdependency. Its accuracy therefore also depends on + * jdependency's limitations. * * @since 1.4 */ @Parameter private boolean minimizeJar; + /** + * Use this option in order to fine-tune {@link #minimizeJar}: By default, all of the target module's classes are + * kept and used as entry points for JAR minimization. By explicitly limiting the set of entry points, you can + * further minimize the set of classes kept in the shaded JAR. This affects both classes in the module itself and + * dependency classes. If {@link #minimizeJar} is inactive, this option has no effect either. + *

+ * Note: This feature requires Java 1.8 or higher due to its use of + * jdependency. Its accuracy therefore also depends on + * jdependency's limitations. + *

+ * Configuration example: + *

{@code
+     * true
+     * 
+     *   org.acme.Application
+     *   org.acme.OtherEntryPoint
+     * 
+     * }
+ * + * @since 3.3.1 + */ + @Parameter + private Set entryPoints; + /** * The path to the output file for the shaded artifact. When this parameter is set, the created archive will neither * replace the project's main artifact nor will it be attached. Hence, this parameter causes the parameters @@ -391,7 +419,7 @@ public class ShadeMojo */ @Parameter( defaultValue = "false" ) private boolean skip; - + /** * @throws MojoExecutionException in case of an error. */ @@ -555,7 +583,7 @@ public void execute() replaceFile( finalFile, testSourcesJar ); testSourcesJar = finalFile; } - + renamed = true; } @@ -965,11 +993,16 @@ private List getFilters() if ( minimizeJar ) { - getLog().info( "Minimizing jar " + project.getArtifact() ); + if ( entryPoints == null ) + { + entryPoints = new HashSet<>(); + } + getLog().info( "Minimizing jar " + project.getArtifact() + + ( entryPoints.isEmpty() ? "" : ", entry points = " + entryPoints ) ); try { - filters.add( new MinijarFilter( project, getLog(), simpleFilters ) ); + filters.add( new MinijarFilter( project, getLog(), simpleFilters, entryPoints ) ); } catch ( IOException e ) { @@ -1149,7 +1182,7 @@ private void rewriteDependencyReducedPomIfWeHaveReduction( List depe } File f = dependencyReducedPomLocation; - // MSHADE-225 + // MSHADE-225 // Works for now, maybe there's a better algorithm where no for-loop is required if ( loopCounter == 0 ) { diff --git a/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java b/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java index a7090184..d2512fba 100644 --- a/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java +++ b/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java @@ -19,11 +19,31 @@ * under the License. */ +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.shade.filter.Filter; +import org.apache.maven.plugins.shade.relocation.Relocator; +import org.apache.maven.plugins.shade.relocation.SimpleRelocator; +import org.apache.maven.plugins.shade.resource.AppendingTransformer; +import org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer; +import org.apache.maven.plugins.shade.resource.ResourceTransformer; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.Os; +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.slf4j.Logger; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; @@ -31,33 +51,20 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import java.util.zip.CRC32; import java.util.zip.ZipEntry; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.shade.filter.Filter; -import org.apache.maven.plugins.shade.relocation.Relocator; -import org.apache.maven.plugins.shade.relocation.SimpleRelocator; -import org.apache.maven.plugins.shade.resource.AppendingTransformer; -import org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer; -import org.apache.maven.plugins.shade.resource.ResourceTransformer; -import org.codehaus.plexus.util.IOUtil; -import org.codehaus.plexus.util.Os; -import org.junit.Assert; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.mockito.ArgumentCaptor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; -import org.slf4j.Logger; - +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.codehaus.plexus.util.FileUtils.forceMkdir; import static java.util.Objects.requireNonNull; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.hasItem; @@ -80,6 +87,9 @@ public class DefaultShaderTest private static final String[] EXCLUDES = new String[] { "org/codehaus/plexus/util/xml/Xpp3Dom", "org/codehaus/plexus/util/xml/pull.*" }; + @ClassRule + public static final TemporaryFolder tmp = new TemporaryFolder(); + @Test public void testNoopWhenNotRelocated() throws IOException, MojoExecutionException { final File plexusJar = new File("src/test/jars/plexus-utils-1.4.1.jar" ); @@ -110,7 +120,7 @@ public void testNoopWhenNotRelocated() throws IOException, MojoExecutionExceptio // Before MSHADE-391, the processed files were written to the uber JAR, which did no harm, but made it // difficult to find out by simple file comparison, if a file was actually relocated or not. Now, Shade // makes sure to always write the original file if the class neither was relocated itself nor references - // other, relocated classes. So we are checking for regressions here. + // other, relocated classes. So we are checking for regressions here. assertTrue( areEqual( originalJar, shadedJar, "org/codehaus/plexus/util/Expand.class" ) ); @@ -280,6 +290,60 @@ public void testShaderWithoutExcludesShouldRemoveReferencesOfOriginalPattern() new String[] {} ); } + @Test + public void testHandleDirectory() + throws Exception + { + final File dir = tmp.getRoot(); + // explode src/test/jars/test-artifact-1.0-SNAPSHOT.jar in this temp dir + try ( final JarInputStream in = new JarInputStream( + new FileInputStream( "src/test/jars/test-artifact-1.0-SNAPSHOT.jar" ) ) ) + { + JarEntry nextJarEntry; + while ( (nextJarEntry = in.getNextJarEntry()) != null ) + { + if ( nextJarEntry.isDirectory() ) + { + continue; + } + final File out = new File( dir, nextJarEntry.getName() ); + forceMkdir( out.getParentFile() ); + try ( final OutputStream outputStream = new FileOutputStream( out ) ) + { + IOUtil.copy( in, outputStream, (int) Math.max( nextJarEntry.getSize(), 512 ) ); + } + } + } + + // do shade + final File shade = new File( "target/testHandleDirectory.jar" ); + shaderWithPattern( "org/shaded/plexus/util", shade, new String[0], singleton( dir ) ); + + // ensure directory was shaded properly + try ( final JarFile jar = new JarFile( shade ) ) + { + final List entries = new ArrayList<>(); + final Enumeration jarEntryEnumeration = jar.entries(); + while ( jarEntryEnumeration.hasMoreElements() ) + { + final JarEntry jarEntry = jarEntryEnumeration.nextElement(); + if ( jarEntry.isDirectory() ) + { + continue; + } + entries.add( jarEntry.getName() ); + } + Collections.sort( entries ); + assertEquals( + asList( + "META-INF/maven/org.apache.maven.plugins.shade/test-artifact/pom.properties", + "META-INF/maven/org.apache.maven.plugins.shade/test-artifact/pom.xml", + "org/apache/maven/plugins/shade/Lib.class" + ), + entries ); + } + } + @Test public void testShaderWithRelocatedClassname() throws Exception @@ -397,16 +461,18 @@ private void writeEntryWithoutCompression( String entryName, byte[] entryBytes, jos.closeEntry(); } - private void shaderWithPattern( String shadedPattern, File jar, String[] excludes ) - throws Exception + private void shaderWithPattern( String shadedPattern, File jar, String[] excludes ) throws Exception { - DefaultShader s = newShader(); - Set set = new LinkedHashSet<>(); - set.add( new File( "src/test/jars/test-project-1.0-SNAPSHOT.jar" ) ); - set.add( new File( "src/test/jars/plexus-utils-1.4.1.jar" ) ); + shaderWithPattern( shadedPattern, jar, excludes, set ); + } + + private void shaderWithPattern( String shadedPattern, File jar, String[] excludes, Set set ) + throws Exception + { + DefaultShader s = newShader(); List relocators = new ArrayList<>(); diff --git a/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java b/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java index bfbaee24..948838be 100644 --- a/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java +++ b/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java @@ -20,6 +20,7 @@ */ import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -28,14 +29,17 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.ArgumentCaptor; @@ -43,16 +47,20 @@ public class MinijarFilterTest { + @Rule + public TemporaryFolder tempFolder = TemporaryFolder.builder().assureDeletion().build(); + private File emptyFile; + private Log log; + private ArgumentCaptor logCaptor; @Before public void init() throws IOException { - TemporaryFolder tempFolder = new TemporaryFolder(); - tempFolder.create(); this.emptyFile = tempFolder.newFile(); - + this.log = mock(Log.class); + logCaptor = ArgumentCaptor.forClass(CharSequence.class); } /** @@ -64,12 +72,8 @@ public void testWithMockProject() { assumeFalse( "Expected to run under JDK8+", System.getProperty("java.version").startsWith("1.7") ); - ArgumentCaptor logCaptor = ArgumentCaptor.forClass( CharSequence.class ); - MavenProject mavenProject = mockProject( emptyFile ); - Log log = mock( Log.class ); - MinijarFilter mf = new MinijarFilter( mavenProject, log ); mf.finished(); @@ -84,14 +88,10 @@ public void testWithMockProject() public void testWithPomProject() throws IOException { - ArgumentCaptor logCaptor = ArgumentCaptor.forClass( CharSequence.class ); - // project with pom packaging and no artifact. MavenProject mavenProject = mockProject( null ); mavenProject.setPackaging( "pom" ); - Log log = mock( Log.class ); - MinijarFilter mf = new MinijarFilter( mavenProject, log ); mf.finished(); @@ -105,7 +105,7 @@ public void testWithPomProject() } - private MavenProject mockProject( File file ) + private MavenProject mockProject( File file, String... classPathElements ) { MavenProject mavenProject = mock( MavenProject.class ); @@ -129,17 +129,18 @@ private MavenProject mockProject( File file ) when( mavenProject.getArtifact().getFile() ).thenReturn( file ); - return mavenProject; + try { + when(mavenProject.getRuntimeClasspathElements()).thenReturn(Arrays.asList(classPathElements)); + } catch (DependencyResolutionRequiredException e) { + fail("Encountered unexpected exception: " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } + return mavenProject; } @Test public void finsishedShouldProduceMessageForClassesTotalNonZero() { - ArgumentCaptor logCaptor = ArgumentCaptor.forClass( CharSequence.class ); - - Log log = mock( Log.class ); - MinijarFilter m = new MinijarFilter( 1, 50, log ); m.finished(); @@ -153,10 +154,6 @@ public void finsishedShouldProduceMessageForClassesTotalNonZero() @Test public void finishedShouldProduceMessageForClassesTotalZero() { - ArgumentCaptor logCaptor = ArgumentCaptor.forClass( CharSequence.class ); - - Log log = mock( Log.class ); - MinijarFilter m = new MinijarFilter( 0, 0, log ); m.finished(); @@ -166,4 +163,5 @@ public void finishedShouldProduceMessageForClassesTotalZero() assertEquals( "Minimized 0 -> 0", logCaptor.getValue() ); } + }