From 98fdd158296ad38b940100afc0ce1acef813956e Mon Sep 17 00:00:00 2001 From: Hannes Wellmann Date: Sun, 15 Oct 2023 10:45:02 +0200 Subject: [PATCH] Add support for Product Update-site names and a test-case for it This a work-around to have https://github.com/eclipse-equinox/p2/pull/353 available now and can be reverted once the mentioned change in P2 is available in Tycho. --- .../publisher/PublishProductToolImpl.java | 120 +++++++++++++++++- .../aProduct.product | 24 ++++ .../product.update_repository/pom.xml | 64 ++++++++++ .../tycho/test/product/ProductBuildTest.java | 53 ++++++++ 4 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 tycho-its/projects/product.update_repository/aProduct.product create mode 100644 tycho-its/projects/product.update_repository/pom.xml diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/publisher/PublishProductToolImpl.java b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/publisher/PublishProductToolImpl.java index 7ae086009b..6e6a8c0d63 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/publisher/PublishProductToolImpl.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/publisher/PublishProductToolImpl.java @@ -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; @@ -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 @@ -77,7 +95,58 @@ public List 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 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 configAdvice) { + String configurationData = ""; //$NON-NLS-1$ + String unconfigurationData = ""; //$NON-NLS-1$ + Set properties = new HashSet<>(); + for (IConfigAdvice advice : configAdvice) { + for (Entry aProperty : advice.getProperties().entrySet()) { + String key = aProperty.getKey(); + if (!PROPERTIES_TO_SKIP.contains(key) && !properties.contains(key)) { + properties.add(key); + Map 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 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())); @@ -126,7 +195,54 @@ private static void addRootFeatures(ExpandedProduct product, List 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$ diff --git a/tycho-its/projects/product.update_repository/aProduct.product b/tycho-its/projects/product.update_repository/aProduct.product new file mode 100644 index 0000000000..92dbb32018 --- /dev/null +++ b/tycho-its/projects/product.update_repository/aProduct.product @@ -0,0 +1,24 @@ + + + + + + + + + + -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts + + + + + + + + + + + + + + diff --git a/tycho-its/projects/product.update_repository/pom.xml b/tycho-its/projects/product.update_repository/pom.xml new file mode 100644 index 0000000000..290fd4df2d --- /dev/null +++ b/tycho-its/projects/product.update_repository/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + foo.bar + aProduct + 1.0.0 + eclipse-repository + + UTF-8 + + + + eclipse-latest + p2 + ${target-platform} + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + org.eclipse.tycho + tycho-p2-director-plugin + ${tycho-version} + + + materialize-products + + materialize-products + + + + + aProduct + + + + + + archive-products + + archive-products + + + + + aProduct + + + + + + + + + diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java index 59ccaf6096..aad80f804c 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java @@ -9,11 +9,15 @@ *******************************************************************************/ 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; @@ -21,6 +25,7 @@ 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; @@ -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 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 expectedReferences, Path path) + throws IOException { + List 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="))); + } + } + } }