I have a sub-project with a classifier, test-fixtures, that I want to include into an adjacent sub-project:
dependencies {
implementation(project(":producer"))
}
I assumed that would be a trivial task, but I can't seem to find out how. Is that possible?
I found that this was not a classifier in the maven sense. The variant (what gradle calls them) was created by the java-test-fixtures-plugin. See user guide on testing.
It is used by importing the dependency like this:
dependencies {
testFixtures(project(":producer"))
}
I had a little problem on this since this inhibits my freedom to select the configuration I needed in order to include this jar in an ear-file. I found a way around this by adding it manually as the earlib(...)-method actually does:
dependencies {
add("earlib", testFixtures(project(":producer")))
}
Related
Received this warning in my gradle build this morning, and trying to figure out how to solve it
Adding a Configuration as a dependency is a confusing behavior
which isn't recommended. This behaviour has been deprecated
and is scheduled to be removed in Gradle 8.0. If you're
interested in inheriting the dependencies from the
Configuration you are adding, you should use extendsFrom
Following up on this answer... I was using the configuration as a dependency approach so I could control ordering.
For example:
configurations {
A
B {
extendsFrom A
}
}
dependencies {
A 'jar1'
B 'jar2'
}
Seems to result in the order of B's path being jar1;jar2
But if I want B to be like A, but override some classes from A, then I need B's dependencies first.
So I was using this approach:
configurations {
A
B
}
dependencies {
A 'jar1'
B 'jar2'
B A
}
Which results in B's path being jar2;jar1
I couldn't figure out a way to get this to work using extendsFrom.
Mainly I tried to use B.extendsFrom(A) with various syntax in the dependencies section but couldn't get that to compile.
Is there a way to get the override/ordering use-case to work using extendsFrom?
I believe it's not possible with extendsFrom.
I am not an expert on the dependencies area, but what could work for you or give you some ideas is something like:
def a = project.configurations.findByName("A")
def b = project.configurations.findByName("A")
b.getIncoming().beforeResolve {
// If you want to get also dependencies from
// configurations that "A" extends use getAlLDependencies
a.getDependencies().forEach{
b.dependencies.add(it)
}
}
Note: getDependencies() and getAllDependencies() don't return resolved dependencies, but just the dependencies that were added to a configuration in build script (e.g. A 'jar1'). If you want resolved dependencies you have to resolve A.
Note2: I don't recommend class shadowing as I think you want to achieve. It can bring a lot of unexpected troubles, e.g. app could compile but might not work at runtime. Maybe you should rather do a dependency substitution or resolve conflicts with capabilities (https://docs.gradle.org/7.2/userguide/dependency_capability_conflict.html#sub:declaring-component-capabilities).
I'm working on a Gradle (Groovy, not Kotlin) library using a bunch of external libraries, and we have a case where we want to implement double-inheritance in our code off of a custom Task provided by an external library. (For reference, that library is specifically the MarkLogic DataHub, and we're extending RunFlowTask, but I've generalized a bit for this example. There are a few restrictions that introduces, but I'm fairly certain all of them can be worked around.)
What I want is the following:
ClassA.gradle
class ClassA extends com.external.plugin.TaskA {}
ClassB.gradle
import com.fasterxml.jackson.databind.ObjectMapper
class ClassB extends ClassA {}
...with no restrictions on where things need to be, just that it works. Worth noting that we have a bunch of examples like ClassA that work on their own, I just need to extend ClassA again.
I've detailed a few attempts I made to get this to work below; any feedback on what I did wrong with any of them is more than welcome, or if there's any advice on an entirely new way to build things, that's totally appreciated as well.
First attempt:
apply from: './ClassA'
apply from: './ClassB'
=>
'unable to resolve class ClassA' in ClassB.gradle, which makes enough sense given what I know about how Gradle compilation works. I tried replacing class ClassB... with println ClassB just to see, and that printed without an issue. Made me think that ClassA needed to be compiled in advance, so I'm pretty sure there's nothing I'm doing wrong here, it just won't work.
Second attempt:
buildSrc/src/main/groovy/ClassA.gradle exists and is the same as above.
buildSrc/build.gradle
import com.fasterxml.jackson.databind.ObjectMapper
buildscript {
repositories { }
dependencies {
classpath fileTree(dir: '/path/to/dependencies', include: '*.jar') // includes jackson
}
}
println ObjectMapper
The println worked, but I get:
/path/to/src/main/groovy/ClassA.groovy: 1: unable to resolve class com.fasterxml.jackson.databind.ObjectMapper
# line 1, column 1.
import com.fasterxml.jackson.databind.ObjectMapper
...and the same thing if I duplicate the buildscript block into the ClassA submodule, or if I remove the import from the ClassA submodule. My question for this is is there anything I'm doing wrong with the imports? Seems like this should work and the imports should just work, but they're not.
Third attempt:
Sparing a few code examples here: I got everything very close to working with copying buildSrc into an includeBuild folder, and ClassA is accessible as a TaskReference in the top-level project, but I don't know how to to actually extend ClassA from there.
gradle.includedBuild('subbuild').task(':ClassA') => org.gradle.composite.internal.IncludedBuildTaskReference
gradle.includedBuild('subbuild').task(':UPMCRunFlowTask').resolveTask() => Task with path ':ClassA' not found in project ':subbuild'.
My question for this is is there a way to dig back to the class reference so it's actually extendable? I tried digging into setting up an include './subbuild' subproject and ran into similar issues.
Any help/advice is welcome - thanks!
Answer was on the second attempt. In build.gradle, it needed to be:
repositories {}
dependencies {
implementation fileTree(dir: '/path/to/deps', include: ['*.jar'])
}
...and nothing needed to be in the sub-files.
Consider the following setup
rootProject
|--projectA
|--projectB
There is a task taskB in projectB and I would like the reference the outputs of that task in a copy task taskA in projectA. For example, taskA might look something like:
task taskA(type: Copy) {
dependsOn ':projectB:taskB'
from ':projectB:taskB.outputs'
into 'someFolder'
}
Of course the above example doesn't actually work. While it's okay to reference the task as :projectB:taskB as a dependency, :projectB:taskB.outputs doesn't seem to mean anything to Gradle. I've tried reading through the Gradle docs but didn't find anything that referenced what I'm trying to do.
The accepted answer has been to only and recommended way to solve this problem. However building up this kind of project dependencies and reaching from one project into another is discouraged by the Gradle team now. Instead of this, projects should only interact with each others using publication variants. So the idiomatic (but sadly at the moment more verbose) way would be:
On the producing side (projectB) define a configuration that is not resolvable but consumable by other projects and creates a new variant (called taskB-variant)
configurations.create("taskElements") {
isCanBeResolved = false
isCanBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class, "taskB-variant"))
}
outgoing.artifact(taskB.outputs)
}
On the consuming side (projectA) define a configuration that is resolvable but not consumable for the same variant and define a dependency to projectB
val taskOutputs by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class, "taskB-variant"))
}
}
dependencies {
taskOutputs(project(":projectB"))
}
tasks.register<Copy>("taskA") {
from taskOutputs
into 'someFolder'
}
This way you decouple how the outputs are produced and the publication variant (called "taskB-variant") becomes the interface between projectA and projectB. So whenever you change the way the output is created you only need to refactor projectB but not projectA as long as you make sure the outputs end up in the taskElements configuration.
At the moment this is still pretty verbose but hopefully Gradle will get more powerful APIs to describe this kind of project relationships in the future.
projectA build.gradle should be:
evaluationDependsOn(':projectB')
task taskA(type:Copy, dependsOn:':projectB:taskB'){
from tasks.getByPath(':projectB:taskB').outputs
into 'someFolder'
}
Are two artifacts considered the same for dependency resolution if they have two different values for their respective groups? For example, will
com.example:artifact
and
org.example:artifact
resolve to the same version of artifact on my classpath (where gradle by default will choose the latest)? Or will I get two copies of artifact (because Gradle considers the artifacts different and puts both on the classpath)?
Gradle considers dependencies unique if they have matching group, name and version. In your example, those two dependencies would not be considered the same since they have different groups, and would therefore be duplicated. If you know in advance that such a duplication exists, you can declare a module replacement.
dependencies {
modules {
module("com.example:artifact") {
replacedBy("org.example:artifact")
}
}
}
On a multi-project gradle build, can someone tell me what exactly is the difference between the "allprojects" section and the "subprojects" one? Just the parent directory? Does anyone use both? If so, do you have general rules that determines what typically is put in each one?
Related question: what is the difference between the two syntaxes (really for allprojects AND subprojects):
subprojects { ...
}
and
configure(subprojects) { ...
}
When would you one over the other?
In a multi-project gradle build, you have a rootProject and the subprojects. The combination of both is allprojects. The rootProject is where the build is starting from. A common pattern is a rootProject has no code and the subprojects are java projects. In which case, you apply the java plugin to only the subprojects:
subprojects {
apply plugin: 'java'
}
This would be equivalent to a maven aggregate pom project that just builds the sub-modules.
Concerning the two syntaxes, they do the exact same thing. The first one just looks better.
Adding to Ryan's answer, the configure method becomes important when you want to configure custom subsets of objects. For example configure([project(":foo"), project(":bar")]) { ... } or configure(tasks.matching { it.name.contains("foo") }) { ... }.
When to use allprojects vs. subprojects depends on the circumstances. Often you'll use both. For example, code related plugins like the Java plugin are typically applied to subprojects, because in many builds the root project doesn't contain any code. The Eclipse and IDEA plugins, on the other hand, are typically applied to allprojects. If in doubt, look at examples and other builds and/or experiment. The general goal is to avoid irrelevant configuration. In that sense, subprojects is better than allprojects as long as it gives the expected results.