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

com.redhat.ceylon.tools.importjar.CeylonImportJarTool 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.tools.importjar;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleSearchResult.ModuleDetails;
import com.redhat.ceylon.cmr.ceylon.LegacyImporter;
import com.redhat.ceylon.cmr.ceylon.LegacyImporter.DependencyErrors;
import com.redhat.ceylon.cmr.ceylon.LegacyImporter.DependencyResults;
import com.redhat.ceylon.cmr.ceylon.LegacyImporter.ImporterFeedback;
import com.redhat.ceylon.common.Messages;
import com.redhat.ceylon.common.ModuleSpec;
import com.redhat.ceylon.common.OSUtil;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Hidden;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.ToolUsageError;
import com.redhat.ceylon.common.tools.CeylonTool;
import com.redhat.ceylon.common.tools.OutputRepoUsingTool;
import com.redhat.ceylon.model.cmr.RepositoryException;

@Summary("Imports a jar file into a Ceylon module repository")
@Description("Imports the given `` using the module name and version " +
        "given by `` into the repository named by the " +
        "`--out` option.\n" +
        "\n" +
        "`` is a module name and version separated with a slash, for example " +
        "`com.example.foobar/1.2.0`.\n" +
        "\n" +
        "`` is the name of the Jar file to import.\n" +
        "\n" +
        "`` is an optional name of a Jar file containing the sources " +
        "for the Jar file to import.\n")
@RemainingSections("## Descriptors" +
        "\n\n" +
        "When the import-jar tool analyses the  and complains about missing " +
        "dependencies a module descriptor file should be provided. The name of this " +
        "file can be provided using the `--descriptor` option or, when left out, the tool " +
        "will look for `.module.properties` or `.module.xml`. " +
        "\n\n" +
        "The format of these `.properties` or `.xml` files is documented online: " +
        "http://www.ceylon-lang.org/documentation/1.1/reference/structure/module/#legacy_modules " +
        "\n\n" +
        "If the option `--update-descriptor` is given the tool will try to update the " +
        "given descriptor file with the available information (for now this only works " +
        "for the `.properties` files). If the file didn't exist yet it will be created." +
        "\n\n" +
        "If the option `--source-jar-file` is given the tool will try to include the " +
        "source jar into the module as well, including a SHA1 of that source jar." +
        "\n\n" +
        OutputRepoUsingTool.DOCSECTION_REPOSITORIES)
public class CeylonImportJarTool extends OutputRepoUsingTool {

    private ModuleSpec module;
    private File jarFile;
    private File sourceJarFile;
    private File descriptor;
    private boolean updateDescriptor;
    private boolean force;
    private boolean dryRun;
    private boolean showClasses;
    private boolean showSuggestions;
    private boolean allowCars;
    private boolean ignoreAnnotations;
	private List missingDependenciesPackages;
	private Map> parsedMissingDependenciesPackages;
    
    public CeylonImportJarTool() {
        super(ImportJarMessages.RESOURCE_BUNDLE);
    }
    
    @OptionArgument(longName="missing-dependency-packages")
    @Description("Specifies which packages a missing dependency contains. Can be specified multiple times. " +
            "Format: `module-name/module-version=package-wildcard(,package-wildcard)*`, where "+
    		"`package-wildcard` supports `*`, `**` and `?` wildcards.")
    public void setMissingDependencyPackages(List missingDependencyPackages) {
        this.missingDependenciesPackages = missingDependencyPackages;
    }
    
    @OptionArgument(argumentName="file")
    @Description("Specify a module.xml or module.properties file to be used "
            + "as the module descriptor")
    public void setDescriptor(File descriptor) {
        this.descriptor = descriptor;
    }
    
    @Argument(argumentName="module", multiplicity="1", order=0)
    public void setModuleSpec(String module) {
        setModuleSpec(ModuleSpec.parse(module, 
                ModuleSpec.Option.VERSION_REQUIRED, 
                ModuleSpec.Option.DEFAULT_MODULE_PROHIBITED));
    }
    
    public void setModuleSpec(ModuleSpec module) {
        this.module = module;
    }

    @Argument(argumentName="jar-file", multiplicity="1", order=1)
    public void setFile(File jarFile) {
        this.jarFile = jarFile;
    }

    @Argument(argumentName="source-jar-file", multiplicity="?", order=2)
    public void setSourceJarFile(File sourceJarFile) {
        this.sourceJarFile = sourceJarFile;
    }
    
    @Option(longName="update-descriptor")
    @Description("Whenever possible will create or adjust the descriptor file with the necessary definitions.")
    public void setUpdateDescriptor(boolean updateDescriptor) {
        this.updateDescriptor = updateDescriptor;
    }
    
    @Option(longName="force")
    @Description("Skips sanity checks and forces publication of the JAR.")
    public void setForce(boolean force) {
        this.force = force;
    }

    @Option(longName="ignore-annotations")
    @Description("Do not check annotations for imports (default: `false`).")
    public void setIgnoreAnnotations(boolean ignoreAnnotations) {
        this.ignoreAnnotations = ignoreAnnotations;
    }

    @Hidden
    @Option(longName="allow-cars")
    @Description("Allows importing car files [for tests only].")
    public void setAllowCars(boolean allowCars) {
        this.allowCars = allowCars;
    }

    @Option(longName="dry-run")
    @Description("Performs all the sanity checks but does not publish the JAR.")
    public void setDryRun(boolean dryRun) {
        this.dryRun = dryRun;
    }
    
    @Option(longName="show-classes")
    @Description("Shows all external classes that are not declared as imports instead of their packages only.")
    public void setShowClasses(boolean showClasses) {
        this.showClasses = showClasses;
    }
    
    @Option(longName="show-suggestions")
    @Description("Shows suggestions for modules based on missing package names (this can take a long time).")
    public void setShowSuggestions(boolean showSuggestions) {
        this.showSuggestions = showSuggestions;
    }
    
    @Override
    protected boolean needsSystemRepo() {
        return false;
    }

    @Override
    public void initialize(CeylonTool mainTool) {
        File f = assertJar(jarFile, "error.jarFile", "error.jarFile.notJar");
        
        if (sourceJarFile != null) {
             assertJar(sourceJarFile, "error.sourceJarFile", "error.sourceJarFile.notJar");
        }
        
        if (descriptor == null) {
            String baseName = f.getName().substring(0, f.getName().length() - 4);
            File desc = new File(f.getParentFile(), baseName + ".module.xml");
            if (!desc.isFile()) {
                desc = new File(f.getParentFile(), baseName + ".module.properties");
                if (desc.isFile() || updateDescriptor) {
                    descriptor = desc;
                }
            } else {
                descriptor = desc;
            }
        }
        if (descriptor != null) {
            checkReadableFile(applyCwd(descriptor), "error.descriptorFile", !updateDescriptor);
            if(!(descriptor.toString().toLowerCase().endsWith(".xml") ||
                    descriptor.toString().toLowerCase().endsWith(".properties")))
                throw new ImportJarException("error.descriptorFile.badSuffix", new Object[]{descriptor}, null);
        }
        if(missingDependenciesPackages != null){
        	parsedMissingDependenciesPackages = new HashMap<>();
        	for(String spec : missingDependenciesPackages){
        		int moduleSeparator = spec.indexOf('=');
        		if(moduleSeparator == -1)
        			throw new ImportJarException("error.missingDependenciesPackages.badSyntax", new Object[]{spec}, null);
        		String moduleSpec = spec.substring(0, moduleSeparator);
        		String packagesSpec = spec.substring(moduleSeparator+1);
        		if(moduleSpec.isEmpty() || packagesSpec.isEmpty())
        			throw new ImportJarException("error.missingDependenciesPackages.badSyntax", new Object[]{spec}, null);
        		List packages = new LinkedList<>();
        		for(String pkg : packagesSpec.split(",")){
            		if(pkg.isEmpty())
            			throw new ImportJarException("error.missingDependenciesPackages.badSyntax", new Object[]{spec}, null);
            		packages.add(pkg);
        		}
        		if(packages.isEmpty())
        			throw new ImportJarException("error.missingDependenciesPackages.badSyntax", new Object[]{spec}, null);
        		parsedMissingDependenciesPackages.put(moduleSpec, packages);
        	}
        }
    }
    
    private File assertJar(File aJarFile, String errorResource1, String errorResource2) {
        File aJarFileWithCwd = applyCwd(aJarFile);
        checkReadableFile(aJarFileWithCwd, errorResource1, true);

        String fileName = aJarFileWithCwd.getName().toLowerCase();
        // allow: jar OK, car OK, other OK
        // !allow: jar OK, car, other
        if (! fileName.endsWith(".jar")
        		 && !(allowCars && fileName.endsWith(".car"))) {
            throw new ImportJarException(errorResource2, 
                                         new Object[] { aJarFile.toString() }, 
                                         null);
        }
        
        return aJarFileWithCwd;
    }

    private void checkReadableFile(File f, String keyPrefix, boolean required) {
        if (f.exists()) {
            if(f.isDirectory())
                throw new ImportJarException(keyPrefix + ".isDirectory", new Object[]{f.toString()}, null);
            if(!f.canRead())
                throw new ImportJarException(keyPrefix + ".notReadable", new Object[]{f.toString()}, null);
        } else if (required) {
            throw new ImportJarException(keyPrefix + ".doesNotExist", new Object[]{f.toString()}, null);
        }
    }
    
    private LegacyImporter createImporter() {
        LegacyImporter importer = new LegacyImporter(module.getName(), module.getVersion(), applyCwd(jarFile), applyCwd(sourceJarFile), getOutputRepositoryManager(), getRepositoryManager(), getLogger(), new ImporterFeedback() {
            @Override
            public void beforeDependencies() throws IOException {
                msg("info.checkingDependencies").newline();
            }
            
            @Override
            public void beforeDependency(ModuleDependencyInfo dep) throws IOException {
                append("    ").append(dep).append(" ... [");
            }
            
            @Override
            public void dependency(DependencyResults result, ModuleDependencyInfo dep) throws IOException {
                String txt = "";
                switch (result) {
                case DEP_OK:
                    txt = OSUtil.color(Messages.msg(bundle, "info.ok"), OSUtil.Color.green);
                    break;
                case DEP_OK_UNUSED:
                    txt = OSUtil.color(Messages.msg(bundle, "info.okButUnused"), OSUtil.Color.green);
                    break;
                case DEP_MARK_SHARED:
                    txt = OSUtil.color(Messages.msg(bundle, "error.markShared"), OSUtil.Color.yellow);
                    break;
                case DEP_MARK_UNSHARED:
                    txt = OSUtil.color(Messages.msg(bundle, "info.markUnshared"), OSUtil.Color.yellow);
                    break;
                case DEP_NOT_FOUND:
                    txt = OSUtil.color(Messages.msg(bundle, "info.notFound"), OSUtil.Color.red);
                    break;
                case DEP_CHECK_FAILED:
                    txt = OSUtil.color(Messages.msg(bundle, "error.checkFailed"), OSUtil.Color.red);
                    break;
                case DEP_TRANSITIVE_ERROR:
                    txt = OSUtil.color(Messages.msg(bundle, "error.transitiveError"), OSUtil.Color.red);
                    break;
                case DEP_JDK:
                    append("    ").append(dep.getName());
                    if(dep.isExport())
                    	append(" ... [shared]");
                    newline();
                    break;
                default:
                    break;
                }
                append(txt);
            }
            
            @Override
            public void dependencyError(DependencyErrors error, ModuleDependencyInfo dep) {
                switch (error) {
                case DEPERR_INVALID_MODULE_DEFAULT:
                    throw new ImportJarException("error.descriptorFile.invalid.module.default");
                case DEPERR_INVALID_MODULE_NAME:
                    throw new ImportJarException("error.descriptorFile.invalid.module", new Object[]{dep.getName()}, null);
                case DEPERR_INVALID_MODULE_VERSION:
                    throw new ImportJarException("error.descriptorFile.invalid.module.version", new Object[]{dep.getVersion()}, null);
                }
            }
            
            @Override
            public void afterDependency(ModuleDependencyInfo dep) throws IOException {
                append("]").newline();
            }
            
            @Override
            public void afterDependencies() {
            }

            @Override
            public void beforeJdkModules() throws IOException {
                msg("info.declare.jdk.imports").newline();
            }

            @Override
            public void afterJdkModules() {
            }

            @Override
            public void beforePackages() throws IOException {
                msg("info.declare.module.imports").newline();
                if (!showSuggestions) {
                    msg("info.try.suggestions").newline();
                }
            }

            @Override
            public void packageName(String pkg, boolean shared) throws IOException {
                append("    ").append(pkg);
                if(shared)
                	append(" ... [shared]");
                newline();
            }

            @Override
            public void afterPackages() {
            }

            @Override
            public void beforeClasses() throws IOException {
                msg("info.declare.class.imports").newline();
            }

            @Override
            public void className(String cls, boolean shared) throws IOException {
                append("    ").append(cls);
                if(shared)
                	append(" ... [shared]");
                newline();
            }

            @Override
            public void afterClasses() {
            }

            @Override
            public ModuleDependencyInfo suggestions(String pkg, Set suggestions) throws IOException {
                ModuleDependencyInfo dep = null;
                if (!suggestions.isEmpty()) {
                    append(", ");
                    if (suggestions.size() > 1) {
                        msg("info.try.importing.multiple");
                        for (ModuleDetails md : suggestions) {
                            newline();
                            String modver = md.getName() + "/" + md.getLastVersion().getVersion();
                            append("        ").append(modver);
                            dep = new ModuleDependencyInfo(null, md.getName(), md.getLastVersion().getVersion(), false, true);
                        }
                    } else {
                        ModuleDetails md = suggestions.iterator().next();
                        String modver = md.getName() + "/" + md.getLastVersion().getVersion();
                        msg("info.try.importing", modver);
                        dep = new ModuleDependencyInfo(null, md.getName(), md.getLastVersion().getVersion(), false, true);
                    }
                }
                return dep;
            }
        });
        return importer;
    }
    
    @Override
    public void run() throws Exception {
        LegacyImporter importer = createImporter()
        		.moduleDescriptor(applyCwd(descriptor))
        		.missingDependenciesPackages(parsedMissingDependenciesPackages);
        importer.setIgnoreAnnotations(ignoreAnnotations);
        if (!force || updateDescriptor) {
            try {
                importer.loadModuleDescriptor();
            } catch (ImportJarException x) {
                throw x;
            } catch (Exception x) {
                String key = "error.descriptorFile.invalid.";
                if (descriptor.getName().endsWith(".xml")) {
                    key += "xml";
                } else {
                    key += "properties";
                }
                throw new ImportJarException(key, new Object[]{descriptor.getPath(), x.getMessage()}, x);
            }
            
            if (!showClasses) {
                importer.listPackages(showSuggestions);
            } else {
                importer.listClasses();
            }
        }
        boolean hasErrors = importer.hasErrors();
        if (importer.hasProblems()) {
            if (updateDescriptor && descriptor != null) {
                if (!dryRun) {
                    importer.updateModuleDescriptor();
                }
            } else {
                hasErrors = true;
            }
        }
        if (!hasErrors || force) {
            if (!hasErrors) {
                if (force && !updateDescriptor) {
                    msg("info.forcedUpdate");
                } else {
                    msg("info.noProblems");
                }
            } else {
                msg("error.problemsFoundForced");
            }
            if (!dryRun) {
                msg("info.noProblems.publishing").newline();
                try {
                    importer.publish();
                } catch(RepositoryException x) {
                    throw new ImportJarException("error.failedWriteArtifact", new Object[]{module, x.getLocalizedMessage()}, x);
                } catch(Exception x) {
                    // FIXME: remove when the whole CMR is using RepositoryException
                    throw new ImportJarException("error.failedWriteArtifact", new Object[]{module, x.getLocalizedMessage()}, x);
                }
                String repoString = this.getOutputRepositoryManager().getRepositoriesDisplayString().toString();
                
                msg("info.published", this.module.toString(), repoString.substring(1, repoString.length()-1));
            }
            append(".").newline();
        } else {
            String msgKey;
            if (!updateDescriptor && descriptor == null) {
                msgKey = "error.problemsFoundSuggest";
            } else {
                msgKey = "error.problemsFound";
            }
            throw new ToolUsageError(Messages.msg(ImportJarMessages.RESOURCE_BUNDLE, msgKey));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy