diff --git a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/transport/TychoRepositoryTransport.java b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/transport/TychoRepositoryTransport.java index d6ade6edce..0b9a96101c 100644 --- a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/transport/TychoRepositoryTransport.java +++ b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/transport/TychoRepositoryTransport.java @@ -20,6 +20,8 @@ import java.io.OutputStream; import java.net.URI; import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.NumberFormat; import java.util.Map; import java.util.concurrent.Executor; @@ -44,25 +46,27 @@ @Component(role = org.eclipse.equinox.internal.p2.repository.Transport.class, hint = "tycho") public class TychoRepositoryTransport extends org.eclipse.equinox.internal.p2.repository.Transport - implements IAgentServiceFactory { + implements IAgentServiceFactory { private static final int MAX_DOWNLOAD_THREADS = Integer.getInteger("tycho.p2.transport.max-download-threads", 4); private static final boolean DEBUG_REQUESTS = Boolean.getBoolean("tycho.p2.transport.debug"); - private static final Executor DOWNLOAD_EXECUTOR = Executors.newFixedThreadPool(MAX_DOWNLOAD_THREADS, new ThreadFactory() { - - private AtomicInteger cnt = new AtomicInteger(); - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r); - thread.setName("Tycho-Download-Thread-" + cnt.getAndIncrement()); - thread.setDaemon(true); - return thread; - } - }); + private static final Executor DOWNLOAD_EXECUTOR = Executors.newFixedThreadPool(MAX_DOWNLOAD_THREADS, + new ThreadFactory() { + + private AtomicInteger cnt = new AtomicInteger(); - private NumberFormat numberFormat = NumberFormat.getNumberInstance(); + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName("Tycho-Download-Thread-" + cnt.getAndIncrement()); + thread.setDaemon(true); + return thread; + } + }); + + private NumberFormat numberFormat = NumberFormat.getNumberInstance(); @Requirement Logger logger; @@ -73,24 +77,24 @@ public Thread newThread(Runnable r) { @Requirement(role = TransportProtocolHandler.class) Map transportProtocolHandlers; - private LongAdder requests = new LongAdder(); - private LongAdder indexRequests = new LongAdder(); + private LongAdder requests = new LongAdder(); + private LongAdder indexRequests = new LongAdder(); public TychoRepositoryTransport() { - numberFormat.setMaximumFractionDigits(2); - } - - @Override - public IStatus download(URI toDownload, OutputStream target, long startPos, IProgressMonitor monitor) { - if (startPos > 0) { - return new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), - "range downloads are not implemented"); - } - return download(toDownload, target, monitor); - } - - @Override - public IStatus download(URI toDownload, OutputStream target, IProgressMonitor monitor) { + numberFormat.setMaximumFractionDigits(2); + } + + @Override + public IStatus download(URI toDownload, OutputStream target, long startPos, IProgressMonitor monitor) { + if (startPos > 0) { + return new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), + "range downloads are not implemented"); + } + return download(toDownload, target, monitor); + } + + @Override + public IStatus download(URI toDownload, OutputStream target, IProgressMonitor monitor) { String id = "p2"; // TODO we might compute the id from the IRepositoryIdManager based on the URI? if (cacheConfig.isInteractive()) { logger.info("Downloading from " + id + ": " + toDownload); @@ -101,41 +105,40 @@ public IStatus download(URI toDownload, OutputStream target, IProgressMonitor mo stream(toDownload, monitor).transferTo(statusOutputStream); DownloadStatus downloadStatus = statusOutputStream.getStatus(); if (cacheConfig.isInteractive()) { - logger.info( - "Downloaded from " + id + ": " + toDownload + " (" - + FileUtils.byteCountToDisplaySize(downloadStatus.getFileSize()) - + " at " + FileUtils.byteCountToDisplaySize(downloadStatus.getTransferRate()) + "/s)"); + logger.info("Downloaded from " + id + ": " + toDownload + " (" + + FileUtils.byteCountToDisplaySize(downloadStatus.getFileSize()) + " at " + + FileUtils.byteCountToDisplaySize(downloadStatus.getTransferRate()) + "/s)"); } return reportStatus(downloadStatus, target); - } catch (AuthenticationFailedException e) { - return new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), - "authentication failed for " + toDownload, e); - } catch (IOException e) { - return reportStatus(new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), - "download from " + toDownload + " failed", e), target); - } catch (CoreException e) { - return reportStatus(e.getStatus(), target); - } - } + } catch (AuthenticationFailedException e) { + return new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), + "authentication failed for " + toDownload, e); + } catch (IOException e) { + return reportStatus(new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), + "download from " + toDownload + " failed", e), target); + } catch (CoreException e) { + return reportStatus(e.getStatus(), target); + } + } private IStatus reportStatus(IStatus status, OutputStream target) { - if (target instanceof IStateful stateful) { - stateful.setStatus(status); - } - return status; - } + if (target instanceof IStateful stateful) { + stateful.setStatus(status); + } + return status; + } - @Override + @Override public InputStream stream(URI toDownload, IProgressMonitor monitor) throws FileNotFoundException, CoreException, AuthenticationFailedException { if (DEBUG_REQUESTS) { - logger.debug("Request stream for " + toDownload); + logger.debug("Request stream for " + toDownload); } - requests.increment(); - if (toDownload.toASCIIString().endsWith("p2.index")) { - indexRequests.increment(); - } - try { + requests.increment(); + if (toDownload.toASCIIString().endsWith("p2.index")) { + indexRequests.increment(); + } + try { TransportProtocolHandler handler = getHandler(toDownload); if (handler != null) { File cachedFile = handler.getFile(toDownload); @@ -146,28 +149,28 @@ public InputStream stream(URI toDownload, IProgressMonitor monitor) return new FileInputStream(cachedFile); } } - return toDownload.toURL().openStream(); - } catch (FileNotFoundException e) { + return toDownload.toURL().openStream(); + } catch (FileNotFoundException e) { if (DEBUG_REQUESTS) { - logger.debug(" --> not found!"); - } - throw e; - } catch (IOException e) { + logger.debug(" --> not found!"); + } + throw e; + } catch (IOException e) { if (e instanceof AuthenticationFailedException afe) { throw afe; } if (DEBUG_REQUESTS) { - logger.debug(" --> generic error: " + e); - } - throw new CoreException(new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), - "download from " + toDownload + " failed", e)); - } finally { + logger.debug(" --> generic error: " + e); + } + throw new CoreException(new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), + "download from " + toDownload + " failed", e)); + } finally { if (DEBUG_REQUESTS) { - logger.debug("Total number of requests: " + requests.longValue() + " (" + indexRequests.longValue() - + " for p2.index)"); - } - } - } + logger.debug("Total number of requests: " + requests.longValue() + " (" + indexRequests.longValue() + + " for p2.index)"); + } + } + } TransportProtocolHandler getHandler(URI uri) { String scheme = uri.getScheme(); @@ -181,9 +184,9 @@ TransportProtocolHandler getHandler(URI uri) { return null; } - @Override - public long getLastModified(URI toDownload, IProgressMonitor monitor) - throws CoreException, FileNotFoundException, AuthenticationFailedException { + @Override + public long getLastModified(URI toDownload, IProgressMonitor monitor) + throws CoreException, FileNotFoundException, AuthenticationFailedException { try { TransportProtocolHandler handler = getHandler(toDownload); if (handler != null) { @@ -199,14 +202,33 @@ public long getLastModified(URI toDownload, IProgressMonitor monitor) throw new CoreException(new Status(IStatus.ERROR, TychoRepositoryTransport.class.getName(), "download from " + toDownload + " failed", e)); } - } - @Override - public Object createService(IProvisioningAgent agent) { - return this; - } + } + + @Override + public Object createService(IProvisioningAgent agent) { + return this; + } public static Executor getDownloadExecutor() { return DOWNLOAD_EXECUTOR; } + public File downloadToFile(URI uri) throws IOException { + TransportProtocolHandler handler = getHandler(uri); + if (handler != null) { + File file = handler.getFile(uri); + if (file != null) { + return file; + } + } + Path tempFile = Files.createTempFile("tycho", ".tmp"); + tempFile.toFile().deleteOnExit(); + try { + Files.copy(stream(uri, null), tempFile); + return tempFile.toFile(); + } catch (CoreException e) { + throw new IOException(e); + } + } + } diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/RepositoryLocationContent.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/RepositoryLocationContent.java new file mode 100644 index 0000000000..ed971f8a90 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/RepositoryLocationContent.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2023 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.p2resolver; + +import java.io.File; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +import org.apache.commons.io.FilenameUtils; +import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser; +import org.eclipse.equinox.internal.p2.repository.Transport; +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.metadata.IArtifactKey; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.publisher.IPublisherInfo; +import org.eclipse.equinox.p2.publisher.PublisherInfo; +import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction; +import org.eclipse.equinox.p2.publisher.eclipse.Feature; +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.osgi.service.resolver.BundleDescription; +import org.eclipse.tycho.core.resolver.target.FileArtifactRepository; +import org.eclipse.tycho.core.resolver.target.SupplierMetadataRepository; +import org.eclipse.tycho.core.shared.MavenLogger; +import org.eclipse.tycho.p2.resolver.BundlePublisher; +import org.eclipse.tycho.p2.resolver.FeaturePublisher; +import org.eclipse.tycho.p2maven.transport.TychoRepositoryTransport; +import org.eclipse.tycho.targetplatform.TargetDefinitionContent; +import org.eclipse.tycho.targetplatform.TargetDefinitionResolutionException; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; + +import aQute.bnd.osgi.repository.ResourcesRepository; +import aQute.bnd.osgi.repository.XMLResourceParser; +import aQute.bnd.osgi.resource.ResourceUtils; +import aQute.bnd.osgi.resource.ResourceUtils.ContentCapability; + +public class RepositoryLocationContent implements TargetDefinitionContent { + private final Map repositoryContent = new HashMap<>(); + private SupplierMetadataRepository metadataRepository; + private FileArtifactRepository artifactRepository; + + public RepositoryLocationContent(URI uri, Collection requirements, IProvisioningAgent agent, + MavenLogger logger) throws TargetDefinitionResolutionException { + TychoRepositoryTransport tychoTransport = (TychoRepositoryTransport) agent.getService(Transport.class); + + metadataRepository = new SupplierMetadataRepository(agent, () -> repositoryContent.values().iterator()); + metadataRepository.setLocation(uri); + metadataRepository.setName(String.valueOf(uri)); + artifactRepository = new FileArtifactRepository(agent, () -> repositoryContent.keySet().stream() + .filter(Predicate.not(FeaturePublisher::isMetadataOnly)).iterator()); + artifactRepository.setName(String.valueOf(uri)); + artifactRepository.setLocation(uri); + List features = new ArrayList<>(); + ResourcesRepository repository; + try (InputStream stream = tychoTransport.stream(uri, null)) { + repository = new ResourcesRepository(XMLResourceParser.getResources(stream, uri)); + } catch (Exception e) { + throw new TargetDefinitionResolutionException("Can't load the repository from URI " + uri, e); + } + Map> providers = repository.findProviders(requirements); + //TODO once we have changed Tycho to use resources this can be optimized to not download all selected content here ... + List contentCapabilities = providers.values().stream().flatMap(Collection::stream) + .map(Capability::getResource).distinct().map(ResourceUtils::getContentCapability) + .filter(Objects::nonNull).toList(); + for (ContentCapability content : contentCapabilities) { + URI url = content.url(); + logger.info("Loading " + url + "..."); + try { + File file = tychoTransport.downloadToFile(url); + if (!"jar".equalsIgnoreCase(FilenameUtils.getExtension(file.getName()))) { + logger.info("Skip non-jar artifact (" + file + ")"); + continue; + } + Feature feature = new FeatureParser().parse(file); + if (feature != null) { + feature.setLocation(file.getAbsolutePath()); + features.add(feature); + continue; + } + BundleDescription bundleDescription = BundlesAction.createBundleDescription(file); + if (bundleDescription == null || bundleDescription.getSymbolicName() == null) { + continue; + } + publish(bundleDescription, file); + } catch (Exception e) { + throw new TargetDefinitionResolutionException("Can't fetch resource from " + url, e); + } + } + FeaturePublisher.publishFeatures(features, repositoryContent::put, logger); + } + + private void publish(BundleDescription bundleDescription, File bundleLocation) { + IArtifactKey key = BundlesAction.createBundleArtifactKey(bundleDescription.getSymbolicName(), + bundleDescription.getVersion().toString()); + IArtifactDescriptor descriptor = FileArtifactRepository.forFile(bundleLocation, key); + PublisherInfo publisherInfo = new PublisherInfo(); + publisherInfo.setArtifactOptions(IPublisherInfo.A_INDEX); + IInstallableUnit iu = BundlePublisher.publishBundle(bundleDescription, descriptor, publisherInfo); + repositoryContent.put(descriptor, iu); + } + + @Override + public IMetadataRepository getMetadataRepository() { + return metadataRepository; + } + + @Override + public IArtifactRepository getArtifactRepository() { + return artifactRepository; + } + +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/TargetDefinitionResolver.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/TargetDefinitionResolver.java index ddc03e720f..bc54520f63 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/TargetDefinitionResolver.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/TargetDefinitionResolver.java @@ -62,6 +62,7 @@ import org.eclipse.tycho.targetplatform.TargetDefinition.PathLocation; import org.eclipse.tycho.targetplatform.TargetDefinition.ProfileLocation; import org.eclipse.tycho.targetplatform.TargetDefinition.Repository; +import org.eclipse.tycho.targetplatform.TargetDefinition.RepositoryLocation; import org.eclipse.tycho.targetplatform.TargetDefinition.TargetReferenceLocation; import org.eclipse.tycho.targetplatform.TargetDefinitionContent; import org.eclipse.tycho.targetplatform.TargetDefinitionFile; @@ -131,6 +132,7 @@ public TargetDefinitionContent resolveContentWithExceptions(TargetDefinition def Map uriRepositories = new LinkedHashMap<>(); List mavenLocations = new ArrayList<>(); List referencedTargetLocations = new ArrayList<>(); + List repositorytLocations = new ArrayList<>(); for (Location locationDefinition : definition.getLocations()) { if (locationDefinition instanceof InstallableUnitLocation installableUnitLocation) { if (installableUnitResolver == null) { @@ -201,6 +203,22 @@ public TargetDefinitionContent resolveContentWithExceptions(TargetDefinition def new LoggingProgressMonitor(logger)); unitResultSet.addAll(result); referencedTargetLocations.add(content); + } else if (locationDefinition instanceof RepositoryLocation repositoryLocation) { + URI resolvedUri; + String uri = repositoryLocation.getUri(); + try { + resolvedUri = new URI(convertRawToUri(resolvePath(uri, definition))); + } catch (URISyntaxException e) { + throw new ResolverException("Invalid URI " + resolvePath(uri, definition) + ": " + e.getMessage(), + e); + } + logger.info("Loading " + resolvedUri + "..."); + RepositoryLocationContent content = new RepositoryLocationContent(resolvedUri, + repositoryLocation.getRequirements(), provisioningAgent, logger); + repositorytLocations.add(content); + IQueryResult result = content.query(QueryUtil.ALL_UNITS, + new LoggingProgressMonitor(logger)); + unitResultSet.addAll(result); } else { logger.warn("Target location type '" + locationDefinition.getTypeDescription() + "' is not supported"); } @@ -234,6 +252,11 @@ public TargetDefinitionContent resolveContentWithExceptions(TargetDefinition def metadataRepositories.add(referenceContent.getMetadataRepository()); artifactRepositories.add(referenceContent.getArtifactRepository()); } + //preliminary step: add all repository locations: + for (TargetDefinitionContent referenceContent : repositorytLocations) { + metadataRepositories.add(referenceContent.getMetadataRepository()); + artifactRepositories.add(referenceContent.getArtifactRepository()); + } //now we can resolve the p2 sources if (installableUnitResolver != null) { //FIXME installableUnitResolver should provide Meta+Artifact repositories so we have a complete view on the target! diff --git a/tycho-its/projects/target.repository/META-INF/MANIFEST.MF b/tycho-its/projects/target.repository/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..711af7395c --- /dev/null +++ b/tycho-its/projects/target.repository/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Target-with-repository +Bundle-SymbolicName: target-with-repository +Bundle-Version: 0.0.1.qualifier +Require-Bundle: org.gecko.bnd.eclipse.bom +Automatic-Module-Name: target.with.repository +Bundle-RequiredExecutionEnvironment: JavaSE-17 diff --git a/tycho-its/projects/target.repository/build.properties b/tycho-its/projects/target.repository/build.properties new file mode 100644 index 0000000000..34d2e4d2da --- /dev/null +++ b/tycho-its/projects/target.repository/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/tycho-its/projects/target.repository/pom.xml b/tycho-its/projects/target.repository/pom.xml new file mode 100644 index 0000000000..2c15b47da7 --- /dev/null +++ b/tycho-its/projects/target.repository/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + org.eclipse.tycho.itests + target-with-repository + eclipse-plugin + 0.0.1-SNAPSHOT + + + 5.0.0-SNAPSHOT + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + org.eclipse.tycho + target-platform-configuration + ${tycho-version} + + + + test.target + + + + + + \ No newline at end of file diff --git a/tycho-its/projects/target.repository/test.target b/tycho-its/projects/target.repository/test.target new file mode 100644 index 0000000000..5496a70a55 --- /dev/null +++ b/tycho-its/projects/target.repository/test.target @@ -0,0 +1,9 @@ + + + + + + osgi.identity;filter:='(osgi.identity=org.gecko.bnd.eclipse.bom)' + + + \ No newline at end of file diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/target/TargetPlatformLocationsTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/target/TargetPlatformLocationsTest.java index 2c18af0c7c..29537b9e2d 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/target/TargetPlatformLocationsTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/target/TargetPlatformLocationsTest.java @@ -180,4 +180,11 @@ public void testTargetDefinedInRepositories() throws Exception { verifier.executeGoal("verify"); verifier.verifyErrorFreeLog(); } + + @Test + public void testTargetRepositoryLocation() throws Exception { + Verifier verifier = getVerifier("target.repository", false, true); + verifier.executeGoal("verify"); + verifier.verifyErrorFreeLog(); + } } diff --git a/tycho-targetplatform/pom.xml b/tycho-targetplatform/pom.xml index 4bfd28fa4e..7be916e9b5 100644 --- a/tycho-targetplatform/pom.xml +++ b/tycho-targetplatform/pom.xml @@ -26,6 +26,10 @@ commons-io commons-io + + biz.aQute.bnd + biz.aQute.bndlib + diff --git a/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinition.java b/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinition.java index a66300912b..ca591b0b7b 100644 --- a/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinition.java +++ b/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinition.java @@ -24,198 +24,228 @@ import org.eclipse.tycho.IArtifactFacade; import org.eclipse.tycho.MavenArtifactRepositoryReference; +import org.osgi.resource.Requirement; import org.w3c.dom.Element; // TODO javadoc public interface TargetDefinition { - public List getLocations(); + public List getLocations(); - /** - * Returns true if the target definition specifies an explicit list of bundles to - * include (i.e. an <includeBundles> in target definition files). - */ - boolean hasIncludedBundles(); + /** + * Returns true if the target definition specifies an explicit list + * of bundles to include (i.e. an <includeBundles> in target + * definition files). + */ + boolean hasIncludedBundles(); - /** - * Returns the origin of the target definition, e.g. a file path. Used for debugging only. - */ - String getOrigin(); + /** + * Returns the origin of the target definition, e.g. a file path. Used for + * debugging only. + */ + String getOrigin(); - /** - * Returns the value of the targetJRE in *.target file if it's a known EE name. - * null will be returned otherwise. - */ - String getTargetEE(); + /** + * Returns the value of the targetJRE in *.target file if it's a known EE name. + * null will be returned otherwise. + */ + String getTargetEE(); - @Override - public boolean equals(Object obj); + @Override + public boolean equals(Object obj); - @Override - public int hashCode(); + @Override + public int hashCode(); - public interface Location { + public interface Location { - /** - * Returns a description of the underlying location implementation. - */ - String getTypeDescription(); + /** + * Returns a description of the underlying location implementation. + */ + String getTypeDescription(); - } + } - public interface InstallableUnitLocation extends Location { + public interface InstallableUnitLocation extends Location { - public static String TYPE = "InstallableUnit"; + public static String TYPE = "InstallableUnit"; - public List getRepositories(); + public List getRepositories(); - public List getUnits(); + public List getUnits(); - public IncludeMode getIncludeMode(); + public IncludeMode getIncludeMode(); - public boolean includeAllEnvironments(); + public boolean includeAllEnvironments(); - public boolean includeSource(); + public boolean includeSource(); - @Override - public default String getTypeDescription() { - return InstallableUnitLocation.TYPE; - } + @Override + public default String getTypeDescription() { + return InstallableUnitLocation.TYPE; + } - } + } - public interface MavenGAVLocation extends Location { + public interface MavenGAVLocation extends Location { - public static final String TYPE = "Maven"; + public static final String TYPE = "Maven"; - enum MissingManifestStrategy { - IGNORE, ERROR, GENERATE; - } + enum MissingManifestStrategy { + IGNORE, ERROR, GENERATE; + } - enum DependencyDepth { - NONE, DIRECT, INFINITE; - } + enum DependencyDepth { + NONE, DIRECT, INFINITE; + } - Collection getIncludeDependencyScopes(); + Collection getIncludeDependencyScopes(); - DependencyDepth getIncludeDependencyDepth(); + DependencyDepth getIncludeDependencyDepth(); - MissingManifestStrategy getMissingManifestStrategy(); + MissingManifestStrategy getMissingManifestStrategy(); - Collection getInstructions(); + Collection getInstructions(); - Collection getRoots(); + Collection getRoots(); - Collection getRepositoryReferences(); + Collection getRepositoryReferences(); - boolean includeSource(); + boolean includeSource(); - Element getFeatureTemplate(); + Element getFeatureTemplate(); - @Override - public default String getTypeDescription() { - return TYPE; - } + @Override + public default String getTypeDescription() { + return TYPE; + } - } + } - public interface TargetReferenceLocation extends Location { - String getUri(); - } + public interface TargetReferenceLocation extends Location { + String getUri(); + } - /** - * Represents the "Directory" location that either contains bundles directly or has - * plugins/features/binaries folders that contains the data - * - * @author Christoph Läubrich - * - */ - public interface DirectoryLocation extends PathLocation { - } + /** + * Implements the PDE + * repository location + * + */ + public interface RepositoryLocation extends Location { - /** - * Represents the "Profile" location that contains an eclipse-sdk or exploded eclipse product - * - * @author Christoph Läubrich - * - */ - public interface ProfileLocation extends PathLocation { - } + static final String TYPE = "Repository"; - /** - * represents the "Feature" location that contains a feature to include from a given - * installation - * - * @author Christoph Läubrich - * - */ - public interface FeaturesLocation extends PathLocation { + /** + * @return the URI to load this repository from + */ + String getUri(); - /** - * - * @return the id of the feature to use - */ - String getId(); + /** + * @return the requirements that make up the content fetched from the repository + */ + Collection getRequirements(); - /** - * - * @return the version of the feature to use - */ - String getVersion(); - } + @Override + default String getTypeDescription() { + return TYPE; + } + } - /** - * Base interface for all Locations that are path based, the path might contains variables that - * need to be resolved before used as a real directory path - * - * @author Christoph Läubrich - * - */ - public interface PathLocation extends Location { - /** - * - * @return the plain path as supplied by the target file - */ - public String getPath(); - } + /** + * Represents the "Directory" location that either contains bundles directly or + * has plugins/features/binaries folders that contains the data + * + * @author Christoph Läubrich + * + */ + public interface DirectoryLocation extends PathLocation { + } - public enum IncludeMode { - SLICER, PLANNER - } + /** + * Represents the "Profile" location that contains an eclipse-sdk or exploded + * eclipse product + * + * @author Christoph Läubrich + * + */ + public interface ProfileLocation extends PathLocation { + } - public interface Repository { - String getLocation(); + /** + * represents the "Feature" location that contains a feature to include from a + * given installation + * + * @author Christoph Läubrich + * + */ + public interface FeaturesLocation extends PathLocation { - String getId(); - } + /** + * + * @return the id of the feature to use + */ + String getId(); - public interface Unit { + /** + * + * @return the version of the feature to use + */ + String getVersion(); + } - public String getId(); - - public String getVersion(); - } - - public interface BNDInstructions { - - public String getReference(); - - public Properties getInstructions(); - } - - public interface MavenDependency { - - String getGroupId(); - - String getArtifactId(); - - String getVersion(); - - String getArtifactType(); - - String getClassifier(); - - boolean isIgnored(IArtifactFacade artifact); - } + /** + * Base interface for all Locations that are path based, the path might contains + * variables that need to be resolved before used as a real directory path + * + * @author Christoph Läubrich + * + */ + public interface PathLocation extends Location { + /** + * + * @return the plain path as supplied by the target file + */ + public String getPath(); + } + + public enum IncludeMode { + SLICER, PLANNER + } + + public interface Repository { + String getLocation(); + + String getId(); + } + + public interface Unit { + + public String getId(); + + public String getVersion(); + } + + public interface BNDInstructions { + + public String getReference(); + + public Properties getInstructions(); + } + + public interface MavenDependency { + + String getGroupId(); + + String getArtifactId(); + + String getVersion(); + + String getArtifactType(); + + String getClassifier(); + + boolean isIgnored(IArtifactFacade artifact); + } } diff --git a/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinitionFile.java b/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinitionFile.java index 0dd577db6e..f64418c913 100644 --- a/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinitionFile.java +++ b/tycho-targetplatform/src/main/java/org/eclipse/tycho/targetplatform/TargetDefinitionFile.java @@ -51,6 +51,7 @@ import org.eclipse.tycho.MavenArtifactRepositoryReference; import org.eclipse.tycho.targetplatform.TargetDefinition.MavenGAVLocation.DependencyDepth; import org.eclipse.tycho.targetplatform.TargetDefinition.MavenGAVLocation.MissingManifestStrategy; +import org.osgi.resource.Requirement; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -58,6 +59,9 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import aQute.bnd.header.Parameters; +import aQute.bnd.osgi.resource.CapReqBuilder; + public final class TargetDefinitionFile implements TargetDefinition { private static final Map FILE_CACHE = new ConcurrentHashMap<>(); @@ -161,6 +165,28 @@ public String getUri() { } + private static final class OSGIRepositoryLocation implements TargetDefinition.RepositoryLocation { + + private String uri; + private Collection requirements; + + public OSGIRepositoryLocation(String uri, Collection requirements) { + this.uri = uri; + this.requirements = requirements; + } + + @Override + public String getUri() { + return uri; + } + + @Override + public Collection getRequirements() { + return requirements; + } + + } + private static class MavenLocation implements TargetDefinition.MavenGAVLocation { private final Collection includeDependencyScopes; @@ -581,6 +607,8 @@ private static List parseLocations(Element locations.add(parseMavenLocation(locationDom)); } else if ("Target".equals(type)) { locations.add(new TargetRef(locationDom.getAttribute("uri"))); + } else if (TargetDefinition.RepositoryLocation.TYPE.equals(type)) { + locations.add(parseRepositoryLocation(locationDom)); } else { locations.add(new OtherLocation(type)); } @@ -589,6 +617,20 @@ private static List parseLocations(Element return Collections.unmodifiableList(locations); } + private static TargetDefinition.RepositoryLocation parseRepositoryLocation(Element dom) { + String uri = dom.getAttribute("uri"); + NodeList childNodes = dom.getChildNodes(); + List requirements = IntStream.range(0, childNodes.getLength()).mapToObj(childNodes::item) + .filter(Element.class::isInstance).map(Element.class::cast) + .filter(element -> element.getNodeName().equalsIgnoreCase("require")) + .flatMap(element -> { + String textContent = element.getTextContent(); + Parameters parameters = new Parameters(textContent); + return CapReqBuilder.getRequirementsFrom(parameters).stream(); + }).toList(); + return new OSGIRepositoryLocation(uri, requirements); + } + private static MavenLocation parseMavenLocation(Element dom) { Set globalExcludes = new LinkedHashSet<>(); for (Element element : getChildren(dom, "exclude")) {