com.bugvm.maven.plugin.AbstractBugVMMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.bugvm.maven.plugin.AbstractBugVMMojo.java

Source

/*
 * Copyright (C) 2013 BugVM AB.
 *
 * 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 com.bugvm.maven.plugin;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
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.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
import org.codehaus.plexus.util.xml.XMLWriter;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;
import com.bugvm.compiler.AppCompiler;
import com.bugvm.compiler.Version;
import com.bugvm.compiler.config.Arch;
import com.bugvm.compiler.config.Config;
import com.bugvm.compiler.config.Config.Home;
import com.bugvm.compiler.config.OS;
import com.bugvm.compiler.log.Logger;
import com.bugvm.compiler.target.ios.ProvisioningProfile;
import com.bugvm.compiler.target.ios.SigningIdentity;

/**
 */
public abstract class AbstractBugVMMojo extends AbstractMojo {

    @Component
    protected MavenProject project;

    @Component
    private ArchiverManager archiverManager;

    @Component
    private ArtifactResolver artifactResolver;

    @Parameter(defaultValue = "${localRepository}")
    private ArtifactRepository localRepository;

    /**
     * Base directory to extract BugVM native distribution files into. The
     * bugvm-dist bundle will be downloaded from Maven and extracted into this
     * directory. Note that each release of BugVM is placed in a separate
     * sub-directory with the version number as suffix.
     *
     * If not set, then the tar file is extracted into the local Maven
     * repository where the tar file is downloaded to.
     */
    @Parameter
    protected File home;

    /**
     * The path to a {@code bugvm.properties} file which contains info for your app.
     */
    @Parameter(property = "bugvm.propertiesFile")
    protected File propertiesFile;

    /**
     * The path to a {@code bugvm.xml} file which configures the BugVM compiler.
     */
    @Parameter(property = "bugvm.configFile")
    protected File configFile;

    /**
     * The identity to sign the app as when building an iOS or tvOS bundle for
     * the app. Default is to look for an identity starting with 'iPhone
     * Developer' or 'iOS Development'. Enclose in '/' to search by regexp, e.g.
     * '/foo|bar/'.
     */
    @Parameter(property = "bugvm.signIdentity", alias = "bugvm.iosSignIdentity")
    protected String signIdentity;

    /**
     * The provisioning profile to use when building for device.
     */
    @Parameter(property = "bugvm.provisioningProfile", alias = "bugvm.iosProvisioningProfile")
    protected String provisioningProfile;

    /**
     * Whether the app should be signed or not. Unsigned apps can only be run on jailbroken
     * devices.
     */
    @Parameter(property = "bugvm.skipSigning", alias = "bugvm.iosSkipSigning")
    protected boolean skipSigning = false;

    // /**
    //  * Keychain password to use when unlocking the codesign keychain. May be
    //  * needed during headless build. The compiler will also look for a
    //  * KEYCHAIN_PASSWORD env variable if this property hasn't been specified.
    //  */
    // @Parameter(property="bugvm.keychainPassword")
    // protected String keychainPassword;

    // /**
    //  * Read the keychain password to use when unlocking the codesign keychain
    //  * from the specified file. May be needed during headless build. The
    //  * compiler will also look for a KEYCHAIN_PASSWORD env variable if this
    //  * property hasn't been specified.
    //  */
    // @Parameter(property="bugvm.keychainPasswordFile")
    // protected File keychainPasswordFile;

    /**
     * The directory into which the BugVM distributable for the project will be built.
     */
    @Parameter(property = "bugvm.installDir", defaultValue = "${project.build.directory}/bugvm")
    protected File installDir;

    /**
     * The directory where cached compiled class files will be placed. Default
     * is ~/.bugvm/cache.
     */
    @Parameter(property = "bugvm.cacheDir")
    protected File cacheDir;

    /**
     * Overrides the arch used when running the app. One of x86, x86_64, thumbv7, arm64.
     * Will be ignored if the specified value isn't supported by the executed goal.
     */
    @Parameter(property = "bugvm.arch")
    protected String arch;

    /**
     * Overrides the os used when running the app. One of macosx, linux, ios, tvos.
     * Will be ignored if the specified value isn't supported by the executed goal.
     */
    @Parameter(property = "bugvm.os")
    protected String os;

    /**
     * If set to {@code true} the app will be launched in debug mode. The app
     * will suspend before the main method is called and will wait for a
     * debugger to connect. If set to {@code "clientmode"} then the application
     * will connect back to the local host to attach to already started
     * debugging server which is waiting for connection on <code>bugvm.debugPort</code>.
     */
    @Parameter(property = "bugvm.debug")
    protected String debug;

    /**
     * The port to listen for debugger connections on when launching in debug
     * mode using {@code debug=true}. If not set a default port will be used.
     * The port actually used will be written to the console before the app is
     * launched.
     */
    @Parameter(property = "bugvm.debugPort")
    protected int debugPort = -1;

