From 79075eed3a5682b3ca9df7ba99be4a7bdaeecc15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Tue, 23 Jan 2024 15:07:56 +0100 Subject: [PATCH] Fork Repo2Runnable / RepositoryDescriptor from P2 --- .../p2tools/copiedfromp2/Repo2Runnable.java | 415 ++++++++++++++++++ .../copiedfromp2/RepositoryDescriptor.java | 136 ++++++ 2 files changed, 551 insertions(+) create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Repo2Runnable.java create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/RepositoryDescriptor.java diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Repo2Runnable.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Repo2Runnable.java new file mode 100644 index 0000000000..38a21b6cb3 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Repo2Runnable.java @@ -0,0 +1,415 @@ +/******************************************************************************* + * Copyright (c) 2009, 2018 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Sonatype, Inc. - ongoing development + * Red Hat, Inc. - fragment creation, Bug 460967 + *******************************************************************************/ +package org.eclipse.equinox.p2.internal.repository.tools; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.URIUtil; +import org.eclipse.equinox.app.IApplication; +import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.equinox.frameworkadmin.BundleInfo; +import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.eclipse.equinox.internal.p2.engine.DownloadManager; +import org.eclipse.equinox.internal.p2.engine.InstallableUnitOperand; +import org.eclipse.equinox.internal.p2.engine.InstallableUnitPhase; +import org.eclipse.equinox.internal.p2.engine.Messages; +import org.eclipse.equinox.internal.p2.engine.Phase; +import org.eclipse.equinox.internal.p2.engine.PhaseSet; +import org.eclipse.equinox.internal.p2.engine.ProfileWriter; +import org.eclipse.equinox.internal.p2.engine.ProfileXMLConstants; +import org.eclipse.equinox.internal.p2.engine.phases.Collect; +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.equinox.p2.engine.IEngine; +import org.eclipse.equinox.p2.engine.IProfile; +import org.eclipse.equinox.p2.engine.IProfileRegistry; +import org.eclipse.equinox.p2.engine.IProvisioningPlan; +import org.eclipse.equinox.p2.engine.ProvisioningContext; +import org.eclipse.equinox.p2.engine.spi.ProvisioningAction; +import org.eclipse.equinox.p2.internal.repository.tools.RepositoryDescriptor; +import org.eclipse.equinox.p2.metadata.IArtifactKey; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IProvidedCapability; +import org.eclipse.equinox.p2.query.IQueryResult; +import org.eclipse.equinox.p2.query.QueryUtil; +import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository; +import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager; +import org.eclipse.equinox.p2.repository.artifact.IArtifactRequest; +import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; +import org.eclipse.equinox.simpleconfigurator.manipulator.SimpleConfiguratorManipulator; + +/** + * The transformer takes an existing p2 repository (local or remote), iterates over its list of IUs, + * and fetches all of the corresponding artifacts to a user-specified location. Once fetched, the + * artifacts will be in "runnable" form... that is directory-based bundles will be extracted into + * folders. + * + * @since 1.0 + */ +@SuppressWarnings("nls") +public class Repo2Runnable extends AbstractApplication implements IApplication { + private static final String NATIVE_ARTIFACTS = "nativeArtifacts"; //$NON-NLS-1$ + private static final String NATIVE_TYPE = "org.eclipse.equinox.p2.native"; //$NON-NLS-1$ + private static final String PARM_OPERAND = "operand"; //$NON-NLS-1$ + private static final String PARM_PROFILE = "profile"; //$NON-NLS-1$ + + private boolean createFragments; + private boolean flagAsRunnable = false; + + protected class CollectNativesAction extends ProvisioningAction { + @Override + public IStatus execute(Map parameters) { + InstallableUnitOperand operand = (InstallableUnitOperand) parameters.get(PARM_OPERAND); + IInstallableUnit installableUnit = operand.second(); + + IArtifactRepositoryManager manager = getArtifactRepositoryManager(); + Collection toDownload = installableUnit.getArtifacts(); + if (toDownload == null) + return Status.OK_STATUS; + + @SuppressWarnings("unchecked") + List artifactRequests = (List) parameters.get(NATIVE_ARTIFACTS); + + IProfile profile = (IProfile) parameters.get(PARM_PROFILE); + String statsParameter = null; + if (profile != null) + statsParameter = profile.getProperty(IProfile.PROP_STATS_PARAMETERS); + + for (IArtifactKey keyToDownload : toDownload) { + IArtifactRequest request = manager.createMirrorRequest(keyToDownload, destinationArtifactRepository, + null, null, statsParameter); + artifactRequests.add(request); + } + return Status.OK_STATUS; + } + + @Override + public IStatus undo(Map parameters) { + // nothing to do for now + return Status.OK_STATUS; + } + } + + protected class CollectNativesPhase extends InstallableUnitPhase { + public CollectNativesPhase(int weight) { + super(NATIVE_ARTIFACTS, weight); + } + + @Override + protected List getActions(InstallableUnitOperand operand) { + IInstallableUnit unit = operand.second(); + if (unit.getTouchpointType().getId().equals(NATIVE_TYPE)) { + return Collections.singletonList(new CollectNativesAction()); + } + return null; + } + + @Override + protected IStatus initializePhase(IProgressMonitor monitor, IProfile profile, Map parameters) { + parameters.put(NATIVE_ARTIFACTS, new ArrayList<>()); + return null; + } + + @Override + protected IStatus completePhase(IProgressMonitor monitor, IProfile profile, Map parameters) { + @SuppressWarnings("unchecked") + List artifactRequests = (List) parameters.get(NATIVE_ARTIFACTS); + ProvisioningContext context = (ProvisioningContext) parameters.get(PARM_CONTEXT); + IProvisioningAgent agent = (IProvisioningAgent) parameters.get(PARM_AGENT); + DownloadManager dm = new DownloadManager(context, agent); + for (IArtifactRequest request : artifactRequests) { + dm.add(request); + } + return dm.start(monitor); + } + } + + // the list of IUs that we actually transformed... could have come from the repo + // or have been user-specified. + private Collection processedIUs = new ArrayList<>(); + + /* + * Perform the transformation. + */ + @Override + public IStatus run(IProgressMonitor monitor) throws ProvisionException { + SubMonitor progress = SubMonitor.convert(monitor, 5); + + initializeRepos(progress); + + // ensure all the right parameters are set + validate(); + + // figure out which IUs we need to process + collectIUs(progress.newChild(1)); + + // call the engine with only the "collect" phase so all we do is download + IProfile profile = createProfile(); + try { + IEngine engine = (IEngine) agent.getService(IEngine.SERVICE_NAME); + if (engine == null) + throw new ProvisionException(Messages.exception_noEngineService); + ProvisioningContext context = new ProvisioningContext(agent); + context.setMetadataRepositories(getRepositories(true)); + context.setArtifactRepositories(getRepositories(false)); + IProvisioningPlan plan = engine.createPlan(profile, context); + for (IInstallableUnit iu : processedIUs) { + plan.addInstallableUnit(iu); + } + IStatus result = engine.perform(plan, getPhaseSet(), progress.newChild(1)); + PhaseSet nativeSet = getNativePhase(); + if (nativeSet != null) + engine.perform(plan, nativeSet, progress.newChild(1)); + + // publish the metadata to a destination - if requested + publishMetadata(progress.newChild(1)); + + setRunnableProperty(destinationArtifactRepository); + // return the resulting status + + if (createFragments) { + File parentDir = new File(destinationArtifactRepository.getLocation().toString().substring(5)); + File pluginsDir = new File(parentDir, "plugins"); + File fragmentInfo = new File(parentDir, "fragment.info"); + HashSet bundles = new HashSet<>(); + try { + for (IInstallableUnit iu : processedIUs) { + if (iu.getId().equals("a.jre")) + continue; + Collection providedCapabilities = iu.getProvidedCapabilities(); + for (IProvidedCapability cap : providedCapabilities) { + if ("org.eclipse.equinox.p2.eclipse.type".equals(cap.getNamespace())) { + if ("bundle".equals(cap.getName())) { + File candidate = new File(pluginsDir, iu.getId() + "_" + iu.getVersion()); + if (candidate.exists()) { + bundles.add(new BundleInfo(iu.getId(), iu.getVersion().toString(), + candidate.toURI(), 4, false)); + } + candidate = new File(pluginsDir, iu.getId() + "_" + iu.getVersion() + ".jar"); + if (candidate.exists()) { + bundles.add(new BundleInfo(iu.getId(), iu.getVersion().toString(), + candidate.toURI(), 4, false)); + } + break; + } + } + } + } + SimpleConfiguratorManipulator simpleManipulator = ServiceHelper + .getService(Activator.getBundleContext(), SimpleConfiguratorManipulator.class); + simpleManipulator.saveConfiguration(bundles.toArray(new BundleInfo[0]), fragmentInfo, + parentDir.toURI()); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return result; + } finally { + // cleanup by removing the temporary profile and unloading the repos which were new + removeProfile(profile); + finalizeRepositories(); + } + } + + static class Writer extends ProfileWriter { + + public Writer(OutputStream output) { + super(output, new ProcessingInstruction[] { ProcessingInstruction + .makeTargetVersionInstruction(PROFILE_TARGET, ProfileXMLConstants.CURRENT_VERSION) }); + } + } + + private void setRunnableProperty(IArtifactRepository destinationArtifactRepository) { + if (flagAsRunnable) + destinationArtifactRepository.setProperty(IArtifactRepository.PROP_RUNNABLE, Boolean.TRUE.toString(), + new NullProgressMonitor()); + } + + protected URI[] getRepositories(boolean metadata) { + List repos = new ArrayList<>(); + for (RepositoryDescriptor repo : sourceRepositories) { + if (metadata ? repo.isMetadata() : repo.isArtifact()) + repos.add(repo.getRepoLocation()); + } + return repos.toArray(new URI[repos.size()]); + } + + protected PhaseSet getPhaseSet() { + return new PhaseSet(new Phase[] { new Collect(100) }) { + /* nothing to override */}; + } + + protected PhaseSet getNativePhase() { + return new PhaseSet(new Phase[] { new CollectNativesPhase(100) }) { + /* nothing to override */}; + } + + /* + * Figure out exactly which IUs we have to process. + */ + private void collectIUs(IProgressMonitor monitor) throws ProvisionException { + // if the user told us exactly which IUs to process, then just set it and return. + if (sourceIUs != null && !sourceIUs.isEmpty()) { + processedIUs = sourceIUs; + return; + } + // get all IUs from the repos + if (!hasMetadataSources()) + throw new ProvisionException(Messages.exception_needIUsOrNonEmptyRepo); + + Iterator itor = getAllIUs(getCompositeMetadataRepository(), monitor).iterator(); + while (itor.hasNext()) + processedIUs.add(itor.next()); + + if (processedIUs.isEmpty()) + throw new ProvisionException(Messages.exception_needIUsOrNonEmptyRepo); + } + + /* + * If there is a destination metadata repository set, then add all our transformed IUs to it. + */ + private void publishMetadata(IProgressMonitor monitor) { + // publishing the metadata is optional + if (destinationMetadataRepository == null) + return; + destinationMetadataRepository.addInstallableUnits(processedIUs); + } + + /* + * Return a collector over all the IUs contained in the given repository. + */ + private IQueryResult getAllIUs(IMetadataRepository repository, IProgressMonitor monitor) { + SubMonitor progress = SubMonitor.convert(monitor, 2); + try { + return repository.query(QueryUtil.createIUAnyQuery(), progress.newChild(1)); + } finally { + progress.done(); + } + } + + /* + * Remove the given profile from the profile registry. + */ + private void removeProfile(IProfile profile) throws ProvisionException { + IProfileRegistry registry = Activator.getProfileRegistry(); + registry.removeProfile(profile.getProfileId()); + } + + /* + * Create and return a new profile. + */ + private IProfile createProfile() throws ProvisionException { + Map properties = new HashMap<>(); + properties.put(IProfile.PROP_CACHE, + URIUtil.toFile(destinationArtifactRepository.getLocation()).getAbsolutePath()); + properties.put(IProfile.PROP_INSTALL_FOLDER, + URIUtil.toFile(destinationArtifactRepository.getLocation()).getAbsolutePath()); + IProfileRegistry registry = Activator.getProfileRegistry(); + return registry.addProfile(System.currentTimeMillis() + "-" + Math.random(), properties); //$NON-NLS-1$ + } + + @Override + public Object start(IApplicationContext context) throws Exception { + String[] args = (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS); + processCommandLineArgs(args); + // perform the transformation + run(null); + return IApplication.EXIT_OK; + } + + /* + * Iterate over the command-line arguments and prepare the transformer for processing. + */ + private void processCommandLineArgs(String[] args) throws URISyntaxException { + if (args == null) + return; + for (int i = 0; i < args.length; i++) { + String option = args[i]; + String arg = null; + if (i != args.length - 1 && !args[i + 1].startsWith("-")) { //$NON-NLS-1$ + arg = args[++i]; + } + + if (option.equalsIgnoreCase("-source")) { //$NON-NLS-1$ + RepositoryDescriptor source = new RepositoryDescriptor(); + source.setLocation(URIUtil.fromString(arg)); + addSource(source); + } + + if (option.equalsIgnoreCase("-destination")) { //$NON-NLS-1$ + RepositoryDescriptor destination = new RepositoryDescriptor(); + destination.setLocation(URIUtil.fromString(arg)); + addDestination(destination); + } + + if (option.equalsIgnoreCase("-flagAsRunnable")) { //$NON-NLS-1$ + setFlagAsRunnable(true); + } + + if (option.equalsIgnoreCase("-createFragments")) { //$NON-NLS-1$ + setCreateFragments(true); + } + } + } + + public void setFlagAsRunnable(boolean runnable) { + flagAsRunnable = runnable; + } + + /* + * Ensure all mandatory parameters have been set. Throw an exception if there are any missing. + * We don't require the user to specify the artifact repository here, we will default to the + * ones already registered in the manager. (callers are free to add more if they wish) + */ + private void validate() throws ProvisionException { + if (!hasMetadataSources() && sourceIUs == null) + throw new ProvisionException(Messages.exception_needIUsOrNonEmptyRepo); + if (destinationArtifactRepository == null) + throw new ProvisionException(Messages.exception_needDestinationRepo); + } + + @Override + public void stop() { + // nothing to do + } + + public void setCreateFragments(boolean createFragments) { + this.createFragments = createFragments; + + } +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/RepositoryDescriptor.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/RepositoryDescriptor.java new file mode 100644 index 0000000000..8ca70dd779 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/RepositoryDescriptor.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.internal.repository.tools; + +import java.net.URI; +import org.eclipse.equinox.internal.p2.repository.helpers.RepositoryHelper; +import org.eclipse.equinox.p2.repository.IRepository; +import org.eclipse.osgi.util.NLS; + +public class RepositoryDescriptor { + + public static final int TYPE_BOTH = -1; + public static final String KIND_ARTIFACT = "A"; //$NON-NLS-1$ + public static final String KIND_METADATA = "M"; //$NON-NLS-1$ + + private boolean compressed = true; + private boolean append = true; + private String name = null; + private URI location = null; + private URI format = null; + private int kind = TYPE_BOTH; + private URI originalLocation = null; + private boolean optional = false; + private String atomic = null; + + public void setCompressed(boolean compress) { + compressed = compress; + } + + public void setName(String repoName) { + name = repoName; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + public boolean isOptional() { + return optional; + } + + public void setLocation(URI repoLocation) { + originalLocation = repoLocation; + location = RepositoryHelper.localRepoURIHelper(repoLocation); + } + + public void setFormat(URI format) { + this.format = RepositoryHelper.localRepoURIHelper(format); + } + + public void setAppend(boolean appendMode) { + append = appendMode; + } + + public boolean isCompressed() { + return compressed; + } + + public boolean isAppend() { + return append; + } + + public String getName() { + return name; + } + + public URI getRepoLocation() { + return location; + } + + public URI getOriginalRepoLocation() { + return originalLocation; + } + + public URI getFormat() { + return format; + } + + public int getKind() { + return kind; + } + + public boolean isBoth() { + return kind == TYPE_BOTH; + } + + public boolean isArtifact() { + return kind == TYPE_BOTH || kind == IRepository.TYPE_ARTIFACT; + } + + public boolean isMetadata() { + return kind == TYPE_BOTH || kind == IRepository.TYPE_METADATA; + } + + public void setKind(String repoKind) { + kind = determineKind(repoKind); + } + + public void setAtomic(String booleanForAtomic) { + atomic = booleanForAtomic; + } + + public String getAtomic() { + return atomic; + } + + /* + * Determine the repository type + */ + public static int determineKind(String repoKind) { + if (kindMatches(repoKind, KIND_METADATA)) + return IRepository.TYPE_METADATA; + else if (kindMatches(repoKind, KIND_ARTIFACT)) + return IRepository.TYPE_ARTIFACT; + + throw new IllegalArgumentException(NLS.bind(Messages.unknown_repository_type, repoKind)); + } + + /* + * Determine if the repository kind matches the identifier kind + */ + public static boolean kindMatches(String repoKind, String kindIdentifier) { + return repoKind.startsWith(kindIdentifier) || repoKind.startsWith(kindIdentifier.toLowerCase()); + } +}