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

org.revapi.standalone.Main Maven / Gradle / Ivy

There is a newer version: 0.12.0
Show newest version
/*
 * Copyright 2014-2021 Lukas Krejci
 * and other contributors as indicated by the @author tags.
 *
 * 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 org.revapi.standalone;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toSet;

import static org.revapi.maven.utils.ArtifactResolver.getRevapiDependencySelector;
import static org.revapi.maven.utils.ArtifactResolver.getRevapiDependencyTraverser;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.annotation.Nullable;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.jboss.modules.DependencySpec;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleSpec;
import org.revapi.API;
import org.revapi.AnalysisContext;
import org.revapi.AnalysisResult;
import org.revapi.PipelineConfiguration;
import org.revapi.Revapi;
import org.revapi.base.FileArchive;
import org.revapi.maven.utils.ArtifactResolver;
import org.slf4j.LoggerFactory;
import pw.krejci.modules.maven.MavenBootstrap;
import pw.krejci.modules.maven.ModuleSpecController;
import pw.krejci.modules.maven.ProjectModule;

/**
 * @author Lukas Krejci
 * @since 0.1
 */
public final class Main {

    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(Main.class);

    private static void usage(@Nullable String progName) {
        if (progName == null) {
            progName = "revapi.(sh|bat)";
        }

        String pad = "";
        for (int i = 0; i < progName.length(); ++i) {
            pad += " ";
        }

        System.out.println(progName +
                " [-u|-h] -e [,]* -o [,]* -n [,]* [-s [,]*] [-t [,]*] [-D=]* [-c [,]*] [-r ]");
        System.out.println();
        System.out.println(pad + " -u");
        System.out.println(pad + " -h");
        System.out.println(pad + " --usage");
        System.out.println(pad + " --help");
        System.out.println(pad + "     Prints this message and exits.");
        System.out.println(pad + " -e");
        System.out.println(pad + " --extensions=[,]*");
        System.out.println(pad + "     Comma-separated list of maven GAVs of revapi extensions.");
        System.out.println(pad + " -o");
        System.out.println(pad + " --old=[,]*");
        System.out.println(pad + "    Comma-separated list of files of the old version of API");
        System.out.println(pad + " -a");
        System.out.println(pad + " --old-gavs=[,]*");
        System.out.println(pad + "    Comma-separated list of GAVs of the old version of API");
        System.out.println(pad + " -s");
        System.out.println(pad + " --old-supplementary=[,]*");
        System.out.println(pad + "    Comma-separated list of files that supplement the old version of API");
        System.out.println(pad + " -n");
        System.out.println(pad + " --new=[,]*");
        System.out.println(pad + "    Comma-separated list of files of the new version of API");
        System.out.println(pad + " -b");
        System.out.println(pad + " --new-gavs=[,]*");
        System.out.println(pad + "    Comma-separated list of GAVs of the new version of API");
        System.out.println(pad + " -t");
        System.out.println(pad + " --new-supplementary=[,]*");
        System.out.println(pad + "    Comma-separated list of files that supplement the new version of API");
        System.out.println(pad + " -D");
        System.out
                .println(pad + "    A key-value pair representing a single configuration option of revapi or one of " +
                        "the loaded extensions");
        System.out.println(pad + " -c");
        System.out.println(pad + " --config-files=[,]*");
        System.out.println(pad + "    Comma-separated list of configuration files in JSON format.");
        System.out.println(pad + " -d");
        System.out.println(pad + " --cache-dir=");
        System.out.println(pad + "    The location of local cache of extensions to use to locate artifacts. " +
                "Defaults to 'extensions' directory under revapi installation dir.");
        System.out.println(pad + " -r");
        System.out.println(pad + " --remote-repositories=[,]*");
        System.out.println(pad + "    The url of the remote Maven repository to use for artifact resolution. " +
                "Defaults to Maven Central (http://repo.maven.apache.org/maven2/).");
        System.out.println();
        System.out.println("You can specify the old API either using -o and -s where you specify the filesystem paths" +
                " to the archives and supplementary archives respectively or you can use -a to specify the GAVs of the" +
                " old API archives and the supplementary archives (i.e. their transitive dependencies) will be determined" +
                " automatically using Maven. But you cannot do both obviously.");
        System.out.println();
        System.out.println("Of course you can do the same for the new version of the API by using -n and -t for file" +
                " paths or -b for GAVs.");
    }

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            usage(null);
            System.exit(1);
        }

        String scriptFileName = args[0];
        String baseDir = args[1];

        String[] realArgs = new String[args.length - 2];
        System.arraycopy(args, 2, realArgs, 0, realArgs.length);

        String[] extensionGAVs = null;
        String[] oldArchivePaths = null;
        String[] oldGavs = null;
        String[] newArchivePaths = null;
        String[] newGavs = null;
        String[] oldSupplementaryArchivePaths = null;
        String[] newSupplementaryArchivePaths = null;
        Map additionalConfigOptions = new HashMap<>();
        String[] configFiles = null;
        File cacheDir = new File(baseDir, "cache");
        String[] remoteRepositoryUrls = null;

        LongOpt[] longOpts = new LongOpt[13];
        longOpts[0] = new LongOpt("usage", LongOpt.NO_ARGUMENT, null, 'u');
        longOpts[1] = new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h');
        longOpts[2] = new LongOpt("extensions", LongOpt.REQUIRED_ARGUMENT, null, 'e');
        longOpts[3] = new LongOpt("old", LongOpt.REQUIRED_ARGUMENT, null, 'o');
        longOpts[4] = new LongOpt("new", LongOpt.REQUIRED_ARGUMENT, null, 'n');
        longOpts[5] = new LongOpt("old-supplementary", LongOpt.REQUIRED_ARGUMENT, null, 's');
        longOpts[6] = new LongOpt("new-supplementary", LongOpt.REQUIRED_ARGUMENT, null, 't');
        longOpts[7] = new LongOpt("D", LongOpt.REQUIRED_ARGUMENT, null, 'D');
        longOpts[8] = new LongOpt("config-files", LongOpt.REQUIRED_ARGUMENT, null, 'c');
        longOpts[9] = new LongOpt("cache-dir", LongOpt.REQUIRED_ARGUMENT, null, 'd');
        longOpts[10] = new LongOpt("old-gavs", LongOpt.REQUIRED_ARGUMENT, null, 'a');
        longOpts[11] = new LongOpt("new-gavs", LongOpt.REQUIRED_ARGUMENT, null, 'b');
        longOpts[12] = new LongOpt("remote-repositories", LongOpt.REQUIRED_ARGUMENT, null, 'r');

        Getopt opts = new Getopt(scriptFileName, realArgs, "uhe:o:n:s:t:D:c:d:a:b:r:", longOpts);
        int c;
        while ((c = opts.getopt()) != -1) {
            switch (c) {
                case 'u':
                case 'h':
                    usage(scriptFileName);
                    System.exit(0);
                case 'e':
                    extensionGAVs = opts.getOptarg().split(",");
                    break;
                case 'o':
                    oldArchivePaths = opts.getOptarg().split(",");
                    break;
                case 'n':
                    newArchivePaths = opts.getOptarg().split(",");
                    break;
                case 's':
                    oldSupplementaryArchivePaths = opts.getOptarg().split(",");
                    break;
                case 't':
                    newSupplementaryArchivePaths = opts.getOptarg().split(",");
                    break;
                case 'c':
                    configFiles = opts.getOptarg().split(",");
                    break;
                case 'D':
                    String[] keyValue = opts.getOptarg().split("=");
                    additionalConfigOptions.put(keyValue[0], keyValue.length > 1 ? keyValue[1] : null);
                    break;
                case 'd':
                    cacheDir = new File(opts.getOptarg());
                    break;
                case 'a':
                    oldGavs = opts.getOptarg().split(",");
                    break;
                case 'b':
                    newGavs = opts.getOptarg().split(",");
                    break;
                case 'r':
                    remoteRepositoryUrls = opts.getOptarg().split(",");
                    break;
                case ':':
                    System.err.println("Argument required for option " +
                            (char) opts.getOptopt());
                    break;
                case '?':
                    System.err.println("The option '" + (char) opts.getOptopt() +
                            "' is not valid");
                    System.exit(1);
                    break;
                default:
                    System.err.println("getopt() returned " + c);
                    System.exit(1);
                    break;
            }
        }

        if (extensionGAVs == null || (oldArchivePaths == null && oldGavs == null) ||
                (newArchivePaths == null && newGavs == null)) {

            usage(scriptFileName);
            System.exit(1);
        }

        final List remoteRepositories = Collections.unmodifiableList(
            remoteRepositories(remoteRepositoryUrls == null ? new String[0] : remoteRepositoryUrls));

        List oldArchives = null;
        List newArchives = null;
        List oldSupplementaryArchives = null;
        List newSupplementaryArchives = null;

        LOG.info("Downloading checked archives");

        if (oldArchivePaths == null) {
            ArchivesAndSupplementaryArchives res = convertGavs(oldGavs, "Old API Maven artifact", cacheDir, remoteRepositories);
            oldArchives = res.archives;
            oldSupplementaryArchives = res.supplementaryArchives;
        } else {
            oldArchives = convertPaths(oldArchivePaths, "Old API files");
            oldSupplementaryArchives = oldSupplementaryArchivePaths == null ? emptyList() :
                    convertPaths(oldSupplementaryArchivePaths, "Old API supplementary files");
        }

        if (newArchivePaths == null) {
            ArchivesAndSupplementaryArchives res = convertGavs(newGavs, "New API Maven artifact", cacheDir, remoteRepositories);
            newArchives = res.archives;
            newSupplementaryArchives = res.supplementaryArchives;
        } else {
            newArchives = convertPaths(newArchivePaths, "New API files");
            newSupplementaryArchives = newSupplementaryArchivePaths == null ? emptyList() :
                    convertPaths(newSupplementaryArchivePaths, "New API supplementary files");
        }

        try {
            run(cacheDir, extensionGAVs, oldArchives, oldSupplementaryArchives, newArchives,
                    newSupplementaryArchives, configFiles, additionalConfigOptions,
                    remoteRepositories);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.exit(0);
    }

    @SuppressWarnings("ConstantConditions")
    private static void run(File cacheDir, String[] extensionGAVs, List oldArchives,
            List oldSupplementaryArchives, List newArchives,
            List newSupplementaryArchives, String[] configFiles, Map additionalConfig,
            List remoteRepositories)
            throws Exception {

        ProjectModule.Builder bld = ProjectModule.build();
        bld.localRepository(cacheDir);
        remoteRepositories.forEach(bld::addRemoteRepository);

        if (extensionGAVs != null) {
            for (String gav : extensionGAVs) {
                bld.addDependency(gav);
            }
        }

        Properties libraryVersionsProps = new Properties();
        libraryVersionsProps.load(Main.class.getResourceAsStream("/library.versions"));
        Set globalArtifacts = libraryVersionsProps.stringPropertyNames().stream()
                .map(p -> {
                    String gav = p + ':' + libraryVersionsProps.get(p);
                    return new DefaultArtifact(gav);
                })
                .collect(toSet());

        bld.moduleSpecController(new ModuleSpecController() {
            private boolean override;
            private String currentModuleName;

            @Override
            public void start(String moduleName) {
                currentModuleName = moduleName;
            }

            @Override
            public DependencySpec modifyDependency(String dependencyName, DependencySpec original) {
                boolean overrideThis = false;
                Artifact a = new DefaultArtifact(dependencyName);

                for (Artifact ga : globalArtifacts) {
                    if (ga.getGroupId().equals(a.getGroupId()) && ga.getArtifactId().equals(a.getArtifactId())
                            && !ga.getVersion().equals(a.getVersion())) {
                        LOG.warn("Detected version conflict in dependencies of extension " + currentModuleName +
                                ". The extension depends on " + a + " while the CLI has " + ga + " on global" +
                                " classpath. This will likely cause problems.");
                    }
                }

                override = override || overrideThis;
                return overrideThis ? null : original;
            }

            @Override
            public void modify(ModuleSpec.Builder bld) {
                if (override) {
                    Set revapiPaths = new HashSet<>(asList("org/revapi", "org/revapi/configuration",
                            "org/revapi/query", "org/revapi/simple"));

                    bld.addDependency(DependencySpec.createSystemDependencySpec(revapiPaths));
                    override = false;
                }
            }

            @Override
            public void end(String moduleName) {
                currentModuleName = null;
            }
        });

        LOG.info("Downloading extensions");

        Module project = bld.create();

        Revapi revapi = new Revapi(PipelineConfiguration.builder()
                .withAllExtensionsFrom(project.getClassLoader())
                .withAllExtensionsFromThreadContextClassLoader().build());

        AnalysisContext.Builder ctxBld = AnalysisContext.builder(revapi)
                .withOldAPI(API.of(oldArchives).supportedBy(oldSupplementaryArchives).build())
                .withNewAPI(API.of(newArchives).supportedBy(newSupplementaryArchives).build());

        if (configFiles != null) {
            for (String cf : configFiles) {
                File f = new File(cf);
                checkCanRead(f, "Configuration file");

                try (FileInputStream is = new FileInputStream(f)) {
                    ctxBld.mergeConfigurationFromJSONStream(is);
                }
            }
        }

        for (Map.Entry e : additionalConfig.entrySet()) {
            String[] keyPath = e.getKey().split("\\.");
            ObjectNode additionalNode = JsonNodeFactory.instance.objectNode();
            ObjectNode keyParent = additionalNode;
            for (int i = 0; i < keyPath.length - 1; ++i) {
                ObjectNode child = JsonNodeFactory.instance.objectNode();
                keyParent.set(keyPath[i], child);
                keyParent = child;
            }

            String key = keyPath[keyPath.length - 1];
            String value = e.getValue();
            if (value.startsWith("[") && value.endsWith("]")) {
                ArrayNode node = keyParent.putArray(key);
                String[] values = value.substring(1, value.length() - 1).split("\\s*,\\s*");
                for (String v : values) {
                    node.add(v);
                }
            } else {
                keyParent.put(key, value);
            }
            ctxBld.mergeConfiguration(additionalNode);
        }

        LOG.info("Starting analysis");

        long time = System.currentTimeMillis();

        try (AnalysisResult result = revapi.analyze(ctxBld.build())) {
            if (!result.isSuccess()) {
                throw result.getFailure();
            }
        } finally {
            LOG.info("Analysis took " + (System.currentTimeMillis() - time) + "ms.");
        }
    }

    private static List convertPaths(String[] paths, String errorMessagePrefix) {
        List archives = new ArrayList<>(paths.length);
        for (String path : paths) {
            File f = new File(path);
            checkCanRead(f, errorMessagePrefix);
            archives.add(new FileArchive(f));
        }

        return archives;
    }

    private static ArchivesAndSupplementaryArchives convertGavs(String[] gavs, String errorMessagePrefix,
            File localRepo, List remoteRepositories) {
        RepositorySystem repositorySystem = MavenBootstrap.newRepositorySystem();
        DefaultRepositorySystemSession session = MavenBootstrap.newRepositorySystemSession(repositorySystem,
                new LocalRepository(localRepo));

        session.setDependencySelector(getRevapiDependencySelector(true, false));
        session.setDependencyTraverser(getRevapiDependencyTraverser(true, false));

        ArtifactResolver resolver = new ArtifactResolver(repositorySystem, session, remoteRepositories);

        List archives = new ArrayList<>();
        List supplementaryArchives = new ArrayList<>();

        for (String gav : gavs) {
            try {
                archives.add(new FileArchive(resolver.resolveArtifact(gav).getFile()));
                ArtifactResolver.CollectionResult res = resolver.collectTransitiveDeps(gav);

                res.getResolvedArtifacts().
                        forEach(a -> supplementaryArchives.add(new FileArchive(a.getFile())));
                if (!res.getFailures().isEmpty()) {
                    LOG.warn("Failed to resolve some transitive dependencies: " + res.getFailures().toString());
                }
            } catch (RepositoryException e) {
                throw new IllegalArgumentException(errorMessagePrefix + " " + e.getMessage());
            }
        }

        return new ArchivesAndSupplementaryArchives(archives, supplementaryArchives);
    }

    private static List remoteRepositories(String[] customRepositoryUrls) {
        List remoteRepositories = new ArrayList<>();

        for (int i = 0; i < customRepositoryUrls.length; i++) {
            String repositoryId = "custom-remote-repository-" + i;
            remoteRepositories.add(new RemoteRepository.Builder(repositoryId, "default", customRepositoryUrls[i]).build());
        }

        if (remoteRepositories.isEmpty()) {
            remoteRepositories.add(new RemoteRepository.Builder("maven-central", "default", "https://repo.maven.apache.org/maven2/").build());
        }

        File localMaven = new File(new File(System.getProperties().getProperty("user.home"), ".m2"), "repository");

        RemoteRepository mavenCache = new RemoteRepository.Builder("~/.m2/repository", "default",
                localMaven.toURI().toString())
                .setPolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_NEVER,
                        RepositoryPolicy.CHECKSUM_POLICY_IGNORE)).build();

        remoteRepositories.add(mavenCache);

        return remoteRepositories;
    }

    private static void checkCanRead(File f, String errorMessagePrefix) throws IllegalArgumentException {
        if (!f.exists()) {
            throw new IllegalArgumentException(errorMessagePrefix + " '" + f.getAbsolutePath() + "' does not exist.");
        }

        if (!f.isFile() || !f.canRead()) {
            throw new IllegalArgumentException(
                    errorMessagePrefix + " '" + f.getAbsolutePath() + "' is not a file or cannot be read.");
        }
    }

    private static class ArchivesAndSupplementaryArchives {
        final List archives;
        final List supplementaryArchives;

        public ArchivesAndSupplementaryArchives(List archives,
                List supplementaryArchives) {
            this.archives = archives;
            this.supplementaryArchives = supplementaryArchives;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy