com.github.wix_maven.AbstractWixMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.github.wix_maven.AbstractWixMojo.java

Source

package com.github.wix_maven;

/*
 * #%L
 * WiX Toolset (Windows Installer XML) Maven Plugin
 * %%
 * Copyright (C) 2013 - 2014 GregDomjan NetIQ
 * %%
 * 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.
 * #L%
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.Commandline;

public abstract class AbstractWixMojo extends AbstractMojo {

    /**
     * Skip running of all wix plugin goals altogether.
     * 
     * @parameter expression="${wix.skip}" default-value="false"
     */
    protected boolean skip;

    /**
     * The output type:
     *  <li> msi - Windows installer
     *  <li> msm - Merge Module
     *  <li> wixlib - Wix library
     *  <li> msp - Windows patch
     *  <li> bundle - wix bootstrapper
     * 
     * @parameter default-value="${project.packaging}"
     * @required
     */
    protected String packaging;

    /**
     * The directory to scan for wix files.
     * For each build type there is at least one wxs file required
     * 
     * @parameter default-value="${project.basedir}/src/main/wix"
     * @required
     */
    protected File wxsInputDirectory;

    /**
     * Should validation be run, and when.
     * <li>linking - Run validation during linking from light/lit. 
     * <li>unit - Run validation as unit test, suppressing validation during linking (light/lit).
     * <li>both - Run validation during linking from light/lit and also as unit test.
     * <li>suppress - Suppressing validation during linking (light/lit)
     * 
     * @parameter expression="${wix.validate}" default-value="unit"
     */
    protected String validate;
    static final String VALIDATE_LINK = "linking";
    static final String VALIDATE_UNIT = "unit";
    static final String VALIDATE_BOTH = "both";
    static final String VALIDATE_SUPPRESS = "suppress";

    /**
     * Reference paths to WiX extensions to use default list containing the wixlib path only.
     * 
     * @parameter
     */
    // protected String[] referencePaths;

    /**
     * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
     * 
     * @parameter expression="${wix.verbose}" default-value="false"
     */
    protected boolean verbose;

    /**
     * The Platforms (Archictecture) for the msi. <br> 
     * Some choices are: x86, intel, x64, intel64, or ia64 <br> 
     * If list is empty default-value="x86" <br> 
     * Will set: 
     * candle -dPlatform= -arch
     * 
     * @parameter 
     */
    private Set<String> platforms;

    /**
     * Intermediate directory - will have ${arch} appended
     * 
     * @parameter expression="${wix.outputDirectory}" default-value="${project.build.directory}/wixobj/Release"
     * @required
     */
    protected File intDirectory;

    /**
     * Where to unpack the wix tools
     * TODO: might need to do something about including tools version in path, or manage the unpacking more cleanly
     * 
     * @parameter expression="${wix.toolsPath}" default-value="${project.build.directory}/wix-tools"
     * @required
     */
    protected File toolDirectory;

    /**
     * When to unpack the wix tools. 
     * Default is to unpack the tools every time and overwrite, set to false to only overwrite if the tools are newer.
     * This is provided to allow newer WIX test binaries to be dropped in, rather than having to install/deploy the wix-tools.
     * 
     * @parameter expression="${wix.toolDirectoryOverwrite}" default-value="true"
     * @required
     */
    protected boolean toolDirectoryOverwrite = true;

    /**
     * Intermediate directory - will have ${arch} appended
     * 
     * @parameter expression="${wix.unpackPath}" default-value="${project.build.directory}/unpack"
     * @required
     */
    protected File unpackDirectory;

    /**
     * A relative base path to shorten commandline references to files in the project.
     * Default is the project base directory, if alternate locations are given for wxs, wxl files it may be appropriate to change this.
     * 
     * @parameter expression="${wix.relativeBase}" default-value="${project.basedir}"
     */
    protected File relativeBase;

    /**
     * Leave the xsd-tools behind after compilation for extended use outside this goal.
     * 
     * @parameter expression="${wix.extendedUse}" default-value="false"
     */
    protected boolean extendedUse;

    /**
     * Artifact id of the toolset jar to unpack.
     * 
     * @parameter default-value="wix-toolset"
     * @required
     */
    private String toolsPluginArtifactId = "wix-toolset";

    /**
     * Group id of the toolset jar to unpack.
     * 
     * @parameter default-value="org.wixtoolset.maven"
     * @required
     * */
    private String toolsPluginGroupId;

    /**
     * Artifact id of the toolset jar to unpack.
     * 
     * @parameter default-value="wix-bootstrap"
     * @required
     */
    private String bootstrapPluginArtifactId = "wix-boostrap";

    /**
     * Group id of the toolset jar to unpack.
     * 
     * @parameter default-value="org.wixtoolset.maven"
     * @required
     * */
    private String bootstrapPluginGroupId;

    /**
     * @parameter default-value="${plugin.artifacts}"
     * @required
     * @readonly
     * */
    private List pluginArtifacts;

    /**
     * The Zip archiver.
     * 
     * @component role="org.codehaus.plexus.archiver.UnArchiver" roleHint="zip"
     * @readonly
     * @required
     */
    protected ZipUnArchiver zipUnArchiver;

    /**
     * To search for artifacts within the reactor and ensure consistent behaviour between Maven 2 and Maven 3.
     * 
     * @parameter expression = "${reactorProjects}"
     * @readonly = true
     * @required = true
     */
    protected List<MavenProject> reactorProjects;

    /**
     * Used to look up Artifacts in the remote repository.
     * 
     * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
     * @required
     * @readonly
     */
    protected ArtifactFactory factory;

    /**
     * Used to look up Artifacts in the remote repository.
     * 
     * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
     * @required
     * @readonly
     */
    protected ArtifactResolver resolver;

    /**
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    protected ArtifactRepository localRepository;

    /**
     * Remote repositories which will be searched for nar attachments.
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    protected List remoteArtifactRepositories;

    /**
     * Artifact collector, needed to resolve dependencies.
     * 
     * @component role="org.apache.maven.artifact.resolver.ArtifactCollector"
     * @required
     * @readonly
     */
    // @Component( role = ArtifactCollector.class )
    //protected ArtifactCollector artifactCollector;

    /**
     * @parameter default-value="${project}"
     * @required
     * @readonly
     * */
    protected MavenProject project;

    public final String PACK_LIB = "wixlib";
    public final String PACK_MERGE = "msm";
    public final String PACK_INSTALL = "msi";
    public final String PACK_PATCH = "msp";
    public final String PACK_BUNDLE = "bundle";

    protected Set<String> getPlatforms() {
        if (platforms == null)
            platforms = new HashSet<String>();
        if (platforms.isEmpty()) {
            // Warning, there are parts of the code that assume there must be at least one arch, and that none will be null.
            getLog().info("No platforms specified, using default 'x86'");
            platforms.add("x86");
        }
        return platforms;
    }

    protected void setPlatforms(Set<String> platforms) {
        this.platforms = platforms;
    }

    protected File getArchIntDirectory(String arch) {
        return new File(intDirectory, arch);
    }

    protected Artifact[] findToolsArtifacts() throws MojoExecutionException {

        ArrayList<Artifact> tools = new ArrayList<Artifact>(2);
        tools.add(findToolsArtifact(toolsPluginGroupId, toolsPluginArtifactId));
        if (PACK_BUNDLE.equalsIgnoreCase(packaging))
            tools.add(findToolsArtifact(bootstrapPluginGroupId, bootstrapPluginArtifactId));

        // TODO: Still deciding if this content should be optional
        // if( visualStudioUse )
        // zipUnArchiver.extract( "etc", toolDirectory );   

        return tools.toArray(new Artifact[1]);
    }

    // TODO: should be a better pattern for lookup of the tools attached to this pluggin
    protected Artifact findToolsArtifact(String pluginGroupId, String pluginArtifactId)
            throws MojoExecutionException {
        // return (Artifact) mavenPlugin.getArtifactMap().get(ArtifactUtils.versionlessKey(mavenPlugin.getGroupId(), toolsId));
        if (null != pluginArtifacts) {
            for (Iterator artifactIterator = pluginArtifacts.iterator(); artifactIterator.hasNext();) {
                Artifact artifact = (Artifact) artifactIterator.next();
                if (artifact.getGroupId().equals(pluginGroupId) && artifact.getArtifactId().equals(pluginArtifactId)
                // && artifact.getClassifier().equals( env.arch )
                ) {
                    return artifact;
                }
            }
        }
        getLog().error(String.format("Tools Artifact %1$s:%2$s not found", pluginGroupId, pluginArtifactId));
        throw new MojoExecutionException(String.format("Unable to find %1$s dependency", pluginArtifactId));
    }

    protected void unpackFileBasedResources() throws MojoExecutionException {
        getLog().debug("unpacking binaries");

        Artifact[] tools = findToolsArtifacts();
        final String subfolder = "bin";
        File pluginJar;
        for (Artifact artifact : tools) {

            getLog().debug(String.format("Using %1$s %2$s", toolsPluginArtifactId, artifact));

            pluginJar = artifact.getFile();

            getLog().debug(String.format("Extracting %1$s to %2$s", pluginJar, toolDirectory));

            zipUnArchiver.setSourceFile(pluginJar);

            getLog().info(String.format("Extracting %3$s %1$s to %2$s", pluginJar, toolDirectory, subfolder));
            zipUnArchiver.extract(subfolder, toolDirectory);
        }

        if (!toolDirectory.exists()) {
            throw new MojoExecutionException("Error extracting resources from mapping-tools.");
        }
    }

    protected void cleanupFileBasedResources() {
        try {
            FileUtils.deleteDirectory(toolDirectory);
        } catch (IOException e) {
            getLog().warn("Post build cleanup - Unable to cleanup mapping-tools folder", e);
        }
    }

    protected void getArtifact(String groupId, String artifactId, String type, Set<Artifact> artifactSet,
            VersionRange vr, String classifier) throws MojoExecutionException {
        Artifact artifact = factory.createDependencyArtifact(groupId, artifactId, vr, type, classifier,
                Artifact.SCOPE_COMPILE);

        // if ( StringUtils.isEmpty( artifactItem.getClassifier() ) )
        // {
        // artifact = factory.createDependencyArtifact( artifactItem.getGroupId(), artifactItem.getArtifactId(), vr,
        // artifactItem.getType(), null, Artifact.SCOPE_COMPILE );
        // }
        // else
        // {
        // artifact = factory.createDependencyArtifact( artifactItem.getGroupId(), artifactItem.getArtifactId(), vr,
        // artifactItem.getType(), artifactItem.getClassifier(),
        // Artifact.SCOPE_COMPILE );
        // }

        // Maven 3 will search the reactor for the artifact but Maven 2 does not
        // to keep consistent behaviour, we search the reactor ourselves.
        Artifact result = getArtifactFomReactor(artifact);
        if (result != null) {
            // return result;
            artifactSet.add(result);
            return;
        }

        try {
            // mdep-50 - rolledback for now because it's breaking some functionality.
            /*
             * List listeners = new ArrayList(); Set theSet = new HashSet(); theSet.add( artifact ); ArtifactResolutionResult artifactResolutionResult
             * = artifactCollector.collect( theSet, project .getArtifact(), managedVersions, this.local, project.getRemoteArtifactRepositories(),
             * artifactMetadataSource, null, listeners ); Iterator iter = artifactResolutionResult.getArtifactResolutionNodes().iterator(); while (
             * iter.hasNext() ) { ResolutionNode node = (ResolutionNode) iter.next(); artifact = node.getArtifact(); }
             */

            resolver.resolve(artifact, remoteArtifactRepositories, localRepository);
            artifactSet.add(artifact);
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException("Unable to resolve artifact.", e);
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException("Unable to find artifact.", e);
        }
    }

    /**
     * Copied from Maven-dependency-plugin Checks to see if the specified artifact is available from the reactor.
     * 
     * @param artifact
     *            The artifact we are looking for.
     * @return The resolved artifact that is the same as the one we were looking for or <code>null</code> if one could not be found.
     */
    private Artifact getArtifactFomReactor(Artifact artifact) {
        // check project dependencies first off
        for (Artifact a : (Set<Artifact>) project.getArtifacts()) {
            if (equals(artifact, a) && hasFile(a)) {
                return a;
            }
        }

        // check reactor projects
        for (MavenProject p : reactorProjects == null ? Collections.<MavenProject>emptyList() : reactorProjects) {
            // check the main artifact
            if (equals(artifact, p.getArtifact()) && hasFile(p.getArtifact())) {
                return p.getArtifact();
            }

            // check any side artifacts
            for (Artifact a : (List<Artifact>) p.getAttachedArtifacts()) {
                if (equals(artifact, a) && hasFile(a)) {
                    return a;
                }
            }
        }

        // not available
        return null;
    }

    protected void addWixExtensions(Commandline cl) throws MojoExecutionException {
        Set<Artifact> dependentExtensions = getExtDependencySets();
        getLog().info("Adding " + dependentExtensions.size() + " dependentExtensions");
        for (Artifact ext : dependentExtensions) {
            getLog().info(ext.getFile().getName());
            cl.addArguments(new String[] { "-ext", ext.getFile().getAbsolutePath() });
        }
        //      if (extensions != null) {
        //         for (String ext : extensions) {
        //            // for toolDirectory + referencePaths
        //            File extension = new File(toolDirectory, ext);
        //            cl.addArguments(new String[] { "-ext", extension.getAbsolutePath() });
        //         }
        //      }
    }

    protected File getOutput(File baseDir, String arch, String culture, String extension) {

        File outFile = getOutputPath(baseDir, arch, culture);

        outFile = new File(outFile, project.getBuild().getFinalName() + "." + extension); // TODO: does this nead to vary with package type? packaging

        return outFile;
    }

    protected File getOutputPath(File baseDir, String arch, String culture) {
        File outFile = null;
        if (baseDir == null) // use project base dir because File uses the CWD by default which may not be the maven project directory that is the expected convention
            outFile = new File(project.getBasedir(), arch);
        else
            outFile = new File(baseDir, arch);

        if (culture != null)
            outFile = new File(outFile, culture);
        return outFile;
    }

    protected Set<Artifact> getExtDependencySets() throws MojoExecutionException {
        // add filters in well known order, least specific to most specific
        FilterArtifacts filter = new FilterArtifacts();

        filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), false));

        // filter.addFilter( new ScopeFilter( DependencyUtil.cleanToBeTokenizedString( this.includeScope ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeScope ) ) );
        //
        // filter.addFilter( new TypeFilter( DependencyUtil.cleanToBeTokenizedString( this.includeTypes ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeTypes ) ) );
        //
        // filter.addFilter( new ClassifierFilter( DependencyUtil.cleanToBeTokenizedString( this.includeClassifiers ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeClassifiers ) ) );
        //
        // filter.addFilter( new GroupIdFilter( DependencyUtil.cleanToBeTokenizedString( this.includeGroupIds ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeGroupIds ) ) );
        //
        // filter.addFilter( new ArtifactIdFilter( DependencyUtil.cleanToBeTokenizedString( this.includeArtifactIds ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeArtifactIds ) ) );

        filter.addFilter(new TypeFilter("wixext,wixExt", ""));
        // String clasfilter = arch+"-"+culture+","+arch+"-neutral";
        // getLog().debug(clasfilter);
        // filter.addFilter( new ClassifierFilter( clasfilter, "" ) );

        // start with all artifacts.
        @SuppressWarnings("unchecked")
        Set<Artifact> artifacts = project.getArtifacts();

        // perform filtering
        try {
            artifacts = filter.filter(artifacts);
        } catch (ArtifactFilterException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

        return artifacts;
    }

    protected void addToolsetGeneralOptions(Commandline cl) {
        if (!verbose)
            cl.addArguments(new String[] { "-nologo" });
        // TODO: work on suppressing warnings config, different list of suppressions for each tool
        //cl.addArguments(new String[] { "-sw" });
    }

    protected Set<Artifact> getJARDependencySets() throws MojoExecutionException {
        FilterArtifacts filter = new FilterArtifacts();
        //      filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), true));
        filter.addFilter(new TypeFilter("jar", ""));

        // start with all artifacts.
        @SuppressWarnings("unchecked")
        Set<Artifact> artifacts = project.getArtifacts();

        // perform filtering
        try {
            artifacts = filter.filter(artifacts);
        } catch (ArtifactFilterException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

        return artifacts;
    }

    /**
     * Copied from Maven-dependency-plugin Returns <code>true</code> if the artifact has a file.
     * 
     * @param artifact
     *            the artifact (may be null)
     * @return <code>true</code> if and only if the artifact is non-null and has a file.
     */
    private static boolean hasFile(Artifact artifact) {
        return artifact != null && artifact.getFile() != null && artifact.getFile().isFile();
    }

    /**
     * Copied from Maven-dependency-plugin Null-safe compare of two artifacts based on groupId, artifactId, version, type and classifier.
     * 
     * @param a
     *            the first artifact.
     * @param b
     *            the second artifact.
     * @return <code>true</code> if and only if the two artifacts have the same groupId, artifactId, version, type and classifier.
     */
    private static boolean equals(Artifact a, Artifact b) {
        return a == b || !(a == null || b == null) && StringUtils.equals(a.getGroupId(), b.getGroupId())
                && StringUtils.equals(a.getArtifactId(), b.getArtifactId())
                && StringUtils.equals(a.getVersion(), b.getVersion())
                && StringUtils.equals(a.getType(), b.getType())
                && StringUtils.equals(a.getClassifier(), b.getClassifier());
    }

    protected String getRelative(File target) {
        try {
            String relPath = getRelativePath(relativeBase.getCanonicalPath(), target);
            if (relPath.length() < target.getAbsolutePath().length())
                return relPath;
        } catch (IOException ex) {
        }
        return target.getPath();
    }

    /**
     * Returns a relative path for the targetFile relative to the base directory.
     * - copied from Ant CPPTasks
     * 
     * @param base
     *            base directory as returned by File.getCanonicalPath()
     * @param targetFile
     *            target file
     * @return relative path of target file. Returns targetFile if there were
     *         no commonalities between the base and the target
     *
     */
    public static String getRelativePath(final String base, final File targetFile) {
        try {
            //
            //   remove trailing file separator
            //
            String canonicalBase = base;
            if (base.charAt(base.length() - 1) != File.separatorChar) {
                canonicalBase = base + File.separatorChar;
            }
            //
            //   get canonical name of target
            //
            String canonicalTarget;
            //            if (System.getProperty("os.name").equals("OS/400"))
            //                canonicalTarget = targetFile.getPath();
            //            else
            canonicalTarget = targetFile.getCanonicalPath();
            if (canonicalBase.startsWith(canonicalTarget + File.separatorChar)) {
                canonicalTarget = canonicalTarget + File.separator;
            }
            if (canonicalTarget.equals(canonicalBase)) {
                return ".";
            }
            //
            //  see if the prefixes are the same
            //
            if (canonicalBase.substring(0, 2).equals("\\\\")) {
                //
                //  UNC file name, if target file doesn't also start with same
                //      server name, don't go there
                int endPrefix = canonicalBase.indexOf('\\', 2);
                String prefix1 = canonicalBase.substring(0, endPrefix);
                String prefix2 = canonicalTarget.substring(0, endPrefix);
                if (!prefix1.equals(prefix2)) {
                    return canonicalTarget;
                }
            } else {
                if (canonicalBase.substring(1, 3).equals(":\\")) {
                    int endPrefix = 2;
                    String prefix1 = canonicalBase.substring(0, endPrefix);
                    String prefix2 = canonicalTarget.substring(0, endPrefix);
                    if (!prefix1.equals(prefix2)) {
                        return canonicalTarget;
                    }
                } else {
                    if (canonicalBase.charAt(0) == '/') {
                        if (canonicalTarget.charAt(0) != '/') {
                            return canonicalTarget;
                        }
                    }
                }
            }
            char separator = File.separatorChar;
            int lastCommonSeparator = -1;
            int minLength = canonicalBase.length();
            if (canonicalTarget.length() < minLength) {
                minLength = canonicalTarget.length();
            }
            //
            //  walk to the shorter of the two paths
            //      finding the last separator they have in common
            for (int i = 0; i < minLength; i++) {
                if (canonicalTarget.charAt(i) == canonicalBase.charAt(i)) {
                    if (canonicalTarget.charAt(i) == separator) {
                        lastCommonSeparator = i;
                    }
                } else {
                    break;
                }
            }
            StringBuffer relativePath = new StringBuffer(50);
            //
            //   walk from the first difference to the end of the base
            //      adding "../" for each separator encountered
            //
            for (int i = lastCommonSeparator + 1; i < canonicalBase.length(); i++) {
                if (canonicalBase.charAt(i) == separator) {
                    if (relativePath.length() > 0) {
                        relativePath.append(separator);
                    }
                    relativePath.append("..");
                }
            }
            if (canonicalTarget.length() > lastCommonSeparator + 1) {
                if (relativePath.length() > 0) {
                    relativePath.append(separator);
                }
                relativePath.append(canonicalTarget.substring(lastCommonSeparator + 1));
            }
            return relativePath.toString();
        } catch (IOException ex) {
        }
        return targetFile.toString();
    }
}