Package com.akathist.maven.plugins.launch4j

Source Code of com.akathist.maven.plugins.launch4j.Launch4jMojo

/*
* Maven Launch4j Plugin
* Copyright (c) 2006 Paul Jungwirth
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package com.akathist.maven.plugins.launch4j;

import net.sf.launch4j.Builder;
import net.sf.launch4j.BuilderException;
import net.sf.launch4j.config.Config;
import net.sf.launch4j.config.ConfigPersister;
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.model.Plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* Wraps a jar in a Windows executable.
*
* @goal launch4j
* @phase package
* @requiresDependencyResolution compile
*/
public class Launch4jMojo extends AbstractMojo {

  /**
   * The dependencies required by the project.
   *
   * @parameter default-value="${project.artifacts}"
   * @required
   * @readonly
   */
  private Set dependencies;
 
  /**
   * The user's current project.
   *
   * @parameter default-value="${project}"
   * @required
   * @readonly
   */
  private MavenProject project;

  /**
   * The user's plugins (including, I hope, this one).
   *
   * @parameter default-value="${project.build.plugins}"
   * @required
   * @readonly
   */
  private List plugins;

  /**
   * Used to look up Artifacts in the remote repository.
   *
   * @@@parameter expression="${component.org.apache.maven.artifact.factory.ArtifactFactory}"
   * @component
   * @required
   * @readonly
   */
  private ArtifactFactory factory;

  /**
   * The user's local repository
   *
   * @parameter default-value="${localRepository}"
   * @required
   * @readonly
   */
  private ArtifactRepository localRepository;

  /**
   * The artifact resolver used to grab the binary bits that launch4j needs.
   *
   * @component
   */
  private ArtifactResolver resolver;

  /**
   * The base of the current project.
   *
   * @parameter default-value="${basedir}"
   * @required
   * @readonly
   */
  private File basedir;

  /**
   * Whether you want a gui or console app.
   * Valid values are "gui" and "console."
   * If you say gui, then launch4j will run your app from javaw instead of java
   * in order to avoid opening a DOS window.
   * Choosing gui also enables other options like taskbar icon and a splash screen.
   *
   * @parameter
   * @required
   */
  private String headerType;

  /**
   * The name of the executable you want launch4j to produce.
   * The path, if relative, is relative to the pom.xml.
   *
   * @parameter default-value="${project.build.directory}/${project.artifactId}.exe"
   */
  private File outfile;

  /**
   * The jar to bundle inside the executable.
   * The path, if relative, is relative to the pom.xml.
   * <p>
   * If you don't want to wrap the jar, then this value should be the runtime path
   * to the jar relative to the executable. You should also set dontWrapJar to true.
   * <p>
   * You can only bundle a single jar. Therefore, you should either create a jar that contains
   * your own code plus all your dependencies, or you should distribute your dependencies alongside
   * the executable.
   *
   * @parameter default-value="${project.build.directory}/${project.build.finalName}.jar"
   */
  private String jar;

  /**
   * Whether the executable should wrap the jar or not.
   *
   * @parameter default-value=false
   */
  private boolean dontWrapJar;

  /**
   * The title of the error popup if something goes wrong trying to run your program,
   * like if java can't be found. If this is a console app and not a gui, then this value
   * is used to prefix any error messages, as in ${errTitle}: ${errorMessage}.
   *
   * @parameter
   */
  private String errTitle;

  /**
   * downloadUrl (?)
   *
   * @parameter
   */
  private String downloadUrl;

  /**
   * supportUrl (?)
   *
   * @parameter
   */
  private String supportUrl;

  /**
   * Constant command line arguments to pass to your program's main method.
   * Actual command line arguments entered by the user will appear after these.
   *
   * @parameter
   */
  private String cmdLine;

  /**
   * Changes to the given directory, relative to the executable, before running your jar.
   * If set to <code>.</code> the current directory will be where the executable is.
   * If omitted, the directory will not be changed.
   *
   * @parameter
   */
  private String chdir;

  /**
   * priority (?)
   *
   * @parameter
   */
  private String priority;

  /**
   * Sets the process name to the executable filename (instead of java) and uses XP-style manifests (if any).
   * Using this parameter creates a launch4j-tmp directory inside the JRE,
   * so don't use it if your app won't have permission to do that.
   *
   * @parameter default-value=false
   */
  private boolean customProcName;

  /**
   * If true, the executable waits for the java application to finish before returning its exit code.
   * Defaults to false for gui applications. Has no effect for console applications, which always wait.
   *
   * @parameter default-value=false
   */
  private boolean stayAlive;

  /**
   * The icon to use in the taskbar. Must be in ico format.
   *
   * @parameter
   */
  private File icon;

