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

io.quarkus.bootstrap.resolver.maven.ApplicationDependencyTreeResolver Maven / Gradle / Ivy

There is a newer version: 3.17.0.CR1
Show newest version
package io.quarkus.bootstrap.resolver.maven;

import static io.quarkus.bootstrap.util.DependencyUtils.getKey;
import static io.quarkus.bootstrap.util.DependencyUtils.newDependencyBuilder;
import static io.quarkus.bootstrap.util.DependencyUtils.toArtifact;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;

import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.collection.DependencyGraphTransformationContext;
import org.eclipse.aether.collection.DependencyGraphTransformer;
import org.eclipse.aether.collection.DependencySelector;
import org.eclipse.aether.graph.DefaultDependencyNode;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
import org.eclipse.aether.util.graph.transformer.ConflictIdSorter;
import org.eclipse.aether.util.graph.transformer.ConflictMarker;
import org.jboss.logging.Logger;

import io.quarkus.bootstrap.BootstrapConstants;
import io.quarkus.bootstrap.BootstrapDependencyProcessingException;
import io.quarkus.bootstrap.model.ApplicationModelBuilder;
import io.quarkus.bootstrap.model.CapabilityContract;
import io.quarkus.bootstrap.model.PlatformImportsImpl;
import io.quarkus.bootstrap.resolver.AppModelResolverException;
import io.quarkus.bootstrap.util.BootstrapUtils;
import io.quarkus.bootstrap.util.DependencyUtils;
import io.quarkus.bootstrap.util.PropertyUtils;
import io.quarkus.bootstrap.workspace.WorkspaceModule;
import io.quarkus.maven.dependency.ArtifactCoords;
import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.maven.dependency.DependencyFlags;
import io.quarkus.maven.dependency.ResolvedDependencyBuilder;
import io.quarkus.paths.PathTree;

public class ApplicationDependencyTreeResolver {

    private static final Logger log = Logger.getLogger(ApplicationDependencyTreeResolver.class);

    private static final String QUARKUS_RUNTIME_ARTIFACT = "quarkus.runtime";
    private static final String QUARKUS_EXTENSION_DEPENDENCY = "quarkus.ext";

    /* @formatter:off */
    private static final byte COLLECT_TOP_EXTENSION_RUNTIME_NODES = 0b001;
    private static final byte COLLECT_DIRECT_DEPS =                 0b010;
    private static final byte COLLECT_RELOADABLE_MODULES =          0b100;
    /* @formatter:on */

    // this is a temporary option, to enable the previous way of initializing runtime classpath dependencies
    private static final boolean CONVERGED_TREE_ONLY = PropertyUtils.getBoolean("quarkus.bootstrap.converged-tree-only", false);

    private static final Artifact[] NO_ARTIFACTS = new Artifact[0];

    public static ApplicationDependencyTreeResolver newInstance() {
        return new ApplicationDependencyTreeResolver();
    }

    public static Artifact getRuntimeArtifact(DependencyNode dep) {
        return (Artifact) dep.getData().get(QUARKUS_RUNTIME_ARTIFACT);
    }

    private byte walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS;
    private final List topExtensionDeps = new ArrayList<>();
    private ExtensionDependency lastVisitedRuntimeExtNode;
    private final Map allExtensions = new HashMap<>();
    private List conditionalDepsToProcess = new ArrayList<>();
    private final Deque> exclusionStack = new ArrayDeque<>();

    private final Map> artifactDeps = new HashMap<>();

    private MavenArtifactResolver resolver;
    private List managedDeps;
    private ApplicationModelBuilder appBuilder;
    private boolean collectReloadableModules;
    private Consumer buildTreeConsumer;
    private List collectCompileOnly;

    public ApplicationDependencyTreeResolver setArtifactResolver(MavenArtifactResolver resolver) {
        this.resolver = resolver;
        return this;
    }

    public ApplicationDependencyTreeResolver setApplicationModelBuilder(ApplicationModelBuilder appBuilder) {
        this.appBuilder = appBuilder;
        return this;
    }

    public ApplicationDependencyTreeResolver setCollectReloadableModules(boolean collectReloadableModules) {
        this.collectReloadableModules = collectReloadableModules;
        return this;
    }

    public ApplicationDependencyTreeResolver setBuildTreeConsumer(Consumer buildTreeConsumer) {
        this.buildTreeConsumer = buildTreeConsumer;
        return this;
    }

    /**
     * In addition to resolving dependencies for the build classpath, also resolve these compile-only dependencies
     * and add them to the application model as {@link DependencyFlags#COMPILE_ONLY}.
     *
     * @param collectCompileOnly compile-only dependencies to add to the model
     * @return self
     */
    public ApplicationDependencyTreeResolver setCollectCompileOnly(List collectCompileOnly) {
        this.collectCompileOnly = collectCompileOnly;
        return this;
    }

    public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolverException {

        this.managedDeps = collectRtDepsRequest.getManagedDependencies();
        DependencyNode root = resolveRuntimeDeps(collectRtDepsRequest);

        if (collectReloadableModules) {
            setWalkingFlag(COLLECT_RELOADABLE_MODULES);
        }
        // we need to be able to take into account whether the deployment dependencies are on an optional dependency branch
        // for that we are going to use a custom dependency selector and re-initialize the resolver to use it
        final MavenArtifactResolver originalResolver = resolver;
        final RepositorySystemSession originalSession = resolver.getSession();
        final DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(originalSession);
        session.setDependencySelector(
                DeploymentDependencySelector.ensureDeploymentDependencySelector(session.getDependencySelector()));
        try {
            this.resolver = new MavenArtifactResolver(new BootstrapMavenContext(BootstrapMavenContext.config()
                    .setRepositorySystem(resolver.getSystem())
                    .setRepositorySystemSession(session)
                    .setRemoteRepositories(resolver.getRepositories())
                    .setRemoteRepositoryManager(resolver.getRemoteRepositoryManager())
                    .setCurrentProject(resolver.getMavenContext().getCurrentProject())
                    .setWorkspaceDiscovery(false)));
        } catch (BootstrapMavenException e) {
            throw new BootstrapDependencyProcessingException("Failed to initialize deployment dependencies resolver",
                    e);
        }

        this.managedDeps = managedDeps.isEmpty() ? new ArrayList<>() : managedDeps;

        visitRuntimeDependencies(root.getChildren());

        List activatedConditionalDeps = List.of();

        if (!conditionalDepsToProcess.isEmpty()) {
            activatedConditionalDeps = new ArrayList<>();
            List unsatisfiedConditionalDeps = new ArrayList<>();
            while (!conditionalDepsToProcess.isEmpty()) {
                final List tmp = unsatisfiedConditionalDeps;
                unsatisfiedConditionalDeps = conditionalDepsToProcess;
                conditionalDepsToProcess = tmp;
                final int totalConditionsToProcess = unsatisfiedConditionalDeps.size();
                final Iterator i = unsatisfiedConditionalDeps.iterator();
                while (i.hasNext()) {
                    final ConditionalDependency cd = i.next();
                    final boolean satisfied = cd.isSatisfied();
                    if (!satisfied) {
                        continue;
                    }
                    i.remove();

                    cd.activate();
                    activatedConditionalDeps.add(cd);
                }
                if (totalConditionsToProcess == unsatisfiedConditionalDeps.size()) {
                    // none of the dependencies was satisfied
                    break;
                }
                conditionalDepsToProcess.addAll(unsatisfiedConditionalDeps);
                unsatisfiedConditionalDeps.clear();
            }
        }

        for (ExtensionDependency extDep : topExtensionDeps) {
            injectDeploymentDependencies(extDep);
        }

        if (!activatedConditionalDeps.isEmpty()) {
            for (ConditionalDependency cd : activatedConditionalDeps) {
                injectDeploymentDependencies(cd.getExtensionDependency());
            }
        }

        root = normalize(originalSession, root);
        // add deployment dependencies
        new BuildDependencyGraphVisitor(originalResolver, appBuilder, buildTreeConsumer).visit(root);

        if (!CONVERGED_TREE_ONLY && collectReloadableModules) {
            for (ResolvedDependencyBuilder db : appBuilder.getDependencies()) {
                if (db.isFlagSet(DependencyFlags.RELOADABLE | DependencyFlags.VISITED)) {
                    continue;
                }
                clearReloadableFlag(db);
            }
        }

        for (ResolvedDependencyBuilder db : appBuilder.getDependencies()) {
            db.clearFlag(DependencyFlags.VISITED);
            appBuilder.addDependency(db);
        }

        collectPlatformProperties();
        collectCompileOnly(collectRtDepsRequest, root);
    }

