How to compile against a jar instead of directories of classes?

Hi,

I am trying to build OSGi bundles using Gradle. The crucial feature of an OSGi bundle is its metadata, which is contained in the jar’s META-INF/MANIFEST.MF. So when I compile a bundle against bundles from other project modules, the only way for the OSGi tool to generate the correct metadata is for Gradle to place the dependent jars on the compile classpath.

Gradle seems to think that putting these projects’ build/classes/*/main directories on my compile classpath instead of the jars is a better idea somehow. Gradle is WRONG.

Can someone tell me how to convince Gradle to put the jars on the classpath instead please? I am assuming that it must involve adding some “variant attributes” to a particular Configuration, but nothing I have tried so far has made any difference.

(Pauses to :fire: SUMMON SATAN :fire:)

I have just discovered that one thing does make a difference. Is there any reason why
invoking this code in a Gradle plugin’s apply() handler:

myConfiguration
    .attributes { attrs ->
        // Set variant attributes suitable for compile classpath
    }.withDependencies { deps ->
        // Add some extra dependencies programmatically.
    }

should behave any differently from:

myConfiguration
    .withDependencies { deps ->
        // Add some extra dependencies programmatically.
    }.attributes { attrs ->
        // Set variant attributes suitable for compile classpath
    }

please? I would expect these two to be equivalent, because in both cases the new attributes will be added immediately, but the extra dependencies won’t be added until my configuration is used for dependency resolution, However, the second one appears to be doing what I want whereas the first one does not, and I have absolutely NO IDEA why?!

Is this an expected feature of Gradle’s programming model??? :confused:

Can anyone explain what is happening here please? (My plugin must remain compatible with Gradle 6.8+.)

Thanks for any help here
Cheers,
Chris

Edit: Actually, it’s possible that neither case is really doing what I want; it’s just that executing the Gradle tasks “clean classes” together has a different result than executing “clean anotherTask” followed by “classes”. I suspect Gradle doesn’t know what it’s doing here any more than I do…

OK, so after sleeping on this issue, it looks like the solution is:

myConfiguration
    .withDependencies { deps ->
        // Add some extra dependencies programmatically.

        // And then set their LibraryElements attribute to JAR.
        deps.filterIsInstance(ModuleDependency::class.java).forEach { dep ->
            dep.attributes { attrs ->
                attrs.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements::class.java, JAR))
            }
        }
    }

This does at least appear to give consistent results. All of my earlier efforts looked like Gradle was randomly selecting either JAR or CLASSES, irrespective of whatever other code changes I was making.

Does anyone who is more familiar with this than me have anything to add, please?
Thanks,
Chris

You can define per dependency which attributes you want, but usually you define on the resolvable configuration you want to resolve which attributes you want to have.

So simply doing this will get you the jars for all project dependencies on the classpath of compileJava for example:

configurations.compileClasspath {
    attributes {
        attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
    }
}

Hi, thanks for replying.

Yes, my original attempts all involved applying variant attributes to myConfiguration. However, I’m not convinced that these attributes were then being applied to anything added via that configuration’s withDependencies {} handler, c.f. the two code examples in the original post.

This is why I changed my code to add the attributes directly to those extra dependencies instead.

Cheers,
Chris

Do you have an MCVE?
Maybe you found a bug you should report.
I cannot see anything strange, even with combining it with withDependencies.

I don’t even know what a MCVE is. However, this is a difficult bug to create a report for because I could only reproduce it in a large (closed-source) project. All my attempts to cut a smaller example out of that project worked as expected :frowning_face:.

Such a cut-out would be the MCVE, yes. How to create a Minimal, Reproducible Example - Help Center - Stack Overflow.
Well, then it is hard to say what goes havoc. :slight_smile:

There is actually a built-in flag for doing that, see The Java Library Plugin

So running with -Dorg.gradle.java.compile-classpath-packaging=true should do the trick.

Cheers,
Stefan

Thanks for replying. I was unaware of the

org.gradle.java.compile-classpath-packaging=true

property. However, I am writing a Gradle plugin and so cannot use this anyway.

Cheers,
Chris

The flag is also just controlling whether that org.gradle.libraryelements attribute is set to JAR or CLASSES, if it is not set already and if it is not a JPMS module that is built. (In the latter case it’s also always JAR if not set already to catch Automatic-Module-Name manifest attributes, so pretty similar use-case to OSGi needing the manifest attributes.)

An MCVE for the “strange” behavior would probably still be interesting I guess. :slight_smile: