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

com.redhat.ceylon.tools.plugin.CeylonPluginTool Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
package com.redhat.ceylon.tools.plugin;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.api.ModuleVersionDetails;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.cmr.ceylon.OutputRepoUsingTool;
import com.redhat.ceylon.cmr.impl.IOUtils;
import com.redhat.ceylon.cmr.impl.ShaSigner;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.Messages;
import com.redhat.ceylon.common.OSUtil;
import com.redhat.ceylon.common.config.DefaultToolOptions;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.ParsedBy;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.StandardArgumentParsers;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.ToolError;
import com.redhat.ceylon.common.tools.CeylonTool;
import com.redhat.ceylon.common.tools.ModuleSpec;
import com.redhat.ceylon.model.cmr.ArtifactResult;
import com.redhat.ceylon.model.typechecker.model.Module;

@Summary("Manages Ceylon command-line plugins")
@Description(
        "There are four modes of action:\n"+
        "\n"+
        "- `list`: will list the scripts installed\n"+
        "- `install`: installs the scripts for the modules listed on the command line. The scripts must either be packed in a\n"+
        "repository, or found in the current script folders\n"+
        "- `uninstall`: uninstalls the scripts for the modules listed on the command line\n"+
        "- `pack`: packs the scripts for the modules listed on the command line and publishes them to the specified output repository\n"+
        "\n"+
        "The list of modules specified can have their versions set, but if missing we will try to find the\n"+
        "version from the current source path. If the list of modules is omitted, we will use the list of\n"+
        "modules found in the current source path."
)
@RemainingSections(
"##EXAMPLE\n" +
"\n" +
"The following would list the plugins currently installed:\n" +
"\n" +
"    ceylon plugin list\n\n"+
"The following would install the ceylon.build plugins:\n" +
"\n" +
"    ceylon plugin install ceylon.build.engine\n"
)
public class CeylonPluginTool extends OutputRepoUsingTool {

    @SuppressWarnings("serial")
    public static class CeylonPluginException extends ToolError {
        public CeylonPluginException(String msgKey, Exception cause, Object... args) {
            super(CeylonPluginMessages.msg(msgKey, args), cause);
        }
        public CeylonPluginException(String msgKey, Object... args) {
            super(CeylonPluginMessages.msg(msgKey, args));
        }
    }

    public static enum Mode {
        pack, list, install, uninstall;
    }

    private List modules;
    private List scriptFolders = DefaultToolOptions.getCompilerScriptDirs();
    private List sourceFolders = DefaultToolOptions.getCompilerSourceDirs();
    private boolean force;
    private boolean system;
    private boolean local;
    private Mode mode;

    public CeylonPluginTool() {
        super(CeylonPluginMessages.RESOURCE_BUNDLE);
    }
    
    @Argument(order = 1, argumentName="module", multiplicity="*")
    public void setModules(List modules) {
        setModuleSpecs(ModuleSpec.parseEachList(modules));
    }
    
    public void setModuleSpecs(List modules) {
        this.modules = modules;
    }
    
    @Argument(order = 0, argumentName="mode", multiplicity = "1")
    public void setMode(Mode mode) {
        this.mode = mode;
    }

    @Option(shortName='f')
    @Description("Force installation even if a previous version exists")
    public void setForce(boolean force) {
        this.force = force;
    }

    @Option
    @Description("Install to or uninstall from the system folder")
    public void setSystem(boolean system) {
        this.system = system;
    }

    @Option
    @Description("Install to or uninstall from a local folder")
    public void setLocal(boolean local) {
        this.local = local;
    }

    @OptionArgument(shortName='s', longName="src", argumentName="dirs")
    @ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
    @Description("A directory containing Ceylon and/or Java/JavaScript source code (default: `./source`)")
    public void setSrcFolders(List sourceFolders) {
        this.sourceFolders = sourceFolders;
    }
    
    @OptionArgument(longName="source", argumentName="dirs")
    @ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
    @Description("An alias for `--src`" +
            " (default: `./source`)")
    public void setSourceFolders(List source) {
        setSrcFolders(source);
    }
    
