All Downloads are FREE. Search and download functionalities are using the official Maven repository.

name.abuchen.mvn.FixInfoPlistMojo Maven / Gradle / Ivy

Go to download

Maven plugin to "fix" the Info.plist file generated by p2 during a Tycho product build. Typically used to change copyright and language information.

There is a newer version: 1.8
Show newest version
package name.abuchen.mvn;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Properties;
import java.util.Scanner;

import org.apache.commons.codec.binary.Hex;
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;
import org.tukaani.xz.LZMA2Options;
import org.tukaani.xz.XZOutputStream;

import com.dd.plist.NSDictionary;
import com.dd.plist.PropertyListParser;

/**
 * Maven plugin to "fix" the Info.plist file generated by p2 during a Tycho
 * product build. Typically used to change copyright and language information.
 */
@Mojo(name = "fix-info-plist", defaultPhase = LifecyclePhase.PACKAGE)
public class FixInfoPlistMojo extends AbstractMojo
{
    @Parameter(defaultValue = "${project.build.directory}", required = true)
    private File outputDirectory;

    @Parameter(defaultValue = "${qualifiedVersion}", required = true)
    private String projectVersion;

    @Parameter(required = true)
    private String productId;

    @Parameter(required = true)
    private String appName;

    @Parameter(required = true)
    private Properties properties;

    @Override
    public void execute() throws MojoExecutionException
    {
        Path infoPlist = Paths.get(outputDirectory.getAbsolutePath(), //
                        "products", //$NON-NLS-1$
                        productId, //
                        "macosx", //$NON-NLS-1$
                        "cocoa", //$NON-NLS-1$
                        "x86_64", //$NON-NLS-1$
                        appName, //
                        "Contents", //$NON-NLS-1$
                        "Info.plist"); //$NON-NLS-1$

        if (!Files.exists(infoPlist))
        {
            getLog().info("Cannot find Info.plist: " + infoPlist.toString()); //$NON-NLS-1$
            return;
        }

        fixInfoPlist(infoPlist);
        fixZippedBinaryArchiveInRepository(infoPlist);
    }

    private void fixInfoPlist(Path infoPlist) throws MojoExecutionException
    {
        try
        {
            NSDictionary dictionary = (NSDictionary) PropertyListParser.parse(infoPlist.toFile());

            new PropertyReplacer().replace(getLog(), dictionary, properties);

            PropertyListParser.saveAsXML(dictionary, infoPlist.toFile());
        }
        catch (Exception e)
        {
            throw new MojoExecutionException("Error reading/writing Info.plist", e); //$NON-NLS-1$
        }
    }

    private void fixZippedBinaryArchiveInRepository(Path infoPlist) throws MojoExecutionException
    {
        Path archive = Paths.get(outputDirectory.getAbsolutePath(), //
                        "repository", //$NON-NLS-1$
                        "binary", //$NON-NLS-1$
                        productId + ".executable.cocoa.macosx.x86_64_" + projectVersion); //$NON-NLS-1$

        if (!Files.exists(archive))
        {
            getLog().info("Skipping archive manipulation; file not found: " + archive.toString()); //$NON-NLS-1$
            return;
        }

        try
        {
            String oldMD5Hash = getChecksum(archive, "MD5"); //$NON-NLS-1$
            String oldSHA256Hash = getChecksum(archive, "SHA-256"); //$NON-NLS-1$

            URI uri = URI.create("jar:" + archive.toUri()); //$NON-NLS-1$
            try (FileSystem fs = FileSystems.newFileSystem(uri, new HashMap()))
            {
                Path fileToUpdate = fs.getPath("Info.plist"); //$NON-NLS-1$
                Files.copy(infoPlist, fileToUpdate, StandardCopyOption.REPLACE_EXISTING);
            }

            String newMD5Hash = getChecksum(archive, "MD5"); //$NON-NLS-1$
            String newSHA256Hash = getChecksum(archive, "SHA-256"); //$NON-NLS-1$

            // read artifacts.xml to update MD5 hash
            getLog().info(MessageFormat.format("Updating binary MD5 hash from {0} to {1}", //$NON-NLS-1$
                            oldMD5Hash, newMD5Hash));
            getLog().info(MessageFormat.format("Updating binary SHA256 hash from {0} to {1}", //$NON-NLS-1$
                            oldSHA256Hash, newSHA256Hash));

            Path artifactsJAR = Paths.get(outputDirectory.getAbsolutePath(), //
                            "repository", //$NON-NLS-1$
                            "artifacts.jar"); //$NON-NLS-1$

            String artifactsXML = readArtifactsXML(artifactsJAR);

            String messageFormat = ""; //$NON-NLS-1$
            artifactsXML = artifactsXML.replace(MessageFormat.format(messageFormat, oldMD5Hash),
                            MessageFormat.format(messageFormat, newMD5Hash));

            messageFormat = ""; //$NON-NLS-1$
            artifactsXML = artifactsXML.replace(MessageFormat.format(messageFormat, oldMD5Hash),
                            MessageFormat.format(messageFormat, newMD5Hash));

            messageFormat = ""; //$NON-NLS-1$
            artifactsXML = artifactsXML.replace(MessageFormat.format(messageFormat, oldSHA256Hash),
                            MessageFormat.format(messageFormat, newSHA256Hash));

            updateArtifactsJAR(artifactsJAR, artifactsXML);
            updateArtifactsXZ(artifactsXML);
        }
        catch (NoSuchAlgorithmException | IOException e)
        {
            throw new MojoExecutionException("Failed to update binary archive", e); //$NON-NLS-1$
        }
    }

