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

com.redhat.ceylon.common.tools.CeylonTool Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * Copyright Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the authors tag. All rights reserved.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License version 2.
 * 
 * This particular file is subject to the "Classpath" exception as provided in the 
 * LICENSE file that accompanied this code.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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 distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package com.redhat.ceylon.common.tools;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.OSUtil;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.common.config.CeylonConfig;
import com.redhat.ceylon.common.config.CeylonConfigFinder;
import com.redhat.ceylon.common.tool.AnnotatedToolModel;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.ArgumentModel;
import com.redhat.ceylon.common.tool.CeylonBaseTool;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.FatalToolError;
import com.redhat.ceylon.common.tool.ModelException;
import com.redhat.ceylon.common.tool.NoSuchToolException;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.OptionArgumentException;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.ScriptToolModel;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.Tool;
import com.redhat.ceylon.common.tool.ToolError;
import com.redhat.ceylon.common.tool.ToolFactory;
import com.redhat.ceylon.common.tool.ToolLoader;
import com.redhat.ceylon.common.tool.ToolModel;
import com.redhat.ceylon.common.tool.Tools;


/**
 * Main entry point for the {@code ceylon} command; the top level tool; the 
 * plugin with no name.
 */
@Summary("The top level Ceylon tool is used to execute other Ceylon tools")
@Description(
        "If `--version` is present, print version information and exit. "+
        "Otherwise `` should begin with the name of a ceylon tool " +
        "or a list of comma-separated tool names which will be invoked one after " +
        "the other with the same remaining command line arguments. " +
        "The named tools are loaded and configured with the remaining command " +
        "line arguments and control passes to those tools.")
@RemainingSections(
"## CONFIGURATION MECHANISM\n\n" +
"Ceylon uses a simple text format to store customizations that are per repository and are per user. Such a configuration file may look like this:"+
"\n\n"+
"    #\n"+
"    # A '#' or ';' character indicates a comment.\n"+
"    #\n"+
"    \n"+
"    ; global settings\n"+
"    [defaults]\n"+
"        encoding = utf8\n"+
"        pager = false\n"+
"    \n"+
"    ; local repository\n"+
"    [repository \"LOCAL\"]\n"+
"        url = ./modules\n"+
"\n\n"+
"Various commands read from the configuration file and adjust their operation accordingly. See ceylon-config(1) for a list and more details about the configuration mechanism."
)
public class CeylonTool implements Tool {

    private static final String ARG_LONG_VERSION = "--version";
    private static final String ARG_SHORT_VERSION = "-v";
    
    /** Normal termination */
    public static final int SC_OK = 0;
    /** 
     * Tool threw an exception, but it's a no an unexpected error. 
     * (e.g. a compiler finding parse or type errors) 
     */
    public static final int SC_TOOL_ERROR = 1;
    /** 
     * Tool threw an exception
     */
    public static final int SC_TOOL_EXCEPTION = 2;
    /** 
     * The supplied tool arguments were bad in some way
     */
    public static final int SC_ARGS = 2;
    /** 
     * The supplied tool name doesn't exist
     */
    public static final int SC_NO_SUCH_TOOL = 3;
    /** 
     * The tool detected a bug 
     */
    public static final int SC_TOOL_BUG = 5;
    /** The tool instance couldn't be created 
     * (i.e. a programming error with the tools API or a bug in the tools API)
     */
    public static final int SC_TOOL_CREATION = 6;
    
    
    private String toolName;
    private List toolArgs = Collections.emptyList();
    private boolean stacktraces = false;
    private ToolLoader pluginLoader;
    private ToolFactory pluginFactory;
    private boolean version;
    private Tool toolCache;
    private Boolean paginate;
    private File cwd;
    private File config;
    private CeylonConfig oldConfig = null;
    private List defines;
    private Boolean showHome;
    private boolean showDistributionError;
    
    public CeylonTool() {
    }
    
    @Option(shortName='v')
    @Description("Print version information and exit, " +
            "*ignoring all other options and arguments*.")
    public void setVersion(boolean version) {
        this.version = version;
    }
    
    @Option
    @Description("If an error propagates to the top level tool, print its stack trace.")
    public void setStacktraces(boolean stacktraces) {
        this.stacktraces = stacktraces;
    }
    
    public boolean getStacktraces() {
        return stacktraces;
    }
    
    @Option
    @Description("Pipe all Ceylon tool output into less (or if set, $CEYLON_PAGER or $PAGER) if standard output is a terminal. This overrides the `help.pager` and `defaults.pager` configuration options (see the \"Configuration Mechanism\" section below).")
    public void setPaginate(boolean paginate) {
        this.paginate = Boolean.TRUE;
    }
    
    public boolean getPaginate() {
        return paginate != null && paginate;
    }

