diff --git a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java index 18f7cdfcb4..6b1aaeb52f 100644 --- a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java +++ b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.tycho.p2maven.repository; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -25,6 +26,8 @@ import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.logging.Logger; import org.eclipse.core.runtime.IStatus; +import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepositoryFactory; +import org.eclipse.equinox.internal.p2.metadata.repository.SimpleMetadataRepositoryFactory; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.core.ProvisionException; import org.eclipse.equinox.p2.metadata.IArtifactKey; @@ -71,6 +74,21 @@ public IArtifactRepository getArtifactRepository(Repository repository) return getArtifactRepository(new URI(repository.getUrl()), repository.getId()); } + /** + * Loads the {@link IArtifactRepository} from the given {@link File}, this + * method does NOT check the type of the repository! + * + * @param repository + * @param flags + * @return the {@link IArtifactRepository} for the given {@link Repository} + * @throws ProvisionException if loading the repository failed + */ + public IArtifactRepository getArtifactRepository(File repository, int flags) throws ProvisionException { + SimpleArtifactRepositoryFactory factory = new SimpleArtifactRepositoryFactory(); + factory.setAgent(agent); + return factory.load(repository.toURI(), flags, null); + } + /** * Loads the {@link IArtifactRepository} from the given {@link Repository}, this * method does NOT check the type of the repository! @@ -111,6 +129,21 @@ public IMetadataRepository getMetadataRepository(Repository repository) return getMetadataRepositor(new URI(repository.getUrl()), repository.getId()); } + /** + * Loads the {@link IMetadataRepository} from the given {@link File}, this + * method does NOT check the type of the repository! + * + * @param repository + * @param flags the flags to use + * @return the {@link IMetadataRepository} for the given {@link Repository} + * @throws ProvisionException if loading the repository failed + */ + public IMetadataRepository getMetadataRepository(File repository, int flags) throws ProvisionException { + SimpleMetadataRepositoryFactory factory = new SimpleMetadataRepositoryFactory(); + factory.setAgent(agent); + return factory.load(repository.toURI(), flags, null); + } + public IQueryable getCompositeMetadataRepository(Collection repositories) throws ProvisionException, URISyntaxException { if (repositories.size() == 1) { diff --git a/src/site/markdown/Category.md b/src/site/markdown/Repositories.md similarity index 78% rename from src/site/markdown/Category.md rename to src/site/markdown/Repositories.md index c8517dc96e..7984001a8c 100644 --- a/src/site/markdown/Category.md +++ b/src/site/markdown/Repositories.md @@ -1,4 +1,8 @@ -# Category +# Repositories + +Repositories (also knows as P2 Updatesites) contain artifacts and metadata to install content into eclipse or use them in a Tycho build. + +## Create Repositories using category.xml A category.xml file can be used to define which content is placed into a p2 repository. It can also specify how to display the content in the p2 installation dialog. @@ -68,3 +72,15 @@ The following is an example, demonstrating a complex category definition. ``` You can read more about P2 Query Syntax [here](https://wiki.eclipse.org/Equinox/p2/Query_Language_for_p2). + +## Managing Repositories + +Tycho offers some tools to manage existing repositories as a replacement for the ant-tasks described [here](https://help.eclipse.org/latest/topic/org.eclipse.platform.doc.isv/guide/p2_repositorytasks.htm) + +### repo2runnable + +See [tycho-p2-repository:repo-to-runnable](tycho-p2-repository-plugin/repo-to-runnable-mojo.html) + +### remove.iu + +See [tycho-p2-repository:remove-iu](tycho-p2-repository-plugin/remove-iu-mojo.html) diff --git a/src/site/site.xml b/src/site/site.xml index fd348f6ad6..a4cb1d1d56 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -20,7 +20,11 @@ + + + + diff --git a/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/IUDescription.java b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/IUDescription.java new file mode 100644 index 0000000000..77e2327ece --- /dev/null +++ b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/IUDescription.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.tycho.plugins.p2.repository; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.QueryUtil; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; + +public class IUDescription { + static private final String QUERY_PROPERTY = "property"; //$NON-NLS-1$ + static private final String QUERY_NAME = "name"; //$NON-NLS-1$ + static private final String QUERY_VALUE = "value"; //$NON-NLS-1$ + static private final String ANT_PREFIX = "${"; //$NON-NLS-1$ + private String id; + private String version; + private String queryString; + private boolean required = true; + private String artifactFilter = null; + + public IUDescription() { + super(); + } + + public void setId(String value) { + if (value != null && !value.startsWith(ANT_PREFIX)) + this.id = value; + } + + public void setVersion(String value) { + if (value != null && !value.startsWith(ANT_PREFIX)) + this.version = value; + } + + public void setQuery(String query) { + if (query != null && !query.startsWith(ANT_PREFIX)) + this.queryString = query; + } + + public void setArtifacts(String filter) { + if (filter != null && !filter.startsWith(ANT_PREFIX)) + this.artifactFilter = filter; + } + + public Filter getArtifactFilter() throws InvalidSyntaxException { + if (artifactFilter != null) + return FrameworkUtil.createFilter(artifactFilter); + return null; + } + + public void setRequired(boolean required) { + this.required = required; + } + + public boolean isRequired() { + return required; + } + + public String getQueryString() { + return queryString; + } + + public String getId() { + return id; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder("Installable Unit ["); //$NON-NLS-1$ + if (id != null) { + buffer.append(" id="); //$NON-NLS-1$ + buffer.append(id); + } + if (version != null) { + buffer.append(" version="); //$NON-NLS-1$ + buffer.append(version); + } + if (queryString != null) { + buffer.append(" query="); //$NON-NLS-1$ + buffer.append(queryString); + } + buffer.append(" ]"); //$NON-NLS-1$ + return buffer.toString(); + } + + public IQuery createQuery() { + List> queries = new ArrayList<>(); + if (id != null) { + if (version == null || version.length() == 0) { + // Get the latest version of the iu + queries.add(QueryUtil.createLatestQuery(QueryUtil.createIUQuery(id))); + } else { + Version iuVersion = Version.parseVersion(version); + queries.add(QueryUtil.createIUQuery(id, iuVersion)); + } + } + + IQuery iuQuery = processQueryString(); + if (iuQuery != null) + queries.add(iuQuery); + + if (queries.size() == 1) + return queries.get(0); + + IQuery query = QueryUtil.createPipeQuery(queries); + return query; + } + + private IQuery processQueryString() { + if (queryString == null) + return null; + int startIdx = queryString.indexOf('['); + int endIdx = queryString.lastIndexOf(']'); + if (startIdx == -1 || endIdx == -1 || endIdx < startIdx) + return null; + String element = queryString.substring(0, startIdx); + Map attributes = processQueryAttributes(queryString.substring(startIdx + 1, endIdx)); + if (element.equals(QUERY_PROPERTY)) { + String name = attributes.get(QUERY_NAME); + String value = attributes.get(QUERY_VALUE); + if (name == null) + return null; + if (value == null) + value = QueryUtil.ANY; + return QueryUtil.createIUPropertyQuery(name, value); + } + + return null; + } + + private Map processQueryAttributes(String attributes) { + if (attributes == null || attributes.length() == 0) + return Collections.emptyMap(); + + Map result = new HashMap<>(); + int start = 0; + int idx = 0; + while ((idx = attributes.indexOf('@', start)) > -1) { + int equals = attributes.indexOf('=', idx); + int startQuote = attributes.indexOf('\'', equals); + int endQuote = attributes.indexOf('\'', startQuote + 1); + if (equals == -1 || startQuote <= equals || endQuote <= startQuote) + break; + String key = attributes.substring(idx + 1, equals).trim(); + String value = attributes.substring(startQuote + 1, endQuote); + result.put(key, value); + + start = endQuote + 1; + } + return result; + } +} diff --git a/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/RemoveIUMojo.java b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/RemoveIUMojo.java new file mode 100644 index 0000000000..ad68cacdcf --- /dev/null +++ b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/RemoveIUMojo.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2009, 2024 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 implementation in P2 as an ant task + * Christoph Läubrich - migration to maven-mojo + *******************************************************************************/ +package org.eclipse.tycho.plugins.p2.repository; + +import java.io.File; +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.equinox.p2.metadata.IArtifactKey; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.query.IQuery; +import org.eclipse.equinox.p2.query.IQueryResult; +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.metadata.IMetadataRepository; +import org.eclipse.tycho.p2maven.repository.P2RepositoryManager; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; + +/** + * Mojo that provides the p2.remove.iu ant task described here. + */ +@Mojo(name = "remove-iu") +public class RemoveIUMojo extends AbstractRepositoryMojo { + + private static final String CLASSIFIER = "classifier"; //$NON-NLS-1$ + private static final String ID = "id"; //$NON-NLS-1$ + private static final String VERSION = "version"; //$NON-NLS-1$ + + @Component + private P2RepositoryManager repositoryManager; + + @Parameter + private List iu; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (iu == null || iu.isEmpty()) { + return; + } + File location = getAssemblyRepositoryLocation(); + try { + IArtifactRepository artifactRepository = repositoryManager.getArtifactRepository(location, + IRepositoryManager.REPOSITORY_HINT_MODIFIABLE); + IMetadataRepository metadataRepository = repositoryManager.getMetadataRepository(location, + IRepositoryManager.REPOSITORY_HINT_MODIFIABLE); + metadataRepository.executeBatch(m -> { + artifactRepository.executeBatch(m2 -> { + removeIUs(metadataRepository, artifactRepository, iu, getLog()); + }, m); + }, null); + } catch (ProvisionException e) { + throw new MojoFailureException("Loading repository failed", e); + } + + } + + private static void removeIUs(IMetadataRepository repository, IArtifactRepository artifacts, + List iuTasks, Log log) { + final Set toRemove = new HashSet<>(); + for (IUDescription iu : iuTasks) { + IQuery iuQuery = iu.createQuery(); + + IQueryResult queryResult = repository.query(iuQuery, null); + + if (queryResult.isEmpty()) { + log.warn(String.format("Unable to find %s.", iu.toString())); + } else { + for (Iterator iterator = queryResult.iterator(); iterator.hasNext();) { + IInstallableUnit unit = iterator.next(); + Collection keys = unit.getArtifacts(); + Filter filter = null; + try { + filter = iu.getArtifactFilter(); + } catch (InvalidSyntaxException e) { + log.warn(String.format("Invalid filter format, skipping %s.", iu.toString())); + continue; + } + //we will only remove the metadata if all artifacts were removed + boolean removeMetadata = (filter != null ? keys.size() > 0 : true); + for (IArtifactKey key : keys) { + if (filter == null) { + artifacts.removeDescriptor(key, new NullProgressMonitor()); + } else { + IArtifactDescriptor[] descriptors = artifacts.getArtifactDescriptors(key); + for (IArtifactDescriptor descriptor : descriptors) { + if (filter.match(createDictionary(descriptor))) { + artifacts.removeDescriptor(descriptor, new NullProgressMonitor()); + } else { + removeMetadata = false; + } + } + } + } + if (removeMetadata) { + toRemove.add(unit); + } + } + } + } + + if (toRemove.size() > 0) { + repository.removeInstallableUnits(toRemove); + } + } + + private static Dictionary createDictionary(IArtifactDescriptor descriptor) { + Hashtable result = new Hashtable<>(5); + result.putAll(descriptor.getProperties()); + IArtifactKey key = descriptor.getArtifactKey(); + result.put(CLASSIFIER, key.getClassifier()); + result.put(ID, key.getId()); + result.put(VERSION, key.getVersion()); + return result; + } +}