    private void updateArtifactsJAR(Path artifactsJAR, String artifactsXML) throws IOException
    {
        Path tempFile = Paths.get(outputDirectory.getAbsolutePath(), "artifacts_md5_updated.xml"); //$NON-NLS-1$
        Files.write(tempFile, artifactsXML.getBytes(StandardCharsets.UTF_8.name()));

        URI uri = URI.create("jar:" + artifactsJAR.toUri()); //$NON-NLS-1$

        try (FileSystem fs = FileSystems.newFileSystem(uri, new HashMap()))
        {
            Path fileToUpdate = fs.getPath("artifacts.xml"); //$NON-NLS-1$
            Files.copy(tempFile, fileToUpdate, StandardCopyOption.REPLACE_EXISTING);
        }

    }

    private void updateArtifactsXZ(String artifactsXML) throws IOException
    {
        Path xzFile = Paths.get(outputDirectory.getAbsolutePath(), "repository", "artifacts.xml.xz"); //$NON-NLS-1$ //$NON-NLS-2$
        try (FileOutputStream outfile = new FileOutputStream(xzFile.toFile()))
        {
            LZMA2Options options = new LZMA2Options();
            options.setDictSize(LZMA2Options.DICT_SIZE_DEFAULT);
            options.setLcLp(3, 0);
            options.setPb(LZMA2Options.PB_MAX);
            options.setMode(LZMA2Options.MODE_NORMAL);
            options.setNiceLen(LZMA2Options.NICE_LEN_MAX);
            options.setMatchFinder(LZMA2Options.MF_BT4);
            options.setDepthLimit(512);

            try (XZOutputStream outxz = new XZOutputStream(outfile, options))
            {
                outxz.write(artifactsXML.getBytes(StandardCharsets.UTF_8.name()));
            }
        }
    }

    private String readArtifactsXML(Path artifactsJAR) throws IOException
    {

        URI uri = URI.create("jar:" + artifactsJAR.toUri()); //$NON-NLS-1$

        try (FileSystem fs = FileSystems.newFileSystem(uri, new HashMap()))
        {
            Path fileToUpdate = fs.getPath("artifacts.xml"); //$NON-NLS-1$

            try (Scanner scanner = new Scanner(fileToUpdate, StandardCharsets.UTF_8.name()))
            {
                return scanner.useDelimiter("\\A").next(); //$NON-NLS-1$
            }
        }
    }

    private String getChecksum(Path file, String algorithm) throws IOException, NoSuchAlgorithmException
    {
        MessageDigest md = MessageDigest.getInstance(algorithm);
        md.update(Files.readAllBytes(file));
        byte[] digest = md.digest();

        return Hex.encodeHexString(digest).toLowerCase(Locale.US);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy