From d1a082e07f777e6db0913df9adc582772e53eda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Tue, 4 Jul 2023 18:07:16 +0200 Subject: [PATCH] Non existing but optional dependencies lead to resolving issue in target (cherry picked from commit edc419c7942a4f4db491537c3a7fb1a8f8d93739) --- .../pde/target/shared/MavenBundleWrapper.java | 413 ++++++++++-------- .../m2e/pde/target/shared/WrappedBundle.java | 97 ++-- .../MavenTargetDefinitionContent.java | 11 + .../target/tests/AbstractMavenTargetTest.java | 371 ++++++++-------- .../tests/OSGiMetadataGenerationTest.java | 53 +++ 5 files changed, 543 insertions(+), 402 deletions(-) diff --git a/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java b/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java index 4bf026ec04..4b3a5c77d4 100644 --- a/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java +++ b/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java @@ -39,11 +39,14 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.graph.DependencyVisitor; +import org.eclipse.aether.impl.SyncContextFactory; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.DependencyRequest; -import org.eclipse.aether.spi.synccontext.SyncContextFactory; import org.eclipse.core.runtime.Platform; import org.eclipse.m2e.pde.target.shared.ProcessingMessage.Type; import org.osgi.framework.Constants; @@ -53,206 +56,248 @@ import aQute.bnd.version.Version; /** - * The {@link MavenBundleWrapper} handle the hard part of the target support - * that is wrapping an existing jar into a bundle that is: + * The {@link MavenBundleWrapper} handle the hard part of the target support that is wrapping an + * existing jar into a bundle that is: * */ public class MavenBundleWrapper { - private MavenBundleWrapper() { - } + private MavenBundleWrapper() { + } - /** - * Wraps an artifact (and possible its dependents if required) to produce a - * manifest with OSGi metadata. - * - * @param artifact the artifact to wrap - * @param instructionsLookup a lookup for bnd instructions - * @param repositories the repositories that should be used to resolve - * dependencies - * @param repoSystem the repository system for lookup dependent items - * @param repositorySession the session to use - * @param syncContextFactory the sync context factory to acquire exclusive - * access to the wrapped artifact and its dependencies - * @return the wrapped artifact - * @throws Exception if wrapping the artifact fails for any reason - */ - public static WrappedBundle getWrappedArtifact(Artifact artifact, - Function instructionsLookup, List repositories, - RepositorySystem repoSystem, RepositorySystemSession repositorySession, - SyncContextFactory syncContextFactory) throws Exception { - CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(new Dependency(artifact, null)); - collectRequest.setRepositories(repositories); - DependencyNode node = repoSystem.collectDependencies(repositorySession, collectRequest).getRoot(); + /** + * Wraps an artifact (and possible its dependents if required) to produce a manifest with OSGi + * metadata. + * + * @param artifact + * the artifact to wrap + * @param instructionsLookup + * a lookup for bnd instructions + * @param repositories + * the repositories that should be used to resolve dependencies + * @param repoSystem + * the repository system for lookup dependent items + * @param repositorySession + * the session to use + * @param syncContextFactory + * the sync context factory to acquire exclusive access to the wrapped artifact and + * its dependencies + * @return the wrapped artifact + * @throws Exception + * if wrapping the artifact fails for any reason + */ + public static WrappedBundle getWrappedArtifact(Artifact artifact, + Function instructionsLookup, List repositories, + RepositorySystem repoSystem, RepositorySystemSession repositorySession, + SyncContextFactory syncContextFactory) throws Exception { + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRoot(new Dependency(artifact, null)); + collectRequest.setRepositories(repositories); + DependencyNode node = repoSystem.collectDependencies(repositorySession, collectRequest).getRoot(); - DependencyRequest dependencyRequest = new DependencyRequest(); - dependencyRequest.setRoot(node); - repoSystem.resolveDependencies(repositorySession, dependencyRequest); + DependencyRequest dependencyRequest = new DependencyRequest(); + dependencyRequest.setRoot(node); + dependencyRequest.setFilter(new DependencyFilter() { - try (SyncContext syncContext = syncContextFactory.newInstance(repositorySession, false)) { - Set lockList = new HashSet<>(); - node.accept(new DependencyVisitor() { + @Override + public boolean accept(DependencyNode node, List parents) { + ArtifactRequest request = new ArtifactRequest(); + request.setRepositories(repositories); + Artifact nodeArtifact = node.getArtifact(); + request.setArtifact(nodeArtifact); + try { + repoSystem.resolveArtifact(repositorySession, request); + } catch (ArtifactResolutionException e) { + return false; + } + return true; + } + }); + repoSystem.resolveDependencies(repositorySession, dependencyRequest); - @Override - public boolean visitLeave(DependencyNode n) { - return true; - } + try (SyncContext syncContext = syncContextFactory.newInstance(repositorySession, false)) { + Set lockList = new HashSet<>(); + node.accept(new DependencyVisitor() { - @Override - public boolean visitEnter(DependencyNode n) { - lockList.add(n.getArtifact()); - return true; - } - }); - syncContext.acquire(lockList, null); - Map visited = new HashMap<>(); - WrappedBundle wrappedNode = getWrappedNode(node, instructionsLookup, visited); - for (WrappedBundle wrap : visited.values()) { - wrap.getJar().close(); - } - return wrappedNode; - } - } + @Override + public boolean visitLeave(DependencyNode n) { + return true; + } - private static WrappedBundle getWrappedNode(DependencyNode node, - Function instructionsLookup, Map visited) - throws Exception { - WrappedBundle wrappedNode = visited.get(node); - if (wrappedNode != null) { - return wrappedNode; - } - Artifact artifact = node.getArtifact(); - File originalFile = artifact.getFile(); - Jar jar = new Jar(originalFile); - Manifest originalManifest = jar.getManifest(); - if (originalManifest != null - && originalManifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME) != null) { - // already a bundle! - visited.put(node, - wrappedNode = new WrappedBundle(node, List.of(), null, originalFile.toPath(), jar, List.of())); - return wrappedNode; - } - List children = node.getChildren(); - List depends = new ArrayList<>(); - for (DependencyNode child : children) { - depends.add(getWrappedNode(child, instructionsLookup, visited)); - } - WrappedBundle wrappedNodeAfterVisit = visited.get(node); - if (wrappedNodeAfterVisit != null) { - return wrappedNodeAfterVisit; - } - Properties instructions = instructionsLookup.apply(node); - String key = getInstructionsKey(instructions, depends); - try (Jar analyzerJar = jar) { - // now we know the key and the depends we enter the critical section of checking - // if the data is already there or needs to be refreshed - File parent = new File(originalFile.getParent(), "bnd-" + key); - File wrapArtifactFile = new File(parent, originalFile.getName()); - Jar cached = getCachedJar(wrapArtifactFile.toPath(), originalFile.toPath()); - if (cached == null) { - List messages = new ArrayList<>(); - wrapArtifactFile.getParentFile().mkdirs(); - try (Analyzer analyzer = new Analyzer(analyzerJar);) { - analyzer.setProperty("mvnGroupId", artifact.getGroupId()); - analyzer.setProperty("mvnArtifactId", artifact.getArtifactId()); - analyzer.setProperty("mvnVersion", artifact.getBaseVersion()); - analyzer.setProperty("mvnClassifier", artifact.getClassifier()); - String versionString = createOSGiVersion(artifact).toString(); - analyzer.setProperty("generatedOSGiVersion", versionString); - for (String property : instructions.stringPropertyNames()) { - // See https://github.com/bndtools/bnd/issues/5659 - String trimValue = instructions.getProperty(property).trim(); - analyzer.setProperty(property, trimValue); - } - for (WrappedBundle dep : depends) { - analyzer.addClasspath(dep.getJar()); - analyzer.removeClose(dep.getJar()); - } - analyzerJar.setManifest(analyzer.calcManifest()); - analyzerJar.write(wrapArtifactFile); - for (String err : analyzer.getErrors()) { - if (err.contains("Classes found in the wrong directory")) { - // ignore message from BND not supporting MR jars... - continue; - } - messages.add(new ProcessingMessage(artifact, Type.ERROR, err)); - } - for (String warn : analyzer.getWarnings()) { - messages.add(new ProcessingMessage(artifact, Type.WARN, warn)); - } - } - wrapArtifactFile.setLastModified(originalFile.lastModified()); - visited.put(node, wrappedNode = new WrappedBundle(node, depends, key, wrapArtifactFile.toPath(), - new Jar(wrapArtifactFile), messages)); - } else { - visited.put(node, wrappedNode = new WrappedBundle(node, depends, key, wrapArtifactFile.toPath(), - new Jar(wrapArtifactFile), List.of())); - } - return wrappedNode; - } - } + @Override + public boolean visitEnter(DependencyNode n) { + lockList.add(n.getArtifact()); + return true; + } + }); + syncContext.acquire(lockList, null); + Map visited = new HashMap<>(); + WrappedBundle wrappedNode = getWrappedNode(node, instructionsLookup, visited); + for (WrappedBundle wrap : visited.values()) { + Jar jar = wrap.getJar(); + if (jar != null) { + jar.close(); + } + } + return wrappedNode; + } + } - private static Jar getCachedJar(Path cacheFile, Path sourceFile) { - try { - if (!isOutdated(cacheFile, sourceFile)) { - return new Jar(cacheFile.toFile()); - } - } catch (IOException e) { - // if any I/O error occurs we assume we need to regenerate the data... - Platform.getLog(MavenBundleWrapper.class) - .error("Reading cached data for " + cacheFile + " failed, will regenerate the data ...", e); - } - return null; - } + private static WrappedBundle getWrappedNode(DependencyNode node, + Function instructionsLookup, Map visited) + throws Exception { + WrappedBundle wrappedNode = visited.get(node); + if (wrappedNode != null) { + return wrappedNode; + } + Artifact artifact = node.getArtifact(); + File originalFile = artifact.getFile(); + if (originalFile == null) { + if (node.getDependency().isOptional()) { + visited.put(node, + wrappedNode = new WrappedBundle(node, List.of(), null, null, null, + List.of(new ProcessingMessage(artifact, Type.WARN, + "Optional artifact " + node.getArtifact() + " was not found")))); + } else { + visited.put(node, wrappedNode = new WrappedBundle(node, List.of(), null, null, null, List.of( + new ProcessingMessage(artifact, Type.ERROR, "Artifact " + node.getArtifact() + " not found")))); + } + return wrappedNode; + } + Jar jar = new Jar(originalFile); + Manifest originalManifest = jar.getManifest(); + if (originalManifest != null + && originalManifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME) != null) { + // already a bundle! + visited.put(node, + wrappedNode = new WrappedBundle(node, List.of(), null, originalFile.toPath(), jar, List.of())); + return wrappedNode; + } + List children = node.getChildren(); + List depends = new ArrayList<>(); + for (DependencyNode child : children) { + depends.add(getWrappedNode(child, instructionsLookup, visited)); + } + WrappedBundle wrappedNodeAfterVisit = visited.get(node); + if (wrappedNodeAfterVisit != null) { + return wrappedNodeAfterVisit; + } + Properties instructions = instructionsLookup.apply(node); + String key = getInstructionsKey(instructions, depends); + try (Jar analyzerJar = jar) { + // now we know the key and the depends we enter the critical section of checking + // if the data is already there or needs to be refreshed + File parent = new File(originalFile.getParent(), "bnd-" + key); + File wrapArtifactFile = new File(parent, originalFile.getName()); + Jar cached = getCachedJar(wrapArtifactFile.toPath(), originalFile.toPath()); + if (cached == null) { + List messages = new ArrayList<>(); + wrapArtifactFile.getParentFile().mkdirs(); + try (Analyzer analyzer = new Analyzer(analyzerJar);) { + analyzer.setProperty("mvnGroupId", artifact.getGroupId()); + analyzer.setProperty("mvnArtifactId", artifact.getArtifactId()); + analyzer.setProperty("mvnVersion", artifact.getBaseVersion()); + analyzer.setProperty("mvnClassifier", artifact.getClassifier()); + String versionString = createOSGiVersion(artifact).toString(); + analyzer.setProperty("generatedOSGiVersion", versionString); + for (String property : instructions.stringPropertyNames()) { + // See https://github.com/bndtools/bnd/issues/5659 + String trimValue = instructions.getProperty(property).trim(); + analyzer.setProperty(property, trimValue); + } + for (WrappedBundle dep : depends) { + Jar depJar = dep.getJar(); + if (depJar == null) { + messages.add(new ProcessingMessage(artifact, Type.WARN, + "Dependency " + dep.getNode().getDependency() + " was ignored!")); + continue; + } + analyzer.addClasspath(depJar); + analyzer.removeClose(depJar); + } + analyzerJar.setManifest(analyzer.calcManifest()); + analyzerJar.write(wrapArtifactFile); + for (String err : analyzer.getErrors()) { + if (err.contains("Classes found in the wrong directory")) { + // ignore message from BND not supporting MR jars... + continue; + } + messages.add(new ProcessingMessage(artifact, Type.ERROR, err)); + } + for (String warn : analyzer.getWarnings()) { + messages.add(new ProcessingMessage(artifact, Type.WARN, warn)); + } + } + wrapArtifactFile.setLastModified(originalFile.lastModified()); + visited.put(node, wrappedNode = new WrappedBundle(node, depends, key, wrapArtifactFile.toPath(), + new Jar(wrapArtifactFile), messages)); + } else { + visited.put(node, wrappedNode = new WrappedBundle(node, depends, key, wrapArtifactFile.toPath(), + new Jar(wrapArtifactFile), List.of())); + } + return wrappedNode; + } + } - private static String getInstructionsKey(Properties properties, List depends) { - Stream instructionsStream = properties == null ? Stream.empty() - : properties.stringPropertyNames().stream().sorted(String.CASE_INSENSITIVE_ORDER) - .map(key -> key.toLowerCase() + ":" + properties.getProperty(key)); - Stream dependsStream = depends.stream().map(WrappedBundle::getInstructionsKey).filter(Objects::nonNull) - .sorted(String.CASE_INSENSITIVE_ORDER).distinct(); - String string = Stream.concat(instructionsStream, dependsStream).collect(Collectors.joining("#")); - return DigestUtils.md5Hex(string); - } + private static Jar getCachedJar(Path cacheFile, Path sourceFile) { + try { + if (!isOutdated(cacheFile, sourceFile)) { + return new Jar(cacheFile.toFile()); + } + } catch (IOException e) { + // if any I/O error occurs we assume we need to regenerate the data... + Platform.getLog(MavenBundleWrapper.class) + .error("Reading cached data for " + cacheFile + " failed, will regenerate the data ...", e); + } + return null; + } - public static Version createOSGiVersion(Artifact artifact) { - String version = artifact.getVersion(); - return createOSGiVersion(version); - } + private static String getInstructionsKey(Properties properties, List depends) { + Stream instructionsStream = properties == null ? Stream.empty() + : properties.stringPropertyNames().stream().sorted(String.CASE_INSENSITIVE_ORDER) + .map(key -> key.toLowerCase() + ":" + properties.getProperty(key)); + Stream dependsStream = depends.stream().map(WrappedBundle::getInstructionsKey).filter(Objects::nonNull) + .sorted(String.CASE_INSENSITIVE_ORDER).distinct(); + String string = Stream.concat(instructionsStream, dependsStream).collect(Collectors.joining("#")); + return DigestUtils.md5Hex(string); + } - public static Version createOSGiVersion(Model model) { - return createOSGiVersion(model.getVersion()); - } + public static Version createOSGiVersion(Artifact artifact) { + String version = artifact.getVersion(); + return createOSGiVersion(version); + } - private static final Pattern DASH = Pattern.compile("-"); + public static Version createOSGiVersion(Model model) { + return createOSGiVersion(model.getVersion()); + } - public static Version createOSGiVersion(String version) { - if (version == null || version.isEmpty()) { - return new Version(0, 0, 1); - } - try { - version = DASH.matcher(version).replaceFirst("."); - return Version.parseVersion(version); - } catch (IllegalArgumentException e) { - return new Version(0, 0, 1, version); - } - } + private static final Pattern DASH = Pattern.compile("-"); - public static boolean isOutdated(Path cacheFile, Path sourceFile) throws IOException { - if (Files.exists(cacheFile)) { - FileTime sourceTimeStamp = Files.getLastModifiedTime(sourceFile); - FileTime cacheTimeStamp = Files.getLastModifiedTime(cacheFile); - return sourceTimeStamp.compareTo(cacheTimeStamp) > 0; - } - return true; - } + public static Version createOSGiVersion(String version) { + if (version == null || version.isEmpty()) { + return new Version(0, 0, 1); + } + try { + version = DASH.matcher(version).replaceFirst("."); + return Version.parseVersion(version); + } catch (IllegalArgumentException e) { + return new Version(0, 0, 1, version); + } + } + + public static boolean isOutdated(Path cacheFile, Path sourceFile) throws IOException { + if (Files.exists(cacheFile)) { + FileTime sourceTimeStamp = Files.getLastModifiedTime(sourceFile); + FileTime cacheTimeStamp = Files.getLastModifiedTime(cacheFile); + return sourceTimeStamp.compareTo(cacheTimeStamp) > 0; + } + return true; + } } diff --git a/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/WrappedBundle.java b/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/WrappedBundle.java index 05137989c0..ed849c59d2 100644 --- a/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/WrappedBundle.java +++ b/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/WrappedBundle.java @@ -23,54 +23,65 @@ public final class WrappedBundle { - private final DependencyNode node; - private final List depends; - private final String instructionsKey; - private final Path file; - private final Jar jar; - private final List messages; + private final DependencyNode node; + private final List depends; + private final String instructionsKey; + private final Path file; + private final Jar jar; + private final List messages; - WrappedBundle(DependencyNode node, List depends, String key, Path file, Jar jar, - List messages) { - this.node = node; - this.depends = depends; - this.instructionsKey = key; - this.file = file; - this.jar = jar; - this.messages = messages; - } + WrappedBundle(DependencyNode node, List depends, String key, Path file, Jar jar, + List messages) { + this.node = node; + this.depends = depends; + this.instructionsKey = key; + this.file = file; + this.jar = jar; + this.messages = messages; + } - String getInstructionsKey() { - return instructionsKey; - } + String getInstructionsKey() { + return instructionsKey; + } - Jar getJar() { - return jar; - } + Jar getJar() { + return jar; + } - /** @return the location of the wrapped bundle's files */ - public Path getFile() { - return file; - } + DependencyNode getNode() { + return node; + } - /** @return the messages that where produced */ - public Stream messages() { - return Stream.concat(messages.stream(), depends.stream().flatMap(dep -> dep.messages())); - } + /** @return the location of the wrapped bundle's files */ + public Path getFile() { + return file; + } - @Override - public int hashCode() { - return Objects.hash(instructionsKey, node); - } + /** + * @param includeDependent + * if true includes messages from dependent items. + * @return the messages that where produced + */ + public Stream messages(boolean includeDependent) { + if (includeDependent) { + return Stream.concat(messages.stream(), depends.stream().flatMap(dep -> dep.messages(true))); + } + return messages.stream(); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - return obj instanceof WrappedBundle other // - && Objects.equals(instructionsKey, other.instructionsKey) // - && Objects.equals(node, other.node); - } + @Override + public int hashCode() { + return Objects.hash(instructionsKey, node); + } -} \ No newline at end of file + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + return obj instanceof WrappedBundle other // + && Objects.equals(instructionsKey, other.instructionsKey) // + && Objects.equals(node, other.node); + } + +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java b/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java index fc4e016f03..59b20febe9 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java @@ -34,6 +34,7 @@ import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import javax.xml.parsers.ParserConfigurationException; @@ -62,6 +63,7 @@ import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository; import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; import org.eclipse.m2e.pde.target.shared.MavenBundleWrapper; +import org.eclipse.m2e.pde.target.shared.ProcessingMessage; import org.eclipse.m2e.pde.target.shared.WrappedBundle; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.tycho.IArtifactFacade; @@ -215,6 +217,15 @@ public MavenTargetDefinitionContent(MavenGAVLocation location, MavenDependencies mavenArtifact.getVersion()), instructionsLookup, repositories, repositorySystem2, mavenSession.getRepositorySession(), syncContextFactory); + List directErrors = wrappedBundle.messages(false) + .filter(msg -> msg.type() == ProcessingMessage.Type.ERROR).toList(); + if (directErrors.isEmpty()) { + wrappedBundle.messages(true).map(ProcessingMessage::message) + .forEach(msg -> logger.warn(asDebugString(mavenArtifact) + ": " + msg)); + } else { + throw new RuntimeException(directErrors.stream().map(ProcessingMessage::message) + .collect(Collectors.joining(System.lineSeparator()))); + } File file = wrappedBundle.getFile().toFile(); BundleDescription description = BundlesAction.createBundleDescription(file); WrappedArtifact wrappedArtifact = new WrappedArtifact(file, mavenArtifact, diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java index cb260a500f..5df3b892e2 100644 --- a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java +++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java @@ -36,6 +36,8 @@ import java.util.stream.Collectors; import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.URIUtil; import org.eclipse.equinox.frameworkadmin.BundleInfo; import org.eclipse.m2e.pde.target.tests.spi.TargetLocationLoader; @@ -47,179 +49,198 @@ import org.junit.rules.TemporaryFolder; public abstract class AbstractMavenTargetTest { - static final String SOURCE_BUNDLE_SUFFIX = ".source"; - static final TargetBundle[] EMPTY = {}; - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private static ServiceLoader LOCATION_LOADER = ServiceLoader.load(TargetLocationLoader.class, - AbstractMavenTargetTest.class.getClassLoader()); - private static TargetLocationLoader loader; - - ITargetLocation resolveMavenTarget(String targetXML) throws Exception { - return getLoader().resolveMavenTarget(targetXML, temporaryFolder.newFolder()); - } - - private static TargetLocationLoader getLoader() { - if (loader == null) { - Provider provider = LOCATION_LOADER.stream().sorted().findFirst().orElseThrow( - () -> new IllegalStateException("No TargetLocationLoader found on classpath of test!")); - loader = provider.get(); - } - return loader; - } - - protected static void assertStatusOk(IStatus status) { - if (!status.isOK()) { - throw new AssertionError(status.toString(), status.getException()); - } - } - - // --- common assertion utilities --- - - private static Map assertTargetContent(List expectedUnits, T[] allUnit, - BiPredicate matcher, Function getLocation, Predicate isSourceUnit, - Function getSourceTarget, Function toString) { - - Map units = new HashMap<>(); - List allElements = new ArrayList<>(Arrays.asList(allUnit)); - for (U expectedUnit : expectedUnits) { - List matchingUnits = allElements.stream().filter(u -> matcher.test(expectedUnit, u)).toList(); - - if (matchingUnits.isEmpty()) { - fail("Expected unit is missing: " + expectedUnit); - } else if (matchingUnits.size() == 1) { - T targetUnit = matchingUnits.get(0); - allElements.remove(targetUnit); - - assertEquals("Unexpected 'original' state of " + targetUnit, expectedUnit.isOriginal(), - isOriginalArtifact(expectedUnit, targetUnit, getLocation)); - assertEquals("Unexpected 'isSource' state of " + targetUnit, expectedUnit.isSourceBundle(), - isSourceUnit.test(targetUnit)); - if (expectedUnit.isSourceBundle()) { - String expectedSourceTarget = expectedUnit.id().substring(0, - expectedUnit.id().length() - SOURCE_BUNDLE_SUFFIX.length()); - assertEquals("Source target id", expectedSourceTarget, getSourceTarget.apply(targetUnit)); - } else { - assertNull(getSourceTarget.apply(targetUnit)); - } - units.put(expectedUnit, targetUnit); - } else { - fail("Expected bundle contaiend multiple times:" + expectedUnit); - } - } - if (!allElements.isEmpty()) { - String unepxectedBundlesList = allElements.stream().map(u -> " " + toString.apply(u)) - .collect(Collectors.joining("\n")); - fail("Encoutnered the following unexpected bundles:" + unepxectedBundlesList); - } - return units; - } - - private static boolean isOriginalArtifact(ExpectedUnit expectedUnit, T unit, Function getLocation) { - ArtifactKey key = expectedUnit.key(); - if (key == null) { - return false; - } - URI location = getLocation.apply(unit); - String expectedPathSuffix = "/" + String.join("/", ".m2", "repository", key.groupId().replace('.', '/'), - key.artifactId(), key.version(), key.artifactId() + "-" + key.version() + ".jar"); - return location.toASCIIString().endsWith(expectedPathSuffix); - } - - // --- assertion utilities for Bundles in target --- - - static ExpectedBundle originalOSGiBundle(String bsn, String version, String groupArtifact) { - return originalOSGiBundle(bsn, version, groupArtifact, version); - } - - static ExpectedBundle originalOSGiBundle(String bsn, String version, String groupArtifact, String mavenVersion) { - return new ExpectedBundle(bsn, version, false, true, - ArtifactKey.fromPortableString(groupArtifact + ":" + mavenVersion + "::")); - } - - static ExpectedBundle generatedBundle(String bsn, String version, String groupArtifact) { - return new ExpectedBundle(bsn, version, false, false, - ArtifactKey.fromPortableString(groupArtifact + ":" + version + "::")); - } - - static List withSourceBundles(List mainBundles) { - return mainBundles.stream().mapMulti((unit, downStream) -> { - downStream.accept(unit); - String sourceId = unit.bsn() + SOURCE_BUNDLE_SUFFIX; - ExpectedBundle sourceUnit = new ExpectedBundle(sourceId, unit.version(), true, false, unit.key()); - downStream.accept(sourceUnit); - }).toList(); - } - - static Attributes getManifestMainAttributes(TargetBundle targetBundle) throws IOException { - BundleInfo bundleInfo = targetBundle.getBundleInfo(); - File file = URIUtil.toFile(bundleInfo.getLocation()); - try (var jar = new JarFile(file)) { - return jar.getManifest().getMainAttributes(); - } - } - - static void assertTargetBundles(ITargetLocation target, List expectedUnits) { - assertTargetContent(expectedUnits, target.getBundles(), // - (expectedBundle, bundle) -> { - BundleInfo info = bundle.getBundleInfo(); - return expectedBundle.bsn().equals(info.getSymbolicName()) - && expectedBundle.version().equals(info.getVersion()); - }, // - tb -> tb.getBundleInfo().getLocation(), // - tb -> tb.isSourceBundle(), - tb -> tb.getSourceTarget() != null ? tb.getSourceTarget().getSymbolicName() : null, - tb -> tb.getBundleInfo().getSymbolicName() + ":" + tb.getBundleInfo().getVersion()); - } - - // --- assertion utilities for Features in a target --- - - static ExpectedFeature originalFeature(String id, String version, String groupArtifact, - List containedPlugins) { - ArtifactKey key = ArtifactKey.fromPortableString(groupArtifact + ":" + version + "::"); - return new ExpectedFeature(id, version, false, true, key, containedPlugins); - } - - static ExpectedFeature generatedFeature(String id, String version, List containedPlugins) { - return new ExpectedFeature(id, version, false, false, null, containedPlugins); - } - - static NameVersionDescriptor featurePlugin(String bsn, String version) { - return new NameVersionDescriptor(bsn, version); - } - - static List withSourceFeatures(List mainFeatures) { - return mainFeatures.stream().mapMulti((feature, downStream) -> { - downStream.accept(feature); - String sourceId = feature.id() + SOURCE_BUNDLE_SUFFIX; - List sourcePlugins = feature.containedPlugins().stream() - .map(d -> featurePlugin(d.getId() + SOURCE_BUNDLE_SUFFIX, d.getVersion())).toList(); - ExpectedFeature sourceUnit = new ExpectedFeature(sourceId, feature.version(), true, false, feature.key(), - sourcePlugins); - downStream.accept(sourceUnit); - }).toList(); - } - - static Map assertTargetFeatures(ITargetLocation target, - List expectedFeatures) { - var encounteredFeatures = assertTargetContent(expectedFeatures, target.getFeatures(), // - (expectedFeature, feature) -> expectedFeature.id().equals(feature.getId()) - && expectedFeature.version().equals(feature.getVersion()), // - f -> Path.of(f.getLocation()).toUri(), // - f -> isSourceFeature(f), // - f -> isSourceFeature(f) ? f.getId().substring(0, f.getId().length() - SOURCE_BUNDLE_SUFFIX.length()) - : null, // - f -> f.getId() + ":" + f.getVersion()); - encounteredFeatures.forEach((expectedFeature, feature) -> { - assertEquals(Set.copyOf(expectedFeature.containedPlugins()), Set.of(feature.getPlugins())); - }); - return encounteredFeatures; - } - - private static boolean isSourceFeature(TargetFeature f) { - return f.getId().endsWith(SOURCE_BUNDLE_SUFFIX) - && Arrays.stream(f.getPlugins()).allMatch(d -> d.getId().endsWith(SOURCE_BUNDLE_SUFFIX)); - } + static final String SOURCE_BUNDLE_SUFFIX = ".source"; + static final TargetBundle[] EMPTY = {}; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private static ServiceLoader LOCATION_LOADER = ServiceLoader.load(TargetLocationLoader.class, + AbstractMavenTargetTest.class.getClassLoader()); + private static TargetLocationLoader loader; + + ITargetLocation resolveMavenTarget(String targetXML) throws Exception { + return getLoader().resolveMavenTarget(targetXML, temporaryFolder.newFolder()); + } + + private static TargetLocationLoader getLoader() { + if (loader == null) { + Provider provider = LOCATION_LOADER.stream().sorted().findFirst().orElseThrow( + () -> new IllegalStateException("No TargetLocationLoader found on classpath of test!")); + loader = provider.get(); + } + return loader; + } + + protected static void assertStatusOk(IStatus status) { + if (!status.isOK()) { + throw new AssertionError(status.toString(), status.getException()); + } + } + + // --- common assertion utilities --- + + private static Map assertTargetContent(List expectedUnits, T[] allUnit, + BiPredicate matcher, Function getLocation, Predicate isSourceUnit, + Function getSourceTarget, Function toString) { + + Map units = new HashMap<>(); + List allElements = new ArrayList<>(Arrays.asList(allUnit)); + for (U expectedUnit : expectedUnits) { + List matchingUnits = allElements.stream().filter(u -> matcher.test(expectedUnit, u)).toList(); + + if (matchingUnits.isEmpty()) { + fail("Expected unit is missing: " + expectedUnit); + } else if (matchingUnits.size() == 1) { + T targetUnit = matchingUnits.get(0); + allElements.remove(targetUnit); + + assertEquals("Unexpected 'original' state of " + targetUnit, expectedUnit.isOriginal(), + isOriginalArtifact(expectedUnit, targetUnit, getLocation)); + assertEquals("Unexpected 'isSource' state of " + targetUnit, expectedUnit.isSourceBundle(), + isSourceUnit.test(targetUnit)); + if (expectedUnit.isSourceBundle()) { + String expectedSourceTarget = expectedUnit.id().substring(0, + expectedUnit.id().length() - SOURCE_BUNDLE_SUFFIX.length()); + assertEquals("Source target id", expectedSourceTarget, getSourceTarget.apply(targetUnit)); + } else { + assertNull(getSourceTarget.apply(targetUnit)); + } + units.put(expectedUnit, targetUnit); + } else { + fail("Expected bundle contaiend multiple times:" + expectedUnit); + } + } + if (!allElements.isEmpty()) { + String unepxectedBundlesList = allElements.stream().map(u -> " " + toString.apply(u)) + .collect(Collectors.joining("\n")); + fail("Encoutnered the following unexpected bundles:" + unepxectedBundlesList); + } + return units; + } + + private static boolean isOriginalArtifact(ExpectedUnit expectedUnit, T unit, Function getLocation) { + ArtifactKey key = expectedUnit.key(); + if (key == null) { + return false; + } + URI location = getLocation.apply(unit); + String expectedPathSuffix = "/" + String.join("/", ".m2", "repository", key.groupId().replace('.', '/'), + key.artifactId(), key.version(), key.artifactId() + "-" + key.version() + ".jar"); + return location.toASCIIString().endsWith(expectedPathSuffix); + } + + // --- assertion utilities for Bundles in target --- + + static ExpectedBundle originalOSGiBundle(String bsn, String version, String groupArtifact) { + return originalOSGiBundle(bsn, version, groupArtifact, version); + } + + static ExpectedBundle originalOSGiBundle(String bsn, String version, String groupArtifact, String mavenVersion) { + return new ExpectedBundle(bsn, version, false, true, + ArtifactKey.fromPortableString(groupArtifact + ":" + mavenVersion + "::")); + } + + static ExpectedBundle generatedBundle(String bsn, String version, String groupArtifact) { + return new ExpectedBundle(bsn, version, false, false, + ArtifactKey.fromPortableString(groupArtifact + ":" + version + "::")); + } + + static List withSourceBundles(List mainBundles) { + return mainBundles.stream(). mapMulti((unit, downStream) -> { + downStream.accept(unit); + String sourceId = unit.bsn() + SOURCE_BUNDLE_SUFFIX; + ExpectedBundle sourceUnit = new ExpectedBundle(sourceId, unit.version(), true, false, unit.key()); + downStream.accept(sourceUnit); + }).toList(); + } + + static Attributes getManifestMainAttributes(TargetBundle targetBundle) throws IOException { + BundleInfo bundleInfo = targetBundle.getBundleInfo(); + File file = URIUtil.toFile(bundleInfo.getLocation()); + try (var jar = new JarFile(file)) { + return jar.getManifest().getMainAttributes(); + } + } + + static void assertTargetBundles(ITargetLocation target, List expectedUnits) { + assertTargetContent(expectedUnits, target.getBundles(), // + (expectedBundle, bundle) -> { + BundleInfo info = bundle.getBundleInfo(); + return expectedBundle.bsn().equals(info.getSymbolicName()) + && expectedBundle.version().equals(info.getVersion()); + }, // + tb -> tb.getBundleInfo().getLocation(), // + tb -> tb.isSourceBundle(), + tb -> tb.getSourceTarget() != null ? tb.getSourceTarget().getSymbolicName() : null, + tb -> tb.getBundleInfo().getSymbolicName() + ":" + tb.getBundleInfo().getVersion()); + } + + // --- assertion utilities for Features in a target --- + + static ExpectedFeature originalFeature(String id, String version, String groupArtifact, + List containedPlugins) { + ArtifactKey key = ArtifactKey.fromPortableString(groupArtifact + ":" + version + "::"); + return new ExpectedFeature(id, version, false, true, key, containedPlugins); + } + + static ExpectedFeature generatedFeature(String id, String version, List containedPlugins) { + return new ExpectedFeature(id, version, false, false, null, containedPlugins); + } + + static NameVersionDescriptor featurePlugin(String bsn, String version) { + return new NameVersionDescriptor(bsn, version); + } + + static List withSourceFeatures(List mainFeatures) { + return mainFeatures.stream(). mapMulti((feature, downStream) -> { + downStream.accept(feature); + String sourceId = feature.id() + SOURCE_BUNDLE_SUFFIX; + List sourcePlugins = feature.containedPlugins().stream() + .map(d -> featurePlugin(d.getId() + SOURCE_BUNDLE_SUFFIX, d.getVersion())).toList(); + ExpectedFeature sourceUnit = new ExpectedFeature(sourceId, feature.version(), true, false, feature.key(), + sourcePlugins); + downStream.accept(sourceUnit); + }).toList(); + } + + static Map assertTargetFeatures(ITargetLocation target, + List expectedFeatures) { + var encounteredFeatures = assertTargetContent(expectedFeatures, target.getFeatures(), // + (expectedFeature, feature) -> expectedFeature.id().equals(feature.getId()) + && expectedFeature.version().equals(feature.getVersion()), // + f -> Path.of(f.getLocation()).toUri(), // + f -> isSourceFeature(f), // + f -> isSourceFeature(f) ? f.getId().substring(0, f.getId().length() - SOURCE_BUNDLE_SUFFIX.length()) + : null, // + f -> f.getId() + ":" + f.getVersion()); + encounteredFeatures.forEach((expectedFeature, feature) -> { + assertEquals(Set.copyOf(expectedFeature.containedPlugins()), Set.of(feature.getPlugins())); + }); + return encounteredFeatures; + } + + static boolean isSourceFeature(TargetFeature f) { + return f.getId().endsWith(SOURCE_BUNDLE_SUFFIX) + && Arrays.stream(f.getPlugins()).allMatch(d -> d.getId().endsWith(SOURCE_BUNDLE_SUFFIX)); + } + + static IStatus getTargetStatus(ITargetLocation target) { + IStatus status = target.getStatus(); + if (status != null && !status.isOK()) { + return status; + } + MultiStatus result = new MultiStatus("org.eclipse.pde.core", 0, + "Problems occurred getting the plug-ins in this container", null); + for (TargetBundle targetBundle : target.getBundles()) { + IStatus bundleStatus = targetBundle.getStatus(); + if (!bundleStatus.isOK()) { + result.add(bundleStatus); + } + } + if (result.isOK()) { + return Status.OK_STATUS; + } + return result; + } } diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java index 5377f074c9..44cda6e77a 100644 --- a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java +++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java @@ -39,6 +39,59 @@ public class OSGiMetadataGenerationTest extends AbstractMavenTargetTest { + @Test + public void testBadDependencyInChain() throws Exception { + ITargetLocation target = resolveMavenTarget(""" + + + + edu.ucar + cdm + 4.5.5 + jar + + + + """); + assertStatusOk(getTargetStatus(target)); + } + + @Test + public void testBadDependencyDirect() throws Exception { + ITargetLocation target = resolveMavenTarget(""" + + + + com.ibm.icu + icu4j + 2.6.1 + jar + + + + """); + IStatus targetStatus = getTargetStatus(target); + assertEquals(String.valueOf(targetStatus), IStatus.ERROR, targetStatus.getSeverity()); + } + + @Test + public void testMissingOptionalDependency() throws Exception { + ITargetLocation target = resolveMavenTarget( + """ + + + + net.sf.saxon + Saxon-HE + 10.9 + jar + + + + """); + assertStatusOk(getTargetStatus(target)); + } + @Test @Ignore("FIXME") public void testNonOSGiArtifact_missingArtifactError() throws Exception {