Skip to content

Commit

Permalink
Add support for mirroring the projects target platform
Browse files Browse the repository at this point in the history
This adds support for converting the projects computed target platform
into a deployable p2 repository. This can be used to create a mirror of
a target-file, or to have a repository that can be used to install one
specific bundle or feature with all its dependencies.
  • Loading branch information
laeubi committed Dec 22, 2023
1 parent 7e8005b commit c7bc195
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2023 Christoph Läubrich and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.target;

import java.io.File;
import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.tycho.ReactorProject;
import org.eclipse.tycho.TargetPlatform;
import org.eclipse.tycho.TargetPlatformService;
import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
import org.eclipse.tycho.p2.repository.ListCompositeMetadataRepository;
import org.eclipse.tycho.p2.repository.PublishingRepository;
import org.eclipse.tycho.p2.tools.FacadeException;
import org.eclipse.tycho.p2.tools.mirroring.facade.MirrorApplicationService;
import org.eclipse.tycho.p2maven.ListCompositeArtifactRepository;
import org.eclipse.tycho.repository.registry.facade.ReactorRepositoryManager;

/**
* Supports mirroring the computed target platform of the current project, this behaves similar to
* what PDE offers with its export deployable feature / plug-in and assembles an update site that
* contains everything this particular project depends on.
*/
@Mojo(name = "mirror-target-platform", threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE, defaultPhase = LifecyclePhase.PREPARE_PACKAGE)
public class MirrorTargetPlatformMojo extends AbstractMojo {

@Parameter(property = "project", readonly = true)
private MavenProject project;

@Parameter(defaultValue = "${project.build.directory}/target-platform-repository")
private File destination;

@Parameter(defaultValue = "${project.id}")
private String name;

@Component
private TargetPlatformService platformService;

@Component
private MirrorApplicationService mirrorService;

@Component
private ReactorRepositoryManager repositoryManager;

@Component
private IProvisioningAgent agent;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
ReactorProject reactorProject = DefaultReactorProject.adapt(project);
TargetPlatform targetPlatform = platformService.getTargetPlatform(reactorProject).orElse(null);
if (targetPlatform == null) {
getLog().info("Project has no target platform, skip execution.");
return;
}
IArtifactRepository sourceArtifactRepository = targetPlatform.getArtifactRepository();
IMetadataRepository sourceMetadataRepository = targetPlatform.getMetadataRepository();
PublishingRepository publishingRepository = repositoryManager.getPublishingRepository(reactorProject);
getLog().info("Mirroring target platform, this can take a while ...");
try {
IArtifactRepository artifactRepository = new ListCompositeArtifactRepository(
List.of(sourceArtifactRepository, publishingRepository.getArtifactRepository()), agent);
IMetadataRepository metadataRepository = new ListCompositeMetadataRepository(
List.of(sourceMetadataRepository, publishingRepository.getMetadataRepository()), agent);
mirrorService.mirrorDirect(artifactRepository, metadataRepository, destination, name);
} catch (FacadeException e) {
throw new MojoFailureException(e.getMessage(), e.getCause());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.util.Collection;
import java.util.Map;

import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.tycho.BuildDirectory;
import org.eclipse.tycho.DependencySeed;
import org.eclipse.tycho.p2.tools.BuildContext;
Expand Down Expand Up @@ -107,6 +109,22 @@ void mirrorStandalone(RepositoryReferences sources, DestinationRepositoryDescrip
Collection<IUDescription> seedUnits, MirrorOptions mirrorOptions, BuildDirectory tempDirectory)
throws FacadeException;

/**
* Mirrors the given sources to the destination, if the destination exits it will be delete
* beforehand.
*
* @param sourceArtifactRepository
* the source artifact repository
* @param sourceMetadataRepository
* the source metadata repository
* @param repositoryDestination
* the destination
* @param repositoryName
* the name of the new repository
*/
void mirrorDirect(IArtifactRepository sourceArtifactRepository, IMetadataRepository sourceMetadataRepository,
File repositoryDestination, String repositoryName) throws FacadeException;

/**
* Modifies the artifact repository to add mapping rules to download Maven released artifacts
* from one of the specified maven repositories (when it's found).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Map;
import java.util.Set;

import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
Expand Down Expand Up @@ -61,15 +62,17 @@ public PreliminaryTargetPlatformImpl(Map<IInstallableUnit, ReactorProjectIdentit
Collection<IInstallableUnit> externalIUs, ExecutionEnvironmentResolutionHints executionEnvironment,
TargetPlatformFilterEvaluator filter, LocalMetadataRepository localMetadataRepository,
IRawArtifactFileProvider externalArtifacts, LocalArtifactRepository localArtifactRepository,
boolean includeLocalRepo, MavenLogger logger, Set<IInstallableUnit> shadowed) {
boolean includeLocalRepo, MavenLogger logger, Set<IInstallableUnit> shadowed,
IProvisioningAgent remoteAgent) {
super(collectAllInstallableUnits(reactorProjectIUs, externalIUs, executionEnvironment), executionEnvironment,
externalArtifacts, localArtifactRepository, reactorProjectIUs, new HashMap<>(), shadowed);
this.externalIUs = externalIUs;
this.filter = filter;
this.localMetadataRepository = localMetadataRepository;
this.includeLocalRepo = includeLocalRepo;
this.logger = logger;
this.artifactRepository = new ProviderOnlyArtifactRepository(artifacts, null, URI.create("preliminary:/"));
this.artifactRepository = new ProviderOnlyArtifactRepository(artifacts, remoteAgent,
URI.create("preliminary:/"));
}

public static LinkedHashSet<IInstallableUnit> collectAllInstallableUnits(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.equinox.internal.p2.updatesite.SiteCategory;
import org.eclipse.equinox.internal.p2.updatesite.SiteXMLAction;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
Expand Down Expand Up @@ -77,6 +82,8 @@
*/
public final class TargetDefinitionResolver {

private static final SiteXMLAction CATEGORY_FACTORY = new SiteXMLAction((URI) null, (String) null);

private final MavenLogger logger;

private final List<TargetEnvironment> environments;
Expand Down Expand Up @@ -181,12 +188,14 @@ public TargetDefinitionContent resolveContentWithExceptions(TargetDefinition def
IQueryResult<IInstallableUnit> result = targetDefinitionContent.query(QueryUtil.ALL_UNITS,
new LoggingProgressMonitor(logger));
unitResultSet.addAll(result);
Set<IInstallableUnit> locationUnits = result.toUnmodifiableSet();
if (logger.isDebugEnabled()) {
logger.debug("The following artifacts were resolved from location " + location);
for (IInstallableUnit iu : result.toUnmodifiableSet()) {
for (IInstallableUnit iu : locationUnits) {
logger.debug("\t" + iu);
}
}
unitResultSet.accept(createCategory(location.getLabel(), result));
} else if (locationDefinition instanceof TargetReferenceLocation referenceLocation) {
logger.info("Resolving " + referenceLocation.getUri());
String resolvePath = resolvePath(referenceLocation.getUri(), definition);
Expand Down Expand Up @@ -294,6 +303,14 @@ public IArtifactRepository getArtifactRepository() {
};
}

private static IInstallableUnit createCategory(String label, IQueryResult<IInstallableUnit> result) {
SiteCategory category = new SiteCategory();
category.setLabel(label);
category.setName("generated.target.category." + UUID.randomUUID());
return CATEGORY_FACTORY.createCategoryIU(category,
result.stream().filter(iu -> !iu.getId().endsWith(".feature.jar")).collect(Collectors.toSet()));
}

/**
* Converts a "raw" URI string into one that can be used to parse it as an {@link URI}. The
* conversion is especially for converting file URIs constructed using maven properties that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public P2TargetPlatform createTargetPlatform(TargetPlatformConfigurationStub tpC
externalArtifactFileProvider, //
localArtifactRepository, //
includeLocalMavenRepo, //
logger, shadowed);
logger, shadowed, remoteAgent);
eeResolutionHandler.readFullSpecification(targetPlatform.getInstallableUnits());

return targetPlatform;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,33 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.io.FileUtils;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository;
import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepositoryFactory;
import org.eclipse.equinox.internal.p2.metadata.repository.SimpleMetadataRepositoryFactory;
import org.eclipse.equinox.internal.p2.repository.Transport;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.internal.repository.mirroring.IArtifactMirrorLog;
import org.eclipse.equinox.p2.internal.repository.mirroring.Mirroring;
import org.eclipse.equinox.p2.internal.repository.tools.Activator;
import org.eclipse.equinox.p2.internal.repository.tools.Messages;
import org.eclipse.equinox.p2.internal.repository.tools.RecreateRepositoryApplication;
import org.eclipse.equinox.p2.internal.repository.tools.RepositoryDescriptor;
import org.eclipse.equinox.p2.internal.repository.tools.SlicingOptions;
Expand All @@ -44,9 +56,11 @@
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.tycho.ArtifactType;
Expand Down Expand Up @@ -436,4 +450,52 @@ public void addMavenMappingRules(File repository, URI[] mavenRepositories) throw
true, false, false);
xzCompress(desc);
}

@Override
public void mirrorDirect(IArtifactRepository sourceArtifactRepository, IMetadataRepository sourceMetadataRepository,
File repositoryDestination, String repositoryName) throws FacadeException {
if (repositoryDestination.exists()) {
FileUtils.deleteQuietly(repositoryDestination);
}
//See https://github.com/eclipse-equinox/p2/pull/418
Objects.requireNonNull(sourceArtifactRepository.getProvisioningAgent(),
"Source repository needs to have an agent");
SimpleMetadataRepositoryFactory metadataRepositoryFactory = new SimpleMetadataRepositoryFactory();
metadataRepositoryFactory.setAgent(agent);
SimpleArtifactRepositoryFactory artifactRepositoryFactory = new SimpleArtifactRepositoryFactory();
artifactRepositoryFactory.setAgent(agent);
IArtifactRepository destinationArtifactRepository = artifactRepositoryFactory
.create(repositoryDestination.toURI(), repositoryName, null, Map.of());
IMetadataRepository destinationMetadataRepository = metadataRepositoryFactory
.create(repositoryDestination.toURI(), repositoryName, null, Map.of());
MultiStatus multiStatus = new MultiStatus(Activator.ID, IStatus.OK, Messages.message_mirroringStatus, null);
Set<IArtifactKey> toMirror = new TreeSet<>(Comparator.comparing(IArtifactKey::getId).thenComparing(
Comparator.comparing(IArtifactKey::getVersion).thenComparing(IArtifactKey::getClassifier)));
multiStatus.add(destinationMetadataRepository.executeBatch(monitor -> {
IQueryResult<IInstallableUnit> result = sourceMetadataRepository.query(QueryUtil.ALL_UNITS, monitor);
Set<IInstallableUnit> units = result.toUnmodifiableSet();
destinationMetadataRepository.addInstallableUnits(units);
for (IInstallableUnit unit : units) {
toMirror.addAll(unit.getArtifacts());
}
}, null));
multiStatus.add(destinationArtifactRepository.executeBatch(monitor -> {
Mirroring mirroring = new Mirroring(sourceArtifactRepository, destinationArtifactRepository, true);
mirroring.setCompare(false);
mirroring.setValidate(false);
mirroring.setTransport(agent.getService(Transport.class));
IArtifactKey[] keys = toMirror.toArray(IArtifactKey[]::new);
mirroring.setArtifactKeys(keys);
multiStatus.addAll(mirroring.run(false, false));
}, null));
if (!multiStatus.isOK()) {
String logMessage = StatusTool.toLogMessage(multiStatus);
if (multiStatus.getSeverity() == IStatus.INFO) {
logger.info(logMessage, StatusTool.findException(multiStatus));
} else if (multiStatus.getSeverity() == IStatus.WARNING) {
logger.warn(logMessage, StatusTool.findException(multiStatus));
}
throw new FacadeException(logMessage, new CoreException(multiStatus));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ enum DependencyDepth {

Element getFeatureTemplate();

String getLabel();

@Override
public default String getTypeDescription() {
return TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -197,18 +198,21 @@ private static class MavenLocation implements TargetDefinition.MavenGAVLocation
private final DependencyDepth dependencyDepth;
private final Collection<MavenArtifactRepositoryReference> repositoryReferences;
private final Element featureTemplate;
private String label;

public MavenLocation(Collection<MavenDependency> roots, Collection<String> includeDependencyScopes,
MissingManifestStrategy manifestStrategy, boolean includeSource,
Collection<BNDInstructions> instructions, DependencyDepth dependencyDepth,
Collection<MavenArtifactRepositoryReference> repositoryReferences, Element featureTemplate) {
Collection<MavenArtifactRepositoryReference> repositoryReferences, Element featureTemplate,
String label) {
this.roots = roots;
this.includeDependencyScopes = includeDependencyScopes;
this.manifestStrategy = manifestStrategy;
this.includeSource = includeSource;
this.instructions = instructions;
this.dependencyDepth = dependencyDepth;
this.repositoryReferences = repositoryReferences;
this.label = label;
this.featureTemplate = featureTemplate == null ? null : (Element) featureTemplate.cloneNode(true);
}

Expand Down Expand Up @@ -265,6 +269,30 @@ public DependencyDepth getIncludeDependencyDepth() {
return dependencyDepth;
}

@Override
public String getLabel() {
if (label != null && !label.isBlank()) {
return label;
}
if (featureTemplate != null) {
String featureLabel = featureTemplate.getAttribute("label");
if (featureLabel != null && !featureLabel.isBlank()) {
return featureLabel;
}
String featureId = featureTemplate.getAttribute("id");
if (featureId != null && !featureId.isBlank()) {
return featureId;
}
}
if (roots.size() == 1) {
MavenDependency dependency = roots.iterator().next();
return MessageFormat.format("{0}:{1} ({2})", dependency.getGroupId(), dependency.getArtifactId(),
dependency.getVersion());
} else {
return MessageFormat.format("{0} Maven Dependencies", roots.size());
}
}

}

private static final class MavenDependencyRoot implements MavenDependency {
Expand Down Expand Up @@ -668,7 +696,8 @@ private static MavenLocation parseMavenLocation(Element dom) {
Element featureTemplate = getChild(dom, "feature");
return new MavenLocation(parseRoots(dom, globalExcludes), scopes, parseManifestStrategy(dom),
Boolean.parseBoolean(dom.getAttribute("includeSource")), parseInstructions(dom),
parseDependencyDepth(dom, scope), parseRepositoryReferences(dom), featureTemplate);
parseDependencyDepth(dom, scope), parseRepositoryReferences(dom), featureTemplate,
dom.getAttribute("label"));
}

private static IULocation parseIULocation(Element dom) {
Expand Down

0 comments on commit c7bc195

Please sign in to comment.