From 315020c52b0a8103cd871dfbe509228841bc287f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Mon, 27 Jan 2025 08:23:10 +0100 Subject: [PATCH] [bp] Add support for version patterns in URLs to update IU locations Some sites just use a given version scheme that is incremented on each release but don'T offer a composite at the parent. This now adds a new detector called 'versionPattern' that allows to match a pattern and let the version be incremented as long as there is a valid repo at that URL. (cherry picked from commit 0f2b5c31b5a1129f61eac842d5f3a8e7f887ca78) --- .../InstallableUnitLocationUpdater.java | 336 ++++++++++-------- .../tycho/versionbump/UpdateTargetMojo.java | 27 +- .../resources/META-INF/maven/extension.xml | 2 + 3 files changed, 204 insertions(+), 161 deletions(-) diff --git a/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/InstallableUnitLocationUpdater.java b/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/InstallableUnitLocationUpdater.java index d04d56f349..ebbbd0c730 100644 --- a/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/InstallableUnitLocationUpdater.java +++ b/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/InstallableUnitLocationUpdater.java @@ -13,15 +13,19 @@ *******************************************************************************/ package org.eclipse.tycho.versionbump; -import static java.util.stream.Collectors.toList; - import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; -import java.util.stream.Stream; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; @@ -36,17 +40,16 @@ import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; import org.eclipse.tycho.p2resolver.TargetDefinitionVariableResolver; import org.eclipse.tycho.targetplatform.TargetDefinition; -import org.eclipse.tycho.targetplatform.TargetDefinition.FollowRepositoryReferences; -import org.eclipse.tycho.targetplatform.TargetDefinition.IncludeMode; -import org.eclipse.tycho.targetplatform.TargetDefinition.InstallableUnitLocation; -import org.eclipse.tycho.targetplatform.TargetDefinition.Unit; -import org.eclipse.tycho.targetplatform.TargetDefinitionFile; import de.pdark.decentxml.Element; +/** + * Updater for installable units + */ @Named public class InstallableUnitLocationUpdater { + private static final String VERSION_PATTERN_PREFIX = "versionPattern"; @Inject private TargetDefinitionVariableResolver varResolver; @Inject @@ -85,37 +88,61 @@ boolean update(Element iuLocation, UpdateTargetMojo context) private IMetadataRepository getMetadataRepository(Element iuLocation, UpdateTargetMojo context, List units) throws URISyntaxException, ProvisionException { ResolvedRepository location = getResolvedLocation(iuLocation); - URI uri = new URI(location.getLocation()); - String discovery = context.getUpdateSiteDiscovery(); + String raw = location.getLocation(); + context.getLog().debug("Look for updates of location " + raw); + URI uri = new URI(raw); + List discovery = context.getUpdateSiteDiscovery(); IMetadataRepositoryManager repositoryManager = agent.getService(IMetadataRepositoryManager.class); - if (discovery != null && !discovery.isBlank()) { - for (String strategy : discovery.split(",")) { - if (strategy.trim().equals("parent")) { - String str = uri.toASCIIString(); - if (!str.endsWith("/")) { - str = str + "/"; - } - URI parentURI = new URI(str + "../"); - try { - IMetadataRepository repository = repositoryManager.loadRepository(parentURI, null); - List bestUnits = units; - URI bestURI = null; - //we now need to find a repository that has all units and they must have the same or higher version - for (URI child : getChildren(repository)) { - List find = findBestUnits(bestUnits, repositoryManager, child, context); - if (find != null) { - bestUnits = find; - bestURI = child; - } - } - if (bestURI != null) { - location.element().setAttribute("location", bestURI.toString()); - return repositoryManager.loadRepository(bestURI, null); + for (String strategy : discovery) { + String trim = strategy.trim(); + if (trim.equals("parent")) { + String str = uri.toASCIIString(); + if (!str.endsWith("/")) { + str = str + "/"; + } + URI parentURI = new URI(str + "../"); + Collection children; + try { + children = getChildren(repositoryManager.loadRepository(parentURI, null)); + } catch (ProvisionException e) { + // if we can't load it we can't use it but this is maybe because that no parent exits. + context.getLog() + .debug("No parent repository found for location " + uri + " using " + parentURI + ": " + e); + continue; + } + IMetadataRepository bestLocation = findBestLocation(context, units, location, repositoryManager, + children); + if (bestLocation != null) { + return bestLocation; + } + } else if (trim.startsWith(VERSION_PATTERN_PREFIX)) { + String substring = trim.substring(VERSION_PATTERN_PREFIX.length()); + Pattern pattern; + if (substring.isEmpty()) { + pattern = Pattern.compile("(\\d+)\\.(\\d+)"); + } else { + pattern = Pattern.compile(substring.substring(1)); + } + context.getLog().debug("Using Pattern " + pattern + " to find version increments..."); + Collection fromPattern = findUpdatesitesFromPattern(raw, pattern, repositoryManager, + context.getLog()::debug); + if (fromPattern.isEmpty()) { + context.getLog().debug("Nothing found to match the pattern " + pattern + " for location " + raw); + } else { + Set repositories = new HashSet<>(); + for (URI repoURI : fromPattern) { + context.getLog().debug("Check location " + repoURI + "..."); + try { + repositories.addAll(expandRepository(repoURI, repositoryManager)); + } catch (ProvisionException e) { + // if we can't load it we can't use it but this is maybe because that no parent exits. + context.getLog().debug("No repository found for location " + repoURI + ": " + e); } - } catch (ProvisionException e) { - // if we can't load it we can't use it but this is maybe because that no parent exits. - context.getLog().debug( - "No parent repository found for location " + uri + " using " + parentURI + ": " + e); + } + IMetadataRepository bestLocation = findBestLocation(context, units, location, repositoryManager, + repositories); + if (bestLocation != null) { + return bestLocation; } } } @@ -124,8 +151,35 @@ private IMetadataRepository getMetadataRepository(Element iuLocation, UpdateTarg return repositoryManager.loadRepository(uri, null); } + private IMetadataRepository findBestLocation(UpdateTargetMojo context, List units, ResolvedRepository location, + IMetadataRepositoryManager repositoryManager, Collection children) throws ProvisionException { + List bestUnits = units; + URI bestURI = null; + //we now need to find a repository that has all units and they must have the same or higher version + for (URI child : children) { + List find = findBestUnits(bestUnits, repositoryManager, child, context); + if (find != null) { + bestUnits = find; + bestURI = child; + } + } + if (bestURI != null) { + location.element().setAttribute("location", bestURI.toString()); + return repositoryManager.loadRepository(bestURI, null); + } + return null; + } +//TODO this method should better be used (with an org.eclipse.equinox.p2.repository in extension.xml) +// But this currently fails the PGP mojo using org.bouncycastle.openpgp.PGPPublicKey +// private static Collection getChildren(IMetadataRepository repository) { +// if (repository instanceof ICompositeRepository composite) { +// return composite.getChildren(); +// } +// return List.of(); +// } + @SuppressWarnings("unchecked") - private Collection getChildren(IMetadataRepository repository) { + private static Collection getChildren(IMetadataRepository repository) { try { Method method = repository.getClass().getDeclaredMethod("getChildren"); if (method.invoke(repository) instanceof Collection c) { @@ -136,7 +190,21 @@ private Collection getChildren(IMetadataRepository repository) { return List.of(); } - private List findBestUnits(List units, IMetadataRepositoryManager repositoryManager, URI child, + private static Collection expandRepository(URI uri, IMetadataRepositoryManager repositoryManager) + throws ProvisionException { + IMetadataRepository repository = repositoryManager.loadRepository(uri, null); + Collection children = getChildren(repository); + if (children.isEmpty()) { + return List.of(uri); + } + List result = new ArrayList<>(); + for (URI child : children) { + result.addAll(expandRepository(child, repositoryManager)); + } + return result; + } + + private static List findBestUnits(List units, IMetadataRepositoryManager repositoryManager, URI child, UpdateTargetMojo context) throws ProvisionException { IMetadataRepository childRepository = repositoryManager.loadRepository(child, null); List list = new ArrayList<>(); @@ -180,119 +248,6 @@ private ResolvedRepository getResolvedLocation(Element iuLocation) { return new ResolvedRepository(element.getAttributeValue("id"), resolved, element); } - private static final class LatestVersionTarget implements TargetDefinition { - - private TargetDefinitionFile delegate; - private TargetDefinitionVariableResolver varResolver; - - public LatestVersionTarget(TargetDefinitionFile delegate, TargetDefinitionVariableResolver varResolver) { - this.delegate = delegate; - this.varResolver = varResolver; - } - - @Override - public List getLocations() { - return delegate.getLocations().stream().map(location -> { - if (location instanceof InstallableUnitLocation iuLocation) { - return new LatestVersionLocation(iuLocation, varResolver); - } else { - return location; - } - }).toList(); - } - - @Override - public boolean hasIncludedBundles() { - return delegate.hasIncludedBundles(); - } - - @Override - public String getOrigin() { - return delegate.getOrigin(); - } - - @Override - public String getTargetEE() { - return delegate.getTargetEE(); - } - - @Override - public Stream implicitDependencies() { - return delegate.implicitDependencies(); - } - - } - - private static final class LatestVersionLocation implements InstallableUnitLocation { - - private InstallableUnitLocation delegate; - private TargetDefinitionVariableResolver varResolver; - - public LatestVersionLocation(InstallableUnitLocation delegate, TargetDefinitionVariableResolver varResolver) { - this.delegate = delegate; - this.varResolver = varResolver; - } - - @Override - public List getRepositories() { - return delegate.getRepositories().stream().map(repo -> { - URI resolvedLocation = URI.create(varResolver.resolve(repo.getLocation())); - return new ResolvedRepository(repo.getId(), resolvedLocation.toString(), null); - }).collect(toList()); - } - - @Override - public List getUnits() { - return delegate.getUnits().stream().map(LatestVersionUnit::new).toList(); - } - - @Override - public IncludeMode getIncludeMode() { - return delegate.getIncludeMode(); - } - - @Override - public boolean includeAllEnvironments() { - return delegate.includeAllEnvironments(); - } - - @Override - public boolean includeSource() { - return delegate.includeSource(); - } - - @Override - public boolean includeConfigurePhase() { - return delegate.includeConfigurePhase(); - } - - @Override - public FollowRepositoryReferences followRepositoryReferences() { - return delegate.followRepositoryReferences(); - } - - } - - private static final class LatestVersionUnit implements TargetDefinition.Unit { - - private Unit delegate; - - public LatestVersionUnit(TargetDefinition.Unit delegate) { - this.delegate = delegate; - } - - @Override - public String getId() { - return delegate.getId(); - } - - @Override - public String getVersion() { - return "0.0.0"; - } - - } - private static final record ResolvedRepository(String id, String location, Element element) implements TargetDefinition.Repository { @@ -312,4 +267,81 @@ private static record IU(String id, String version, Element element) { } + private static Collection findUpdatesitesFromPattern(String input, Pattern pattern, + IMetadataRepositoryManager repositoryManager, Consumer debug) { + Matcher matcher = pattern.matcher(input); + if (matcher.find()) { + int count = matcher.groupCount(); + int[] versions = new int[count]; + String[] prefix = new String[count]; + int offset = 0; + for (int i = 0; i < count; i++) { + int g = i + 1; + String group = matcher.group(g); + int start = matcher.start(g); + int end = matcher.end(g); + prefix[i] = input.substring(offset, start); + offset = end; + try { + versions[i] = Integer.parseInt(group); + } catch (RuntimeException e) { + versions[i] = -1; + } + } + Set uris = new LinkedHashSet<>(); + for (int i = 0; i < versions.length; i++) { + if (versions[i] < 0) { + buildVersionString(versions, prefix, repositoryManager, debug).ifPresent(uris::add); + break; + } + } + for (int i = versions.length - 1; i >= 0; i--) { + int v = versions[i]; + if (v > -1) { + int[] copy = versions.clone(); + for (int j = i + 1; j < versions.length; j++) { + if (copy[j] > 0) { + copy[j] = 0; + } + } + Optional versionRepo; + do { + copy[i]++; + versionRepo = buildVersionString(copy, prefix, repositoryManager, debug); + } while (!versionRepo.isEmpty() && uris.add(versionRepo.get())); + } + } + return uris; + } + return List.of(); + } + + private static Optional buildVersionString(int[] versions, String[] prefix, + IMetadataRepositoryManager repositoryManager, Consumer debug) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < versions.length; i++) { + sb.append(prefix[i]); + int v = versions[i]; + if (v > -1) { + sb.append(v); + } + } + String string = sb.toString(); + try { + URI uri = new URI(string); + try { + //if we can load the repository everything is fine + repositoryManager.loadRepository(uri, null); + return Optional.of(uri); + } catch (ProvisionException e) { + debug.accept("Candidate URI '" + uri + "' can not be loaded: " + e.getMessage()); + return Optional.empty(); + } + } catch (URISyntaxException e) { + debug.accept("Resulting candidate string '" + string + "' can not be parsed as a valid location uri: " + + e.getMessage()); + return Optional.empty(); + } + } + } diff --git a/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/UpdateTargetMojo.java b/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/UpdateTargetMojo.java index bd60b13ed8..ea3856a935 100644 --- a/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/UpdateTargetMojo.java +++ b/tycho-extras/tycho-version-bump-plugin/src/main/java/org/eclipse/tycho/versionbump/UpdateTargetMojo.java @@ -53,14 +53,15 @@ * mvn -f [path to target project] tycho-version-bump:update-target * *