    /**
     * Resolves and adds compile-only dependencies to the application model with the {@link DependencyFlags#COMPILE_ONLY} flag.
     * Compile-only dependencies are resolved as direct dependencies of the root with all the previously resolved dependencies
     * enforced as version constraints to make sure compile-only dependencies do not override runtime dependencies of the final
     * application.
     *
     * @param collectRtDepsRequest original runtime dependencies collection request
     * @param root the root node of the Quarkus build time dependency tree
     * @throws BootstrapMavenException in case of a failure
     */
    private void collectCompileOnly(CollectRequest collectRtDepsRequest, DependencyNode root) throws BootstrapMavenException {
        if (collectCompileOnly.isEmpty()) {
            return;
        }
        // add all the build time dependencies as version constraints
        var depStack = new ArrayDeque>();
        var children = root.getChildren();
        while (children != null) {
            for (DependencyNode node : children) {
                managedDeps.add(node.getDependency());
                if (!node.getChildren().isEmpty()) {
                    depStack.add(node.getChildren());
                }
            }
            children = depStack.poll();
        }
        final CollectRequest request = new CollectRequest()
                .setDependencies(collectCompileOnly)
                .setManagedDependencies(managedDeps)
                .setRepositories(collectRtDepsRequest.getRepositories());
        if (collectRtDepsRequest.getRoot() != null) {
            request.setRoot(collectRtDepsRequest.getRoot());
        } else {
            request.setRootArtifact(collectRtDepsRequest.getRootArtifact());
        }

        try {
            root = resolver.getSystem().collectDependencies(resolver.getSession(), request).getRoot();
        } catch (DependencyCollectionException e) {
            throw new BootstrapDependencyProcessingException(
                    "Failed to collect compile-only dependencies of " + root.getArtifact(), e);
        }
        children = root.getChildren();
        int flags = DependencyFlags.DIRECT | DependencyFlags.COMPILE_ONLY;
        while (children != null) {
            for (DependencyNode node : children) {
                var extInfo = getExtensionInfoOrNull(node.getArtifact(), node.getRepositories());
                var dep = appBuilder.getDependency(getKey(node.getArtifact()));
                if (dep == null) {
                    dep = newDependencyBuilder(node, resolver).setFlags(flags);
                    if (extInfo != null) {
                        dep.setFlags(DependencyFlags.RUNTIME_EXTENSION_ARTIFACT);
                        if (dep.isFlagSet(DependencyFlags.DIRECT)) {
                            dep.setFlags(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT);
                        }
                    }
                    appBuilder.addDependency(dep);
                } else {
                    dep.setFlags(DependencyFlags.COMPILE_ONLY);
                }
                if (!node.getChildren().isEmpty()) {
                    depStack.add(node.getChildren());
                }
            }
            flags = DependencyFlags.COMPILE_ONLY;
            children = depStack.poll();
        }
    }