    @OptionArgument(shortName='x', longName="script", argumentName="dirs")
    @ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
    @Description("A directory containing your module documentation (default: `./script`)")
    public void setScriptFolders(List scriptFolders) {
        this.scriptFolders = scriptFolders;
    }

    @Override
    protected List getSourceDirs() {
        return sourceFolders;
    }

    @Override
    public void run() throws Exception {
        // make sure we have a list of modules to work on if required
        if(modules == null)
            modules = new ArrayList();
        boolean errorIfMissing = true;
        if(mode != Mode.list){
            if(modules.isEmpty()){
                modules.addAll(getSourceModules(applyCwd(sourceFolders)));
                errorIfMissing = false;
            }
            if(modules.isEmpty()){
                throw new CeylonPluginException("error.no.module.specified", sourceFolders);
            }
        }

        boolean worked = false;
        switch(mode){
        case pack:
        {
            RepositoryManager outputRepositoryManager = getOutputRepositoryManager();
            for(ModuleSpec module : modules){
                worked |= addScripts(outputRepositoryManager, module, errorIfMissing);
            }
            break;
        }
        case install:
        {
            RepositoryManager repositoryManager = getRepositoryManager();
            for(ModuleSpec module : modules){
                worked |= installScripts(repositoryManager, module, errorIfMissing);
            }
            break;
        }
        case uninstall:
        {
            for(ModuleSpec module : modules){
                worked |= uninstallScripts(module, errorIfMissing);
            }
            break;
        }
        case list:
            listScripts();
            worked = true;
            break;
        }
        if(!worked)
            throw new CeylonPluginException("error.no.script.found");
        flush();
    }

    private void listScripts() throws IOException {
        ArrayList scripts = new ArrayList();
        // Look in /etc/ceylon/bin/ and /etc/ceylon/bin/{moduleName}
        File systemDir = new File(FileUtil.getSystemConfigDir(), Constants.CEYLON_BIN_DIR);
        listScripts(systemDir, "system", scripts);
        // They are in ~/.ceylon/bin/ and ~/.ceylon/bin/{moduleName}
        File defUserDir = new File(FileUtil.getDefaultUserDir(), Constants.CEYLON_BIN_DIR);
        listScripts(defUserDir, "user", scripts);
        // They are in ./.ceylon/bin/ and ./.ceylon/bin/{moduleName}
        File localDir = applyCwd(new File(Constants.CEYLON_CONFIG_DIR, Constants.CEYLON_BIN_DIR));
        listScripts(localDir, "local", scripts);
        Collections.sort(scripts);
        for (String script : scripts) {
            append(script);
            newline();
        }
    }

    private void listScripts(File dir, String location, List scripts) throws IOException {
        File[] children = dir.listFiles();
        if (children != null) {
            for (File child : children) {
                if (child.isDirectory()) {
                    File[] modfiles = child.listFiles();
                    for (File f : modfiles) {
                        if (isScript(f) || isPlugin(f)) {
                            scripts.add(scriptName(f) + " (from " + location + " module '" + child.getName() + "')");
                        }
                    }
                } else if (isScript(child) || isPlugin(child)) {
                    scripts.add(scriptName(child));
                }
            }
        }
    }

    private boolean isScript(File f) {
        if (f.isFile() && f.getName().startsWith("ceylon-") && f.canExecute()) {
            boolean isWinScript = OSUtil.isWindows() && f.getName().toLowerCase().endsWith(".bat");
            boolean isUnixScript = !OSUtil.isWindows() && !f.getName().toLowerCase().endsWith(".bat");
            return isWinScript || isUnixScript;
        }
        return false;
    }
    
    private boolean isPlugin(File f) {
        return (f.isFile() && f.getName().startsWith("ceylon-") && f.getName().endsWith(".plugin"));
    }
    
    private String scriptName(File f) {
        String name = f.getName().substring(7);
        if (name.toLowerCase().endsWith(".bat")) {
            name = name.substring(0, name.length() - 4);
        } else if (name.toLowerCase().endsWith(".plugin")) {
            name = name.substring(0, name.length() - 7);
        }
        return name;
    }
    