- * For updating maven target locations the mojo support + * For updating maven target locations the mojo supports * Version * number comparison rule-sets similar to the * Versions Maven Plugin * please check the documentation there for further information about ruleset files. *

*

- * For updating installable unit locations (also known as update sites) + * For updating installable unit locations (also known as update sites) you can configure + * different strategies (see {@link #getUpdateSiteDiscovery()}) to discover updates. *

*/ @Mojo(name = "update-target") @@ -100,15 +101,20 @@ public class UpdateTargetMojo extends AbstractUpdateMojo { private boolean allowSubIncrementalUpdates; /** - * A comma separated list of update site discovery strategies, the following is currently - * supported: + * A list of update site discovery strategies, the following is currently supported: *
    - *
  • parent - search the parent path for a composite that can be used to find newer - * versions
  • + *
  • parent - search the parent path for a composite that can be used to find + * newer versions
  • + *
  • versionPattern[:pattern] - specifies a pattern to match in the URL (defaults + * to (\d+)\.(\d+) where it increments each numeric part beginning at the last + * group, if no repository is found using the next group setting the previous to zero. Any non + * numeric pattern will be replaced by the empty string
  • *
+ * If used on the CLI, individual values must be separated by a comma see here */ @Parameter(property = "discovery") - private String updateSiteDiscovery; + private List updateSiteDiscovery; /** *

@@ -239,8 +245,11 @@ MojoExecution getMojoExecution() { return mojoExecution; } - String getUpdateSiteDiscovery() { - return updateSiteDiscovery; + List getUpdateSiteDiscovery() { + if (updateSiteDiscovery == null) { + return List.of(); + } + return updateSiteDiscovery.stream().map(String::trim).toList(); } Set getMavenIgnoredVersions() { 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..469140362d 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,6 +18,8 @@ org.eclipse.equinox.p2.publisher org.eclipse.equinox.p2.publisher.eclipse org.eclipse.equinox.p2.query + + org.eclipse.equinox.p2.repository.artifact org.eclipse.equinox.p2.repository.metadata org.eclipse.equinox.internal.p2.metadata