    private void collectPlatformProperties() throws AppModelResolverException {
        final PlatformImportsImpl platformReleases = new PlatformImportsImpl();
        for (Dependency d : managedDeps) {
            final Artifact artifact = d.getArtifact();
            final String extension = artifact.getExtension();
            final String artifactId = artifact.getArtifactId();
            if ("json".equals(extension)
                    && artifactId.endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)) {
                platformReleases.addPlatformDescriptor(artifact.getGroupId(), artifactId, artifact.getClassifier(), extension,
                        artifact.getVersion());
            } else if ("properties".equals(artifact.getExtension())
                    && artifactId.endsWith(BootstrapConstants.PLATFORM_PROPERTIES_ARTIFACT_ID_SUFFIX)) {
                platformReleases.addPlatformProperties(artifact.getGroupId(), artifactId, artifact.getClassifier(), extension,
                        artifact.getVersion(), resolver.resolve(artifact).getArtifact().getFile().toPath());
            }
        }
        appBuilder.setPlatformImports(platformReleases);
    }

    private void clearReloadableFlag(ResolvedDependencyBuilder db) {
        final Set deps = artifactDeps.get(db.getArtifactCoords());
        if (deps == null || deps.isEmpty()) {
            return;
        }
        for (ArtifactKey key : deps) {
            final ResolvedDependencyBuilder dep = appBuilder.getDependency(key);
            if (dep == null || dep.isFlagSet(DependencyFlags.VISITED)) {
                continue;
            }
            dep.setFlags(DependencyFlags.VISITED);
            dep.clearFlag(DependencyFlags.RELOADABLE);
            clearReloadableFlag(dep);
        }
    }

    private DependencyNode normalize(RepositorySystemSession session, DependencyNode root) throws AppModelResolverException {
        final DependencyGraphTransformationContext context = new SimpleDependencyGraphTransformationContext(session);
        try {
            // add conflict IDs to the added deployments
            root = new ConflictMarker().transformGraph(root, context);
            // resolves version conflicts
            root = new ConflictIdSorter().transformGraph(root, context);
            root = session.getDependencyGraphTransformer().transformGraph(root, context);
        } catch (RepositoryException e) {
            throw new AppModelResolverException("Failed to normalize the dependency graph", e);
        }
        return root;
    }

    private DependencyNode resolveRuntimeDeps(CollectRequest request) throws AppModelResolverException {
        var session = resolver.getSession();
        if (!CONVERGED_TREE_ONLY && collectReloadableModules) {
            final DefaultRepositorySystemSession mutableSession;
            mutableSession = new DefaultRepositorySystemSession(resolver.getSession());
            mutableSession.setDependencyGraphTransformer(new DependencyGraphTransformer() {

                @Override
                public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
                        throws RepositoryException {
                    final Map visited = new IdentityHashMap<>();
                    for (DependencyNode c : node.getChildren()) {
                        walk(c, visited);
                    }
                    return resolver.getSession().getDependencyGraphTransformer().transformGraph(node, context);
                }

                private void walk(DependencyNode node, Map visited) {
                    if (visited.put(node, node) != null || node.getChildren().isEmpty()) {
                        return;
                    }
                    final Set deps = artifactDeps
                            .computeIfAbsent(DependencyUtils.getCoords(node.getArtifact()),
                                    k -> new HashSet<>(node.getChildren().size()));
                    for (DependencyNode c : node.getChildren()) {
                        deps.add(getKey(c.getArtifact()));
                        walk(c, visited);
                    }
                }
            });
            session = mutableSession;
        }
        try {
            return resolver.getSystem().resolveDependencies(session,
                    new DependencyRequest().setCollectRequest(request))
                    .getRoot();
        } catch (DependencyResolutionException e) {
            final Artifact a = request.getRoot() == null ? request.getRootArtifact() : request.getRoot().getArtifact();
            throw new BootstrapMavenException("Failed to resolve dependencies for " + a, e);
        }
    }

    private boolean isRuntimeArtifact(ArtifactKey key) {
        final ResolvedDependencyBuilder dep = appBuilder.getDependency(key);
        return dep != null && dep.isFlagSet(DependencyFlags.RUNTIME_CP);
    }

    private void visitRuntimeDependencies(List list) {
        for (DependencyNode n : list) {
            visitRuntimeDependency(n);
        }
    }

    private void visitRuntimeDependency(DependencyNode node) {

        final byte prevWalkingFlags = walkingFlags;
        final ExtensionDependency prevLastVisitedRtExtNode = lastVisitedRuntimeExtNode;

        final boolean popExclusions = !node.getDependency().getExclusions().isEmpty();
        if (popExclusions) {
            exclusionStack.addLast(node.getDependency().getExclusions());
        }

        Artifact artifact = node.getArtifact();
        final ArtifactKey key = getKey(artifact);
        ResolvedDependencyBuilder dep = appBuilder.getDependency(key);
        if (dep == null) {
            artifact = resolve(artifact, node.getRepositories());
        }

        try {
            final ExtensionDependency extDep = getExtensionDependencyOrNull(node, artifact);

            if (dep == null) {
                // in case it was relocated it might not survive conflict resolution in the deployment graph
                if (!node.getRelocations().isEmpty()) {
                    ((DefaultDependencyNode) node).setRelocations(List.of());
                }
                WorkspaceModule module = null;
                if (resolver.getProjectModuleResolver() != null) {
                    module = resolver.getProjectModuleResolver().getProjectModule(artifact.getGroupId(),
                            artifact.getArtifactId(), artifact.getVersion());
                }
                dep = DependencyUtils.toAppArtifact(artifact, module)
                        .setOptional(node.getDependency().isOptional())
                        .setScope(node.getDependency().getScope())
                        .setDirect(isWalkingFlagOn(COLLECT_DIRECT_DEPS))
                        .setRuntimeCp()
                        .setDeploymentCp();
                if (JavaScopes.PROVIDED.equals(dep.getScope())) {
                    dep.setFlags(DependencyFlags.COMPILE_ONLY);
                }
                if (extDep != null) {
                    dep.setRuntimeExtensionArtifact();
                    if (isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) {
                        dep.setFlags(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT);
                    }
                }
                if (isWalkingFlagOn(COLLECT_RELOADABLE_MODULES)) {
                    if (module != null) {
                        dep.setReloadable();
                        appBuilder.addReloadableWorkspaceModule(key);
                    } else {
                        clearWalkingFlag(COLLECT_RELOADABLE_MODULES);
                    }
                }
                appBuilder.addDependency(dep);
            }
            clearWalkingFlag(COLLECT_DIRECT_DEPS);

            if (extDep != null) {
                extDep.info.ensureActivated();
                visitExtensionDependency(extDep);
            }
            visitRuntimeDependencies(node.getChildren());
        } catch (DeploymentInjectionException e) {
            throw e;
        } catch (Exception t) {
            throw new DeploymentInjectionException("Failed to inject extension deployment dependencies", t);
        }

        if (popExclusions) {
            exclusionStack.pollLast();
        }
        walkingFlags = prevWalkingFlags;
        lastVisitedRuntimeExtNode = prevLastVisitedRtExtNode;
    }

    private ExtensionDependency getExtensionDependencyOrNull(DependencyNode node, Artifact artifact)
            throws BootstrapDependencyProcessingException {
        ExtensionDependency extDep = ExtensionDependency.get(node);
        if (extDep != null) {
            return extDep;
        }
        final ExtensionInfo extInfo = getExtensionInfoOrNull(artifact, node.getRepositories());
        if (extInfo != null) {
            final Collection exclusions;
            if (exclusionStack.isEmpty()) {
                exclusions = List.of();
            } else if (exclusionStack.size() == 1) {
                exclusions = exclusionStack.peekLast();
            } else {
                exclusions = new ArrayList<>();
                for (Collection set : exclusionStack) {
                    exclusions.addAll(set);
                }
            }
            return new ExtensionDependency(extInfo, node, exclusions);
        }
        return null;
    }

    private void visitExtensionDependency(ExtensionDependency extDep)
            throws BootstrapDependencyProcessingException {

        managedDeps.add(new Dependency(extDep.info.deploymentArtifact, JavaScopes.COMPILE));

        collectConditionalDependencies(extDep);

        if (isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) {
            clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES);
            topExtensionDeps.add(extDep);
        }
        if (lastVisitedRuntimeExtNode != null) {
            lastVisitedRuntimeExtNode.addExtensionDependency(extDep);
        }
        lastVisitedRuntimeExtNode = extDep;
    }

    private void collectConditionalDependencies(ExtensionDependency dependent)
            throws BootstrapDependencyProcessingException {
        if (dependent.info.conditionalDeps.length == 0 || dependent.conditionalDepsQueued) {
            return;
        }
        dependent.conditionalDepsQueued = true;

        final DependencySelector selector = dependent.exclusions == null ? null
                : new ExclusionDependencySelector(dependent.exclusions);
        for (Artifact conditionalArtifact : dependent.info.conditionalDeps) {
            if (selector != null && !selector.selectDependency(new Dependency(conditionalArtifact, JavaScopes.RUNTIME))) {
                continue;
            }
            final ExtensionInfo conditionalInfo = getExtensionInfoOrNull(conditionalArtifact,
                    dependent.runtimeNode.getRepositories());
            if (conditionalInfo == null) {
                log.warn(dependent.info.runtimeArtifact + " declares a conditional dependency on " + conditionalArtifact
                        + " that is not a Quarkus extension and will be ignored");
                continue;
            }
            if (conditionalInfo.activated) {
                continue;
            }
            final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalInfo, dependent);
            conditionalDepsToProcess.add(conditionalDep);
            collectConditionalDependencies(conditionalDep.getExtensionDependency());
        }
    }

    private ExtensionInfo getExtensionInfoOrNull(Artifact artifact, List repos)
            throws BootstrapDependencyProcessingException {
        if (!artifact.getExtension().equals(ArtifactCoords.TYPE_JAR)) {
            return null;
        }
        final ArtifactKey extKey = getKey(artifact);
        ExtensionInfo ext = allExtensions.get(extKey);
        if (ext != null) {
            return ext;
        }

        artifact = resolve(artifact, repos);
        final Path path = artifact.getFile().toPath();
        final Properties descriptor = PathTree.ofDirectoryOrArchive(path).apply(BootstrapConstants.DESCRIPTOR_PATH, visit -> {
            if (visit == null) {
                return null;
            }
            try {
                return readDescriptor(visit.getPath());
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        });
        if (descriptor != null) {
            ext = new ExtensionInfo(artifact, descriptor);
            allExtensions.put(extKey, ext);
        }
        return ext;
    }

    private void injectDeploymentDependencies(ExtensionDependency extDep)
            throws BootstrapDependencyProcessingException {
        log.debugf("Injecting deployment dependency %s", extDep.info.deploymentArtifact);
        final DependencyNode deploymentNode = collectDependencies(extDep.info.deploymentArtifact, extDep.exclusions,
                extDep.runtimeNode.getRepositories());
        if (deploymentNode.getChildren().isEmpty()) {
            throw new BootstrapDependencyProcessingException(
                    "Failed to collect dependencies of " + deploymentNode.getArtifact()
                            + ": either its POM could not be resolved from the available Maven repositories "
                            + "or the artifact does not have any dependencies while at least a dependency on the runtime artifact "
                            + extDep.info.runtimeArtifact + " is expected");
        }

        if (resolver.getProjectModuleResolver() != null && collectReloadableModules) {
            clearReloadable(deploymentNode);
        }

        final List deploymentDeps = deploymentNode.getChildren();
        if (!replaceDirectDepBranch(extDep, deploymentDeps)) {
            throw new BootstrapDependencyProcessingException(
                    "Quarkus extension deployment artifact " + deploymentNode.getArtifact()
                            + " does not appear to depend on the corresponding runtime artifact "
                            + extDep.info.runtimeArtifact);
        }

        final DependencyNode runtimeNode = extDep.runtimeNode;
        runtimeNode.setData(QUARKUS_RUNTIME_ARTIFACT, runtimeNode.getArtifact());
        runtimeNode.setArtifact(deploymentNode.getArtifact());
        runtimeNode.getDependency().setArtifact(deploymentNode.getArtifact());
        runtimeNode.setChildren(deploymentDeps);
    }

    private void clearReloadable(DependencyNode node) {
        for (DependencyNode child : node.getChildren()) {
            clearReloadable(child);
        }
        final ResolvedDependencyBuilder dep = appBuilder.getDependency(getKey(node.getArtifact()));
        if (dep != null) {
            dep.clearFlag(DependencyFlags.RELOADABLE);
        }
    }

    private boolean replaceDirectDepBranch(ExtensionDependency extDep, List deploymentDeps)
            throws BootstrapDependencyProcessingException {
        int i = 0;
        DependencyNode inserted = null;
        while (i < deploymentDeps.size()) {
            final Artifact a = deploymentDeps.get(i).getArtifact();
            if (a == null) {
                continue;
            }
            if (isSameKey(extDep.info.runtimeArtifact, a)) {
                // we are not comparing the version in the above condition because the runtime version
                // may appear to be different then the deployment one and that's ok
                // e.g. the version of the runtime artifact could be managed by a BOM
                // but overridden by the user in the project config. The way the deployment deps
                // are resolved here, the deployment version of the runtime artifact will be the one from the BOM.
                inserted = new DefaultDependencyNode(extDep.runtimeNode);
                inserted.setChildren(extDep.runtimeNode.getChildren());
                deploymentDeps.set(i, inserted);
                break;
            }
            ++i;
        }
        if (inserted == null) {
            return false;
        }

        if (extDep.runtimeExtensionDeps != null) {
            for (ExtensionDependency dep : extDep.runtimeExtensionDeps) {
                for (DependencyNode deploymentDep : deploymentDeps) {
                    if (deploymentDep == inserted) {
                        continue;
                    }
                    if (replaceRuntimeBranch(dep, deploymentDep.getChildren())) {
                        break;
                    }
                }
            }
        }

        return true;
    }

    private boolean replaceRuntimeBranch(ExtensionDependency extNode, List deploymentNodes)
            throws BootstrapDependencyProcessingException {
        if (replaceDirectDepBranch(extNode, deploymentNodes)) {
            return true;
        }
        for (DependencyNode deploymentNode : deploymentNodes) {
            if (replaceRuntimeBranch(extNode, deploymentNode.getChildren())) {
                return true;
            }
        }
        return false;
    }

    private DependencyNode collectDependencies(Artifact artifact, Collection exclusions,
            List repos) {
        final CollectRequest request;
        if (managedDeps.isEmpty()) {
            request = new CollectRequest()
                    .setRoot(new Dependency(artifact, JavaScopes.COMPILE, false, exclusions))
                    .setRepositories(repos);
        } else {
            final ArtifactDescriptorResult descr;
            try {
                descr = resolver.resolveDescriptor(artifact, repos);
            } catch (BootstrapMavenException e) {
                throw new DeploymentInjectionException("Failed to resolve descriptor for " + artifact, e);
            }
            final List mergedManagedDeps = new ArrayList<>(
                    managedDeps.size() + descr.getManagedDependencies().size());
            final Map managedVersions = new HashMap<>(managedDeps.size());
            for (Dependency dep : managedDeps) {
                managedVersions.put(DependencyUtils.getKey(dep.getArtifact()), dep.getArtifact().getVersion());
                mergedManagedDeps.add(dep);
            }
            for (Dependency dep : descr.getManagedDependencies()) {
                final ArtifactKey key = DependencyUtils.getKey(dep.getArtifact());
                if (!managedVersions.containsKey(key)) {
                    mergedManagedDeps.add(dep);
                }
            }

            var directDeps = DependencyUtils.mergeDeps(List.of(), descr.getDependencies(), managedVersions,
                    Set.of(JavaScopes.PROVIDED, JavaScopes.TEST));

            request = new CollectRequest()
                    .setDependencies(directDeps)
                    .setManagedDependencies(mergedManagedDeps)
                    .setRepositories(repos);
            if (exclusions.isEmpty()) {
                request.setRootArtifact(artifact);
            } else {
                request.setRoot(new Dependency(artifact, JavaScopes.COMPILE, false, exclusions));
            }
        }
        try {
            return resolver.getSystem().collectDependencies(resolver.getSession(), request).getRoot();
        } catch (DependencyCollectionException e) {
            throw new DeploymentInjectionException("Failed to collect dependencies for " + artifact, e);
        }
    }

    private Artifact resolve(Artifact artifact, List repos) {
        if (artifact.getFile() != null) {
            return artifact;
        }
        try {
            return resolver.getSystem().resolveArtifact(resolver.getSession(),
                    new ArtifactRequest()
                            .setArtifact(artifact)
                            .setRepositories(repos))
                    .getArtifact();
        } catch (ArtifactResolutionException e) {
            throw new DeploymentInjectionException("Failed to resolve artifact " + artifact, e);
        }
    }

    private void setWalkingFlag(byte flag) {
        walkingFlags |= flag;
    }

    private boolean isWalkingFlagOn(byte flag) {
        return (walkingFlags & flag) > 0;
    }

    private void clearWalkingFlag(byte flag) {
        if ((walkingFlags & flag) > 0) {
            walkingFlags ^= flag;
        }
    }

    private static Properties readDescriptor(Path path) throws IOException {
        final Properties rtProps = new Properties();
        try (BufferedReader reader = Files.newBufferedReader(path)) {
            rtProps.load(reader);
        }
        return rtProps;
    }

    private class ExtensionInfo {
        final Artifact runtimeArtifact;
        final Properties props;
        final Artifact deploymentArtifact;
        final Artifact[] conditionalDeps;
        final ArtifactKey[] dependencyCondition;
        boolean activated;

        ExtensionInfo(Artifact runtimeArtifact, Properties props) throws BootstrapDependencyProcessingException {
            this.runtimeArtifact = runtimeArtifact;
            this.props = props;

            String value = props.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT);
            if (value == null) {
                throw new BootstrapDependencyProcessingException("Extension descriptor from " + runtimeArtifact
                        + " does not include " + BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT);
            }
            Artifact deploymentArtifact = toArtifact(value);
            if (deploymentArtifact.getVersion() == null || deploymentArtifact.getVersion().isEmpty()) {
                deploymentArtifact = deploymentArtifact.setVersion(runtimeArtifact.getVersion());
            }
            this.deploymentArtifact = deploymentArtifact;

            value = props.getProperty(BootstrapConstants.CONDITIONAL_DEPENDENCIES);
            if (value != null) {
                final String[] deps = BootstrapUtils.splitByWhitespace(value);
                conditionalDeps = new Artifact[deps.length];
                for (int i = 0; i < deps.length; ++i) {
                    try {
                        conditionalDeps[i] = toArtifact(deps[i]);
                    } catch (Exception e) {
                        throw new BootstrapDependencyProcessingException(
                                "Failed to parse conditional dependencies configuration of " + runtimeArtifact, e);
                    }
                }
            } else {
                conditionalDeps = NO_ARTIFACTS;
            }

            dependencyCondition = BootstrapUtils
                    .parseDependencyCondition(props.getProperty(BootstrapConstants.DEPENDENCY_CONDITION));
        }

        void ensureActivated() {
            if (activated) {
                return;
            }
            activated = true;
            appBuilder.handleExtensionProperties(props, runtimeArtifact.toString());

            final String providesCapabilities = props.getProperty(BootstrapConstants.PROP_PROVIDES_CAPABILITIES);
            final String requiresCapabilities = props.getProperty(BootstrapConstants.PROP_REQUIRES_CAPABILITIES);
            if (providesCapabilities != null || requiresCapabilities != null) {
                appBuilder.addExtensionCapabilities(
                        CapabilityContract.of(toCompactCoords(runtimeArtifact), providesCapabilities, requiresCapabilities));
            }
        }
    }

    private static class ExtensionDependency {

        static ExtensionDependency get(DependencyNode node) {
            return (ExtensionDependency) node.getData().get(QUARKUS_EXTENSION_DEPENDENCY);
        }

        final ExtensionInfo info;
        final DependencyNode runtimeNode;
        final Collection exclusions;
        boolean conditionalDepsQueued;
        private List runtimeExtensionDeps;

        ExtensionDependency(ExtensionInfo info, DependencyNode node, Collection exclusions) {
            this.runtimeNode = node;
            this.info = info;
            this.exclusions = exclusions;

            @SuppressWarnings("unchecked")
            final Map data = (Map) node.getData();
            if (data.isEmpty()) {
                node.setData(QUARKUS_EXTENSION_DEPENDENCY, this);
            } else if (data.put(QUARKUS_EXTENSION_DEPENDENCY, this) != null) {
                throw new IllegalStateException(
                        "Dependency node " + node + " has already been associated with an extension dependency");
            }
        }

        void addExtensionDependency(ExtensionDependency dep) {
            if (runtimeExtensionDeps == null) {
                runtimeExtensionDeps = new ArrayList<>();
            }
            runtimeExtensionDeps.add(dep);
        }
    }

    private class ConditionalDependency {

        final ExtensionInfo info;
        final ExtensionDependency dependent;
        private ExtensionDependency dependency;
        private boolean activated;

        private ConditionalDependency(ExtensionInfo info, ExtensionDependency dependent) {
            this.info = Objects.requireNonNull(info, "Extension info is null");
            this.dependent = dependent;
        }

        ExtensionDependency getExtensionDependency() {
            if (dependency == null) {
                final DefaultDependencyNode rtNode = new DefaultDependencyNode(
                        new Dependency(info.runtimeArtifact, JavaScopes.COMPILE));
                rtNode.setVersion(new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()));
                rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint(
                        new BootstrapArtifactVersion(info.runtimeArtifact.getVersion())));
                rtNode.setRepositories(dependent.runtimeNode.getRepositories());
                dependency = new ExtensionDependency(info, rtNode, dependent.exclusions);
            }
            return dependency;
        }

        void activate() {
            if (activated) {
                return;
            }
            activated = true;
            clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES);
            final ExtensionDependency extDep = getExtensionDependency();
            final DependencyNode originalNode = collectDependencies(info.runtimeArtifact, extDep.exclusions,
                    extDep.runtimeNode.getRepositories());
            final DefaultDependencyNode rtNode = (DefaultDependencyNode) extDep.runtimeNode;
            rtNode.setRepositories(originalNode.getRepositories());
            // if this node has conditional dependencies on its own, they may have been activated by this time
            // in which case they would be included into its children
            List currentChildren = rtNode.getChildren();
            if (currentChildren == null || currentChildren.isEmpty()) {
                rtNode.setChildren(originalNode.getChildren());
            } else {
                currentChildren.addAll(originalNode.getChildren());
            }
            visitRuntimeDependency(rtNode);
            dependent.runtimeNode.getChildren().add(rtNode);
        }

        boolean isSatisfied() {
            if (info.dependencyCondition == null) {
                return true;
            }
            for (ArtifactKey key : info.dependencyCondition) {
                if (!isRuntimeArtifact(key)) {
                    return false;
                }
            }
            return true;
        }
    }

    private static boolean isSameKey(Artifact a1, Artifact a2) {
        return a2.getArtifactId().equals(a1.getArtifactId())
                && a2.getGroupId().equals(a1.getGroupId())
                && a2.getClassifier().equals(a1.getClassifier())
                && a2.getExtension().equals(a1.getExtension());
    }

    private static String toCompactCoords(Artifact a) {
        final StringBuilder b = new StringBuilder();
        b.append(a.getGroupId()).append(':').append(a.getArtifactId()).append(':');
        if (!a.getClassifier().isEmpty()) {
            b.append(a.getClassifier()).append(':');
        }
        if (!ArtifactCoords.TYPE_JAR.equals(a.getExtension())) {
            b.append(a.getExtension()).append(':');
        }
        b.append(a.getVersion());
        return b.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy