Skip to content

Commit

Permalink
Add support for Product Update-site names and a test-case for that
Browse files Browse the repository at this point in the history
This a work-around to have
eclipse-equinox/p2#353 available now and can be
reverted once the mentioned change in P2 is available in Tycho.
  • Loading branch information
HannesWell committed Oct 21, 2023
1 parent a9b7b40 commit 954c96e
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,38 @@
import static org.eclipse.tycho.p2.tools.publisher.DependencySeedUtil.createSeed;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.equinox.internal.p2.metadata.TouchpointInstruction;
import org.eclipse.equinox.internal.p2.publisher.eclipse.IProductDescriptor;
import org.eclipse.equinox.internal.p2.publisher.eclipse.ProductFile;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.publisher.AdviceFileAdvice;
import org.eclipse.equinox.p2.publisher.IPublisherAction;
import org.eclipse.equinox.p2.publisher.IPublisherAdvice;
import org.eclipse.equinox.p2.publisher.eclipse.ConfigCUsAction;
import org.eclipse.equinox.p2.publisher.eclipse.IConfigAdvice;
import org.eclipse.equinox.p2.publisher.eclipse.ProductAction;
import org.eclipse.equinox.p2.publisher.eclipse.ProductFileAdvice;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.IRepositoryReference;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.spi.RepositoryReference;
import org.eclipse.tycho.ArtifactKey;
import org.eclipse.tycho.ArtifactType;
import org.eclipse.tycho.BuildFailureException;
Expand All @@ -41,6 +58,7 @@
import org.eclipse.tycho.p2.repository.PublishingRepository;
import org.eclipse.tycho.p2.tools.publisher.facade.PublishProductTool;
import org.eclipse.tycho.targetplatform.P2TargetPlatform;
import org.xml.sax.Attributes;

/**
* Tool for transforming product definition source files into p2 metadata and artifacts. Includes
Expand Down Expand Up @@ -77,7 +95,58 @@ public List<DependencySeed> publishProduct(File productFile, File launcherBinari

IPublisherAdvice[] advice = getProductSpecificAdviceFileAdvice(productFile, expandedProduct);

ProductAction action = new ProductAction(null, expandedProduct, flavor, launcherBinaries);
ProductAction action = new ProductAction(null, expandedProduct, flavor, launcherBinaries) {
//TODO: Remove this anonymous extension once https://github.com/eclipse-equinox/p2/pull/353 is available
@Override
protected IPublisherAction createConfigCUsAction() {
return new ConfigCUsAction(info, flavor, id, version) {
private static final Collection<String> PROPERTIES_TO_SKIP = Set.of("osgi.frameworkClassPath",
"osgi.framework", "osgi.bundles", "eof", "eclipse.p2.profile", "eclipse.p2.data.area",
"org.eclipse.update.reconcile", "org.eclipse.equinox.simpleconfigurator.configUrl");

@Override
protected String[] getConfigurationStrings(Collection<IConfigAdvice> configAdvice) {
String configurationData = ""; //$NON-NLS-1$
String unconfigurationData = ""; //$NON-NLS-1$
Set<String> properties = new HashSet<>();
for (IConfigAdvice advice : configAdvice) {
for (Entry<String, String> aProperty : advice.getProperties().entrySet()) {
String key = aProperty.getKey();
if (!PROPERTIES_TO_SKIP.contains(key) && !properties.contains(key)) {
properties.add(key);
Map<String, String> parameters = new LinkedHashMap<>();
parameters.put("propName", key); //$NON-NLS-1$
parameters.put("propValue", aProperty.getValue()); //$NON-NLS-1$
configurationData += TouchpointInstruction.encodeAction("setProgramProperty", //$NON-NLS-1$
parameters);
parameters.put("propValue", ""); //$NON-NLS-1$//$NON-NLS-2$
unconfigurationData += TouchpointInstruction.encodeAction("setProgramProperty", //$NON-NLS-1$
parameters);
}
}
if (advice instanceof ProductFileAdvice) {
for (IRepositoryReference repo : ((ProductFileAdvice) advice).getUpdateRepositories()) {
Map<String, String> parameters = new LinkedHashMap<>();
parameters.put("type", Integer.toString(repo.getType())); //$NON-NLS-1$
parameters.put("location", repo.getLocation().toString()); //$NON-NLS-1$
if (repo.getNickname() != null) {
parameters.put("name", repo.getNickname()); //$NON-NLS-1$
}
parameters.put("enabled", Boolean.toString( //$NON-NLS-1$
(repo.getOptions() & IRepository.ENABLED) == IRepository.ENABLED));
configurationData += TouchpointInstruction.encodeAction("addRepository", //$NON-NLS-1$
parameters);
parameters.remove("enabled"); //$NON-NLS-1$
unconfigurationData += TouchpointInstruction.encodeAction("removeRepository", //$NON-NLS-1$
parameters);
}
}
}
return new String[] { configurationData, unconfigurationData };
}
};
}
};
IMetadataRepository metadataRepository = publishingRepository.getMetadataRepository();
IArtifactRepository artifactRepository = publishingRepository
.getArtifactRepositoryForWriting(new ProductBinariesWriteSession(expandedProduct.getId()));
Expand Down Expand Up @@ -126,7 +195,54 @@ private static void addRootFeatures(ExpandedProduct product, List<DependencySeed

private static IProductDescriptor loadProductFile(File productFile) throws IllegalArgumentException {
try {
return new ProductFile(productFile.getAbsolutePath());
return new ProductFile(productFile.getAbsolutePath()) {
//TODO: Remove this anonymous extension once https://github.com/eclipse-equinox/p2/pull/353 is available
private static final int STATE_REPOSITORIES = 28;
private static final Field STATE_FILED;
static {
Field state = null;
try {
state = ProductFile.class.getDeclaredField("state");
state.trySetAccessible();
} catch (NoSuchFieldException | SecurityException e) {
}
STATE_FILED = state;
}

private int getState() {
try {
return (Integer) STATE_FILED.get(this);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException("Failed to get processing state", e);
}
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
int state = getState();
if (state == STATE_REPOSITORIES && "repository".equals(localName)) {
processRepositoryInformation(attributes);
} else {
super.startElement(uri, localName, qName, attributes);
}
}

private void processRepositoryInformation(Attributes attributes) {
try {
List<IRepositoryReference> repositories = getRepositoryEntries();
URI uri = URIUtil.fromString(attributes.getValue("location"));
String name = attributes.getValue("name");
boolean enabled = Boolean.parseBoolean(attributes.getValue("enabled"));
int options = enabled ? IRepository.ENABLED : IRepository.NONE;
// First add a metadata repository
repositories.add(new RepositoryReference(uri, name, IRepository.TYPE_METADATA, options));
// Now a colocated artifact repository
repositories.add(new RepositoryReference(uri, name, IRepository.TYPE_ARTIFACT, options));
} catch (URISyntaxException e) {
// ignore malformed URI's. These should have already been caught by the UI
}
}
};
} catch (Exception e) {
throw new BuildFailureException(
"Cannot parse product file " + productFile.getAbsolutePath() + ": " + e.getMessage(), e); //$NON-NLS-1$
Expand Down
24 changes: 24 additions & 0 deletions tycho-its/projects/product.update_repository/aProduct.product
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<?pde version="3.5"?>

<product uid="aProduct" id="org.eclipse.platform.ide" version="1.0.0" type="mixed" includeLaunchers="false" autoIncludeRequirements="true">

<configIni use="default">
</configIni>

<launcherArgs>
<vmArgsMac>-XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts
</vmArgsMac>
</launcherArgs>

<features>
<feature id="org.eclipse.equinox.p2.core.feature"/>
</features>

<repositories>
<repository location="https://foo.bar.org" enabled="true" />
<repository location="https://foo.bar.org/releases" name="Latest release" enabled="true" />
<repository location="https://foo.bar.org/snapshots" name="Latest snapshot" enabled="false" />
</repositories>

</product>
64 changes: 64 additions & 0 deletions tycho-its/projects/product.update_repository/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>foo.bar</groupId>
<artifactId>aProduct</artifactId>
<version>1.0.0</version>
<packaging>eclipse-repository</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>eclipse-latest</id>
<layout>p2</layout>
<url>${target-platform}</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-maven-plugin</artifactId>
<version>${tycho-version}</version>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-director-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>materialize-products</id>
<goals>
<goal>materialize-products</goal>
</goals>
<configuration>
<products>
<product>
<id>aProduct</id>
</product>
</products>
</configuration>
</execution>
<execution>
<id>archive-products</id>
<goals>
<goal>archive-products</goal>
</goals>
<configuration>
<products>
<product>
<id>aProduct</id>
</product>
</products>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,23 @@
*******************************************************************************/
package org.eclipse.tycho.test.product;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.maven.it.Verifier;
import org.eclipse.tycho.TargetEnvironment;
import org.eclipse.tycho.TychoConstants;
import org.eclipse.tycho.test.AbstractTychoIntegrationTest;
import org.junit.Test;
Expand Down Expand Up @@ -68,4 +73,52 @@ protected void checkPGP(Verifier verifier, String repositoryArtifacts) throws IO

}
}

@Test
public void testAdditionOfUpdateRepositories() throws Exception {
Verifier verifier = getVerifier("product.update_repository", true);
verifier.executeGoals(List.of("clean", "verify"));
verifier.verifyErrorFreeLog();
TargetEnvironment env = TargetEnvironment.getRunningEnvironment();
Path baseDir = Path.of(verifier.getBasedir());
Path productPath = baseDir.resolve("target/products/aProduct");
Path osPlatformPath = productPath.resolve(Path.of(env.getOs(), env.getWs(), env.getArch()));
if (env.getOs().equals("macosx")) {
osPlatformPath = osPlatformPath.resolve("Eclipse.app/Contents/Eclipse");
}
Path p2EnginePath = osPlatformPath.resolve("p2/org.eclipse.equinox.p2.engine");

List<UpdateSiteReference> expectedReferences = List.of(
new UpdateSiteReference("https://foo.bar.org", null, true),
new UpdateSiteReference("https://foo.bar.org/releases", "Latest release", true),
new UpdateSiteReference("https://foo.bar.org/snapshots", "Latest snapshot", false));

assertUpdateRepositoryReferences(expectedReferences,
p2EnginePath.resolve(".settings/org.eclipse.equinox.p2.artifact.repository.prefs"));
assertUpdateRepositoryReferences(expectedReferences,
p2EnginePath.resolve(".settings/org.eclipse.equinox.p2.metadata.repository.prefs"));

assertUpdateRepositoryReferences(expectedReferences, p2EnginePath.resolve(
"profileRegistry/DefaultProfile.profile/.data/.settings/org.eclipse.equinox.p2.artifact.repository.prefs"));
assertUpdateRepositoryReferences(expectedReferences, p2EnginePath.resolve(
"profileRegistry/DefaultProfile.profile/.data/.settings/org.eclipse.equinox.p2.metadata.repository.prefs"));
}

private record UpdateSiteReference(String uri, String name, boolean enabled) {
}

private static void assertUpdateRepositoryReferences(List<UpdateSiteReference> expectedReferences, Path path)
throws IOException {
List<String> prefLines = Files.readAllLines(path);
for (UpdateSiteReference reference : expectedReferences) {
String preferencePrefix = "repositories/" + reference.uri.replace(":", "\\:").replace("/", "_");
assertThat(prefLines, hasItem(preferencePrefix + "/uri=" + reference.uri.replace(":", "\\:")));
assertThat(prefLines, hasItem(preferencePrefix + "/enabled=" + reference.enabled));
if (reference.name != null) {
assertThat(prefLines, hasItem(preferencePrefix + "/nickname=" + reference.name));
} else {
assertFalse(prefLines.stream().anyMatch(l -> l.startsWith(preferencePrefix + "/nickname=")));
}
}
}
}

0 comments on commit 954c96e

Please sign in to comment.