diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 0e0f5a96d8..6aa4d7bda2 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -86,6 +86,36 @@ You can enable this for example like this:
```
+### New `remove-iu` mojo
+
+This is a replacement for the [p2.remove.iu ant task](https://help.eclipse.org/latest/topic/org.eclipse.platform.doc.isv/guide/p2_repositorytasks.htm), example:
+
+```xml
+
+ org.eclipse.tycho
+ tycho-p2-repository-plugin
+ ${tycho-version}
+
+
+ remove-iu
+
+ remove-iu
+
+ package
+
+
+
+ property[@name='org.eclipse.equinox.p2.name' @value='Uncategorized']
+
+
+ eclipse-junit-tests
+
+
+
+
+
+
+```
### New `repo-to-runnable` mojo
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 f6a7e056bc..30a0d0bef7 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;
@@ -76,6 +79,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!
@@ -116,6 +134,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 6b0bc13256..0a2fdff149 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -20,7 +20,11 @@
-
+
+ -
+
+
-
diff --git a/tycho-its/projects/p2Repository.removeUI/category.xml b/tycho-its/projects/p2Repository.removeUI/category.xml
new file mode 100644
index 0000000000..ed5d8bb3fb
--- /dev/null
+++ b/tycho-its/projects/p2Repository.removeUI/category.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tycho-its/projects/p2Repository.removeUI/pom.xml b/tycho-its/projects/p2Repository.removeUI/pom.xml
new file mode 100644
index 0000000000..5d22b1e3c2
--- /dev/null
+++ b/tycho-its/projects/p2Repository.removeUI/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ tycho-its-project.p2Repository
+ testrepo
+ 1.0.0
+
+
+ 5.0.0-SNAPSHOT
+
+ eclipse-repository
+
+
+
+
+ org.eclipse.tycho
+ tycho-maven-plugin
+ ${tycho-version}
+ true
+
+
+ org.eclipse.tycho
+ tycho-p2-repository-plugin
+ ${tycho-version}
+
+ false
+
+
+
+
+ remove-iu
+
+ package
+
+
+
+ property[@name='org.eclipse.equinox.p2.name' @value='Uncategorized']
+
+
+ a.jre.javase
+
+
+
+
+
+
+
+
+
+
+
+ test-data-repo
+ p2
+ ${test-data-repo}
+
+
+
+
diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/P2ExtrasPlugin.java b/tycho-its/src/test/java/org/eclipse/tycho/test/P2ExtrasPluginTest.java
similarity index 97%
rename from tycho-its/src/test/java/org/eclipse/tycho/test/P2ExtrasPlugin.java
rename to tycho-its/src/test/java/org/eclipse/tycho/test/P2ExtrasPluginTest.java
index b83d4de6fa..a9b4238b43 100644
--- a/tycho-its/src/test/java/org/eclipse/tycho/test/P2ExtrasPlugin.java
+++ b/tycho-its/src/test/java/org/eclipse/tycho/test/P2ExtrasPluginTest.java
@@ -31,7 +31,7 @@
import de.pdark.decentxml.XMLIOSource;
import de.pdark.decentxml.XMLParser;
-public class P2ExtrasPlugin extends AbstractTychoIntegrationTest {
+public class P2ExtrasPluginTest extends AbstractTychoIntegrationTest {
@Test
public void testBaseline() throws Exception {
@@ -171,7 +171,7 @@ private static boolean hasChildWithZippedAttribute(Element element) {
if ("zipped".equals(element.getAttributeValue("key"))) {
return true;
}
- return element.getChildren().stream().anyMatch(P2ExtrasPlugin::hasChildWithZippedAttribute);
+ return element.getChildren().stream().anyMatch(P2ExtrasPluginTest::hasChildWithZippedAttribute);
}
}
diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/P2RepositoryPluginTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/P2RepositoryPluginTest.java
new file mode 100644
index 0000000000..dd098f0022
--- /dev/null
+++ b/tycho-its/src/test/java/org/eclipse/tycho/test/P2RepositoryPluginTest.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2024 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.test;
+
+import static org.junit.Assert.assertNotEquals;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.maven.it.Verifier;
+import org.eclipse.tycho.test.util.P2RepositoryTool;
+import org.eclipse.tycho.test.util.P2RepositoryTool.IU;
+import org.eclipse.tycho.test.util.P2RepositoryTool.IdAndVersion;
+import org.eclipse.tycho.test.util.ResourceUtil;
+import org.junit.Test;
+
+public class P2RepositoryPluginTest extends AbstractTychoIntegrationTest {
+
+ @Test
+ public void testP2RemoveUI() throws Exception {
+ Verifier verifier = getVerifier("/p2Repository.removeUI");
+ verifier.addCliOption("-Dtest-data-repo=" + ResourceUtil.P2Repositories.ECLIPSE_352);
+ verifier.executeGoals(List.of("clean", "package"));
+ verifier.verifyErrorFreeLog();
+ P2RepositoryTool p2Repo = P2RepositoryTool.forEclipseRepositoryModule(new File(verifier.getBasedir()));
+ List allUnits = p2Repo.getAllUnits();
+ for (IdAndVersion idAndVersion : allUnits) {
+ IU iu = p2Repo.getIU(idAndVersion.id(), idAndVersion.version());
+ for (String prop : iu.getProperties()) {
+ assertNotEquals("org.eclipse.equinox.p2.name=Uncategorized", prop);
+ }
+ }
+ }
+
+}
diff --git a/tycho-maven-plugin/src/main/resources/META-INF/maven/extension.xml b/tycho-maven-plugin/src/main/resources/META-INF/maven/extension.xml
index 1648eaff0f..d0a2caf423 100644
--- a/tycho-maven-plugin/src/main/resources/META-INF/maven/extension.xml
+++ b/tycho-maven-plugin/src/main/resources/META-INF/maven/extension.xml
@@ -18,10 +18,13 @@
org.eclipse.equinox.p2.publisher
org.eclipse.equinox.p2.publisher.eclipse
org.eclipse.equinox.p2.query
+ org.eclipse.equinox.p2.repository
org.eclipse.equinox.p2.repository.artifact
org.eclipse.equinox.p2.repository.metadata
org.eclipse.equinox.internal.p2.metadata
org.eclipse.equinox.internal.p2.publisher.eclipse
+ org.eclipse.core.runtime.IProgressMonitor
+ org.eclipse.core.runtime.NullProgressMonitor
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;
+ }
+}