Hello, I'm trying to create a variant build for a ...
# community-support
k
Hello, I'm trying to create a variant build for a shared library that is divergent for some dependencies:
Copy code
val externalImplementation by configurations.creating {
    extendsFrom(configurations["implementation"])
}

val internalImplementation by configurations.creating {
    extendsFrom(configurations["implementation"])
}

val external by sourceSets.creating {
    kotlin.srcDir(sourceSets["main"].kotlin.srcDirs)
}

dependencies {
    implementation(project("<some internal module>"))

    internalImplementation("org.springframework.boot:spring-boot-starter-webflux:3.3.4")

    externalImplementation(""org.springframework.boot:spring-boot-starter-webflux:3.1.4")
}

java {
    registerFeature("external") {
        usingSourceSet(external)
        capability("$group", project.name, "$version")
    }
}

tasks {
    named("jar") {
        dependsOn("externalJar")
    }
}
This works for the "external" variant, but I cannot consume the
internalImplementation
configuration for the regular build. I know there is a simple way to get the necessary tasks to consume the new configuration without infecting the dependencies in the new variant. Still, I'm having trouble finding the syntax.
v
You can easily declare two feature variants on the
main
source set, not quite like you did it but similar. Unfortunately, this is deprecated. See here: https://github.com/gradle/gradle/issues/29455 It also contains a cumbersome way to work-around the deprecation.
k
@Vampire Thank you for the response. I’ve read everything I can find on variants multiple times and have found the documentation disappointing. Thank you for all of the responses you’ve provided on variants, because those have proven to be the most useful. I think a legitimate use case for variants is to allow a library to generate artifacts that do not break downstream consumers. In my specific use-case, we have an in-house service that was upgraded to spring-boot 3.3.4 for reasons, but the library it provides to access the service does not require spring-boot 3.3.4. It seemed variants were made for a case like this. In the case above, I need to generate two artifacts A and B that can be published and consumed by downstream projects. Artifact-A needs to depend upon spring-boot:3.4.1 and Artifact-B needs to depend on spring-boot:3.3.4. It seems like I need two separate configurations to support this, both extending
implemenation
as a common ancestor. In your example, there is a feature,
java9
, which is able to declare its own dependency, but in my case above, I require require two mutually exclusive dependencies required by the artifacts. Your example shows how one might change
targetCompatiblity
, which would be presumably overridden by the variant. In my case, one dependency does not override the other (except by way of dependency resolution). I my case, I tried to use the main source set for one artifact and the variant for the other, but it would still require me to keep the spring-boot dependencies for the two variants separated. I almost got that working, but I could not figure out how to attach the additional configuration to the
javaCompile
task. I would also appreciate any guidance as to how I might be completely bastardizing the use of variants.
v
That example is with auto-selecting variant by Java version, yes. If you want manual selection of the variants, just do not set the capability, them you can select by capability manually. Why should you need to change
compileJava
? That sounds like a very bad idea.
k
With regards to changing
compileJava
, it is really related to the dependencies required for generating the two artifacts. Both artifacts would require mutually exclusive dependencies which I had declared under different configurations. It would seem like I could ignore the main artifact and create two separate variant artifacts OR use the main artifact for one and the variant artifact for the other. In the case where I used the main artifact, I would need to attach one of the new configurations to be used for the compile task. I’m probably doing things incorrectly here, but I’m kind of feeling around in the dark given the documentation available. 🙂
In the build file from my original question, if any code in
src/main
uses any dependencies from
spring-boot
I will get a build failure because it cannot find the dependency since it simply uses the
implemenation
configuration and not the
internalImplementation
dependency.
However,
externalCompileJava
will work fine.
Perhaps, I’m really off base by looking at variants. Originally, the problem was related to using different JDK version, which is directly the use case for variants. Later the problem morphed into different versions of a specific dependency, which is not directly in the use case for variants. Would that be your take? This isn’t a good use case for variants since Gradle is attempting to select the right variant? Is there something else I should be looking at instead for generating two different artifacts from the same source using separate dependencies?
v
No, variants are probably fine. But why don't you just do it like in the with-around in the ticket? As I said, you would leave out the target versions and not manipulate the capability. Then the customer selects manually which battery to use. Also, in your example you set the same sources for main and external source sets, why? That makes the code being compiled twice needlessly, it was there a source compatible but binary incompatible change? Why do you need two variants at all anyway? Especially without code specific to the variants. And the explicit
dependsOn
is evil too of course. Practically any explicit
dependsOn
where not a lifecycle task is on the left-hand side is a code smell and sign that you're doing something wrong.
k
Sure, I can appreciate that I’m “doing something wrong”. Completely agree, but I’m desperately trying to solve an immediate problem and provide a plan moving forward. I have a library that breaks a downstream consumer because of a difference between “spring-boot:3.1.4" and “spring-boot:3.3.4” which is pure dependency hell. This “break” is completely related to “spring-boot:3.3.4” dependency and not related to our library at all. Our library creates the problem because of the way Gradle resolves its dependencies (i.e., newest wins). I’ve determined three solutions: 1. Provide the consumer with a
resolutionStrategy
block they must place in their root build file. This resolves their immediate problem. 2. Create divergent local dependencies within our set of projects (i.e., consume
spring-boot:3.3.4
where it matters and
spring-boot:3.1.4
where it doesn’t), but this seems wrong (we use a shared version catalog for our set of projects). 3. Generate two different variants/artifacts for the library which have two different sets of dependencies. I’d like to run tests against each set of dependencies independently. This is the reason for the question.
v
If your library declares it needs at least
spring-boot:3.3.4
and a consumer says it needs at least
spring-boot:3.1.4
, of course
3.3.4
wins, so it kind of is a problem with your library. 😄 If your library does not need
3.3.4
but is also compatible with
3.1.4
, why does it not say so? You could maybe also instead use rich version and use
3.3.4
as
prefer
version instead of
require
, then the
3.1.4
of the consumer might simply win in conflict resolution if that really is what you want.