This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-shade-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new 45dcf07  [MSHADE-400] Self-minimisation with custom entry points
45dcf07 is described below

commit 45dcf07f09cd4c896812024898cd1906a74a17f2
Author: Alexander Kriegisch <[email protected]>
AuthorDate: Thu Jul 22 15:06:23 2021 +0700

    [MSHADE-400] Self-minimisation with custom entry points
    
    Also add self-minimisation + SPI services IT.
    
    Remove MinijarFilterTest.removeServicesShouldIgnoreDirectories, because
    now removing services precisely does **not** ignore directories anymore.
    
    Co-authored-by: Romain Manni-Bucau <[email protected]>
    Co-authored-by: Jan Mosig <[email protected]>
    
    This closes #110
---
 .../invoker.properties                             |   1 +
 .../MSHADE-313_minimized-services/verify.bsh       |   2 +
 src/it/projects/MSHADE-316/invoker.properties      |   6 +-
 .../invoker.properties                             |   1 +
 .../MSHADE-400_self-minimized-services/pom.xml     |  60 +++++++
 .../src/main/java/org/acme/Application.java        |  31 ++++
 .../src/main/java/org/acme/UnusedClass.java        |  23 +++
 .../src/main/java/org/acme/UnusedService.java      |  24 +++
 .../src/main/java/org/acme/UnusedServiceImplA.java |  27 +++
 .../src/main/java/org/acme/UnusedServiceImplB.java |  27 +++
 .../src/main/java/org/acme/UsedClass.java          |  23 +++
 .../src/main/java/org/acme/UsedService.java        |  24 +++
 .../main/java/org/acme/UsedServiceUnusedImpl.java  |  27 +++
 .../main/java/org/acme/UsedServiceUsedImpl.java    |  27 +++
 .../META-INF/services/org.acme.UnusedService}      |   6 +-
 .../META-INF/services/org.acme.UsedService}        |   7 +-
 .../verify.bsh                                     |  27 ++-
 .../apache/maven/plugins/shade/DefaultShader.java  | 188 +++++++++++++++------
 .../maven/plugins/shade/filter/MinijarFilter.java  | 103 ++++++++++-
 .../apache/maven/plugins/shade/mojo/ShadeMojo.java |  51 +++++-
 .../maven/plugins/shade/DefaultShaderTest.java     |  80 ++++++++-
 .../plugins/shade/filter/MinijarFilterTest.java    |  22 +--
 22 files changed, 671 insertions(+), 116 deletions(-)

diff --git a/src/it/projects/MSHADE-313_minimized-services/invoker.properties 
b/src/it/projects/MSHADE-313_minimized-services/invoker.properties
index c8b7b3c..31f873f 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 2a58a84..2529359 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 f1e7ddb..31f873f 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-313_minimized-services/invoker.properties 
b/src/it/projects/MSHADE-400_self-minimized-services/invoker.properties
similarity index 95%
copy from src/it/projects/MSHADE-313_minimized-services/invoker.properties
copy to src/it/projects/MSHADE-400_self-minimized-services/invoker.properties
index c8b7b3c..31f873f 100644
--- a/src/it/projects/MSHADE-313_minimized-services/invoker.properties
+++ b/src/it/projects/MSHADE-400_self-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-400_self-minimized-services/pom.xml 
b/src/it/projects/MSHADE-400_self-minimized-services/pom.xml
new file mode 100644
index 0000000..b48424d
--- /dev/null
+++ b/src/it/projects/MSHADE-400_self-minimized-services/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.acme</groupId>
+    <artifactId>module-with-services</artifactId>
+    <version>1.0</version>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>@project.version@</version>
+                <executions>
+                    <execution>
+                        <id>shade</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                        <minimizeJar>true</minimizeJar>
+                        <entryPoints>
+                            <entryPoint>org.acme.Application</entryPoint>
+                        </entryPoints>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
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 0000000..1dfaeda
--- /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 0000000..500782b
--- /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 0000000..7f6410a
--- /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 0000000..0269a72
--- /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 0000000..dc1d3d6
--- /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 0000000..1531521
--- /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 0000000..23c2840
--- /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 0000000..800ddf0
--- /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 0000000..345235c
--- /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-313_minimized-services/invoker.properties 
b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UnusedService
similarity index 82%
copy from src/it/projects/MSHADE-313_minimized-services/invoker.properties
copy to 
src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UnusedService
index c8b7b3c..c4f9684 100644
--- a/src/it/projects/MSHADE-313_minimized-services/invoker.properties
+++ 
b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UnusedService
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,5 +15,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
 
