org.eclipse.tycho.p2.resolver.P2TargetPlatformResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.tycho.p2.resolver.P2TargetPlatformResolver.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2011 Sonatype Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Sonatype Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.tycho.p2.resolver;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.MavenExecutionException;
import org.apache.maven.ProjectDependenciesResolver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.Authentication;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.resolver.MultipleArtifactsNotFoundException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Server;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.component.repository.ComponentDependency;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
import org.eclipse.sisu.equinox.EquinoxServiceFactory;
import org.eclipse.tycho.ArtifactKey;
import org.eclipse.tycho.ReactorProject;
import org.eclipse.tycho.artifacts.DependencyArtifacts;
import org.eclipse.tycho.artifacts.TargetPlatform;
import org.eclipse.tycho.core.DependencyResolverConfiguration;
import org.eclipse.tycho.core.TargetEnvironment;
import org.eclipse.tycho.core.TargetPlatformConfiguration;
import org.eclipse.tycho.core.TargetPlatformResolver;
import org.eclipse.tycho.core.TychoConstants;
import org.eclipse.tycho.core.TychoProject;
import org.eclipse.tycho.core.maven.MavenDependencyInjector;
import org.eclipse.tycho.core.maven.utils.PluginRealmHelper;
import org.eclipse.tycho.core.maven.utils.PluginRealmHelper.PluginFilter;
import org.eclipse.tycho.core.osgitools.AbstractTychoProject;
import org.eclipse.tycho.core.osgitools.BundleReader;
import org.eclipse.tycho.core.osgitools.DefaultArtifactKey;
import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
import org.eclipse.tycho.core.osgitools.targetplatform.AbstractTargetPlatformResolver;
import org.eclipse.tycho.core.osgitools.targetplatform.DefaultTargetPlatform;
import org.eclipse.tycho.core.osgitools.targetplatform.MultiEnvironmentTargetPlatform;
import org.eclipse.tycho.core.p2.P2ArtifactRepositoryLayout;
import org.eclipse.tycho.core.resolver.shared.OptionalResolutionAction;
import org.eclipse.tycho.core.utils.ExecutionEnvironment;
import org.eclipse.tycho.core.utils.ExecutionEnvironmentUtils;
import org.eclipse.tycho.core.utils.PlatformPropertiesUtils;
import org.eclipse.tycho.core.utils.TychoProjectUtils;
import org.eclipse.tycho.p2.facade.internal.AttachedArtifact;
import org.eclipse.tycho.p2.facade.internal.ReactorArtifactFacade;
import org.eclipse.tycho.p2.metadata.DependencyMetadataGenerator;
import org.eclipse.tycho.p2.metadata.IDependencyMetadata;
import org.eclipse.tycho.p2.repository.LocalRepositoryP2Indices;
import org.eclipse.tycho.p2.resolver.facade.P2ResolutionResult;
import org.eclipse.tycho.p2.resolver.facade.P2Resolver;
import org.eclipse.tycho.p2.resolver.facade.P2ResolverFactory;
import org.eclipse.tycho.p2.target.facade.TargetDefinition;
import org.eclipse.tycho.p2.target.facade.TargetDefinition.InstallableUnitLocation;
import org.eclipse.tycho.p2.target.facade.TargetDefinition.Location;
import org.eclipse.tycho.p2.target.facade.TargetDefinitionResolutionException;
import org.eclipse.tycho.p2.target.facade.TargetDefinitionSyntaxException;
import org.eclipse.tycho.p2.target.facade.TargetPlatformBuilder;

// TODO 364134 rename this class
@Component(role = TargetPlatformResolver.class, hint = P2TargetPlatformResolver.ROLE_HINT, instantiationStrategy = "per-lookup")
public class P2TargetPlatformResolver extends AbstractTargetPlatformResolver
        implements TargetPlatformResolver, Initializable {

    public static final String ROLE_HINT = "p2";

    @Requirement
    private EquinoxServiceFactory equinox;

    @Requirement
    private BundleReader bundleReader;

    @Requirement
    private RepositorySystem repositorySystem;

    @Requirement(hint = "p2")
    private ArtifactRepositoryLayout p2layout;

    @Requirement
    private ProjectDependenciesResolver projectDependenciesResolver;

    @Requirement(role = TychoProject.class)
    private Map<String, TychoProject> projectTypes;

    @Requirement
    private PlexusContainer plexus;

    @Requirement
    private PluginRealmHelper pluginRealmHelper;

    private P2ResolverFactory resolverFactory;

    private DependencyMetadataGenerator generator;

    private static final ArtifactRepositoryPolicy P2_REPOSITORY_POLICY = new ArtifactRepositoryPolicy(true,
            ArtifactRepositoryPolicy.UPDATE_POLICY_NEVER, ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE);

    public void setupProjects(final MavenSession session, final MavenProject project,
            final ReactorProject reactorProject) {
        TargetPlatformConfiguration configuration = (TargetPlatformConfiguration) project
                .getContextValue(TychoConstants.CTX_TARGET_PLATFORM_CONFIGURATION);
        List<Map<String, String>> environments = getEnvironments(configuration);
        Map<String, IDependencyMetadata> metadata = getDependencyMetadata(session, project, environments,
                OptionalResolutionAction.OPTIONAL);
        for (Map.Entry<String, IDependencyMetadata> entry : metadata.entrySet()) {
            reactorProject.setDependencyMetadata(entry.getKey(), true, entry.getValue().getMetadata(true));
            reactorProject.setDependencyMetadata(entry.getKey(), false, entry.getValue().getMetadata(false));
        }
    }

    protected Map<String, IDependencyMetadata> getDependencyMetadata(final MavenSession session,
            final MavenProject project, final List<Map<String, String>> environments,
            final OptionalResolutionAction optionalAction) {

        final Map<String, IDependencyMetadata> metadata = new LinkedHashMap<String, IDependencyMetadata>();
        metadata.put(null, generator.generateMetadata(new AttachedArtifact(project, project.getBasedir(), null),
                environments, optionalAction));

        // let external providers contribute additional metadata
        try {
            pluginRealmHelper.execute(session, project, new Runnable() {
                public void run() {
                    try {
                        for (P2MetadataProvider provider : plexus.lookupList(P2MetadataProvider.class)) {
                            Map<String, IDependencyMetadata> providedMetadata = provider
                                    .getDependencyMetadata(session, project, null, optionalAction);
                            if (providedMetadata != null) {
                                metadata.putAll(providedMetadata);
                            }
                        }
                    } catch (ComponentLookupException e) {
                        // have not found anything
                    }
                }
            }, new PluginFilter() {
                public boolean accept(PluginDescriptor descriptor) {
                    return isTychoP2Plugin(descriptor);
                }
            });
        } catch (MavenExecutionException e) {
            throw new RuntimeException(e);
        }

        return metadata;
    }

    protected boolean isTychoP2Plugin(PluginDescriptor pluginDescriptor) {
        if (pluginDescriptor.getArtifactMap().containsKey("org.eclipse.tycho:tycho-p2-facade")) {
            return true;
        }
        for (ComponentDependency dependency : pluginDescriptor.getDependencies()) {
            if ("org.eclipse.tycho".equals(dependency.getGroupId())
                    && "tycho-p2-facade".equals(dependency.getArtifactId())) {
                return true;
            }
        }
        return false;
    }

    public TargetPlatform computeTargetPlatform(MavenSession session, MavenProject project,
            List<ReactorProject> reactorProjects) {
        TargetPlatformConfiguration configuration = TychoProjectUtils.getTargetPlatformConfiguration(project);

        ExecutionEnvironment ee = projectTypes.get(project.getPackaging()).getExecutionEnvironment(project);

        TargetPlatformBuilder tpBuilder = resolverFactory.createTargetPlatformBuilder(//
                ee != null ? ee.getProfileName() : null, configuration.isDisableP2Mirrors());
        tpBuilder.setProjectLocation(project.getBasedir());

        addThisReactorProjectToTargetPlatform(session, project, configuration, tpBuilder);

        addOtherReactorProjectsToTargetPlatform(project, reactorProjects, tpBuilder);

        if (TargetPlatformConfiguration.POM_DEPENDENCIES_CONSIDER.equals(configuration.getPomDependencies())) {
            addPomDependenciesToTargetPlatform(project, tpBuilder, reactorProjects, session);
        }

        for (ArtifactRepository repository : project.getRemoteArtifactRepositories()) {
            addEntireP2RepositoryToTargetPlatform(repository, tpBuilder, session);
        }

        if (configuration.getTarget() != null) {
            addTargetFileContentToTargetPlatform(configuration, tpBuilder, session);
        }

        tpBuilder.addFilters(configuration.getFilters());

        return tpBuilder.buildTargetPlatform();
    }

    private void addThisReactorProjectToTargetPlatform(MavenSession session, MavenProject project,
            TargetPlatformConfiguration configuration, TargetPlatformBuilder tpBuilder) {
        // 'this' project should obey optionalDependencnies configuration

        final List<Map<String, String>> environments = getEnvironments(configuration);
        final OptionalResolutionAction optionalAction = configuration.getDependencyResolverConfiguration()
                .getOptionalResolutionAction();
        Map<String, IDependencyMetadata> dependencyMetadata = getDependencyMetadata(session, project, environments,
                optionalAction);
        final Map<String, Set<Object>> metadata = new LinkedHashMap<String, Set<Object>>();
        final Map<String, Set<Object>> secondaryMetadata = new LinkedHashMap<String, Set<Object>>();
        for (Map.Entry<String, IDependencyMetadata> entry : dependencyMetadata.entrySet()) {
            metadata.put(entry.getKey(), entry.getValue().getMetadata(true));
            secondaryMetadata.put(entry.getKey(), entry.getValue().getMetadata(false));
        }
        ReactorProject reactorProjet = new DefaultReactorProject(project) {
            @Override
            protected Map<String, Set<Object>> getDependencyMetadata(boolean primary) {
                return primary ? metadata : secondaryMetadata;
            }
        };
        for (String classifier : dependencyMetadata.keySet()) {
            tpBuilder.addReactorArtifact(new ReactorArtifactFacade(reactorProjet, classifier));
        }
    }

    private void addOtherReactorProjectsToTargetPlatform(MavenProject project, List<ReactorProject> reactorProjects,
            TargetPlatformBuilder resolutionContext) {

        for (ReactorProject otherProject : reactorProjects) {
            if (otherProject.sameProject(project)) {
                continue;
            }
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("P2resolver.addMavenProject " + otherProject.getId());
            }

            Map<String, Set<Object>> dependencyMetadata = otherProject.getDependencyMetadata();
            if (dependencyMetadata != null) {
                for (String classifier : dependencyMetadata.keySet()) {
                    resolutionContext.addReactorArtifact(new ReactorArtifactFacade(otherProject, classifier));
                }
            }
        }
    }

    private void addPomDependenciesToTargetPlatform(MavenProject project, TargetPlatformBuilder resolutionContext,
            List<ReactorProject> reactorProjects, MavenSession session) {
        Set<String> projectIds = new HashSet<String>();
        for (ReactorProject p : reactorProjects) {
            String key = ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion());
            projectIds.add(key);
        }

        ArrayList<String> scopes = new ArrayList<String>();
        scopes.add(Artifact.SCOPE_COMPILE);
        Collection<Artifact> artifacts;
        try {
            artifacts = projectDependenciesResolver.resolve(project, scopes, session);
        } catch (MultipleArtifactsNotFoundException e) {
            Collection<Artifact> missing = new HashSet<Artifact>(e.getMissingArtifacts());

            for (Iterator<Artifact> it = missing.iterator(); it.hasNext();) {
                Artifact a = it.next();
                String key = ArtifactUtils.key(a.getGroupId(), a.getArtifactId(), a.getBaseVersion());
                if (projectIds.contains(key)) {
                    it.remove();
                }
            }

            if (!missing.isEmpty()) {
                throw new RuntimeException("Could not resolve project dependencies", e);
            }

            artifacts = e.getResolvedArtifacts();
            artifacts.removeAll(e.getMissingArtifacts());
        } catch (AbstractArtifactResolutionException e) {
            throw new RuntimeException("Could not resolve project dependencies", e);
        }
        List<Artifact> externalArtifacts = new ArrayList<Artifact>(artifacts.size());
        for (Artifact artifact : artifacts) {
            String key = ArtifactUtils.key(artifact.getGroupId(), artifact.getArtifactId(),
                    artifact.getBaseVersion());
            if (projectIds.contains(key)) {
                // resolved to an older snapshot from the repo, we only want the current project in the reactor
                continue;
            }
            externalArtifacts.add(artifact);
        }
        List<Artifact> explicitArtifacts = MavenDependencyInjector.filterInjectedDependencies(externalArtifacts); // needed when the resolution is done again for the test runtime
        PomDependencyProcessor pomDependencyProcessor = new PomDependencyProcessor(session, repositorySystem,
                equinox.getService(LocalRepositoryP2Indices.class), getLogger());
        pomDependencyProcessor.addPomDependenciesToResolutionContext(project, explicitArtifacts, resolutionContext);
    }

    private void addEntireP2RepositoryToTargetPlatform(ArtifactRepository repository,
            TargetPlatformBuilder resolutionContext, MavenSession session) {
        try {
            URI uri = new URL(repository.getUrl()).toURI();

            if (repository.getLayout() instanceof P2ArtifactRepositoryLayout) {
                if (session.isOffline()) {
                    getLogger().debug("Offline mode, using local cache only for repository " + repository.getId()
                            + " (" + repository.getUrl() + ")");
                }

                try {
                    Authentication auth = repository.getAuthentication();
                    if (auth != null) {
                        resolutionContext.setCredentials(uri, auth.getUsername(), auth.getPassword());
                    }

                    resolutionContext.addP2Repository(uri);

                    getLogger()
                            .debug("Added p2 repository " + repository.getId() + " (" + repository.getUrl() + ")");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        } catch (MalformedURLException e) {
            getLogger().warn("Could not parse repository URL", e);
        } catch (URISyntaxException e) {
            getLogger().warn("Could not parse repository URL", e);
        }
    }

    private void addTargetFileContentToTargetPlatform(TargetPlatformConfiguration configuration,
            TargetPlatformBuilder resolutionContext, MavenSession session) {
        final TargetDefinitionFile target;
        try {
            target = TargetDefinitionFile.read(configuration.getTarget());
        } catch (TargetDefinitionSyntaxException e) {
            throw new RuntimeException(
                    "Invalid syntax in target definition " + configuration.getTarget() + ": " + e.getMessage(), e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Set<URI> uris = new HashSet<URI>();

        for (Location location : target.getLocations()) {
            if (!(location instanceof InstallableUnitLocation)) {
                continue;
            }
            for (TargetDefinition.Repository repository : ((InstallableUnitLocation) location).getRepositories()) {

                try {
                    URI uri = getMirror(repository, session.getRequest().getMirrors());
                    if (uris.add(uri)) {
                        if (!session.isOffline()) {
                            String id = repository.getId();
                            if (id != null) {
                                Server server = session.getSettings().getServer(id);

                                if (server != null) {
                                    // TODO don't do this via magic side-effects, but when loading repositories
                                    resolutionContext.setCredentials(uri, server.getUsername(),
                                            server.getPassword());
                                } else {
                                    getLogger().info("Unknown server id=" + id + " for repository location="
                                            + repository.getLocation());
                                }
                            }

                            // TODO mirrors are no longer considered -> lookup mirrors when loading p2 repositories
                        }
                    }
                } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
                }
            }

            try {
                getLogger().debug("Resolving target definition file \"" + configuration.getTarget() + "\"");
                resolutionContext.addTargetDefinition(target, getEnvironments(configuration));
            } catch (TargetDefinitionSyntaxException e) {
                throw new RuntimeException(
                        "Invalid syntax in target definition " + configuration.getTarget() + ": " + e.getMessage(),
                        e);
            } catch (TargetDefinitionResolutionException e) {
                throw new RuntimeException("Failed to resolve target definition " + configuration.getTarget(), e);
            }
        }
    }

    public DependencyArtifacts resolveDependencies(final MavenSession session, final MavenProject project,
            TargetPlatform resolutionContext, List<ReactorProject> reactorProjects,
            DependencyResolverConfiguration resolverConfiguration) {

        // TODO 364134 For compatibility reasons, target-platform-configuration includes settings for the dependency resolution
        // --> split this information logically, e.g. through two distinct interfaces
        TargetPlatformConfiguration configuration = TychoProjectUtils.getTargetPlatformConfiguration(project);

        P2Resolver osgiResolverImpl = resolverFactory.createResolver();

        return doResolvePlatform(session, project, reactorProjects, resolverConfiguration, resolutionContext,
                osgiResolverImpl, configuration);
    }

    protected DependencyArtifacts doResolvePlatform(final MavenSession session, final MavenProject project,
            List<ReactorProject> reactorProjects, DependencyResolverConfiguration resolverConfiguration,
            TargetPlatform resolutionContext, P2Resolver resolver, TargetPlatformConfiguration configuration) {

        Map<File, ReactorProject> projects = new HashMap<File, ReactorProject>();

        resolver.setEnvironments(getEnvironments(configuration));

        for (ReactorProject otherProject : reactorProjects) {
            projects.put(otherProject.getBasedir(), otherProject);
        }

        if (resolverConfiguration != null) {
            for (Dependency dependency : resolverConfiguration.getExtraRequirements()) {
                resolver.addDependency(dependency.getType(), dependency.getArtifactId(), dependency.getVersion());
            }
        }

        if (!isAllowConflictingDependencies(project, configuration)) {
            List<P2ResolutionResult> results = resolver.resolveProject(resolutionContext, project.getBasedir());

            MultiEnvironmentTargetPlatform multiPlatform = new MultiEnvironmentTargetPlatform(
                    DefaultReactorProject.adapt(project));

            // FIXME this is just wrong
            for (int i = 0; i < configuration.getEnvironments().size(); i++) {
                TargetEnvironment environment = configuration.getEnvironments().get(i);
                P2ResolutionResult result = results.get(i);

                DefaultTargetPlatform platform = newDefaultTargetPlatform(session,
                        DefaultReactorProject.adapt(project), projects, result);

                // addProjects( session, platform );

                multiPlatform.addPlatform(environment, platform);
            }

            return multiPlatform;
        } else {
            P2ResolutionResult result = resolver.collectProjectDependencies(resolutionContext,
                    project.getBasedir());

            return newDefaultTargetPlatform(session, DefaultReactorProject.adapt(project), projects, result);
        }
    }

    private boolean isAllowConflictingDependencies(MavenProject project,
            TargetPlatformConfiguration configuration) {
        String packaging = project.getPackaging();

        if (org.eclipse.tycho.ArtifactKey.TYPE_ECLIPSE_UPDATE_SITE.equals(packaging)
                || org.eclipse.tycho.ArtifactKey.TYPE_ECLIPSE_FEATURE.equals(packaging)) {
            Boolean allow = configuration.getAllowConflictingDependencies();
            if (allow != null) {
                return allow.booleanValue();
            }
        }

        // conflicting dependencies do not make sense for products and bundles
        return false;
    }

    protected DefaultTargetPlatform newDefaultTargetPlatform(MavenSession session, ReactorProject project,
            Map<File, ReactorProject> projects, P2ResolutionResult result) {
        DefaultTargetPlatform platform = new DefaultTargetPlatform(project);

        platform.addNonReactorUnits(result.getNonReactorUnits());

        for (P2ResolutionResult.Entry entry : result.getArtifacts()) {
            ArtifactKey key = new DefaultArtifactKey(entry.getType(), entry.getId(), entry.getVersion());
            ReactorProject otherProject = projects.get(entry.getLocation());
            if (otherProject != null) {
                platform.addReactorArtifact(key, otherProject, entry.getClassifier(), entry.getInstallableUnits());
            } else {
                platform.addArtifactFile(key, entry.getLocation(), entry.getInstallableUnits());
            }
        }
        return platform;
    }

    private List<Map<String, String>> getEnvironments(TargetPlatformConfiguration configuration) {
        ArrayList<Map<String, String>> environments = new ArrayList<Map<String, String>>();

        for (TargetEnvironment environment : configuration.getEnvironments()) {
            Properties properties = new Properties();
            properties.put(PlatformPropertiesUtils.OSGI_OS, environment.getOs());
            properties.put(PlatformPropertiesUtils.OSGI_WS, environment.getWs());
            properties.put(PlatformPropertiesUtils.OSGI_ARCH, environment.getArch());
            ExecutionEnvironmentUtils.loadVMProfile(properties);

            Map<String, String> map = new LinkedHashMap<String, String>();
            for (Object key : properties.keySet()) {
                map.put(key.toString(), properties.getProperty(key.toString()));
            }
            environments.add(map);
        }

        return environments;
    }

    private URI getMirror(TargetDefinition.Repository location, List<Mirror> mirrors) throws URISyntaxException {
        URI p2RepositoryLocation = location.getLocation();
        String id = location.getId();
        if (id == null) {
            id = p2RepositoryLocation.toString();
        }

        ArtifactRepository repository = repositorySystem.createArtifactRepository(id,
                p2RepositoryLocation.toString(), p2layout, P2_REPOSITORY_POLICY, P2_REPOSITORY_POLICY);

        Mirror mirror = repositorySystem.getMirror(repository, mirrors);

        return mirror != null ? new URI(mirror.getUrl()) : p2RepositoryLocation;
    }

    public void initialize() throws InitializationException {
        this.resolverFactory = equinox.getService(P2ResolverFactory.class);
        this.generator = equinox.getService(DependencyMetadataGenerator.class, "(role-hint=dependency-only)");
    }

    public void injectDependenciesIntoMavenModel(MavenProject project, AbstractTychoProject projectType,
            DependencyArtifacts dependencyArtifacts, Logger logger) {
        MavenDependencyInjector.injectMavenDependencies(project, dependencyArtifacts, bundleReader, logger);
    }
}