Skip to content

Commit

Permalink
[MSHADE-400] Self-minimisation with custom entry points
Browse files Browse the repository at this point in the history
  • Loading branch information
kriegaex committed Jul 22, 2021
1 parent 452f797 commit b91b89a
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 46 deletions.
20 changes: 11 additions & 9 deletions src/main/java/org/apache/maven/plugins/shade/DefaultShader.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,19 @@ private void shadeJars( ShadeRequest shadeRequest, Set<String> resources, List<R
List<Filter> jarFilters = getFilters( jar, shadeRequest.getFilters() );
if ( jar.isDirectory() )
{
shadeDir( shadeRequest, resources, transformers, remapper, jos, duplicates, jar, jar, "", jarFilters );
shadeDir( shadeRequest, resources, transformers, packageMapper, jos, duplicates,
jar, jar, "", jarFilters );
}
else
{
shadeJar( shadeRequest, resources, transformers, remapper, jos, duplicates, jar, jarFilters );
shadeJar( shadeRequest, resources, transformers, packageMapper, jos, duplicates,
jar, jarFilters );
}
}
}

private void shadeDir( ShadeRequest shadeRequest, Set<String> resources,
List<ResourceTransformer> transformers, RelocatorRemapper remapper,
List<ResourceTransformer> transformers, DefaultPackageMapper packageMapper,
JarOutputStream jos, MultiValuedMap<String, File> duplicates,
File jar, File current, String prefix, List<Filter> jarFilters ) throws IOException
{
Expand All @@ -264,7 +266,7 @@ private void shadeDir( ShadeRequest shadeRequest, Set<String> resources,
try
{
shadeDir(
shadeRequest, resources, transformers, remapper, jos,
shadeRequest, resources, transformers, packageMapper, jos,
duplicates, jar, file,
prefix + file.getName() + '/', jarFilters );
continue;
Expand All @@ -283,7 +285,7 @@ private void shadeDir( ShadeRequest shadeRequest, Set<String> resources,
try
{
shadeJarEntry(
shadeRequest, resources, transformers, remapper, jos, duplicates, jar,
shadeRequest, resources, transformers, packageMapper, jos, duplicates, jar,
new Callable<InputStream>()
{
@Override
Expand All @@ -302,7 +304,7 @@ public InputStream call() throws Exception
}

private void shadeJar( ShadeRequest shadeRequest, Set<String> resources,
List<ResourceTransformer> transformers, RelocatorRemapper remapper,
List<ResourceTransformer> transformers, DefaultPackageMapper packageMapper,
JarOutputStream jos, MultiValuedMap<String, File> duplicates,
File jar, List<Filter> jarFilters ) throws IOException
{
Expand All @@ -323,7 +325,7 @@ private void shadeJar( ShadeRequest shadeRequest, Set<String> resources,
try
{
shadeJarEntry(
shadeRequest, resources, transformers, remapper, jos, duplicates, jar,
shadeRequest, resources, transformers, packageMapper, jos, duplicates, jar,
new Callable<InputStream>()
{
@Override
Expand Down Expand Up @@ -617,7 +619,7 @@ 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 );
Expand All @@ -643,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 )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -111,8 +124,44 @@ public MinijarFilter( MavenProject project, Log log, List<SimpleFilter> 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<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 );
Expand All @@ -133,12 +182,11 @@ private void removeServices( final MavenProject project, final Clazzpath cp )
{
if ( new File( fileName ).isDirectory() )
{
log.debug( "Not a JAR file candidate. Ignoring classpath element '" + fileName + "'." );
continue;
repeatScan |= removeServicesFromDir( cp, neededClasses, fileName );
}
if ( removeServicesFromJar( cp, neededClasses, fileName ) )
else
{
repeatScan = true;
repeatScan |= removeServicesFromJar( cp, neededClasses, fileName );
}
}
}
Expand All @@ -150,6 +198,43 @@ private void removeServices( final MavenProject project, final Clazzpath cp )
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;
Expand Down
53 changes: 43 additions & 10 deletions src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,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;
Expand All @@ -164,7 +164,7 @@ public class ShadeMojo

/**
* Packages to be relocated. For example:
*
*
* <pre>
* &lt;relocations&gt;
* &lt;relocation&gt;
Expand All @@ -179,7 +179,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" )
Expand All @@ -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:
*
*
* <pre>
* &lt;filters&gt;
* &lt;filter&gt;
Expand Down Expand Up @@ -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. <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.3.1
*/
@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
Expand Down Expand Up @@ -391,7 +419,7 @@ public class ShadeMojo
*/
@Parameter( defaultValue = "false" )
private boolean skip;

/**
* @throws MojoExecutionException in case of an error.
*/
Expand Down Expand Up @@ -555,7 +583,7 @@ public void execute()
replaceFile( finalFile, testSourcesJar );
testSourcesJar = finalFile;
}

renamed = true;
}

Expand Down Expand Up @@ -965,11 +993,16 @@ private List<Filter> 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 )
{
Expand Down Expand Up @@ -1149,7 +1182,7 @@ private void rewriteDependencyReducedPomIfWeHaveReduction( List<Dependency> 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 )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,23 +165,4 @@ public void finishedShouldProduceMessageForClassesTotalZero()

}

/**
* 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.getRoot().getAbsolutePath();
MavenProject mockedProject = mockProject(emptyFile, classPathElementToIgnore);

new MinijarFilter(mockedProject, log);

verify(log, never()).warn(logCaptor.capture());
verify(log, times(1)).debug(
"Not a JAR file candidate. Ignoring classpath element '" + classPathElementToIgnore + "'."
);
}

}

0 comments on commit b91b89a

Please sign in to comment.