net.sf.mavenjython.JythonMojo.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.mavenjython.JythonMojo.java

Source

package net.sf.mavenjython;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 * 
 * 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.
 */

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

/**
 * Goal which generates jython-standalone into .
 * 
 * @goal jython
 * 
 * @phase compile
 */
public class JythonMojo extends AbstractMojo {

    private static final String SETUPTOOLS_EGG = "setuptools-0.6c11-py2.7.egg";

    /**
     * @parameter property="project.build.outputDirectory"
     * @required
     */
    private File outputDirectory;

    /**
     * Libraries needed to include.
     * 
     * @parameter
     * @optional
     */
    private List<String> libraries;

    /**
     * Caching directory to download and build python packages, as well as
     * extracted jython dir
     * 
     * @parameter property="jython.temporaryDirectory"
     *            default="target/jython-build-tmp"
     * @optional
     */
    private File temporaryBuildDirectory;

    /**
     * Dependencies. Will be searched for jython
     * 
     * @parameter property="plugin.artifacts"
     */
    private List<DefaultArtifact> pluginArtifacts;

    /**
     * Lib/
     */
    private File libdir;

    /**
     * Lib/ in the output
     */
    private File installlibdir;

    /**
     * The Jython dependency
     */
    private DefaultArtifact jythonArtifact;

    /**
     * The setuptools jar resource
     */
    private URL setuptoolsResource;

    /**
     * The setuptools jar, once copied from the resource
     */
    private File setuptoolsJar;

    /**
     * Lib/site-packages
     */
    private File sitepackagesdir;

    /**
     * Where packages are downloaded and built
     */
    private File packageDownloadCacheDir;

    /**
     * Should we override files during extraction if they already exist?
     * 
     * if true: will never work on tainted files; if false: will be faster.
     */
    private static final boolean OVERRIDE = false;

    private void setupVariables() throws MojoExecutionException {
        if (temporaryBuildDirectory == null) {
            temporaryBuildDirectory = new File("target/jython-plugins-tmp");
        }
        temporaryBuildDirectory.mkdirs();
        packageDownloadCacheDir = new File(temporaryBuildDirectory, "build");
        packageDownloadCacheDir.mkdir();
        libdir = new File(temporaryBuildDirectory, "Lib");
        installlibdir = new File(outputDirectory, "Lib");

        jythonArtifact = findJythonArtifact();
        if (!jythonArtifact.getFile().getName().endsWith(".jar")) {
            throw new MojoExecutionException(
                    "I expected " + jythonArtifact + " to provide a jar, but got " + jythonArtifact.getFile());
        }

        setuptoolsResource = getClass().getResource(SETUPTOOLS_EGG);
        if (setuptoolsResource == null)
            throw new MojoExecutionException("resource setuptools egg not found");
        setuptoolsJar = new File(packageDownloadCacheDir, SETUPTOOLS_EGG);
        sitepackagesdir = new File(libdir, "site-packages");
    }

    /**
     * Strategy A: include jython in plugin. Extract on the run.
     * 
     * Strategy B: Project also has dependency on jython. We find that jar and
     * extract it and work from there.
     * 
     * B has the benefit that we don't have to update this plugin for every
     * version and the user needs the jython dependency anyway to call the
     * Python Console
     */
    public void execute() throws MojoExecutionException {
        setupVariables();

        extractJarToDirectory(jythonArtifact.getFile(), temporaryBuildDirectory);

        // now what? we have the jython content, now we need
        // easy_install
        getLog().info("installing easy_install ...");
        try {
            if (OVERRIDE || !setuptoolsJar.exists()) {
                FileUtils.copyInputStreamToFile(setuptoolsResource.openStream(), setuptoolsJar);
            }
        } catch (IOException e) {
            throw new MojoExecutionException("copying setuptools failed", e);
        }
        extractJarToDirectory(setuptoolsJar, new File(sitepackagesdir, SETUPTOOLS_EGG));
        try {
            IOUtils.write("./" + SETUPTOOLS_EGG + "\n",
                    new FileOutputStream(new File(sitepackagesdir, "setuptools.pth")));
        } catch (IOException e) {
            throw new MojoExecutionException("writing path entry for setuptools failed", e);
        }
        getLog().info("installing easy_install done");

        if (libraries == null) {
            getLog().info("no python libraries requested");
        } else {
            getLog().info("installing requested python libraries");
            // then we need to call easy_install to install the other
            // dependencies.
            runJythonScriptOnInstall(temporaryBuildDirectory,
                    getEasyInstallArgs("Lib/site-packages/" + SETUPTOOLS_EGG + "/easy_install.py"));
            getLog().info("installing requested python libraries done");
        }

        getLog().info("copying requested libraries");
        /**
         * we installed the packages into our temporary build directory now we
         * want to move these libraries into outputDirectory/Lib
         * 
         * <pre>
         * mv --no-override temporaryBuildDirectory/Lib/site-packages/*.egg/* outputDirectory/Lib/
         * </pre>
         */
        for (File i : sitepackagesdir.listFiles(
                (FileFilter) new AndFileFilter(DirectoryFileFilter.INSTANCE, new SuffixFileFilter(".egg")))) {
            getLog().info("copying " + i + " into " + new File(outputDirectory, "Lib"));
            try {
                FileUtils.deleteDirectory(new File(installlibdir, i.getName()));
            } catch (IOException e) {
            }
            try {
                FileUtils.copyDirectory(i, installlibdir);
            } catch (IOException e) {
                throw new MojoExecutionException(
                        "copying " + i + " to " + new File(outputDirectory, "Lib") + " failed", e);
            }
        }

        /**
         * Setuptools installs a site.py for every package. This might conflict
         * with the jython installation. All it does anyway is help look for
         * eggs, so it wouldn't help us. Errors are ignored here.
         */
        new File(installlibdir, "site.py").delete();
        new File(installlibdir, "site$py.class").delete();
        getLog().info("copying requested libraries done");

        /**
         * If the project does not want its python sources to be in Lib/ it
         * needs to call
         * 
         * PySystemState.addPaths(path, jarFileName + "/myLibFolder");
         * 
         * before starting Python up.
         */
    }