    private boolean installScripts(RepositoryManager repositoryManager, ModuleSpec module, boolean errorIfMissing) throws IOException {
        String version = module.getVersion();
        if((version == null || version.isEmpty()) && !module.getName().equals(Module.DEFAULT_MODULE_NAME)){
            version = checkModuleVersionsOrShowSuggestions(getRepositoryManager(), module.getName(), null, 
            		ModuleQuery.Type.ALL, null, null, null, null);
            if(version == null)
                return false;
        }
        
        File zipSource = null;
        List existingScriptFolders = null;
        if(isSourceModule(module.getName(), version, applyCwd(sourceFolders))){
            // copy it directly from the source
            existingScriptFolders = findExistingScriptFolders(module.getName(), errorIfMissing);
            
            if(existingScriptFolders.isEmpty()){
                return false;
            }
        }else{
            // obtain it from the repo
            ArtifactContext context = new ArtifactContext(module.getName(), version, ArtifactContext.SCRIPTS_ZIPPED);
            ArtifactResult result = repositoryManager.getArtifactResult(context);
            if(result == null){
                String err = getModuleNotFoundErrorMessage(repositoryManager, module.getName(), version);
                errorAppend(err);
                errorNewline();
                return false;
            }
            zipSource = result.artifact();
        }

        File moduleScriptDir = getModuleScriptDir(module);
        if(moduleScriptDir.exists()){
            if(force)
                FileUtil.delete(moduleScriptDir);
            else{
                errorMsg("error.module.already.installed", module.getName(), moduleScriptDir);
                return false;
            }
        }
        if(!FileUtil.mkdirs(moduleScriptDir)){
            errorMsg("error.unable.create.dest.dir", moduleScriptDir);
            return false;
        }
        if(zipSource != null)
            extractScripts(zipSource, moduleScriptDir);
        else{
            copyScripts(existingScriptFolders, moduleScriptDir);
        }
        msg("success.installed", module.getName(), moduleScriptDir);
        newline();
        return true;
    }

    private boolean uninstallScripts(ModuleSpec module, boolean errorIfMissing) throws IOException {
        File moduleScriptDir = getModuleScriptDir(module);
        if(moduleScriptDir.exists()){
            FileUtil.delete(moduleScriptDir);
            msg("success.uninstalled", module.getName(), moduleScriptDir);
            newline();
            return true;
        }else{
            if(errorIfMissing)
                errorMsg("error.no.script.installed.for.module", module.getName(), moduleScriptDir);
            return false;
        }
    }

    private File getModuleScriptDir(ModuleSpec module) {
        File installDir;
        if (system) {
            // Put them in /etc/ceylon/bin/{moduleName} (or equivalent on Windows / MacOS)
            installDir = FileUtil.getSystemConfigDir();
        } if (local) {
            // Put them in ./.ceylon/bin/{moduleName}
            installDir = new File(Constants.CEYLON_CONFIG_DIR);
        } else {
            // Put them in ~/.ceylon/bin/{moduleName}
            installDir = FileUtil.getDefaultUserDir();
        }
        File binDir = new File(installDir, Constants.CEYLON_BIN_DIR);
        File moduleScriptDir = new File(binDir, module.getName());
        return moduleScriptDir;
    }

    private void extractScripts(File zip, File dir) throws IOException {
        try{
            IOUtils.extractArchive(zip, dir);
        }catch(IOUtils.UnzipException x){
            switch(x.failure){
            case CannotCreateDestination:
                throw new RuntimeException(CeylonPluginMessages.msg("error.unable.create.dest.dir", x.dir));
            case CopyError:
                throw new RuntimeException(CeylonPluginMessages.msg("error.unable.extract.entry", x.entryName, zip.getAbsolutePath()), x.getCause());
            case DestinationNotDirectory:
                throw new RuntimeException(CeylonPluginMessages.msg("error.not.dir.dest.dir", x.dir));
            default:
                throw x;
            }
        }
        // make all the files executable since we can't preserve that info the the zip file
        Files.walkFileTree(dir.toPath(), new SimpleFileVisitor(){
            @Override
            public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) throws IOException {
                if(Files.isRegularFile(path)){
                    path.toFile().setExecutable(true);
                }
                return FileVisitResult.CONTINUE;
            }
        });

    }

    private void copyScripts(List existingScriptFolders, File moduleScriptDir) throws IOException {
        for(File root : existingScriptFolders){
            FileUtil.copyAll(root, moduleScriptDir);
        }
    }

    private boolean addScripts(RepositoryManager outputRepositoryManager, ModuleSpec module, boolean errorIfMissing) throws IOException {
        // find all doc folders to zip
        List existingScriptFolders = findExistingScriptFolders(module.getName(), errorIfMissing);
        
        if(existingScriptFolders.isEmpty()){
            return false;
        }

        String version;
        if (!module.getName().equals(Module.DEFAULT_MODULE_NAME)){
            ModuleVersionDetails mvd = getVersionFromSource(module.getName());
            if (mvd == null) {
                errorMsg("error.no.script.version", module.getName());
                return false;
            }
            version = mvd.getVersion();
        } else {
            version = null;
        }
        ArtifactContext artifactScriptsZip = new ArtifactContext(module.getName(), version, ArtifactContext.SCRIPTS_ZIPPED);
        
        // make the doc zip roots
        IOUtils.ZipRoot[] roots = new IOUtils.ZipRoot[existingScriptFolders.size()];
        int d=0;
        for(File scriptFolder : existingScriptFolders){
            roots[d] = new IOUtils.ZipRoot(scriptFolder, "");
        }
        File scriptZipFile = IOUtils.zipFolders(roots);
        File scriptZipSha1File = ShaSigner.sign(scriptZipFile, log, verbose != null);
        
        if(!repositoryRemoveArtifact(outputRepositoryManager, artifactScriptsZip)) return true;
        if(!repositoryRemoveArtifact(outputRepositoryManager, artifactScriptsZip.getSha1Context())) return true;
        
        if(!repositoryPutArtifact(outputRepositoryManager, artifactScriptsZip, scriptZipFile)) return true;
        if(!repositoryPutArtifact(outputRepositoryManager, artifactScriptsZip.getSha1Context(), scriptZipSha1File)) return true;
        
        scriptZipFile.delete();
        scriptZipSha1File.delete();

        msg("success.packed", module.getName());
        newline();

        return true;
    }

    private List findExistingScriptFolders(String moduleName, boolean errorIfMissing) throws IOException {
        String scriptPath = moduleName.replace('.', File.separatorChar);
        List existingScriptFolders = new ArrayList(scriptFolders.size());
        for(File scriptFolder : scriptFolders){
            File moduleScriptFolder = new File(applyCwd(scriptFolder), scriptPath);
            if(moduleScriptFolder.exists() && FileUtil.containsFile(moduleScriptFolder))
                existingScriptFolders.add(moduleScriptFolder);
        }
        if(errorIfMissing && existingScriptFolders.isEmpty()){
            errorMsg("error.no.script.found.for.module", moduleName, scriptFolders, scriptPath);
        }
        return existingScriptFolders;
    }

    private boolean repositoryRemoveArtifact(RepositoryManager outputRepository, ArtifactContext artifactContext) throws IOException {
        try {
            outputRepository.removeArtifact(artifactContext);
            return true;
        } catch (Exception e) {
            errorMsg("error.failed.remove.artifact", artifactContext, e.getLocalizedMessage());
            return false;
        }
    }

    private boolean repositoryPutArtifact(RepositoryManager outputRepository, ArtifactContext artifactContext, File content) throws IOException {
        try {
            outputRepository.putArtifact(artifactContext, content);
            return true;
        } catch (Exception e) {
            errorMsg("error.failed.write.artifact", artifactContext, e.getLocalizedMessage());
            return false;
        }
    }

    @Override
    public void initialize(CeylonTool mainTool) throws Exception {
        if (system && local) {
            throw new IllegalArgumentException(Messages.msg(bundle, "conflicting.destinations"));
        }
        if (mode == Mode.pack) {
            for(ModuleSpec module : modules){
                if (module.isVersioned()) {
                    throw new IllegalArgumentException(Messages.msg(bundle, "invalid.module.or.file", module.getName()));
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy