StripJarMojo.java

/*
 * Licensed 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 io.github.zlika.reproducible;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
 * Fixes the produced artifacts (ZIP/JAR/WAR/EAR) to make the build reproducible.
 */
@Mojo(name = "strip-jar", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST)
public final class StripJarMojo extends AbstractMojo
{
    private static final String[] ZIP_EXT = { "zip", "jar", "war", "ear" };
    
    /**
     * Directory where to find zip/jar/war/ear files for stripping.
     */
    @Parameter(defaultValue = "${project.build.directory}", required = true)
    private File outputDirectory;
    
    /**
     * By default, the stripping is done in-place.
     * To create new files without changing the original ones, set this parameter to "false".
     * The new files are named by appending "-stripped" to the original file name.
     */
    @Parameter(defaultValue = "true", property = "reproducible.overwrite")
    private boolean overwrite;
    
    /**
     * If true, skips the execution of the goal.
     */
    @Parameter(defaultValue = "false", property = "reproducible.skip")
    private boolean skip;

    public void execute() throws MojoExecutionException
    {
        if (skip)
        {
            getLog().info("Skipping execution of goal \"strip\"");
        }
        else
        {
            strip();
        }
    }
    
    private void strip() throws MojoExecutionException
    {
        final File[] zipFiles = findZipFiles(outputDirectory);
        for (File zip : zipFiles)
        {
            getLog().info("Stripping " + zip.getAbsolutePath());
            try
            {
                final File stripped = createStrippedFilename(zip);
                new ZipStripper()
                    .addFileStripper("META-INF/MANIFEST.MF", new ManifestStripper())
                    .addFileStripper("META-INF/maven/\\S*/pom.properties", new PomPropertiesStripper())
                    .strip(zip, stripped);
                if (overwrite)
                {
                    Files.move(stripped.toPath(), zip.toPath(), StandardCopyOption.REPLACE_EXISTING);
                }
            }
            catch (IOException e)
            {
                throw new MojoExecutionException("Error when stripping " + zip.getAbsolutePath(), e);
            }
        }
    }
    
    private File[] findZipFiles(File folder)
    {
        final File[] zipFiles = folder.listFiles((dir, name) ->
                Arrays.stream(ZIP_EXT).anyMatch(ext -> name.toLowerCase().endsWith(ext)));
        return zipFiles != null ? zipFiles : new File[0];
    }
    
    private File createStrippedFilename(File originalFile)
    {
        final String filenameWithoutExt = FileUtils.getNameWithoutExtension(originalFile);
        final String ext = FileUtils.getFileExtension(originalFile);
        return new File(originalFile.getParentFile(), filenameWithoutExt + "-stripped"
                                                        + (ext.isEmpty() ? "" : ".") + ext);
    }
}