    private List<String> getEasyInstallArgs(String easy_install_script) throws MojoExecutionException {
        List<String> args = new ArrayList<String>();

        // I want to launch
        args.add("java");
        // to run the generated jython installation here
        args.add("-cp");
        args.add("." + getClassPathSeparator() + "Lib");
        // which should know about itself
        args.add("-Dpython.home=.");
        File jythonFakeExecutable = new File(temporaryBuildDirectory, "jython");
        try {
            jythonFakeExecutable.createNewFile();
        } catch (IOException e) {
            throw new MojoExecutionException("couldn't create file", e);
        }
        args.add("-Dpython.executable=" + jythonFakeExecutable.getName());
        args.add("org.python.util.jython");
        // and it should run easy_install
        args.add(easy_install_script);
        // with some arguments
        // args.add("--optimize");
        // args.add("--install-dir");
        // args.add(outputDirectory.getAbsolutePath());
        // and cache here
        args.add("--build-directory");
        args.add(packageDownloadCacheDir.getAbsolutePath());
        // and install these libraries
        args.addAll(libraries);

        return args;
    }

    private String getClassPathSeparator() {
        if (File.separatorChar == '\\')
            return ";";
        else
            return ":";
    }

    public void runJythonScriptOnInstall(File outputDirectory, List<String> args) throws MojoExecutionException {
        getLog().info("running " + args + " in " + outputDirectory);
        ProcessBuilder pb = new ProcessBuilder(args);
        pb.directory(outputDirectory);
        final Process p;
        try {
            p = pb.start();
        } catch (IOException e) {
            throw new MojoExecutionException("Executing jython failed. tried to run: " + pb.command(), e);
        }
        copyIO(p.getInputStream(), System.out);
        copyIO(p.getErrorStream(), System.err);
        copyIO(System.in, p.getOutputStream());
        try {
            if (p.waitFor() != 0) {
                throw new MojoExecutionException("Jython failed with return code: " + p.exitValue());
            }
        } catch (InterruptedException e) {
            throw new MojoExecutionException("Python tests were interrupted", e);
        }

    }

    public Collection<File> extractJarToDirectory(File jar, File outputDirectory) throws MojoExecutionException {
        getLog().info("extracting " + jar);
        JarFile ja = openJarFile(jar);
        Enumeration<JarEntry> en = ja.entries();
        Collection<File> files = extractAllFiles(outputDirectory, ja, en);
        closeFile(ja);
        return files;
    }

    private JarFile openJarFile(File jar) throws MojoExecutionException {
        try {
            return new JarFile(jar);
        } catch (IOException e) {
            throw new MojoExecutionException("opening jython artifact jar failed", e);
        }
    }

    private void closeFile(ZipFile ja) throws MojoExecutionException {
        try {
            ja.close();
        } catch (IOException e) {
            throw new MojoExecutionException("closing jython artifact jar failed", e);
        }
    }

    private Collection<File> extractAllFiles(File outputDirectory, ZipFile ja, Enumeration<JarEntry> en)
            throws MojoExecutionException {
        List<File> files = new ArrayList<File>();
        while (en.hasMoreElements()) {
            JarEntry el = en.nextElement();
            // getLog().info(" > " + el);
            if (!el.isDirectory()) {
                File destFile = new File(outputDirectory, el.getName());
                // destFile = new File(outputDirectory, destFile.getName());
                if (OVERRIDE || !destFile.exists()) {
                    destFile.getParentFile().mkdirs();
                    try {
                        FileOutputStream fo = new FileOutputStream(destFile);
                        IOUtils.copy(ja.getInputStream(el), fo);
                        fo.close();
                    } catch (IOException e) {
                        throw new MojoExecutionException(
                                "extracting " + el.getName() + " from jython artifact jar failed", e);
                    }
                }
                files.add(destFile);
            }
        }
        return files;
    }

    private DefaultArtifact findJythonArtifact() throws MojoExecutionException {
        for (DefaultArtifact i : pluginArtifacts) {
            if (i.getArtifactId().equals("jython-standalone") && i.getGroupId().equals("org.python")) {
                return i;
            }
        }
        throw new MojoExecutionException("org.python.jython-standalone dependency not found. " + "\n"
                + "Add a dependency to jython-standalone to your project: \n" + "   <dependency>\n"
                + "      <groupId>org.python</groupId>\n" + "      <artifactId>jython-standalone</artifactId>\n"
                + "      <version>2.7.0</version>\n" + "   </dependency>" + "\n");
    }

    private void copyIO(final InputStream input, final OutputStream output) {
        new Thread(new Runnable() {

            public void run() {
                try {
                    IOUtils.copy(input, output);
                } catch (IOException e) {
                    getLog().error(e);
                }
            }
        }).start();

    }
}