  /**
   * Object files to include. Used for custom headers only.
   *
   * @parameter
   */
  private List objs;

  /**
   * Win32 libraries to include. Used for custom headers only.
   *
   * @parameter
   */
  private List libs;

  /**
   * Variables to set.
   *
   * @parameter
   */
  private List vars;

  /**
   * Details about the supported jres.
   *
   * @parameter
   * @required
   */
  private Jre jre;

  /**
   * Details about the classpath your application should have.
   * This is required if you are not wrapping a jar.
   *
   * @parameter
   */
  private ClassPath classPath;

  /**
   * Details about whether to run as a single instance.
   *
   * @parameter
   */
  private SingleInstance singleInstance;

  /**
   * Details about the splash screen.
   *
   * @parameter
   */
  private Splash splash;
 
  /**
   * Lots of information you can attach to the windows process.
   *
   * @parameter
   */
  private VersionInfo versionInfo;

  /**
   * Various messages you can display.
   *
   * @parameter
   */
  private Messages messages;

    /**
     * Windows manifest file (a XML file) with the same name as .exe file (myapp.exe.manifest)
     *
     * @parameter
     */
    private File manifest;

    private File getJar() {
    return new File(jar);
  }

  public void execute() throws MojoExecutionException {
    if (getLog().isDebugEnabled()) printState();

    Config c = new Config();

    c.setHeaderType(headerType);
    c.setOutfile(outfile);
    c.setJar(getJar());
    c.setDontWrapJar(dontWrapJar);
    c.setErrTitle(errTitle);
    c.setDownloadUrl(downloadUrl);
    c.setSupportUrl(supportUrl);
    c.setCmdLine(cmdLine);
    c.setChdir(chdir);
    c.setPriority(priority);
    c.setCustomProcName(customProcName);
    c.setStayAlive(stayAlive);
        c.setManifest(manifest);
    c.setIcon(icon);
    c.setHeaderObjects(objs);
    c.setLibs(libs);
    c.setVariables(vars);

    if (classPath != null) {
      c.setClassPath(classPath.toL4j(dependencies));
    }
    if (jre != null) {
      c.setJre(jre.toL4j());
    }
    if (splash != null) {
      c.setSplash(splash.toL4j());
    }
    if (versionInfo != null) {
      c.setVersionInfo(versionInfo.toL4j());
    }
    if (messages != null) {
      c.setMessages(messages.toL4j());
    }

    ConfigPersister.getInstance().setAntConfig(c, getBaseDir());
    File workdir = setupBuildEnvironment();
    Builder b = new Builder(new MavenLog(getLog()), workdir);

    try {
      b.build();
    } catch (BuilderException e) {
      getLog().error(e);
      throw new MojoExecutionException("Failed to build the executable; please verify your configuration.", e);
    }
  }

  /**
   * Prepares a little directory for launch4j to do its thing. Launch4j needs a bunch of object files
   * (in the w32api and head directories) and the ld and windres binaries (in the bin directory).
   * The tricky part is that launch4j picks this directory based on where its own jar is sitting.
   * In our case, the jar is going to be sitting in the user's ~/.m2 repository. That's okay: we know
   * maven is allowed to write there. So we'll just add our things to that directory.
   * <p>
   * This approach is not without flaws.
   * It risks two processes writing to the directory at the same time.
   * But fortunately, once the binary bits are in place, we don't do any more writing there,
   * and launch4j doesn't write there either.
   * Usually ~/.m2 will only be one system or another.
   * But if it's an NFS mount shared by several system types, this approach will break.
   * <p>
   * Okay, so here is a better proposal: package the plugin without these varying binary files,
   * and put each set of binaries in its own tarball. Download the tarball you need to ~/.m2 and
   * unpack it. Then different systems won't contend for the same space. But then I'll need to hack
   * the l4j code so it permits passing in a work directory and doesn't always base it on
   * the location of its own jarfile.
   *
   * @return the work directory.
   */
  private File setupBuildEnvironment() throws MojoExecutionException {
    Artifact binaryBits = chooseBinaryBits();
    retrieveBinaryBits(binaryBits);
    return unpackWorkDir(binaryBits);
  }

