From 8a991db364e1436ffb9ae6f27a6ddc3276b205a9 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 27 Nov 2023 23:20:52 +0100 Subject: [PATCH 1/3] Show blacklisted req/caps on resolve failure 1st draft of an attempt to improve and enrich the Resolution failure output with information about blacklisted items which were not considered for resolution. The goal is to make it easier to spot resolution failure because of a too broad blacklist. This is based on a real use case where the blacklist was the reason, but the current tooling gave no indication about this. TODO discussion and cleanup needed Signed-off-by: Christoph Rueger --- .../aQute/resolve/AbstractResolveContext.java | 17 ++++- .../ResolutionExceptionWithDetails.java | 38 +++++++++++ .../src/biz/aQute/resolve/ResolveProcess.java | 64 +++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java diff --git a/biz.aQute.resolve/src/biz/aQute/resolve/AbstractResolveContext.java b/biz.aQute.resolve/src/biz/aQute/resolve/AbstractResolveContext.java index 44626be548..8719613ac9 100644 --- a/biz.aQute.resolve/src/biz/aQute/resolve/AbstractResolveContext.java +++ b/biz.aQute.resolve/src/biz/aQute/resolve/AbstractResolveContext.java @@ -100,6 +100,7 @@ public abstract class AbstractResolveContext extends ResolveContext { private Resource systemResource; private Resource inputResource; private Set blacklistedResources = new HashSet<>(); + private final Set blacklistedCapabilities = new HashSet<>(); private int level = 0; private Resource framework; private final AtomicBoolean reported = new AtomicBoolean(); @@ -285,10 +286,20 @@ protected ArrayList findProvidersFromRepositories(Requirement requir protected Collection findProviders(Repository repo, Requirement requirement) { Map> map = repo.findProviders(Collections.singleton(requirement)); Collection caps = map.get(requirement); - caps.removeIf(capability -> blacklistedResources.contains(capability.getResource())); + caps.removeIf(capability -> isBlacklisted(capability)); return caps; } + private boolean isBlacklisted(Capability capability) { + + boolean contains = blacklistedResources.contains(capability.getResource()); + if (contains) { + blacklistedCapabilities.add(capability); + } + + return contains; + } + private void setResourcePriority(int priority, Resource resource) { resourcePriorities.putIfAbsent(resource, priority); } @@ -695,6 +706,10 @@ public Set getBlackList() { return blacklistedResources; } + public Set getBlacklistedCapabilities() { + return blacklistedCapabilities; + } + public void setLevel(int n) { this.level = n; } diff --git a/biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java b/biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java new file mode 100644 index 0000000000..b11437a9bc --- /dev/null +++ b/biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java @@ -0,0 +1,38 @@ +package biz.aQute.resolve; + +import java.util.Collection; +import java.util.Set; + +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; +import org.osgi.service.resolver.ResolutionException; + +/** + * A {@link ResolutionException} providing more details about why resolution has + * failed. + */ +public class ResolutionExceptionWithDetails extends ResolutionException { + + private static final long serialVersionUID = 1L; + + private Set blackList; + private Set blacklistedCapabilities; + + public ResolutionExceptionWithDetails(String message, Throwable cause, + Collection unresolvedRequirements, Set blackList, + Set blacklistedCapabilities) { + super(message, cause, unresolvedRequirements); + this.blackList = blackList; + this.blacklistedCapabilities = blacklistedCapabilities; + + } + + public Set getBlackList() { + return blackList; + } + + public Set getBlacklistedCapabilities() { + return blacklistedCapabilities; + } +} diff --git a/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java b/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java index 26745fdb61..fd69d58304 100644 --- a/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java +++ b/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java @@ -241,6 +241,7 @@ public static String format(ResolutionException re) { * output * @return the report */ + @SuppressWarnings("unlikely-arg-type") public static String format(ResolutionException re, boolean reportOptional) { List chain = getCausalChain(re); @@ -287,6 +288,55 @@ public static String format(ResolutionException re, boolean reportOptional) { .forEach(formatGroup(f)); } + // 4. Check Blacklist + + if (re instanceof ResolutionExceptionWithDetails detailedExc) { + Set blackList = detailedExc.getBlackList(); + Set blacklistedCapabilities = detailedExc.getBlacklistedCapabilities(); + + if (!blacklistedCapabilities.isEmpty()) { + + f.format( + "%n%nBlacklisted Capabilities: Some requirements could not be satisfied because of blacklisted capabilities in -runblacklist:%n"); + + for (Requirement req : chain) { + + String namespace = req.getNamespace(); + String filter = req.getDirectives() + .get("filter"); + + for (Resource blacklistedRes : blackList) { + + List findCapability = ResourceUtils.findCapability(blacklistedRes, namespace, + filter); + if (!findCapability.isEmpty()) { + f.format( + "%s is ignored because it is blacklisted although providing required capability %s%s%n", + blacklistedRes, namespace, filter); + } + + } + + } + + f.format("%n%nAll Blacklisted Capabilities:%n"); + + for (Capability cap : blacklistedCapabilities) { + f.format("%s providing capability %s:%s ignored%n", cap.getResource(), cap.getNamespace(), + cap.getAttributes() + .get(cap.getNamespace())); + } + + f.format("%n%nAll Blacklisted Resources%n"); + + for (Resource res : blackList) { + f.format("%s%n", res); + } + } + + + } + return f.toString(); } } @@ -435,6 +485,20 @@ private static ResolutionException augment(Collection unresolved, R } } } catch (TimeoutException toe) {} + + if (context instanceof AbstractResolveContext arctx) { + Set blackList = arctx.getBlackList(); + Set blacklistedCapabilities = arctx.getBlacklistedCapabilities(); + + if (!blacklistedCapabilities.isEmpty()) { + return new ResolutionExceptionWithDetails(re.getMessage(), re, list, blackList, + blacklistedCapabilities); + } else { + return new ResolutionException(re.getMessage(), re, list); + } + + } + return new ResolutionException(re.getMessage(), re, list); } From 80638255b7a98977d29b55f7212628a707a39616 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Tue, 28 Nov 2023 16:23:15 +0100 Subject: [PATCH 2/3] remove @SuppressWarnings, add Null check, renaming Signed-off-by: Christoph Rueger --- ...ceptionWithDetails.java => BndResolutionException.java} | 4 ++-- .../src/biz/aQute/resolve/ResolveProcess.java | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) rename biz.aQute.resolve/src/biz/aQute/resolve/{ResolutionExceptionWithDetails.java => BndResolutionException.java} (86%) diff --git a/biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java b/biz.aQute.resolve/src/biz/aQute/resolve/BndResolutionException.java similarity index 86% rename from biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java rename to biz.aQute.resolve/src/biz/aQute/resolve/BndResolutionException.java index b11437a9bc..a71e26d8d5 100644 --- a/biz.aQute.resolve/src/biz/aQute/resolve/ResolutionExceptionWithDetails.java +++ b/biz.aQute.resolve/src/biz/aQute/resolve/BndResolutionException.java @@ -12,14 +12,14 @@ * A {@link ResolutionException} providing more details about why resolution has * failed. */ -public class ResolutionExceptionWithDetails extends ResolutionException { +public class BndResolutionException extends ResolutionException { private static final long serialVersionUID = 1L; private Set blackList; private Set blacklistedCapabilities; - public ResolutionExceptionWithDetails(String message, Throwable cause, + public BndResolutionException(String message, Throwable cause, Collection unresolvedRequirements, Set blackList, Set blacklistedCapabilities) { super(message, cause, unresolvedRequirements); diff --git a/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java b/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java index fd69d58304..3451f6fa64 100644 --- a/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java +++ b/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java @@ -241,7 +241,6 @@ public static String format(ResolutionException re) { * output * @return the report */ - @SuppressWarnings("unlikely-arg-type") public static String format(ResolutionException re, boolean reportOptional) { List chain = getCausalChain(re); @@ -290,11 +289,11 @@ public static String format(ResolutionException re, boolean reportOptional) { // 4. Check Blacklist - if (re instanceof ResolutionExceptionWithDetails detailedExc) { + if (re instanceof BndResolutionException detailedExc) { Set blackList = detailedExc.getBlackList(); Set blacklistedCapabilities = detailedExc.getBlacklistedCapabilities(); - if (!blacklistedCapabilities.isEmpty()) { + if (blacklistedCapabilities != null && !blacklistedCapabilities.isEmpty()) { f.format( "%n%nBlacklisted Capabilities: Some requirements could not be satisfied because of blacklisted capabilities in -runblacklist:%n"); @@ -491,7 +490,7 @@ private static ResolutionException augment(Collection unresolved, R Set blacklistedCapabilities = arctx.getBlacklistedCapabilities(); if (!blacklistedCapabilities.isEmpty()) { - return new ResolutionExceptionWithDetails(re.getMessage(), re, list, blackList, + return new BndResolutionException(re.getMessage(), re, list, blackList, blacklistedCapabilities); } else { return new ResolutionException(re.getMessage(), re, list); From fe0ed35ddb2c6ca72252143ce8c06db1edc4464a Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Tue, 28 Nov 2023 16:39:52 +0100 Subject: [PATCH 3/3] extract methods and Signed-off-by: Christoph Rueger --- .../src/biz/aQute/resolve/ResolveProcess.java | 85 ++++++++++++------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java b/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java index 3451f6fa64..c7f61234d0 100644 --- a/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java +++ b/biz.aQute.resolve/src/biz/aQute/resolve/ResolveProcess.java @@ -290,53 +290,76 @@ public static String format(ResolutionException re, boolean reportOptional) { // 4. Check Blacklist if (re instanceof BndResolutionException detailedExc) { - Set blackList = detailedExc.getBlackList(); - Set blacklistedCapabilities = detailedExc.getBlacklistedCapabilities(); + printBlacklistDebugLog(chain, f, detailedExc); + } - if (blacklistedCapabilities != null && !blacklistedCapabilities.isEmpty()) { + return f.toString(); + } + } - f.format( - "%n%nBlacklisted Capabilities: Some requirements could not be satisfied because of blacklisted capabilities in -runblacklist:%n"); + /** + * Print -runblacklist related debug output if exists. + * + * @param unresolvedRequirements + * @param f + * @param detailedExc + */ + private static void printBlacklistDebugLog(List unresolvedRequirements, Formatter f, + BndResolutionException detailedExc) { + Set blackList = detailedExc.getBlackList(); + Set blacklistedCapabilities = detailedExc.getBlacklistedCapabilities(); - for (Requirement req : chain) { + if (blacklistedCapabilities != null && !blacklistedCapabilities.isEmpty()) { - String namespace = req.getNamespace(); - String filter = req.getDirectives() - .get("filter"); + f.format( + "%n%nBlacklisted Capabilities: Some requirements could not be satisfied because of blacklisted capabilities in -runblacklist:%n"); - for (Resource blacklistedRes : blackList) { + printBlacklistSummary(unresolvedRequirements, f, blackList); - List findCapability = ResourceUtils.findCapability(blacklistedRes, namespace, - filter); - if (!findCapability.isEmpty()) { - f.format( - "%s is ignored because it is blacklisted although providing required capability %s%s%n", - blacklistedRes, namespace, filter); - } + f.format("%n%nAll blacklisted Capabilities:%n"); - } + for (Capability cap : blacklistedCapabilities) { + f.format("'%s' providing capability '%s: %s' ignored%n", cap.getResource(), cap.getNamespace(), + cap.getAttributes() + .get(cap.getNamespace())); + } - } + f.format("%n%nAll blacklisted Resources:%n"); - f.format("%n%nAll Blacklisted Capabilities:%n"); + for (Resource res : blackList) { + f.format("%s%n", res); + } + } + } - for (Capability cap : blacklistedCapabilities) { - f.format("%s providing capability %s:%s ignored%n", cap.getResource(), cap.getNamespace(), - cap.getAttributes() - .get(cap.getNamespace())); - } + /** + * Tries to determine which of the blacklisted capability (resource) is + * responsible for an unresolved requirement. + * + * @param unresolvedRequirements + * @param f + * @param blackList + */ + private static void printBlacklistSummary(List unresolvedRequirements, Formatter f, + Set blackList) { + for (Requirement req : unresolvedRequirements) { - f.format("%n%nAll Blacklisted Resources%n"); + String namespace = req.getNamespace(); + String filter = req.getDirectives() + .get("filter"); - for (Resource res : blackList) { - f.format("%s%n", res); - } - } + for (Resource blacklistedRes : blackList) { + List findCapability = ResourceUtils.findCapability(blacklistedRes, namespace, + filter); + if (!findCapability.isEmpty()) { + f.format( + "'%s' is ignored because it is blacklisted although providing required capability '%s: %s'%n", + blacklistedRes, namespace, filter); + } } - return f.toString(); } }