-invoker.java.version = 1.8+
+# 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-313_minimized-services/invoker.properties 
b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UsedService
similarity index 88%
copy from src/it/projects/MSHADE-313_minimized-services/invoker.properties
copy to 
src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UsedService
index c8b7b3c..0cf82b0 100644
--- a/src/it/projects/MSHADE-313_minimized-services/invoker.properties
+++ 
b/src/it/projects/MSHADE-400_self-minimized-services/src/main/resources/META-INF/services/org.acme.UsedService
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,5 +15,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
+
+org.acme.UsedServiceUsedImpl
 
-invoker.java.version = 1.8+
+# This implementation is *not* used:
+# org.acme.UsedServiceUnusedImpl
diff --git a/src/it/projects/MSHADE-313_minimized-services/verify.bsh 
b/src/it/projects/MSHADE-400_self-minimized-services/verify.bsh
similarity index 68%
copy from src/it/projects/MSHADE-313_minimized-services/verify.bsh
copy to src/it/projects/MSHADE-400_self-minimized-services/verify.bsh
index 2a58a84..2708621 100644
--- a/src/it/projects/MSHADE-313_minimized-services/verify.bsh
+++ b/src/it/projects/MSHADE-400_self-minimized-services/verify.bsh
@@ -21,26 +21,25 @@ import java.util.jar.*;
 
 String[] wanted =
 {
-    "Main.class",
-    "META-INF/services/SomeServiceInterface",
-    "SomeServiceInterface.class",
-    "SomeServiceClass.class",
-    "SomeReferencedClass.class",
-    "META-INF/services/DependencyServiceInterface",
-    "DependencyServiceInterface.class",
-    "DependencyServiceClass.class",
-    "DependencyReferencedClass.class"
+    "META-INF/services/org.acme.UsedService",
+    "org/acme/Application.class",
+    "org/acme/UsedClass.class",
+    "org/acme/UsedService.class",
+    "org/acme/UsedServiceUsedImpl.class"
 };
 
 String[] unwanted =
 {
-    "UnusedServiceInterface.class",
-    "UnusedServiceClass.class",
-    "SomeUnreferencedClass.class",
-    "DependencyUnreferencedClass.class"
+    // 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, "test/target/test-1.0.jar" ) 
);
+JarFile jarFile = new JarFile( new File( basedir, 
"target/module-with-services-1.0.jar" ) );
 
 for ( String path : wanted )
 {
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 b0be384..16f20d9 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 @@ package org.apache.maven.plugins.shade;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -40,6 +41,7 @@ import java.util.LinkedList;
 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;
@@ -236,60 +238,142 @@ public class DefaultShader
             logger.debug( "Processing JAR " + jar );
 
             List<Filter> 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<JarEntry> j = jarFile.entries(); 
j.hasMoreElements(); )
+    private void shadeDir( ShadeRequest shadeRequest, Set<String> resources,
+                           List<ResourceTransformer> transformers, 
DefaultPackageMapper packageMapper,
+                           JarOutputStream jos, MultiValuedMap<String, File> 
duplicates,
+                           File jar, File current, String prefix, List<Filter> 
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<InputStream>()
+                        {
+                            @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<String> resources,
+                           List<ResourceTransformer> transformers, 
DefaultPackageMapper packageMapper,
+                           JarOutputStream jos, MultiValuedMap<String, File> 
duplicates,
+                           File jar, List<Filter> 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<JarEntry> 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<InputStream>()
+                            {
+                                @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<String> 
resources,
                                  List<ResourceTransformer> transformers, 
DefaultPackageMapper packageMapper,
                                  JarOutputStream jos, MultiValuedMap<String, 
File> duplicates, File jar,
-                                 JarFile jarFile, JarEntry entry, String name )
-        throws IOException, MojoExecutionException
+                                 Callable<InputStream> 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 );
 
@@ -300,14 +384,14 @@ public class DefaultShader
                 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" ) )
             {
@@ -317,12 +401,12 @@ public class DefaultShader
                     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 ) )
@@ -331,7 +415,7 @@ public class DefaultShader
                         return;
                     }
 