  /**
   * Unzips the given artifact in-place and returns the newly-unzipped top-level directory.
   * Writes a marker file to prevent unzipping more than once.
   */
  private File unpackWorkDir(Artifact a) throws MojoExecutionException {
    String version = a.getVersion();
    File platJar = a.getFile();
    File dest = platJar.getParentFile();
    File marker = new File(dest, platJar.getName() + ".unpacked");

    // If the artifact is a SNAPSHOT, then a.getVersion() will report the long timestamp,
    // but getFile() will be 1.1-SNAPSHOT.
    // Since getFile() doesn't use the timestamp, all timestamps wind up in the same place.
    // Therefore we need to expand the jar every time, if the marker file is stale.
    if (marker.exists() && marker.lastModified() > platJar.lastModified()) {
    // if (marker.exists() && marker.platJar.getName().indexOf("SNAPSHOT") == -1) {
      getLog().info("Platform-specific work directory already exists: " + dest.getAbsolutePath());
    } else {
      JarFile jf = null;
      try {
        // trying to use plexus-archiver here is a miserable waste of time:
        jf = new JarFile(platJar);
        Enumeration en = jf.entries();
        while (en.hasMoreElements()) {
          JarEntry je = (JarEntry)en.nextElement();
          File outFile = new File(dest, je.getName());
          File parent = outFile.getParentFile();
          if (parent != null) parent.mkdirs();
          if (je.isDirectory()) {
            outFile.mkdirs();
          } else {
            InputStream in = jf.getInputStream(je);
            byte[] buf = new byte[1024];
            int len;
            FileOutputStream fout = null;
            try {
              fout = new FileOutputStream(outFile);
              while ((len = in.read(buf)) >= 0) {
                fout.write(buf, 0, len);
              }
              fout.close();
              fout = null;
            } finally {
              if (fout != null) {
                try {
                  fout.close();
                } catch (IOException e2) {} // ignore
              }
            }
            outFile.setLastModified(je.getTime());
          }
        }
      } catch (IOException e) {
        throw new MojoExecutionException("Error unarchiving " + platJar, e);
      } finally {
        try {
          if (jf != null) jf.close();
        } catch (IOException e) {} // ignore
      }

      try {
        marker.createNewFile();
        marker.setLastModified(new Date().getTime());
      } catch (IOException e) {
        getLog().warn("Trouble creating marker file " + marker, e);
      }
    }

    String n = platJar.getName();
    File workdir = new File(dest, n.substring(0, n.length() - 4));
    setPermissions(workdir);
    return workdir;
  }

  /**
   * Chmods the helper executables ld and windres on systems where that is necessary.
   */
  private void setPermissions(File workdir) throws MojoExecutionException {
    if ( ! System.getProperty("os.name").startsWith("Windows")) {
      Runtime r = Runtime.getRuntime();
      try {
        r.exec("chmod 755 " + workdir + "/bin/ld").waitFor();
        r.exec("chmod 755 " + workdir + "/bin/windres").waitFor();
      } catch (InterruptedException e) {
        getLog().warn("Interrupted while chmodding platform-specific binaries", e);
      } catch (IOException e) {
        getLog().warn("Unable to set platform-specific binaries to 755", e);
      }
    }
  }

  /**
   * Downloads the platform-specific parts, if necessary.
   */
  private void retrieveBinaryBits(Artifact a) throws MojoExecutionException {
    try {
      resolver.resolve(a, project.getRemoteArtifactRepositories(), localRepository);

    } catch (ArtifactNotFoundException e) {
      throw new MojoExecutionException("Can't find platform-specific components", e);
    } catch (ArtifactResolutionException e) {
      throw new MojoExecutionException("Can't retrieve platform-specific components", e);
    }
  }

  /**
   * Decides which platform-specific bundle we need, based on the current operating system.
   */
  private Artifact chooseBinaryBits() throws MojoExecutionException {
    String plat;
    String os = System.getProperty("os.name");
    getLog().debug("OS = " + os);

    // See here for possible values of os.name:
    // http://lopica.sourceforge.net/os.html
    if (os.startsWith("Windows")) {
      plat = "win32";
    } else if ("Linux".equals(os)) {
      plat = "linux";
    } else if ("Solaris".equals(os) || "SunOS".equals(os)) {
      plat = "solaris";
    } else if ("Mac OS X".equals(os) || "Darwin".equals(os)) {
      plat = "mac";
    } else {
      throw new MojoExecutionException("Sorry, Launch4j doesn't support the '" + os + "' OS.");
    }

    return factory.createArtifactWithClassifier("com.akathist.maven.plugins.launch4j", "launch4j-maven-plugin",
        getMyVersion(), "jar", "workdir-" + plat);
  }