    @Option
    @Description("Do not pipe Ceylon tool output into a pager.")
    public void setNoPager(boolean noPager) {
        this.paginate = Boolean.FALSE;
    }
    
    public boolean getNoPager() {
        return paginate != null && !paginate;
    }

    @Option
    @Description("Outputs the value for CEYLON_HOME and exits.")
    public void setShowHome(boolean showHome) {
        this.showHome = showHome;
    }
    
    public boolean getShowHome() {
        return showHome != null && showHome;
    }

    public File getCwd() {
        return cwd;
    }
    
    @OptionArgument(longName="cwd", argumentName="dir")
    @Description("Specifies the current working directory for this tool. " +
            "(default: the directory where the tool is run from)")
    public void setCwd(File cwd) {
        this.cwd = cwd;
    }
    
    public File getConfig() {
        return config;
    }
    
    @OptionArgument(longName="config", argumentName="file")
    @Description("Specifies the configuration file to use for this tool. " +
            "(default: `./.ceylon/config`)")
    public void setConfig(File config) {
        this.config = config;
    }
    
    @OptionArgument(shortName='D', argumentName = "key>= defines) {
        this.defines = defines;
    }
    
    @OptionArgument(argumentName = "version-or-url")
    @Description("Determines which Ceylon distribution will be used to run the command. "
            + "Passing any version except the current (" + Versions.CEYLON_VERSION_NUMBER + ") "
            + "will result in a one-time download of the requested version from the "
            + "official Ceylon website.")
    public void setDistribution(String distribution) {
        // We should actually never come here because this is an option that
        // is handled by the Bootstrap and removed by it before passing control
        // over to the Launcher and CeylonTool.
        // It's just here so it will show up in the help
        this.showDistributionError = !distribution.isEmpty() && !distribution.equals(Versions.CEYLON_VERSION_NUMBER);
    }
    
    private void setSystemProperties() {
        if (defines != null) {
            for (String prop : defines) {
                int p = prop.indexOf('=');
                if (p > 0) {
                    String key = prop.substring(0, p);
                    String val = prop.substring(p + 1);
                    System.setProperty(key, val);
                }
            }
        }
    }

    //@Argument(argumentName="command", multiplicity="?", order=1)
    public void setCommand(String command) {
        this.toolName = command;
    }
    
    //@Argument(argumentName="command-arguments", multiplicity="*", order=2)
    public void setCommandArguments(List commandArgs) {
        this.toolArgs = commandArgs;
    }
    
    @Argument(argumentName="tool-arguments", multiplicity="*")
    public void setArgs(List args) {
        setCommand(args.get(0));
        setCommandArguments(args.subList(1, args.size()));
    }

    /** The name of the tool to run */
    public String getToolName() {
        return toolName;
    }
    
    public String[] getToolNames() {
        if(toolName == null)
            return new String[0];
        if(toolName.indexOf(',') != -1)
            return toolName.split(",");
        return new String[]{toolName};
    }

    /** The arguments passed to the tool to run */
    public List getToolArguments() {
        return toolArgs;
    }
    
    private static List rearrangeArgs(String[] args) {
        // cey
        // ceylon --help
        // ceylon help
        // ceylon foo --help
        // ceylon --help foo
        // ceylon --stacktraces
        // ceylon foo --bar 
        ArrayList result = new ArrayList(args.length + 1);
        if (args.length == 0) {
            result.add("help");
        } else {
            List argsList = Arrays.asList(args);
            for (int ii = 0; ii < args.length; ii++) {
                String arg = args[ii];
                if (arg.equals("--help") || arg.equals("-h") || arg.equals("help")) {
                    result.add("help");
                    result.add("--");
                } else if (arg.equals(ARG_LONG_VERSION) || arg.equals(ARG_SHORT_VERSION)) {
                    //result.clear();
                    result.add("--version");
                    break;
                } else if (arg.startsWith("-")) {
                    result.add(arg);
                } else {
                    if (arg.isEmpty()) {
                        // Trying to get the top level tool to run itself 
                        // (ceylon's argument is the empty string)
                        result.clear();
                        result.add("help");
                        break;
                    }
                    List rest = argsList.subList(ii, args.length);
                    int helpIndex = rest.indexOf("--help");
                    if (helpIndex == -1) {
                        helpIndex = rest.indexOf("-h");
                    }
                    if (helpIndex != -1) {
                        int doubleDashIndex = rest.indexOf("--");
                        if (doubleDashIndex == -1 || (helpIndex < doubleDashIndex)) {
                            result.add("help");
                            result.add(arg);
                            break;
                        }
                    }
                    result.add("--");
                    result.addAll(rest);
                    break;
                }
            }
        }
        return result;
    }

