bndtools / bnd

Bnd/Bndtools. Tooling to build OSGi bundles including Eclipse, Maven, and Gradle plugins.
https://bndtools.org
Other
527 stars 305 forks source link

ClassCastException in Gradle build after upgrade to 6.3.0 #5275

Closed marcphilipp closed 2 years ago

marcphilipp commented 2 years ago

I tried updating the JUnit 5 build to 6.3.0 but it resulted in the following failure:

Failed to apply plugin 'shadow-conventions'.
> Could not create task ':junit-platform-console:shadowJar'.
  > class aQute.bnd.gradle.BundleTaskExtension$EffectiveManifest cannot be cast to class com.github.jengelman.gradle.plugins.shadow.tasks.InheritManifest (aQute.bnd.gradle.BundleTaskExtension$EffectiveManifest and com.github.jengelman.gradle.plugins.shadow.tasks.InheritManifest are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader @a6bd1fc)

Stacktrace: https://ge.junit.org/s/jgs5agcbr2obq/failure#1

It looks like both the BND and the Shadow plugin replace manifest with their own implementation. Is that something that was introduced in 6.3.0?

bjhargrave commented 2 years ago

Is that something that was introduced in 6.3.0?

Yes. Per a user request from a user migrating from the old Gradle osgi support, I added https://github.com/bndtools/bnd/pull/5232/commits/9feea15b2570769a449e6a8b74edbe06ee2f2126

In looking at the shadow plugin code

https://github.com/johnrengelman/shadow/blob/2b44f7b874ccc8c01c020195e0f762bb2171be6f/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy#L71

it assumes the task's manifest object implements the shadow plugin's InheritManifest type as set in

https://github.com/johnrengelman/shadow/blob/2b44f7b874ccc8c01c020195e0f762bb2171be6f/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java#L60

The ClassCastException occurs here:

https://github.com/johnrengelman/shadow/blob/2b44f7b874ccc8c01c020195e0f762bb2171be6f/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java#L110

It would seem that a jar task cannot both be a bundle task and a shadow task since they both set the manifest to a custom implementation and shadow assumes the manifest object always implements its own InheritManifest type.

There is not a great way to handle this as gradle does not have a means for a plugin/task to influence the effective manifest besides using a custom manifest implementation (and still using internal gradle types).

I suppose one thing Bnd could do is inspect the type of the manifest object returned by getManifest() and only replace it when it is not a custom type.

diff --git a/gradle-plugins/biz.aQute.bnd.gradle/src/main/java/aQute/bnd/gradle/BundleTaskExtension.java b/gradle-plugins/biz.aQute.bnd.gradle/src/main/java/aQute/bnd/gradle/BundleTaskExtension.java
index a07b78b17..c35a16cc7 100644
--- a/gradle-plugins/biz.aQute.bnd.gradle/src/main/java/aQute/bnd/gradle/BundleTaskExtension.java
+++ b/gradle-plugins/biz.aQute.bnd.gradle/src/main/java/aQute/bnd/gradle/BundleTaskExtension.java
@@ -219,7 +219,10 @@ public class BundleTaskExtension {
                // Wrap manifest
                org.gradle.api.java.archives.Manifest manifest = task.getManifest();
                effectiveManifest = new EffectiveManifest(manifest);
-               if (manifest != null) {
+               // Only set manifest if no one else has set a custom implementation
+               if ((manifest != null) && manifest.getClass()
+                       .getName()
+                       .startsWith("org.gradle.")) {
                        task.setManifest(effectiveManifest);
                }

Note: I don't test for DefaultManifest to allow for some wiggle room should Gradle change some implementation details. This test only assumes the any default manifest implementation will come from a org.gradle package.

marcphilipp commented 2 years ago

Could the manifest be generated by a separate task or made available via an extension registered on the task instead?

bjhargrave commented 2 years ago

Could the manifest be generated by a separate task or made available via an extension registered on the task instead?

The manifest is generated by the action of the BundleTaskExtension and only "lives" in the output jar file after the action completes. So I don't think a separate task makes sense.

The effective manifest could be made available as a new output property from BundleTaskExtension. But I think the life cycle is a mismatch as the property value is not available at end of configuration time. It is only available at after task execution.

I chose to use the task's effective manifest as that makes sense (since it is the actual effective manifest in the output jar) and also what users of the old Gradle osgi plugin did.

I made a different solution, https://github.com/bndtools/bnd/pull/5276, which uses proxies to support the custom interface used by shadow. When testing on your JUnit 5 marc/bnd-6.3.0 branch, it avoids the exception you identified but then runs into https://github.com/gradle/gradle/issues/20928 where the Manifest merging support does not support any Manifest implementation. It requires the Manifest be an instanceof DefaultManifest. I submitted https://github.com/gradle/gradle/pull/20929 to fix the Gradle bug.

bjhargrave commented 2 years ago

I made a different solution, #5276, which uses proxies to support the custom interface used by shadow.

Upon further thought, I completely redid the code to use the existing manifest merge support to merge the built manifest into the existing task manifest object.

With the update, the fix now works on the marc/bnd-6.3.0 branch.

bjhargrave commented 2 years ago

I marked this issue for a possible 6.3.1 release.

marcphilipp commented 2 years ago

Thanks for the quick turnaround! 👍