  /**
   * All this work just to get the version of the current plugin!
   * We want to download the platform-specific bundle whose version matches the plugin's version,
   * so we have to figure out what version we are.
   */
  private String getMyVersion() throws MojoExecutionException {
    /*
    getLog().info("version = " + plugin.getVersion());
    return plugin.getVersion();  // plugin was set by ${plugin}, but it doesn't work: getVersion returns null!
    */
    Log log = getLog();
    log.debug("searching for launch4j plugin");
    Iterator i = plugins.iterator();
    while (i.hasNext()) {
      Plugin p = (Plugin)i.next();
      if (log.isDebugEnabled()) log.debug(p.getGroupId() + " ## " + p.getArtifactId() + " ## " + p.getVersion());
      if ("launch4j-maven-plugin".equals(p.getArtifactId()) &&
          "com.akathist.maven.plugins.launch4j".equals(p.getGroupId())) {
        String v = p.getVersion();
        log.debug("Found launch4j version " + v);
        return v;
      }
    }
    throw new MojoExecutionException("Launch4j isn't among this project's plugins. How can that be?");
  }

  private File getBaseDir() {
    return basedir;
  }

  /**
   * Just prints out how we were configured.
   */
  private void printState() {
    Log log = getLog();

    log.debug("headerType = " + headerType);
    log.debug("outfile = " + outfile);
    log.debug("jar = " + jar);
    log.debug("dontWrapJar = " + dontWrapJar);
    log.debug("errTitle = " + errTitle);
    log.debug("downloadUrl = " + downloadUrl);
    log.debug("supportUrl = " + supportUrl);
    log.debug("cmdLine = " + cmdLine);
    log.debug("chdir = " + chdir);
    log.debug("priority = " + priority);
    log.debug("customProcName = " + customProcName);
    log.debug("stayAlive = " + stayAlive);
    log.debug("icon = " + icon);
    log.debug("objs = " + objs);
    log.debug("libs = " + libs);
    log.debug("vars = " + vars);
    if (singleInstance != null) {
      log.debug("singleInstance.mutexName = " + singleInstance.mutexName);
      log.debug("singleInstance.windowTitle = " + singleInstance.windowTitle);
    } else {
      log.debug("singleInstance = null");
    }
    if (jre != null) {
      log.debug("jre.path = " + jre.path);
      log.debug("jre.minVersion = " + jre.minVersion);
      log.debug("jre.maxVersion = " + jre.maxVersion);
      log.debug("jre.jdkPreference = " + jre.jdkPreference);
      log.debug("jre.initialHeapSize = " + jre.initialHeapSize);
      log.debug("jre.initialHeapPercent = " + jre.initialHeapPercent);
      log.debug("jre.maxHeapSize = " + jre.maxHeapSize);
      log.debug("jre.maxHeapPercent = " + jre.maxHeapPercent);
      log.debug("jre.opts = "+ jre.opts);
    } else {
      log.debug("jre = null");
    }
    if (classPath != null) {
      log.debug("classPath.mainClass = " + classPath.mainClass);
      log.debug("classPath.addDependencies = " + classPath.addDependencies);
      log.debug("classPath.jarLocation = " + classPath.jarLocation);
      log.debug("classPath.preCp = " + classPath.preCp);
      log.debug("classPath.postCp = " + classPath.postCp);
    } else {
      log.info("classpath = null");
    }
    if (splash != null) {
      log.debug("splash.file = " + splash.file);
      log.debug("splash.waitForWindow = " + splash.waitForWindow);
      log.debug("splash.timeout = " + splash.timeout);
      log.debug("splash.timoutErr = " + splash.timeoutErr);
    } else {
      log.debug("splash = null");
    }
    if (versionInfo != null) {
      log.debug("versionInfo.fileVersion = " + versionInfo.fileVersion);
      log.debug("versionInfo.txtFileVersion = " + versionInfo.txtFileVersion);
      log.debug("versionInfo.fileDescription = " + versionInfo.fileDescription);
      log.debug("versionInfo.copyright = " + versionInfo.copyright);
      log.debug("versionInfo.productVersion = " + versionInfo.productVersion);
      log.debug("versionInfo.txtProductVersion = " + versionInfo.txtProductVersion);
      log.debug("versionInfo.productName = " + versionInfo.productName);
      log.debug("versionInfo.companyName = " + versionInfo.companyName);
      log.debug("versionInfo.internalName = " + versionInfo.internalName);
      log.debug("versionInfo.originalFilename = " + versionInfo.originalFilename);
    } else {
      log.debug("versionInfo = null");
    }
    if (messages != null) {
      log.debug("messages.startupErr = " + messages.startupErr);
      log.debug("messages.bundledJreErr = " + messages.bundledJreErr);
      log.debug("messages.jreVersionErr = " + messages.jreVersionErr);
      log.debug("messages.launcherErr = " + messages.launcherErr);
    } else {
      log.debug("messages = null");
    }
  }

}
TOP

Related Classes of com.akathist.maven.plugins.launch4j.Launch4jMojo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.