    private static void version(PrintStream out) {
        out.println(Tools.progName() + " version " + Versions.CEYLON_VERSION);
    }
    
    public static void main(String... args) throws Exception {
        int exit = start(args);
        // WARNING: NEVER CALL EXIT IF WE STILL HAVE DAEMON THREADS RUNNING AND WE'VE NO REASON TO EXIT WITH A NON-ZERO CODE
        if(exit != 0)
            System.exit(exit);
    }

    public static int start(String... args) throws Exception {
        if (args.length > 0 && (ARG_LONG_VERSION.equals(args[0]) || ARG_SHORT_VERSION.equals(args[0]))) {
            version(System.out);
            return SC_OK;
        } else {
            return new CeylonTool().bootstrap(args);
        }
    }

    /**
     * Bootstraps this tool instance.
     * 
    *
  1. Use the given command line arguments to configure this instance
  2. *
  3. Calls {@link #run()} on this instance
  4. *
  5. Returns the status code that {@link #main(String[])} would invoke * {@link System#exit(int)} with.
  6. *
* @param args Command line arguments * @return The exit code * @throws Exception */ public int bootstrap(String... args) throws Exception { int result = setup(args); if (result == SC_OK) { result = execute(); } return result; } public int setup(String... args) throws Exception { int result; try { ToolModel model = getToolModel(""); List myArgs = rearrangeArgs(CommandLine.parse(args)); getPluginFactory().bindArguments(model, this, this, myArgs); setSystemProperties(); oldConfig = setupConfig(); result = SC_OK; } catch (Exception e) { result = handleException(this, e); } System.out.flush(); return result; } // Warning: this method called by reflection in Launcher public int execute() throws Exception { int result = SC_OK; try { String[] names = (toolName != null) ? getToolNames() : new String[] { null }; for (String singleToolName : names) { ToolModel model = getToolModel(singleToolName); Tool tool = getTool(model); CeylonConfig oldConfig2 = null; if (oldConfig == null) { oldConfig2 = setupConfig(tool); } syncCwd(tool); try { run(model, tool); result = SC_OK; } finally { if (oldConfig2 != null) { CeylonConfig.set(oldConfig2); } } } } catch (Exception e) { result = handleException(this, e); } finally { if (oldConfig != null) { CeylonConfig.set(oldConfig); oldConfig = null; } } System.out.flush(); return result; } public static int handleException(CeylonTool mainTool, Exception ex) throws Exception { int result; if (ex instanceof NoSuchToolException) { result = SC_NO_SUCH_TOOL; } else if (ex instanceof ModelException) { result = SC_TOOL_CREATION; } else if (ex instanceof OptionArgumentException) { result = SC_ARGS; } else if (ex instanceof FatalToolError) { result = SC_TOOL_BUG; } else if (ex instanceof ToolError) { ToolError err = (ToolError)ex; result = err.isExitCodeProvided() ? err.getExitCode() : SC_TOOL_ERROR; } else { result = SC_TOOL_EXCEPTION; } Usage.handleException(mainTool, mainTool.getToolName(), ex); return result; } // Here we set up the global configuration for this thread // if (and only if) the setup deviates from the default // (meaning either `cwd` or `config` was set for the main tool) private CeylonConfig setupConfig() throws IOException { File cwd = getCwd(); File cfgFile = getConfig(); if (cfgFile != null) { File absCfgFile = FileUtil.applyCwd(cwd, cfgFile); CeylonConfig config = CeylonConfigFinder.DEFAULT.loadConfigFromFile(absCfgFile); return CeylonConfig.set(config); } if (cwd != null && cwd.isDirectory()) { CeylonConfig config = CeylonConfigFinder.loadDefaultConfig(cwd); return CeylonConfig.set(config); } return null; } // Here we set up the global configuration for this thread // if (and only if) the setup deviates from the default // (meaning `cwd` was set for the given tool) private CeylonConfig setupConfig(Tool tool) throws IOException { if (tool instanceof CeylonBaseTool) { CeylonBaseTool cbt = (CeylonBaseTool)tool; File cwd = cbt.getCwd(); if (cwd != null && cwd.isDirectory()) { CeylonConfig config = CeylonConfigFinder.loadDefaultConfig(cwd); return CeylonConfig.set(config); } if (getCwd() != null) { // If the main tool's `cwd` options is set it // always overrides the one in the given tool cbt.setCwd(getCwd()); } } return null; } // Here we set up the global configuration for this thread // if (and only if) the setup deviates from the default // (meaning `cwd` was set for the given tool) private void syncCwd(Tool tool) throws IOException { if (tool instanceof CeylonBaseTool) { CeylonBaseTool cbt = (CeylonBaseTool)tool; if (getCwd() != null) { // If the main tool's `cwd` options is set it // always overrides the one in the given tool cbt.setCwd(getCwd()); } } } @Override public void initialize(CeylonTool mainTool) { } @Override public void run() throws Exception { if (showDistributionError) { throw new IllegalArgumentException("Requested and actual Ceylon distribution versions are not the same"); } } private void run(ToolModel model, Tool tool) throws Exception { if (version) { // --version is also handled in main(), so that you can at least do // --version with a Java <7 JVM, but also do it here for consistency version(System.out); } else if (getShowHome()) { System.out.println(System.getProperty(Constants.PROP_CEYLON_HOME_DIR)); } else { if(model instanceof ScriptToolModel){ runScript((ScriptToolModel)model); }else{ // Run the tool tool.run(); } } } private void runScript(ScriptToolModel model) { List args = new ArrayList(3+toolArgs.size()); if (OSUtil.isWindows()) { args.add("cmd.exe"); args.add("/C"); } args.add(model.getScriptName()); args.addAll(toolArgs); ProcessBuilder processBuilder = new ProcessBuilder(args); setupScriptEnvironment(processBuilder, model.getScriptName()); processBuilder.redirectError(Redirect.INHERIT); processBuilder.redirectInput(Redirect.INHERIT); if (OSUtil.isWindows()) { processBuilder.redirectOutput(Redirect.INHERIT); } try { Process process = processBuilder.start(); if (!OSUtil.isWindows()) { InputStream in = process.getInputStream(); InputStreamReader inread = new InputStreamReader(in); BufferedReader bufferedreader = new BufferedReader(inread); String line; while ((line = bufferedreader.readLine()) != null) { System.out.println(line); } } int exit = process.waitFor(); if(exit != 0) throw new ToolError("Script "+model.getScriptName()+" returned error exit code "+exit) {}; } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void setupScriptEnvironment(ProcessBuilder processBuilder, String script) { Map env = processBuilder.environment(); String ceylonHome = System.getProperty(Constants.PROP_CEYLON_HOME_DIR); if (ceylonHome != null) { env.put(Constants.ENV_CEYLON_HOME_DIR, ceylonHome); String ceylonBin = ceylonHome + File.separator + "bin" + File.separator + "ceylon"; if (OSUtil.isWindows()) { ceylonBin += ".bat"; } env.put("CEYLON", ceylonBin); } // FIXME: more info? env.put("JAVA_HOME", System.getProperty("java.home")); env.put("CEYLON_VERSION_MAJOR", Integer.toString(Versions.CEYLON_VERSION_MAJOR)); env.put("CEYLON_VERSION_MINOR", Integer.toString(Versions.CEYLON_VERSION_MINOR)); env.put("CEYLON_VERSION_RELEASE", Integer.toString(Versions.CEYLON_VERSION_RELEASE)); env.put("CEYLON_VERSION", Versions.CEYLON_VERSION_NUMBER); env.put("CEYLON_VERSION_FULL", Versions.CEYLON_VERSION); env.put("CEYLON_VERSION_NAME", Versions.CEYLON_VERSION_NAME); env.put("SCRIPT", script); env.put("SCRIPT_DIR", (new File(script)).getParent()); } // WARNING: this is called by reflection in Launcher: do not REMOVE!!! public Tool[] getTools() { String[] toolNames = getToolNames(); Tool[] tools = new Tool[toolNames.length]; for(int i=0;i model) { Tool tool = null; if (model == null) { ArgumentModel argumentModel = getToolModel("").getArguments().get(0); // XXX Very evil hack to work around the fact that the CeyonTool does // not use subtools yet, and so the model used to generate help doc // doesn't quite work right. argumentModel.setName("command"); throw new NoSuchToolException(argumentModel, getToolName()); } if(!(model instanceof AnnotatedToolModel)) return null; boolean useCache = false; if(toolName != null && toolName.equals(model.getName())) useCache = true; if(useCache && toolCache != null) return toolCache; tool = getPluginFactory().bindArguments(model, this, toolArgs); if(useCache) toolCache = tool; return tool; } ToolModel getToolModel(String toolName) { final ToolModel model = getPluginLoader().loadToolModel(toolName); return model; } public ToolModel getToolModel() { return getToolModel(getToolName()); } ToolFactory getPluginFactory() { if (pluginFactory == null) { pluginFactory =new ToolFactory(); } return pluginFactory; } public void setPluginFactory(ToolFactory toolFactory) { this.pluginFactory = toolFactory; } ToolLoader getPluginLoader() { if (pluginLoader == null) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //ClassLoader classLoader = this.getClass().getClassLoader(); pluginLoader = new CeylonToolLoader(classLoader); } return pluginLoader; } public void setToolLoader(ToolLoader toolLoader) { this.pluginLoader = toolLoader; } public Boolean getWantsPager() { return paginate; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy