Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Product Update-site names #2929

Merged
merged 1 commit into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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=")));
}
}
}
}
Loading