-                    addResource( resources, jos, mappedName, entry, jarFile );
+                    addResource( resources, jos, mappedName, inputProvider, 
time, method );
                 }
                 else
                 {
@@ -537,12 +621,12 @@ public class DefaultShader
 
             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.
@@ -564,7 +648,7 @@ public class DefaultShader
             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 )
         {
@@ -659,24 +743,24 @@ public class DefaultShader
         resources.add( name );
     }
 
-    private void addResource( Set<String> resources, JarOutputStream jos, 
String name, JarEntry originalEntry,
-                              JarFile jarFile ) throws IOException
+    private void addResource( Set<String> resources, JarOutputStream jos, 
String name, Callable<InputStream> 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 );
 
@@ -694,7 +778,7 @@ public class DefaultShader
     {
         /**
          * 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 a996bd4..0ce1c87 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.<SimpleFilter>emptyList() );
+        this( project, log, Collections.<SimpleFilter>emptyList(), 
Collections.<String>emptySet() );
+    }
+
+    /**
+     * @param project {@link MavenProject}
+     * @param log {@link Log}
+     * @param entryPoints
+     * @throws IOException in case of error.
+     */
+    public MinijarFilter( MavenProject project, Log log, Set<String> 
entryPoints )
+        throws IOException
+    {
+        this( project, log, Collections.<SimpleFilter>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<SimpleFilter> 
simpleFilters )
+    public MinijarFilter( MavenProject project, Log log, List<SimpleFilter> 
simpleFilters, Set<String> entryPoints )
         throws IOException
     {
       this.log = log;
@@ -111,8 +124,44 @@ public class MinijarFilter
                 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<Clazz> artifactUnitClazzes = artifactUnit.getClazzes();
+                Set<Clazz> 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.<SimpleFilter>emptyList() 
: simpleFilters );
             removeServices( project, cp );
@@ -137,14 +186,13 @@ public class MinijarFilter
                 // minification process.
                 for ( final String fileName : 
project.getRuntimeClasspathElements() )
                 {
-                    // Ignore the build directory from this project
-                    if ( fileName.equals( 
project.getBuild().getOutputDirectory() ) )
+                    if ( new File( fileName ).isDirectory() )
                     {
-                        continue;
+                        repeatScan |= removeServicesFromDir( cp, 
neededClasses, fileName );
                     }
-                    if ( removeServicesFromJar( cp, neededClasses, fileName ) )
+                    else
                     {
-                        repeatScan = true;
+                        repeatScan |= removeServicesFromJar( cp, 
neededClasses, fileName );
                     }
                 }
             }
@@ -156,6 +204,43 @@ public class MinijarFilter
         while ( repeatScan );
     }
 
+    private boolean removeServicesFromDir( Clazzpath cp, Set<Clazz> 
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<Clazz> 
neededClasses, String fileName )
     {
         boolean repeatScan = false;
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 40f712c..c83d27c 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
@@ -111,7 +111,7 @@ public class ShadeMojo
      * syntax <code>groupId</code> is equivalent to 
<code>groupId:*:*:*</code>, <code>groupId:artifactId</code> is
      * equivalent to <code>groupId:artifactId:*:*</code> and 
<code>groupId:artifactId:classifier</code> is equivalent to
      * <code>groupId:artifactId:*:classifier</code>. For example:
-     * 
+     *
      * <pre>
      * &lt;artifactSet&gt;
      *   &lt;includes&gt;
@@ -128,7 +128,7 @@ public class ShadeMojo
 
     /**
      * Packages to be relocated. For example:
-     * 
+     *
      * <pre>
      * &lt;relocations&gt;
      *   &lt;relocation&gt;
@@ -143,7 +143,7 @@ public class ShadeMojo
      *   &lt;/relocation&gt;
      * &lt;/relocations&gt;
      * </pre>
-     * 
+     *
      * <em>Note:</em> Support for includes exists only since version 1.4.
      */
     @SuppressWarnings( "MismatchedReadAndWriteOfArray" )
@@ -164,7 +164,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:
-     * 
+     *
      * <pre>
      * &lt;filters&gt;
      *   &lt;filter&gt;
@@ -310,13 +310,41 @@ public class ShadeMojo
 
     /**
      * When true, dependencies will be stripped down on the class level to 
only the transitive hull required for the
-     * artifact. <em>Note:</em> Usage of this feature requires Java 1.5 or 
higher.
+     * artifact. See also {@link #entryPoints}, if you wish to further 
optimize JAR minimization.
+     * <p>
+     * <em>Note:</em> This feature requires Java 1.8 or higher due to its use 
of
+     * <a href="https://github.com/tcurdt/jdependency";>jdependency</a>. 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.
+     * <p>
+     * <em>Note:</em> This feature requires Java 1.8 or higher due to its use 
of
+     * <a href="https://github.com/tcurdt/jdependency";>jdependency</a>. Its 
accuracy therefore also depends on
+     * jdependency's limitations.
+     * <p>
+     * Configuration example:
+     * <pre>{@code
+     * <minimizeJar>true</minimizeJar>
+     * <entryPoints>
+     *   <entryPoint>org.acme.Application</entryPoint>
+     *   <entryPoint>org.acme.OtherEntryPoint</entryPoint>
+     * </entryPoints>
+     * }</pre>
+     *
+     * @since 3.5.0
+     */
+    @Parameter
+    private Set<String> 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
@@ -551,7 +579,7 @@ public class ShadeMojo
                         replaceFile( finalFile, testSourcesJar );
                         testSourcesJar = finalFile;
                     }