    private Logger roboVMLogger;

    protected Config.Builder configure(Config.Builder builder) throws MojoExecutionException {
        builder.logger(getBugVMLogger());

        // load config base file if it exists (and properties)

        if (os != null) {
            builder.os(OS.valueOf(os));
        }

        if (propertiesFile != null) {
            if (!propertiesFile.exists()) {
                throw new MojoExecutionException(
                        "Invalid 'propertiesFile' specified for BugVM compile: " + propertiesFile);
            }
            try {
                getLog().debug(
                        "Including properties file in BugVM compiler config: " + propertiesFile.getAbsolutePath());
                builder.addProperties(propertiesFile);
            } catch (IOException e) {
                throw new MojoExecutionException(
                        "Failed to add properties file to BugVM config: " + propertiesFile);
            }
        } else {
            try {
                builder.readProjectProperties(project.getBasedir(), false);
            } catch (IOException e) {
                throw new MojoExecutionException("Failed to read BugVM project properties file(s) in "
                        + project.getBasedir().getAbsolutePath(), e);
            }
        }

        if (configFile != null) {
            if (!configFile.exists()) {
                throw new MojoExecutionException("Invalid 'configFile' specified for BugVM compile: " + configFile);
            }
            try {
                getLog().debug("Loading config file for BugVM compiler: " + configFile.getAbsolutePath());
                builder.read(configFile);
            } catch (Exception e) {
                throw new MojoExecutionException("Failed to read BugVM config file: " + configFile);
            }
        } else {
            try {
                builder.readProjectConfig(project.getBasedir(), false);
            } catch (Exception e) {
                throw new MojoExecutionException(
                        "Failed to read project BugVM config file in " + project.getBasedir().getAbsolutePath(), e);
            }
        }

        // Read embedded BugVM <config> if there is one
        Plugin plugin = project.getPlugin("com.bugvm:bugvm-maven-plugin");
        MavenProject p = project;
        while (p != null && plugin == null) {
            plugin = p.getPluginManagement().getPluginsAsMap().get("com.bugvm:bugvm-maven-plugin");
            if (plugin == null)
                p = p.getParent();
        }
        if (plugin != null) {
            getLog().debug("Reading BugVM plugin configuration from " + p.getFile().getAbsolutePath());
            Xpp3Dom configDom = (Xpp3Dom) plugin.getConfiguration();
            if (configDom != null && configDom.getChild("config") != null) {
                StringWriter sw = new StringWriter();
                XMLWriter xmlWriter = new PrettyPrintXMLWriter(sw, "UTF-8", null);
                Xpp3DomWriter.write(xmlWriter, configDom.getChild("config"));
                try {
                    builder.read(new StringReader(sw.toString()), project.getBasedir());
                } catch (Exception e) {
                    throw new MojoExecutionException("Failed to read BugVM config embedded in POM", e);
                }
            }
        }

        File tmpDir = new File(project.getBuild().getDirectory(), "bugvm.tmp");
        try {
            FileUtils.deleteDirectory(tmpDir);
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to clean output dir " + tmpDir, e);
        }
        tmpDir.mkdirs();

        Home home = null;
        try {
            home = Home.find();
        } catch (Throwable t) {
        }
        if (home == null || !home.isDev()) {
            home = new Config.Home(unpackBugVMDist());
        }
        builder.home(home).tmpDir(tmpDir).skipInstall(true).installDir(installDir);
        if (home.isDev()) {
            builder.useDebugLibs(Boolean.getBoolean("bugvm.useDebugLibs"));
            builder.dumpIntermediates(true);
        }

        if (debug != null && !debug.equals("false")) {
            builder.debug(true);
            if (debugPort != -1) {
                builder.addPluginArgument("debug:jdwpport=" + debugPort);
            }
            if ("clientmode".equals(debug)) {
                builder.addPluginArgument("debug:clientmode=true");
            }
        }

        if (skipSigning) {
            builder.iosSkipSigning(true);
        } else {
            if (signIdentity != null) {
                getLog().debug("Using explicit signing identity: " + signIdentity);
                builder.iosSignIdentity(SigningIdentity.find(SigningIdentity.list(), signIdentity));
            }

            if (provisioningProfile != null) {
                getLog().debug("Using explicit provisioning profile: " + provisioningProfile);
                builder.iosProvisioningProfile(
                        ProvisioningProfile.find(ProvisioningProfile.list(), provisioningProfile));
            }

            // if (keychainPassword != null) {
            //     builder.keychainPassword(keychainPassword);
            // } else if (keychainPasswordFile != null) {
            //     builder.keychainPasswordFile(keychainPasswordFile);
            // }
        }

        if (cacheDir != null) {
            builder.cacheDir(cacheDir);
        }

        builder.clearClasspathEntries();

        // configure the runtime classpath

        try {
            for (Object object : project.getRuntimeClasspathElements()) {
                String path = (String) object;
                if (getLog().isDebugEnabled()) {
                    getLog().debug("Including classpath element for BugVM app: " + path);
                }
                builder.addClasspathEntry(new File(path));
            }
        } catch (DependencyResolutionRequiredException e) {
            throw new MojoExecutionException("Error resolving application classpath for BugVM build", e);
        }

        return builder;
    }

