From 07bb70ca8e203aee784ec0fabc7287ce0e3d4e48 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Thu, 3 Oct 2024 07:42:46 +0200 Subject: [PATCH 01/17] move logic to MetaInfServiceParser it leverages the recent addition of PR https://github.com/bndtools/bnd/pull/5912 that allowed to add annotations in comments of files in META-INF/services. But in this PR we basically pretent and add a aQute.bnd.annotation.spi.ServiceProvider annotation artificially. this causes bnd to generate Provide-Capability manifest headers for the services Signed-off-by: Christoph Rueger --- .../src/aQute/bnd/osgi/Constants.java | 9 ++++++ .../bnd/osgi/metainf/MetaInfService.java | 1 + .../osgi/metainf/MetaInfServiceParser.java | 30 ++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java index 2c52a44797..3f3499bfd5 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java @@ -567,6 +567,14 @@ public interface Constants { String SERVICELOADER_REGISTER_DIRECTIVE = "register:"; String SERVICELOADER_NAMESPACE = "osgi.serviceloader"; + /* + * processing of META-INF/services folder section. + */ + String METAINF_SERVICES = "-metainf-services"; + String METAINF_SERVICES_STRATEGY_ANNOTATION = "annotation"; + String METAINF_SERVICES_STRATEGY_AUTO = "auto"; + String METAINF_SERVICES_STRATEGY_NONE = "none"; + /** * Launch constants that should be shared by launchers */ @@ -602,6 +610,7 @@ public interface Constants { String INTERNAL_PREFIX = "-internal-"; + /* * Deprecated Section */ diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java index 358b7f7927..c65670f84e 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java @@ -228,6 +228,7 @@ public MetaInfService(String serviceName, String source) { // just comment continue; } + implementations.put(line, new Implementation(line, annotations, comments)); annotations.clear(); comments.clear(); diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java index fd10c7393c..a6f0f32437 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java @@ -1,9 +1,15 @@ package aQute.bnd.osgi.metainf; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_ANNOTATION; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_AUTO; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_NONE; + import java.lang.annotation.RetentionPolicy; import java.util.Map; import aQute.bnd.header.Attrs; +import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Annotation; import aQute.bnd.osgi.Annotation.ElementType; @@ -25,6 +31,14 @@ public class MetaInfServiceParser implements AnalyzerPlugin { */ @Override public boolean analyzeJar(Analyzer analyzer) throws Exception { + + String strategy = strategy(analyzer); + + if (METAINF_SERVICES_STRATEGY_NONE.equals(strategy)) { + // do not process META-INF/services files + return false; + } + MetaInfService.getServiceFiles(analyzer.getJar()) .values() .stream() @@ -32,7 +46,18 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { .values() .stream()) .forEach(impl -> { - impl.getAnnotations() + Parameters annotations = impl.getAnnotations(); + + if (annotations.isEmpty() && METAINF_SERVICES_STRATEGY_AUTO.equals(strategy)) { + // if there are no annotations at the impl + // we add one artificially to create the capabilities for + // Service without any attributes in the manifest e.g. + // Provide-Capability', + // "osgi.serviceloader;osgi.serviceloader=serviceName + annotations.add("aQute.bnd.annotation.spi.ServiceProvider", Attrs.EMPTY_ATTRS); + } + + annotations .forEach((annotationName, attrs) -> { doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); }); @@ -57,4 +82,7 @@ private void doAnnotationsforMetaInf(Analyzer analyzer, Implementation impl, Str } } + private String strategy(Analyzer analyzer) { + return analyzer.getProperty(METAINF_SERVICES, METAINF_SERVICES_STRATEGY_ANNOTATION); + } } From e959863f22f2fb68bb3a063f533e5f13479b7d37 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Thu, 3 Oct 2024 18:32:13 +0200 Subject: [PATCH 02/17] docs for -metainf-services Signed-off-by: Christoph Rueger --- .../bnd/osgi/metainf/MetaInfService.java | 1 - docs/_chapters/230-manifest-annotations.md | 8 ++++ docs/_instructions/metainf-services.md | 47 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/_instructions/metainf-services.md diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java index c65670f84e..358b7f7927 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java @@ -228,7 +228,6 @@ public MetaInfService(String serviceName, String source) { // just comment continue; } - implementations.put(line, new Implementation(line, annotations, comments)); annotations.clear(); comments.clear(); diff --git a/docs/_chapters/230-manifest-annotations.md b/docs/_chapters/230-manifest-annotations.md index 14ddecde52..4e79cefafd 100644 --- a/docs/_chapters/230-manifest-annotations.md +++ b/docs/_chapters/230-manifest-annotations.md @@ -42,6 +42,8 @@ META-INF/services/com.example.ServiceType: ``` The processing is identical to the normal class based annotation processing. The `#class` macro will be set to the implementation class. The `#value` will be set in all cases to the service type unless overridden. +This behavior can be controlled with the [-metainf-services](/instructions/metainf-services.html) instruction. You can set `-metainf-services: auto` to also automatically create `Provide-Capability` headers for services without annotations. + Clearly using the class annotation is far superior: * Help from the IDE @@ -52,6 +54,12 @@ The analysis of these files happens after the analyzer plugins have been run. Th Since the annotations & imports happen in the comments, it is not possible to diagnose any errors. If the comment does not match its regular expression, it will be silently ignored. + +**Note:** Make sure `biz.aQute.bnd.annotation` is on the classpath / buildpath which contains the `aQute.bnd.annotation.spi.ServiceProvider` annotation. Otherwise the annotation won't be processed and you might see a warning in the log like this: + +`Unable to determine whether the meta annotation aQute.bnd.annotation.spi.ServiceProvider applied to type xyz provides bundle annotations as it is not on the project build path. If this annotation does provide bundle annotations then it must be present on the build path in order to be processed` + + ### @Requirement & @Capability Annotations Though Java class files contain enough information to find code dependencies, there are many dependencies that are indirect. OSGi _extenders_ for instance are often a requirement to make a bundle function correctly but often client bundles have no code dependency on the extender. For example, Declarative Services (DS) went out of its way to allow components to be Plain Old Java Objects (POJO). The result is that resolving a closure of bundles starting from a DS client bundle would not drag in the Service Component Runtime (SCR), resulting in a satisfied but rather idle closure. diff --git a/docs/_instructions/metainf-services.md b/docs/_instructions/metainf-services.md new file mode 100644 index 0000000000..b9562a7011 --- /dev/null +++ b/docs/_instructions/metainf-services.md @@ -0,0 +1,47 @@ +--- +layout: default +class: Analyzer +title: -metainf-services +summary: Controls how META-INF/services files are processed. +--- + +The `-metainf-services` instruction tells **bnd** how files in the `META-INF/services` should be processed. +See the chapter about [META-INF/services Annotations](/chapters/230-manifest-annotations.html#meta-infservices-annotations) how to apply Bundle annotations to the files in the `META-INF/services`. + +This instruction can have the following values: + +- `annotation` (default if not set): Scan files and only process annotations. +- `auto`: process annotations and special treatment and auto-register services without annotations. That means that bnd auto-generates a `aQute.bnd.annotation.spi.ServiceProvider` annotation without attributes if an Implementation doesn't have one. This is useful if you just want to have bnd generate the `Provide-Capability` headers for `osgi.serviceloader`. +- `none`: disable processing of files in `META-INF/services` + +## Example + +Assume we want to wrap a non-OSGi library. +Create `bnd.bnd` file and a library having a `META-INF/services` folder e.g. [groovy-3.0.22.jar](https://mvnrepository.com/artifact/org.codehaus.groovy/groovy/3.0.22) in a local `lib` folder. +We will use `-includeresource: @lib/groovy-3.0.22.jar` to [unroll the jar](/instructions/includeresource.html#rolling). + +``` +# bnd.bnd +-buildpath: biz.aQute.bnd.annotation;version='7.0' +-includeresource: @lib/groovy-3.0.22.jar +-metainf-services: auto +``` + +This creates a new jar with the following MANIFEST.MF headers from the `META-INF/services` folder: + +``` +# MANIFEST.MF +Provide-Capability osgi.service;objectClass:List="org.codehaus.groovy.transform.ASTTransformation";effective:=active + osgi.serviceloader;osgi.serviceloader="org.codehaus.groovy.transform.ASTTransformation";register:="groovy.grape.GrabAnnotationTransformation" +Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))" + osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))" +``` + +`-metainf-services: auto` causes bnd to process annotations and also auto-generate an annotation for services without annotations. +To prevent the latter (auto-generation) use the default `-metainf-services: annotation` or remove the instruction completely. + +**Note:** Make sure `biz.aQute.bnd.annotation` is on the classpath / buildpath which contains `aQute.bnd.annotation.spi.ServiceProvider` annotation. + + + +[source](https://github.com/bndtools/bnd/blob/master/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java) \ No newline at end of file From f9edbc6dbea4149d78515a0f428d2f37312f727f Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Thu, 3 Oct 2024 23:06:00 +0200 Subject: [PATCH 03/17] add test Signed-off-by: Christoph Rueger --- .../ServiceProviderFileTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java index 1d67cad542..137c128057 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java @@ -99,4 +99,42 @@ public void testBothMetaInfoAndAnnotationsNoParentheses() throws Exception { } } + @SuppressWarnings("unchecked") + @Test + public void testAutoGenerateServiceProviderAnnotation() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setProperty("-includeresource", """ + META-INF/services/com.example.service.Type;\ + literal='\ + java.lang.String' + """); + b.setProperty("-metainf-services", "auto"); + b.build(); + assertTrue(b.check()); + Domain manifest = Domain.domain(b.getJar() + .getManifest()); + Parameters provideCapability = manifest.getProvideCapability(); + + assertThat(provideCapability.get("osgi.service")).isNotNull(); + assertThat(provideCapability.get("osgi.service")).containsEntry("objectClass", "com.example.service.Type"); + + assertThat(provideCapability.get("osgi.serviceloader")).isNotNull(); + assertThat(provideCapability.get("osgi.serviceloader") + .get("osgi.serviceloader")).isEqualTo("com.example.service.Type"); + assertThat(provideCapability.get("osgi.serviceloader")) + .containsEntry("osgi.serviceloader", "com.example.service.Type") + .containsEntry("register:", "java.lang.String"); + + Parameters requireCapability = manifest.getRequireCapability(); + + System.out.println(provideCapability.toString() + .replace(',', '\n')); + System.out.println(requireCapability.toString() + .replace(',', '\n')); + } + } + + + } From 55b46db5cca15e9125b6bfcf2c373823f8272b9f Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Fri, 4 Oct 2024 13:51:15 +0200 Subject: [PATCH 04/17] add Syntax help e.g. onmouseover in bnd.bnd editor shows help text. Signed-off-by: Christoph Rueger --- biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java b/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java index 790a337723..569f157200 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java +++ b/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java @@ -736,7 +736,11 @@ null, null, new Syntax("name", "The display name of the developer", "name='Peter WORKINGSET + "=Implementations, Drivers", null, null), new Syntax("-x-overwritestrategy", "On windows we sometimes cannot delete a file because someone holds a lock in our or another process. So if we set the -overwritestrategy flag we use an avoiding strategy.", - "-x-overwritestrategy=gc", "(classic|delay|gc|windows-only-disposable-names|disposable-names)", null) + "-x-overwritestrategy=gc", "(classic|delay|gc|windows-only-disposable-names|disposable-names)", null), + new Syntax(METAINF_SERVICES, "Controls how META-INF/services files are processed.", METAINF_SERVICES + ": auto", + "(" + METAINF_SERVICES_STRATEGY_ANNOTATION + "|" + METAINF_SERVICES_STRATEGY_AUTO + "|" + + METAINF_SERVICES_STRATEGY_NONE + ")", + Pattern.compile("auto|annotation|none")) }; final static Map, Pattern> BASE_PATTERNS = Maps.ofEntries( From 974ace1c12e167a51e53bc74878138c6ac97b14e Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Fri, 4 Oct 2024 14:11:49 +0200 Subject: [PATCH 05/17] BndHover: show values in hover tooltip From this all instructions would benefit Signed-off-by: Christoph Rueger --- bndtools.core/src/bndtools/editor/completion/BndHover.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bndtools.core/src/bndtools/editor/completion/BndHover.java b/bndtools.core/src/bndtools/editor/completion/BndHover.java index f6787989b1..82aa169249 100644 --- a/bndtools.core/src/bndtools/editor/completion/BndHover.java +++ b/bndtools.core/src/bndtools/editor/completion/BndHover.java @@ -63,7 +63,12 @@ public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { if (syntax != null) { sb.append(syntax.getLead()); - sb.append("\nE.g. "); + String values = syntax.getValues(); + if (values != null && !values.isBlank()) { + sb.append("\nValues: "); + sb.append(syntax.getValues()); + } + sb.append("\nExample: "); sb.append(syntax.getExample()); } Parameters decorated = properties.decorated(key); From 2c68ec82a1531186119a22ff708ac6159d50943f Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Sat, 5 Oct 2024 17:14:32 +0200 Subject: [PATCH 06/17] fix setPendantic Signed-off-by: Christoph Rueger experimental: BndMavenPlugin set pendantic:true Experimental: this causes more logging output which could be important during building bundles (but maybe not for baselining, so we only do it in BndMavenPlugin) Signed-off-by: Christoph Rueger --- .../java/aQute/bnd/maven/plugin/BndMavenPlugin.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java b/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java index c75ad18859..8414d5952f 100644 --- a/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java +++ b/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java @@ -3,7 +3,10 @@ import java.io.File; import java.util.List; +import aQute.bnd.osgi.Builder; +import aQute.bnd.osgi.Constants; import org.apache.maven.model.Resource; +import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -77,4 +80,12 @@ public boolean isSkip() { return skip; } + @Override + protected void processBuilder(Builder builder) throws MojoFailureException { + // pendantic=true causes more log output which maybe important during + // building + if (builder.getProperty(Constants.PEDANTIC) == null) { + builder.setPedantic(true); + } + } } From 045bc1105b19811920a8bc9961d841f411bd9a51 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 7 Oct 2024 20:34:09 +0200 Subject: [PATCH 07/17] Revert "fix setPendantic" This reverts commit 96d6fdcf878cfb32f515cb79282b9b5b178d89a6. Signed-off-by: Christoph Rueger --- .../java/aQute/bnd/maven/plugin/BndMavenPlugin.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java b/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java index 8414d5952f..c75ad18859 100644 --- a/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java +++ b/maven-plugins/bnd-maven-plugin/src/main/java/aQute/bnd/maven/plugin/BndMavenPlugin.java @@ -3,10 +3,7 @@ import java.io.File; import java.util.List; -import aQute.bnd.osgi.Builder; -import aQute.bnd.osgi.Constants; import org.apache.maven.model.Resource; -import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -80,12 +77,4 @@ public boolean isSkip() { return skip; } - @Override - protected void processBuilder(Builder builder) throws MojoFailureException { - // pendantic=true causes more log output which maybe important during - // building - if (builder.getProperty(Constants.PEDANTIC) == null) { - builder.setPedantic(true); - } - } } From 16b43b3d63bbe3bc6cb1077ed8651b7bb12dc65d Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 7 Oct 2024 19:37:49 +0200 Subject: [PATCH 08/17] avoid requiring bnd.annotations dep This is a patch by and on behalf of @pkriens which fixes that a dependency to biz.aQute.bnd.annotation needs to be on the classpath. This simplifies wrapping bundles and use the new -metainf-services: auto instruction by not requiring additional bnd dependencies in the case where they are using the textual way of annotating metainf/services files with annotations in comments Signed-off-by: Christoph Rueger --- .../src/aQute/bnd/osgi/Analyzer.java | 32 +++++++++++++++++-- .../osgi/metainf/MetaInfServiceParser.java | 25 +++++++-------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java index 5002d8ebb6..05f2393511 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java @@ -2188,7 +2188,6 @@ Set findProvidedPackages() throws Exception { return providers; } - boolean isProvider(TypeRef t) { if (t == null || t.isJava()) return false; @@ -3904,7 +3903,7 @@ public void addDelta(Jar delta) { public void addAnnotation(Annotation ann, TypeRef c) throws Exception { Clazz clazz = findClass(c); if (clazz == null) { - error("analyzer processing annotation %s but the associated class %s is not found in the JAR", c); + error("analyzer processing annotation %s but the associated class is not found in the JAR", c); return; } annotationHeaders.classStart(clazz); @@ -3912,4 +3911,33 @@ public void addAnnotation(Annotation ann, TypeRef c) throws Exception { annotationHeaders.classEnd(); } + /** + * Get a class from our own class path + * + * @param type a local type on the bnd classpath + */ + public void addClasspathDefault(Class type) { + assert type != null : "type must be given"; + + try { + TypeRef ref = getTypeRefFrom(type); + URL resource = type.getClassLoader() + .getResource(ref.getPath()); + if (resource == null) { + error("analyzer.addclasspathdefault expected class %s to be on the classpath since we have a type", + type); + } else { + + Resource r = new URLResource(resource, null); + Clazz c = new Clazz(this, ref.getFQN(), r); + if (c != null) { + c.parseClassFile(); + classspace.putIfAbsent(ref, c); + } + } + } catch (Exception e) { + error("analyzer.findclassorlocal Failed to read a class from the bnd classpath"); + } + } + } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java index a6f0f32437..40194f711a 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java @@ -8,6 +8,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.Map; +import aQute.bnd.annotation.spi.ServiceProvider; import aQute.bnd.header.Attrs; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Analyzer; @@ -33,10 +34,12 @@ public class MetaInfServiceParser implements AnalyzerPlugin { public boolean analyzeJar(Analyzer analyzer) throws Exception { String strategy = strategy(analyzer); - - if (METAINF_SERVICES_STRATEGY_NONE.equals(strategy)) { - // do not process META-INF/services files - return false; + switch (strategy) { + case METAINF_SERVICES_STRATEGY_NONE : + return false; + case METAINF_SERVICES_STRATEGY_AUTO : { + analyzer.addClasspathDefault(ServiceProvider.class); + } } MetaInfService.getServiceFiles(analyzer.getJar()) @@ -49,18 +52,12 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { Parameters annotations = impl.getAnnotations(); if (annotations.isEmpty() && METAINF_SERVICES_STRATEGY_AUTO.equals(strategy)) { - // if there are no annotations at the impl - // we add one artificially to create the capabilities for - // Service without any attributes in the manifest e.g. - // Provide-Capability', - // "osgi.serviceloader;osgi.serviceloader=serviceName - annotations.add("aQute.bnd.annotation.spi.ServiceProvider", Attrs.EMPTY_ATTRS); + annotations.add(ServiceProvider.class.getName(), Attrs.EMPTY_ATTRS); } - annotations - .forEach((annotationName, attrs) -> { - doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); - }); + annotations.forEach((annotationName, attrs) -> { + doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); + }); }); return false; } From f31f44c9a02c6e32064ea9accc5803dc1308eeb7 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 7 Oct 2024 20:32:58 +0200 Subject: [PATCH 09/17] update docs to recent patch - 'auto' as the new default makes things much easier for library authors since no additional configuration is required Signed-off-by: Christoph Rueger --- docs/_chapters/230-manifest-annotations.md | 7 +------ docs/_instructions/metainf-services.md | 16 +++++----------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/_chapters/230-manifest-annotations.md b/docs/_chapters/230-manifest-annotations.md index 4e79cefafd..df1d0ad796 100644 --- a/docs/_chapters/230-manifest-annotations.md +++ b/docs/_chapters/230-manifest-annotations.md @@ -42,7 +42,7 @@ META-INF/services/com.example.ServiceType: ``` The processing is identical to the normal class based annotation processing. The `#class` macro will be set to the implementation class. The `#value` will be set in all cases to the service type unless overridden. -This behavior can be controlled with the [-metainf-services](/instructions/metainf-services.html) instruction. You can set `-metainf-services: auto` to also automatically create `Provide-Capability` headers for services without annotations. +This behavior can be controlled with the [-metainf-services](/instructions/metainf-services.html) instruction. Default is `auto` which automatically create `Provide-Capability` headers for services without textual annotations. Clearly using the class annotation is far superior: @@ -55,11 +55,6 @@ The analysis of these files happens after the analyzer plugins have been run. Th Since the annotations & imports happen in the comments, it is not possible to diagnose any errors. If the comment does not match its regular expression, it will be silently ignored. -**Note:** Make sure `biz.aQute.bnd.annotation` is on the classpath / buildpath which contains the `aQute.bnd.annotation.spi.ServiceProvider` annotation. Otherwise the annotation won't be processed and you might see a warning in the log like this: - -`Unable to determine whether the meta annotation aQute.bnd.annotation.spi.ServiceProvider applied to type xyz provides bundle annotations as it is not on the project build path. If this annotation does provide bundle annotations then it must be present on the build path in order to be processed` - - ### @Requirement & @Capability Annotations Though Java class files contain enough information to find code dependencies, there are many dependencies that are indirect. OSGi _extenders_ for instance are often a requirement to make a bundle function correctly but often client bundles have no code dependency on the extender. For example, Declarative Services (DS) went out of its way to allow components to be Plain Old Java Objects (POJO). The result is that resolving a closure of bundles starting from a DS client bundle would not drag in the Service Component Runtime (SCR), resulting in a satisfied but rather idle closure. diff --git a/docs/_instructions/metainf-services.md b/docs/_instructions/metainf-services.md index b9562a7011..100c2970b4 100644 --- a/docs/_instructions/metainf-services.md +++ b/docs/_instructions/metainf-services.md @@ -10,8 +10,8 @@ See the chapter about [META-INF/services Annotations](/chapters/230-manifest-ann This instruction can have the following values: -- `annotation` (default if not set): Scan files and only process annotations. -- `auto`: process annotations and special treatment and auto-register services without annotations. That means that bnd auto-generates a `aQute.bnd.annotation.spi.ServiceProvider` annotation without attributes if an Implementation doesn't have one. This is useful if you just want to have bnd generate the `Provide-Capability` headers for `osgi.serviceloader`. +- `auto` (default if not set): auto-register services without textual annotations. That means that bnd auto-generates a `aQute.bnd.annotation.spi.ServiceProvider` annotation without attributes 'under the hood' if an Implementation doesn't have one. This is useful if you just want to have bnd generate the `Provide-Capability` headers for `osgi.serviceloader`. Additionally 'auto' behaves like 'annotation'. +- `annotation`: Scan files and only process textual annotations in comments of META-INF/services files. - `none`: disable processing of files in `META-INF/services` ## Example @@ -22,12 +22,10 @@ We will use `-includeresource: @lib/groovy-3.0.22.jar` to [unroll the jar](/inst ``` # bnd.bnd --buildpath: biz.aQute.bnd.annotation;version='7.0' -includeresource: @lib/groovy-3.0.22.jar --metainf-services: auto ``` -This creates a new jar with the following MANIFEST.MF headers from the `META-INF/services` folder: +This uses the default `-metainf-services: auto` and creates a new jar with the following MANIFEST.MF headers from the `META-INF/services` folder: ``` # MANIFEST.MF @@ -37,11 +35,7 @@ Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(vers osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))" ``` -`-metainf-services: auto` causes bnd to process annotations and also auto-generate an annotation for services without annotations. -To prevent the latter (auto-generation) use the default `-metainf-services: annotation` or remove the instruction completely. - -**Note:** Make sure `biz.aQute.bnd.annotation` is on the classpath / buildpath which contains `aQute.bnd.annotation.spi.ServiceProvider` annotation. - - +Because the default `-metainf-services: auto` is used, it instructs bnd to process textual annotations and also auto-generate a `@ServiceProvider` annotation annotation under the hood for services without annotations. +To prevent the latter (auto-generation) use the default `-metainf-services: annotation` or use `-metainf-services: none`. [source](https://github.com/bndtools/bnd/blob/master/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java) \ No newline at end of file From cfbef6f716160d91f66e2e16c500ce32aca8b684 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 7 Oct 2024 20:21:01 +0200 Subject: [PATCH 10/17] make 'auto' the new default This makes 'auto' the default again and fixes the previously failing tests. The problem was that the "@ServiceProvider" from our own classpath added Java-17 (or whatever was the Java version on the machine you are compiling) to the list of ees which had an impact on how osgi.ee was calculated... this broke all the tests. the fix was in Analyzer to ignore all classes added via addClasspathDefault() for calculation of the ees variable. Signed-off-by: Christoph Rueger fix failing gradle tests filter out invalid fqn names e.g. key=value e.g. fixes [ERROR] [org.gradle.api.Task] error : analyzer processing annotation key=value but the associated class is not found in the JAR because key=value is not a valid serviceImplementation name Signed-off-by: Christoph Rueger add resolution: optional to try to fix test auto-registered annotations are added with "resolution: optional" directive, in order to fix tests which failed due to Unresolved requirements: osgi.extender; (&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0))) which was added by the auto-registed annotations Signed-off-by: Christoph Rueger --- .../annotationheaders/SPIAnnotationsTest.java | 20 ++++-- .../ServiceProviderFileTest.java | 27 ++++++++ .../src/aQute/bnd/osgi/Analyzer.java | 63 ++++++++++--------- .../bnd/osgi/metainf/MetaInfService.java | 10 +++ .../osgi/metainf/MetaInfServiceParser.java | 14 ++++- 5 files changed, 99 insertions(+), 35 deletions(-) diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java index c072505fd5..ea6826a3d3 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java @@ -83,12 +83,12 @@ public void testServiceProvider_existingdescriptor() throws Exception { .getMainAttributes(); Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); - assertEquals(1, req.size()); + assertEquals(2, req.size()); assertEE(req); Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); - assertEquals(1, cap.size()); + assertEquals(3, cap.size()); Props p = cap.get("osgi.serviceloader"); assertNotNull(p); @@ -151,7 +151,11 @@ public void testServiceProvider_nowarning_onexisting() throws Exception { b.getJar() .getManifest() .write(System.out); - assertTrue(b.check()); + assertFalse(b.check()); + assertThat(b.getErrors()).hasSize(1); + assertThat(b.getErrors() + .get(0)).contains( + "analyzer processing annotation some.other.Provider but the associated class is not found in the JAR"); Attributes mainAttributes = b.getJar() .getManifest() @@ -188,7 +192,15 @@ public void testServiceProvider_mergeDescriptor() throws Exception { b.getJar() .getManifest() .write(System.out); - assertTrue(b.check()); + assertFalse(b.check()); + + assertThat(b.getErrors()).hasSize(2); + assertThat(b.getErrors() + .get(0)).contains( + "analyzer processing annotation some.provider.Provider but the associated class is not found in the JAR"); + assertThat(b.getErrors() + .get(1)).contains( + "analyzer processing annotation another.provider.ProviderImpl but the associated class is not found in the JAR"); Attributes mainAttributes = b.getJar() .getManifest() diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java index 137c128057..0b04f1a1ef 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java @@ -135,6 +135,33 @@ public void testAutoGenerateServiceProviderAnnotation() throws Exception { } } + @SuppressWarnings("unchecked") + @Test + public void testInvalidServiceImplementationNamesShouldBeIgnored() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setProperty("-includeresource", """ + META-INF/services/com.example.service.Type;\ + literal='\ + key=value' + """); + b.setProperty("-metainf-services", "auto"); + b.build(); + assertTrue(b.check()); + Domain manifest = Domain.domain(b.getJar() + .getManifest()); + Parameters provideCapability = manifest.getProvideCapability(); + + assertThat(provideCapability.get("osgi.service")).isNull(); + + Parameters requireCapability = manifest.getRequireCapability(); + + System.out.println(provideCapability.toString() + .replace(',', '\n')); + System.out.println(requireCapability.toString() + .replace(',', '\n')); + } + } } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java index 05f2393511..4620989f84 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java @@ -117,6 +117,7 @@ public class Analyzer extends Processor { private final static Logger logger = LoggerFactory.getLogger(Analyzer.class); private final static Version frameworkR7 = new Version("1.9"); private final SortedSet ees = new TreeSet<>(); + private final Set ignoreForEE = new HashSet<>(); // Bundle parameters private Jar dot; @@ -224,7 +225,7 @@ private void analyze0() throws Exception { // classspace.values() .stream() - .filter(c -> !c.isModule()) + .filter(c -> !c.isModule() && !ignoreForEE.contains(c)) .map(Clazz::getFormat) .forEach(ees::add); @@ -3911,33 +3912,37 @@ public void addAnnotation(Annotation ann, TypeRef c) throws Exception { annotationHeaders.classEnd(); } - /** - * Get a class from our own class path - * - * @param type a local type on the bnd classpath - */ - public void addClasspathDefault(Class type) { - assert type != null : "type must be given"; - - try { - TypeRef ref = getTypeRefFrom(type); - URL resource = type.getClassLoader() - .getResource(ref.getPath()); - if (resource == null) { - error("analyzer.addclasspathdefault expected class %s to be on the classpath since we have a type", - type); - } else { - - Resource r = new URLResource(resource, null); - Clazz c = new Clazz(this, ref.getFQN(), r); - if (c != null) { - c.parseClassFile(); - classspace.putIfAbsent(ref, c); - } - } - } catch (Exception e) { - error("analyzer.findclassorlocal Failed to read a class from the bnd classpath"); - } - } + /** + * Get a class from our own class path + * + * @param type a local type on the bnd classpath + */ + public void addClasspathDefault(Class type) { + assert type != null : "type must be given"; + + try { + TypeRef ref = getTypeRefFrom(type); + URL resource = type.getClassLoader() + .getResource(ref.getPath()); + if (resource == null) { + error("analyzer.addclasspathdefault expected class %s to be on the classpath since we have a type", + type); + } else { + + Resource r = new URLResource(resource, null); + Clazz c = new Clazz(this, ref.getFQN(), r); + if (c != null) { + c.parseClassFile(); + classspace.putIfAbsent(ref, c); + + // classes we add here need to be ignored + // when calculating class versions to use + ignoreForEE.add(c); + } + } + } catch (Exception e) { + error("analyzer.findclassorlocal Failed to read a class from the bnd classpath"); + } + } } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java index 358b7f7927..9f6c20d6d9 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java @@ -43,6 +43,7 @@ public class MetaInfService { static final String META_INF_SERVICES_STEM = "META-INF/services"; static final String META_INF_SERVICES_PREFIX = META_INF_SERVICES_STEM + "/"; static final String FQN_S = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + static final Pattern FQN = Pattern.compile(FQN_S); static final Pattern IMPORT_P = Pattern .compile("#import\\s+(?" + FQN_S + ")\\s*;?\\s*$"); static final Pattern ANNOTATION_P = Pattern @@ -228,6 +229,15 @@ public MetaInfService(String serviceName, String source) { // just comment continue; } + else { + + // valid implementationName? + Matcher m = FQN.matcher(line); + if (!m.matches()) { + // invalid fqn: skip + continue; + } + } implementations.put(line, new Implementation(line, annotations, comments)); annotations.clear(); comments.clear(); diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java index 40194f711a..33e68a8204 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java @@ -8,12 +8,14 @@ import java.lang.annotation.RetentionPolicy; import java.util.Map; +import aQute.bnd.annotation.Resolution; import aQute.bnd.annotation.spi.ServiceProvider; import aQute.bnd.header.Attrs; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Annotation; import aQute.bnd.osgi.Annotation.ElementType; +import aQute.bnd.osgi.Constants; import aQute.bnd.osgi.Descriptors.TypeRef; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.metainf.MetaInfService.Implementation; @@ -39,6 +41,11 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { return false; case METAINF_SERVICES_STRATEGY_AUTO : { analyzer.addClasspathDefault(ServiceProvider.class); + break; + } + case METAINF_SERVICES_STRATEGY_ANNOTATION : { + analyzer.addClasspathDefault(ServiceProvider.class); + break; } } @@ -52,7 +59,10 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { Parameters annotations = impl.getAnnotations(); if (annotations.isEmpty() && METAINF_SERVICES_STRATEGY_AUTO.equals(strategy)) { - annotations.add(ServiceProvider.class.getName(), Attrs.EMPTY_ATTRS); + Attrs attrs = new Attrs(); + attrs.put(Constants.RESOLUTION_DIRECTIVE, Resolution.OPTIONAL); + attrs.addDirectiveAliases(); + annotations.add(ServiceProvider.class.getName(), attrs); } annotations.forEach((annotationName, attrs) -> { @@ -80,6 +90,6 @@ private void doAnnotationsforMetaInf(Analyzer analyzer, Implementation impl, Str } private String strategy(Analyzer analyzer) { - return analyzer.getProperty(METAINF_SERVICES, METAINF_SERVICES_STRATEGY_ANNOTATION); + return analyzer.getProperty(METAINF_SERVICES, METAINF_SERVICES_STRATEGY_AUTO); } } From ca16c5070b6639b8d9f1b9dd88185240639f44da Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Wed, 9 Oct 2024 20:32:56 +0200 Subject: [PATCH 11/17] add lookAsideClasses to avoid classspace addClasspathDefault() adds the annotation class to lookAsideClasses instead of classspace and adjust findClass() which now also looks into this new map. That way we don't not put stuff in classspace which should not be there Signed-off-by: Christoph Rueger --- biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java index 4620989f84..ff0d4f7402 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java @@ -117,7 +117,6 @@ public class Analyzer extends Processor { private final static Logger logger = LoggerFactory.getLogger(Analyzer.class); private final static Version frameworkR7 = new Version("1.9"); private final SortedSet ees = new TreeSet<>(); - private final Set ignoreForEE = new HashSet<>(); // Bundle parameters private Jar dot; @@ -137,6 +136,7 @@ public class Analyzer extends Processor { private final Descriptors descriptors = new Descriptors(); private final List classpath = list(); private final Map classspace = map(); + private final Map lookAsideClasses = map(); private final Map importedClassesCache = map(); private boolean analyzed = false; private boolean diagnostics = false; @@ -225,7 +225,7 @@ private void analyze0() throws Exception { // classspace.values() .stream() - .filter(c -> !c.isModule() && !ignoreForEE.contains(c)) + .filter(c -> !c.isModule()) .map(Clazz::getFormat) .forEach(ees::add); @@ -3111,6 +3111,10 @@ public Clazz findClass(TypeRef typeRef) throws Exception { if (c != null) return c; + c = lookAsideClasses.get(typeRef); + if (c != null) + return c; + Resource r = findResource(typeRef.getPath()); if (r == null) { getClass().getClassLoader(); @@ -3933,11 +3937,8 @@ public void addClasspathDefault(Class type) { Clazz c = new Clazz(this, ref.getFQN(), r); if (c != null) { c.parseClassFile(); - classspace.putIfAbsent(ref, c); - - // classes we add here need to be ignored - // when calculating class versions to use - ignoreForEE.add(c); + // we don't want that class in our classspace + lookAsideClasses.putIfAbsent(ref, c); } } } catch (Exception e) { From eb6945309d7971cf7b7b1c9a7f53153dcfefdd9e Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Wed, 9 Oct 2024 20:34:04 +0200 Subject: [PATCH 12/17] remove FQN validation from MetaInfService we rely on the error in Analyzer.findClass() if someone puts garbage in services files Signed-off-by: Christoph Rueger --- .../src/aQute/bnd/osgi/metainf/MetaInfService.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java index 9f6c20d6d9..358b7f7927 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfService.java @@ -43,7 +43,6 @@ public class MetaInfService { static final String META_INF_SERVICES_STEM = "META-INF/services"; static final String META_INF_SERVICES_PREFIX = META_INF_SERVICES_STEM + "/"; static final String FQN_S = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - static final Pattern FQN = Pattern.compile(FQN_S); static final Pattern IMPORT_P = Pattern .compile("#import\\s+(?" + FQN_S + ")\\s*;?\\s*$"); static final Pattern ANNOTATION_P = Pattern @@ -229,15 +228,6 @@ public MetaInfService(String serviceName, String source) { // just comment continue; } - else { - - // valid implementationName? - Matcher m = FQN.matcher(line); - if (!m.matches()) { - // invalid fqn: skip - continue; - } - } implementations.put(line, new Implementation(line, annotations, comments)); annotations.clear(); comments.clear(); From 1e130b280db0620cf12c02fe9c98ed78f709ee9a Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Wed, 9 Oct 2024 21:00:03 +0200 Subject: [PATCH 13/17] split processing of services & default to 'auto' we decided to make '-metainf-services: annotation the default. so '-metainf-services: auto' has to explicity be enabled in bnd file. This decision was due to the fact that it is a brand new feature, and the impact on projects in the wild are hard to predict, since it changes metadata in MANIFEST.MF. So it is unclear at the moment if this would be considered a breaking change. So to be safe, we do not do 'auto' by default but 'annotation'. that means that textual annotations in META-INF/services files are automatically processed, because they were put there on purpose. this commit also adjusts and adds some testcases based on that behavior. Signed-off-by: Christoph Rueger --- .../annotationheaders/SPIAnnotationsTest.java | 144 ++++++++++++++++-- .../ServiceProviderFileTest.java | 30 +++- .../osgi/metainf/MetaInfServiceParser.java | 61 ++++++-- 3 files changed, 212 insertions(+), 23 deletions(-) diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java index ea6826a3d3..428ac939d3 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java @@ -72,6 +72,47 @@ public void testServiceProvider_existingdescriptor() throws Exception { "META-INF/services/test.annotationheaders.spi.SPIService;literal='test.annotationheaders.spi.providerE.Provider'"); b.setProperty("Provide-Capability", "osgi.serviceloader;osgi.serviceloader='test.annotationheaders.spi.SPIService';register:='test.annotationheaders.spi.providerE.Provider'"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_ANNOTATION); + b.build(); + b.getJar() + .getManifest() + .write(System.out); + assertTrue(b.check()); + + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + + Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); + assertEquals(1, req.size()); + + assertEE(req); + + Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); + assertEquals(1, cap.size()); + + Props p = cap.get("osgi.serviceloader"); + assertNotNull(p); + assertNotNull(p.get("osgi.serviceloader")); + assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); + assertNotNull(p.get("register:")); + assertEquals("test.annotationheaders.spi.providerE.Provider", p.get("register:")); + + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", + "test.annotationheaders.spi.providerE.Provider"); + } + } + + @Test + public void testServiceProvider_existingdescriptorAuto() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setPrivatePackage("test.annotationheaders.spi.providerE"); + b.setProperty("-includeresource", + "META-INF/services/test.annotationheaders.spi.SPIService;literal='test.annotationheaders.spi.providerE.Provider'"); + b.setProperty("Provide-Capability", + "osgi.serviceloader;osgi.serviceloader='test.annotationheaders.spi.SPIService';register:='test.annotationheaders.spi.providerE.Provider'"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_AUTO); b.build(); b.getJar() .getManifest() @@ -144,18 +185,15 @@ public void testServiceProvider_nowarning_onexisting() throws Exception { b.addClasspath(IO.getFile("bin_test")); b.setPrivatePackage("test.annotationheaders.spi.providerE"); b.setProperty("-includeresource", - "META-INF/services/test.annotationheaders.spi.SPIService;literal='some.other.Provider'"); + "META-INF/services/test.annotationheaders.spi.SPIService;literal='java.lang.String'"); b.setProperty("Provide-Capability", "osgi.serviceloader;osgi.serviceloader=\"test.annotationheaders.spi.SPIService\""); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_ANNOTATION); b.build(); b.getJar() .getManifest() .write(System.out); - assertFalse(b.check()); - assertThat(b.getErrors()).hasSize(1); - assertThat(b.getErrors() - .get(0)).contains( - "analyzer processing annotation some.other.Provider but the associated class is not found in the JAR"); + assertTrue(b.check()); Attributes mainAttributes = b.getJar() .getManifest() @@ -175,7 +213,47 @@ public void testServiceProvider_nowarning_onexisting() throws Exception { assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); assertNull(p.get("register:")); - assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", "some.other.Provider"); + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", "java.lang.String"); + assertServiceMappingFileNotContains(b.getJar(), "test.annotationheaders.spi.SPIService", + "another.provider.ProviderImpl"); + } + } + + @Test + public void testServiceProvider_nowarning_onexistingAuto() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setPrivatePackage("test.annotationheaders.spi.providerE"); + b.setProperty("-includeresource", + "META-INF/services/test.annotationheaders.spi.SPIService;literal='java.lang.String'"); + b.setProperty("Provide-Capability", + "osgi.serviceloader;osgi.serviceloader=\"test.annotationheaders.spi.SPIService\""); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_AUTO); + b.build(); + b.getJar() + .getManifest() + .write(System.out); + assertTrue(b.check()); + + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + + Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); + assertEquals(2, req.size()); + + assertEE(req); + + Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); + assertEquals(3, cap.size()); + + Props p = cap.get("osgi.serviceloader"); + assertNotNull(p); + assertNotNull(p.get("osgi.serviceloader")); + assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); + assertNull(p.get("register:")); + + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", "java.lang.String"); assertServiceMappingFileNotContains(b.getJar(), "test.annotationheaders.spi.SPIService", "another.provider.ProviderImpl"); } @@ -188,12 +266,60 @@ public void testServiceProvider_mergeDescriptor() throws Exception { b.setPrivatePackage("test.annotationheaders.spi.providerD"); b.setProperty("-includeresource", "META-INF/services/test.annotationheaders.spi.SPIService=testresources/services"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_ANNOTATION); + b.build(); + b.getJar() + .getManifest() + .write(System.out); + assertTrue(b.check()); + + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + + Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); + assertEquals(2, req.size()); + + assertExtender(req, "osgi.serviceloader.registrar"); + assertEE(req); + + Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); + assertEquals(2, cap.size()); + + Props p = cap.get("osgi.serviceloader"); + assertNotNull(p); + assertNotNull(p.get("osgi.serviceloader")); + assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); + assertNotNull(p.get("register:")); + assertThat(p.get("register:")).isIn("test.annotationheaders.spi.providerD.Provider"); + assertNotNull(p.get("foo")); + assertEquals("bar", p.get("foo")); + + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", + "test.annotationheaders.spi.providerD.Provider"); + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", + "another.provider.ProviderImpl"); + } + } + + @Test + public void testServiceProvider_mergeDescriptorAuto() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setPrivatePackage("test.annotationheaders.spi.providerD"); + b.setProperty("-includeresource", + "META-INF/services/test.annotationheaders.spi.SPIService=testresources/services"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_AUTO); b.build(); b.getJar() .getManifest() .write(System.out); assertFalse(b.check()); + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + assertThat(b.getErrors()).hasSize(2); assertThat(b.getErrors() .get(0)).contains( @@ -202,10 +328,6 @@ public void testServiceProvider_mergeDescriptor() throws Exception { .get(1)).contains( "analyzer processing annotation another.provider.ProviderImpl but the associated class is not found in the JAR"); - Attributes mainAttributes = b.getJar() - .getManifest() - .getMainAttributes(); - Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); assertEquals(2, req.size()); diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java index 0b04f1a1ef..482c25d4fb 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java @@ -1,13 +1,20 @@ package test.annotationheaders; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; import org.junit.jupiter.api.Test; +import org.osgi.resource.Requirement; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Builder; import aQute.bnd.osgi.Domain; +import aQute.bnd.osgi.resource.CapReqBuilder; import aQute.lib.io.IO; public class ServiceProviderFileTest { @@ -96,6 +103,16 @@ public void testBothMetaInfoAndAnnotationsNoParentheses() throws Exception { Parameters requireCapability = manifest.getRequireCapability(); assertThat(provideCapability.size()).isEqualTo(4); assertThat(requireCapability.size()).isEqualTo(2); + + Requirement req = CapReqBuilder.getRequirementsFrom(requireCapability) + .get(0); + assertEquals("osgi.extender", req.getNamespace()); + assertNull(req.getDirectives() + .get("resolution")); + assertEquals("(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))", + req.getDirectives() + .get("filter")); + } } @@ -127,6 +144,14 @@ public void testAutoGenerateServiceProviderAnnotation() throws Exception { .containsEntry("register:", "java.lang.String"); Parameters requireCapability = manifest.getRequireCapability(); + Requirement req = CapReqBuilder.getRequirementsFrom(requireCapability) + .get(0); + assertEquals("osgi.extender", req.getNamespace()); + assertEquals("optional", req.getDirectives() + .get("resolution")); + assertEquals("(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))", + req.getDirectives() + .get("filter")); System.out.println(provideCapability.toString() .replace(',', '\n')); @@ -147,7 +172,10 @@ public void testInvalidServiceImplementationNamesShouldBeIgnored() throws Except """); b.setProperty("-metainf-services", "auto"); b.build(); - assertTrue(b.check()); + assertFalse(b.check()); + List errors = b.getErrors(); + assertEquals("analyzer processing annotation key=value but the associated class is not found in the JAR", + errors.get(0)); Domain manifest = Domain.domain(b.getJar() .getManifest()); Parameters provideCapability = manifest.getProvideCapability(); diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java index 33e68a8204..514cbbc6a0 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java @@ -6,6 +6,8 @@ import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_NONE; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import aQute.bnd.annotation.Resolution; @@ -23,7 +25,8 @@ /** * process the META-INF/services/* files. These files can contain bnd - * annotations. + * annotations. Use instruction {@link Constants#METAINF_SERVICES} to control + * this. */ public class MetaInfServiceParser implements AnalyzerPlugin { @@ -49,8 +52,50 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { } } - MetaInfService.getServiceFiles(analyzer.getJar()) - .values() + Collection allServices = MetaInfService.getServiceFiles(analyzer.getJar()) + .values(); + + // "auto" applies only to services without any annotation at all. so + // divide them + Collection withAnnotations = new ArrayList(); + Collection withoutAnnotations = new ArrayList(); + + allServices.forEach(mis -> { + mis.getImplementations() + .values() + .forEach(impl -> { + Parameters annotations = impl.getAnnotations(); + + if (!annotations.isEmpty()) { + withAnnotations.add(mis); + } else { + withoutAnnotations.add(mis); + } + + }); + }); + + if (METAINF_SERVICES_STRATEGY_AUTO.equals(strategy)) { + withoutAnnotations + .stream() + .flatMap(mis -> mis.getImplementations() + .values() + .stream()) + .forEach(impl -> { + + Parameters annotations = new Parameters(); + Attrs attrs1 = new Attrs(); + attrs1.put(Constants.RESOLUTION_DIRECTIVE, Resolution.OPTIONAL); + attrs1.addDirectiveAliases(); + annotations.add(ServiceProvider.class.getName(), attrs1); + + annotations.forEach((annotationName, attrs) -> { + doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); + }); + }); + } + + withAnnotations .stream() .flatMap(mis -> mis.getImplementations() .values() @@ -58,17 +103,11 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { .forEach(impl -> { Parameters annotations = impl.getAnnotations(); - if (annotations.isEmpty() && METAINF_SERVICES_STRATEGY_AUTO.equals(strategy)) { - Attrs attrs = new Attrs(); - attrs.put(Constants.RESOLUTION_DIRECTIVE, Resolution.OPTIONAL); - attrs.addDirectiveAliases(); - annotations.add(ServiceProvider.class.getName(), attrs); - } - annotations.forEach((annotationName, attrs) -> { doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); }); }); + return false; } @@ -90,6 +129,6 @@ private void doAnnotationsforMetaInf(Analyzer analyzer, Implementation impl, Str } private String strategy(Analyzer analyzer) { - return analyzer.getProperty(METAINF_SERVICES, METAINF_SERVICES_STRATEGY_AUTO); + return analyzer.getProperty(METAINF_SERVICES, METAINF_SERVICES_STRATEGY_ANNOTATION); } } From 6a4153856b1b0f17a24727d9c8604411fd694fdf Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Wed, 9 Oct 2024 21:31:47 +0200 Subject: [PATCH 14/17] adjust docs to default: 'annotation' Signed-off-by: Christoph Rueger --- docs/_chapters/230-manifest-annotations.md | 11 +++++++---- docs/_instructions/metainf-services.md | 21 +++++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/_chapters/230-manifest-annotations.md b/docs/_chapters/230-manifest-annotations.md index df1d0ad796..a6a253dae2 100644 --- a/docs/_chapters/230-manifest-annotations.md +++ b/docs/_chapters/230-manifest-annotations.md @@ -28,9 +28,10 @@ Export-Package: com.acme;version="1.3.4" ### META-INF/services Annotations -Some developers do not want to rely on the bnd/OSGi annotations. For this reason, it is possible to also apply the annotations to the files in the `META-INF/services`. These Service Loader files have the name of a Service Loader _service_ and contain the names of the implementation classes. One fully qualified name per line. +Some developers do not want to rely on the additional dependency of bnd/OSGi annotations. For this reason, it is possible to also apply the annotations textually in comments to the files in the `META-INF/services`. +This trick avoids a compile time dependency. These Service Loader files have the name of a Service Loader _service_ and contain the names of the implementation classes. One fully qualified name per line. -The annotations can be applied in the comments. To make them more readable, it is possible to import the fully qualified name of the annotation. +To make these annotations in the comments more readable, it is possible to import the fully qualified name of the annotation. ``` META-INF/services/com.example.ServiceType: @@ -42,9 +43,11 @@ META-INF/services/com.example.ServiceType: ``` The processing is identical to the normal class based annotation processing. The `#class` macro will be set to the implementation class. The `#value` will be set in all cases to the service type unless overridden. -This behavior can be controlled with the [-metainf-services](/instructions/metainf-services.html) instruction. Default is `auto` which automatically create `Provide-Capability` headers for services without textual annotations. +This behavior can be controlled with the [-metainf-services](/instructions/metainf-services.html) instruction. Default is `annotation` which processes the textual annotations above, while the other convenience strategy `auto` automatically creates `Provide-Capability` headers for services without any textual annotations. -Clearly using the class annotation is far superior: +
+ +While the above is a compromise, clearly using the real class annotation is far superior: * Help from the IDE * Impossible to make typos diff --git a/docs/_instructions/metainf-services.md b/docs/_instructions/metainf-services.md index 100c2970b4..235c030b3a 100644 --- a/docs/_instructions/metainf-services.md +++ b/docs/_instructions/metainf-services.md @@ -10,32 +10,33 @@ See the chapter about [META-INF/services Annotations](/chapters/230-manifest-ann This instruction can have the following values: -- `auto` (default if not set): auto-register services without textual annotations. That means that bnd auto-generates a `aQute.bnd.annotation.spi.ServiceProvider` annotation without attributes 'under the hood' if an Implementation doesn't have one. This is useful if you just want to have bnd generate the `Provide-Capability` headers for `osgi.serviceloader`. Additionally 'auto' behaves like 'annotation'. -- `annotation`: Scan files and only process textual annotations in comments of META-INF/services files. -- `none`: disable processing of files in `META-INF/services` +- `annotation` (default if not set): Scan META-INF/services files and only process those files which contain at least one textual annotations. +- `auto`: The convenience strategy. Scans `META-INF/services` files and auto-registers implementations in services files which do not have a single textual annotation. The latter means that bnd behaves as if there was a `aQute.bnd.annotation.spi.ServiceProvider` annotation present on each implementation. This is useful if you just want to have bnd generate the `Provide-Capability` headers for `osgi.serviceloader`. Additionally `auto` behaves like `annotation` and would process all other files with textual annotations. +- `none`: skip processing of files in `META-INF/services` completely -## Example +## Example of auto-registration -Assume we want to wrap a non-OSGi library. -Create `bnd.bnd` file and a library having a `META-INF/services` folder e.g. [groovy-3.0.22.jar](https://mvnrepository.com/artifact/org.codehaus.groovy/groovy/3.0.22) in a local `lib` folder. +Assume we want to wrap a non-OSGi library containing `META-INF/services`. +Create `bnd.bnd` file and put a library having a `META-INF/services` folder e.g. [groovy-3.0.22.jar](https://mvnrepository.com/artifact/org.codehaus.groovy/groovy/3.0.22) in a local `lib` folder. We will use `-includeresource: @lib/groovy-3.0.22.jar` to [unroll the jar](/instructions/includeresource.html#rolling). ``` # bnd.bnd -includeresource: @lib/groovy-3.0.22.jar +-metainf-services: auto ``` -This uses the default `-metainf-services: auto` and creates a new jar with the following MANIFEST.MF headers from the `META-INF/services` folder: +This creates a new jar with the following MANIFEST.MF headers from the `META-INF/services` folder: ``` # MANIFEST.MF Provide-Capability osgi.service;objectClass:List="org.codehaus.groovy.transform.ASTTransformation";effective:=active osgi.serviceloader;osgi.serviceloader="org.codehaus.groovy.transform.ASTTransformation";register:="groovy.grape.GrabAnnotationTransformation" Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))" - osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))" + osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))";resolution:=optional ``` -Because the default `-metainf-services: auto` is used, it instructs bnd to process textual annotations and also auto-generate a `@ServiceProvider` annotation annotation under the hood for services without annotations. -To prevent the latter (auto-generation) use the default `-metainf-services: annotation` or use `-metainf-services: none`. +Because `-metainf-services: auto` is used, it instructs bnd to auto-generate a `@ServiceProvider` annotation under the hood for services without annotations. +To prevent the latter (auto-generation) use the default `-metainf-services: annotation` (to process only textual annotations) or use `-metainf-services: none` to skip processing of `META-INF/services` files completely. [source](https://github.com/bndtools/bnd/blob/master/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java) \ No newline at end of file From caa0a9bfc9badf1683aed63dd536615d4ce2b327 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Wed, 9 Oct 2024 21:32:36 +0200 Subject: [PATCH 15/17] css: horizontal scrollbar for wide code blocks fixes the little annoyance that some code blocks appeared cutoff on the right side Signed-off-by: Christoph Rueger --- docs/css/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/css/style.scss b/docs/css/style.scss index c4911fe274..0e4d0f46f0 100644 --- a/docs/css/style.scss +++ b/docs/css/style.scss @@ -196,4 +196,8 @@ ul .side-nav-section{ .side-nav{ padding-top: 1px !important; +} + +pre.highlight { + overflow: auto; } \ No newline at end of file From d3424832dc908a4dd433e0f4bfad17e4300221f6 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Wed, 9 Oct 2024 22:57:04 +0200 Subject: [PATCH 16/17] avoid duplicates if > 1 impls Signed-off-by: Christoph Rueger --- .../src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java index 514cbbc6a0..fe1f44da4b 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java @@ -6,8 +6,8 @@ import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_NONE; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map; import aQute.bnd.annotation.Resolution; @@ -57,8 +57,8 @@ public boolean analyzeJar(Analyzer analyzer) throws Exception { // "auto" applies only to services without any annotation at all. so // divide them - Collection withAnnotations = new ArrayList(); - Collection withoutAnnotations = new ArrayList(); + Collection withAnnotations = new LinkedHashSet<>(); + Collection withoutAnnotations = new LinkedHashSet<>(); allServices.forEach(mis -> { mis.getImplementations() From e52fcdeac60291ed039349bf60d031951b0f9da1 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Thu, 10 Oct 2024 11:03:06 +0200 Subject: [PATCH 17/17] add METAINF_SERVICES to Constants.options used by BndScanner Signed-off-by: Christoph Rueger --- .../src/aQute/bnd/osgi/Constants.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java index 3f3499bfd5..2e5e102f7a 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java @@ -309,6 +309,14 @@ public interface Constants { String WORKINGSET_MEMBER = "member"; String REQUIRE_BND = "-require-bnd"; + /* + * processing of META-INF/services folder section. + */ + String METAINF_SERVICES = "-metainf-services"; + String METAINF_SERVICES_STRATEGY_ANNOTATION = "annotation"; + String METAINF_SERVICES_STRATEGY_AUTO = "auto"; + String METAINF_SERVICES_STRATEGY_NONE = "none"; + // Deprecated String CLASSPATH = "-classpath"; String OUTPUT = "-output"; @@ -329,7 +337,7 @@ public interface Constants { CONNECTION_SETTINGS, RUNPROVIDEDCAPABILITIES, WORKINGSET, RUNSTORAGE, REPRODUCIBLE, INCLUDEPACKAGE, CDIANNOTATIONS, REMOTEWORKSPACE, MAVEN_DEPENDENCIES, BUILDERIGNORE, STALECHECK, MAVEN_SCOPE, RUNSTARTLEVEL, RUNOPTIONS, NOCLASSFORNAME, EXPORT_APIGUARDIAN, RESOLVE, DEFINE_CONTRACT, GENERATE, RUNFRAMEWORKRESTART, - NOIMPORTJAVA, VERSIONDEFAULTS, LIBRARY); + NOIMPORTJAVA, VERSIONDEFAULTS, LIBRARY, METAINF_SERVICES); // Ignore bundle specific headers. These headers do not make a lot of sense // to inherit @@ -567,13 +575,6 @@ public interface Constants { String SERVICELOADER_REGISTER_DIRECTIVE = "register:"; String SERVICELOADER_NAMESPACE = "osgi.serviceloader"; - /* - * processing of META-INF/services folder section. - */ - String METAINF_SERVICES = "-metainf-services"; - String METAINF_SERVICES_STRATEGY_ANNOTATION = "annotation"; - String METAINF_SERVICES_STRATEGY_AUTO = "auto"; - String METAINF_SERVICES_STRATEGY_NONE = "none"; /** * Launch constants that should be shared by launchers