-                
+
                     renamed = true;
                 }
 
@@ -964,11 +992,16 @@ public class ShadeMojo
 
         if ( minimizeJar )
         {
-            getLog().info( "Minimizing jar " + project.getArtifact() );
+            if ( entryPoints == null )
+            {
+                entryPoints = new HashSet<>();
+            }
+            getLog().info( "Minimizing jar " + project.getArtifact()
+                    + ( entryPoints.isEmpty() ? "" : " with entry points" ) );
 
             try
             {
-                filters.add( new MinijarFilter( project, getLog(), 
simpleFilters ) );
+                filters.add( new MinijarFilter( project, getLog(), 
simpleFilters, entryPoints ) );
             }
             catch ( IOException e )
             {
@@ -1153,7 +1186,7 @@ public class ShadeMojo
                 }
 
                 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 14786ec..960cf8d 100644
--- a/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java
+++ b/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java
@@ -25,6 +25,7 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.InputStreamReader;
 import java.lang.reflect.Field;
 import java.net.URL;
@@ -36,11 +37,13 @@ import java.time.temporal.ChronoUnit;
 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.stream.Collectors;
 import java.util.zip.CRC32;
@@ -57,6 +60,7 @@ import 
org.apache.maven.plugins.shade.resource.ServicesResourceTransformer;
 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;
@@ -65,6 +69,9 @@ 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;
@@ -87,6 +94,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();
+
     private final String NEWLINE = "\n";
 
     @Test
@@ -119,7 +129,7 @@ public class DefaultShaderTest
             // 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" ) );
 
@@ -289,6 +299,60 @@ public class DefaultShaderTest
                            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<String> entries = new ArrayList<>();
+            final Enumeration<JarEntry> 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
@@ -531,16 +595,18 @@ public class DefaultShaderTest
         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<File> 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<File> set )
+        throws Exception
+    {
+        DefaultShader s = newShader();
 
         List<Relocator> 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 25be4d8..9f13a12 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
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.when;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -70,7 +71,7 @@ public class MinijarFilterTest
         this.outputDirectory = tempFolder.newFolder();
         this.emptyFile = tempFolder.newFile();
         this.jarFile = tempFolder.newFile();
-        new JarOutputStream( new FileOutputStream( this.jarFile ) ).close();
+        new 
JarOutputStream(Files.newOutputStream(this.jarFile.toPath())).close();
         this.log = mock(Log.class);
         logCaptor = ArgumentCaptor.forClass(CharSequence.class);
     }
@@ -187,23 +188,4 @@ public class MinijarFilterTest
 
     }
 
-    /**
-     * Verify that directories are ignored when scanning the classpath for 
JARs containing services,
-     * but warnings are logged instead
-     *
-     * @see <a 
href="https://issues.apache.org/jira/browse/MSHADE-366";>MSHADE-366</a>
-     */
-    @Test
-    public void removeServicesShouldIgnoreDirectories() throws Exception {
-        String classPathElementToIgnore = 
tempFolder.newFolder().getAbsolutePath();
-        MavenProject mockedProject = mockProject( outputDirectory, jarFile, 
classPathElementToIgnore );
-
-        new MinijarFilter(mockedProject, log);
-
-        verify( log, times( 1 ) ).warn( logCaptor.capture() );
-
-        assertThat( logCaptor.getValue().toString(), startsWith(
-                "Not a JAR file candidate. Ignoring classpath element '" + 
classPathElementToIgnore + "' (" ) );
-    }
-
 }

Reply via email to