    protected AppCompiler build(OS os, Arch arch, String targetType)
            throws MojoExecutionException, MojoFailureException {

        getLog().info("Building BugVM app for: " + os + " (" + arch + ")");

        Config.Builder builder;
        try {
            builder = new Config.Builder();
        } catch (IOException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

        configure(builder).os(os).arch(arch).targetType(targetType);

        // execute the BugVM build

        try {

            getLog().info("Compiling BugVM app, this could take a while, especially the first time round");
            AppCompiler compiler = new AppCompiler(builder.build());
            compiler.build();

            return compiler;

        } catch (IOException e) {
            throw new MojoExecutionException("Error building BugVM executable for app", e);
        }
    }

    protected String getBugVMVersion() {
        return Version.getVersion();
    }

    protected File unpackBugVMDist() throws MojoExecutionException {

        Artifact distTarArtifact = resolveBugVMDistArtifact();
        File distTarFile = distTarArtifact.getFile();
        File unpackBaseDir;
        if (home != null) {
            unpackBaseDir = home;
        } else {
            // by default unpack into the local repo directory
            unpackBaseDir = new File(distTarFile.getParent(), "unpacked");
        }
        if (unpackBaseDir.exists() && distTarArtifact.isSnapshot()) {
            getLog().debug("Deleting directory for unpacked snapshots: " + unpackBaseDir);
            try {
                FileUtils.deleteDirectory(unpackBaseDir);
            } catch (IOException e) {
                throw new MojoExecutionException("Failed to delete " + unpackBaseDir, e);
            }
        }
        unpack(distTarFile, unpackBaseDir);
        File unpackedDir = new File(unpackBaseDir, "bugvm-" + getBugVMVersion());
        return unpackedDir;
    }

    protected Artifact resolveBugVMDistArtifact() throws MojoExecutionException {

        MavenArtifactHandler handler = new MavenArtifactHandler("tar.gz");
        Artifact artifact = new DefaultArtifact("com.bugvm", "bugvm-dist", getBugVMVersion(), "", "tar.gz", null,
                handler);
        return resolveArtifact(artifact);
    }

    protected Artifact resolveArtifact(Artifact artifact) throws MojoExecutionException {

        ArtifactResolutionRequest request = new ArtifactResolutionRequest();
        request.setArtifact(artifact);
        if (artifact.isSnapshot()) {
            request.setForceUpdate(true);
        }
        request.setLocalRepository(localRepository);
        final List<ArtifactRepository> remoteRepositories = project.getRemoteArtifactRepositories();
        request.setRemoteRepositories(remoteRepositories);

        getLog().debug("Resolving artifact " + artifact);

        ArtifactResolutionResult result = artifactResolver.resolve(request);
        if (!result.isSuccess()) {
            throw new MojoExecutionException("Unable to resolve artifact: " + artifact);
        }
        Collection<Artifact> resolvedArtifacts = result.getArtifacts();
        artifact = (Artifact) resolvedArtifacts.iterator().next();
        return artifact;
    }

    protected void unpack(File archive, File targetDirectory) throws MojoExecutionException {

        if (!targetDirectory.exists()) {

            getLog().info("Extracting '" + archive + "' to: " + targetDirectory);
            if (!targetDirectory.mkdirs()) {
                throw new MojoExecutionException(
                        "Unable to create base directory to unpack into: " + targetDirectory);
            }

            try {
                UnArchiver unArchiver = archiverManager.getUnArchiver(archive);
                unArchiver.setSourceFile(archive);
                unArchiver.setDestDirectory(targetDirectory);
                unArchiver.extract();
            } catch (NoSuchArchiverException e) {
                throw new MojoExecutionException("Unable to unpack archive " + archive + " to " + targetDirectory,
                        e);
            }
            getLog().debug("Archive '" + archive + "' unpacked to: " + targetDirectory);

        } else {
            getLog().debug("Archive '" + archive + "' was already unpacked in: " + targetDirectory);
        }
    }

    protected Logger getBugVMLogger() {

        if (roboVMLogger == null) {
            roboVMLogger = new Logger() {
                public void debug(String s, Object... objects) {
                    getLog().debug(String.format(s, objects));
                }

                public void info(String s, Object... objects) {
                    getLog().info(String.format(s, objects));
                }

                public void warn(String s, Object... objects) {
                    getLog().warn(String.format(s, objects));
                }

                public void error(String s, Object... objects) {
                    getLog().error(String.format(s, objects));
                }
            };
        }
        return roboVMLogger;
    }
}