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'
}
Related
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")))
}
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 a little bit confused about the correct way to create custom tasks on Gradle. On the tutorial for the Creation of custom tasks, they use tasks.register like this:
def check = tasks.register("check")
def verificationTask = tasks.register("verificationTask") {
// Configure verificationTask
}
check.configure {
dependsOn verificationTask
}
Instead here (still official Gradle documentation), they create new tasks that way:
task('hello') {
doLast {
println "hello"
}
}
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
and
tasks.create('hello') {
doLast {
println "hello"
}
}
tasks.create('copy', Copy) {
from(file('srcDir'))
into(buildDir)
}
Finally, according to the document https://docs.gradle.org/current/userguide/task_configuration_avoidance.html, they suggest to move from the second/third case to the first one. Does it mean that the second/third cases are obsolete? If yes, why is Gradle still making massive usage of the old API inside its documentation?
Which variant should a user use?
The Gradle API has many ways to define tasks. There is no "right" or "wrong" way for application developers so long as you are consistent, but it does matter for Gradle plugin authors.
The Task Configuration Avoidance doc you linked states (emphasis mine):
As of Gradle 5.1, we recommend that the configuration avoidance APIs be used whenever tasks are created by custom plugins.
So if you are a plugin author, use task configuration avoidance wherever possible
For everyone else (application developers), it doesn't particularly matter, to an extent, so long as your as consistent across your entire application.
I'm learning Gradle and am confused to see two styles of how plugins are configured, depending on which tutorial/book I read:
checkstyle {
ignoreFailures = true
}
tasks.withType(Checkstyle) {
ignoreFailures = true
}
The first one looks cleaner but the second one would also apply to custom tasks that inherit from "Checkstyle". I suspect that the latter makes it easier for the IDE to guess the type and allow proper auto completion, is that right?
Is there a general trend towards one or the other that I should follow?
The two are slightly different
checkstyle {...} will configure a single task named "checkstyle". It will fail if a task named "checkstyle" does not exist
tasks.withType(Checkstyle) {...} will configure any tasks in the project of type Checkstyle. This could result in zero, one or multiple task instances being configured.
I have a project with 21 subprojects. one of those subprojects has its own build.gradle file because its a little obscure.
Now, i have a configuration setting in the build that is needed during the config phase. So, in my plugin i have
project.afterEvaluate {
if (project == project.rootProject) {
project.allprojects.each { proj ->
MetaExtension config = proj.getExtensions().getByType(MetaExtension)
if (config.inventoryHash == null){
throw new ProjectHashNotSetException(proj.name)
}
}
}
}
Now, if i have everything in one build.gradle file, it all works perfectly. but, as soon as i broke the 21st subproject into its own build.gradle file, it now always comes back as null. copy and past back into one build.gradle file, works fine, 2 gradle files, fails.
Why would this be?
afterEvaluate is a method of the project and is evaluated after the project is evaluated, not after all projects are evaluated.
You need to restructure your logic. You could e. g. add an evaluationDependsOnChildren() or evaluationDependsOn '21st-project', then you don't need the afterEvaluate at all.
Actually if the shown code is your actual code, it is a bit strange anyway. You add an afterEvaluate action to all projects, but only execute it if it is the root project. You could as well just add the action to the root project and then leave out the condition. Or maybe even better, just add an afterEvaluate to each project that checks exactly that project like
project.afterEvaluate {
MetaExtension config = extensions.getByType(MetaExtension)
if (!config.inventoryHash) {
throw new ProjectHashNotSetException(name)
}
}
Then the evaluation order is not important, as each is checked individually. This is also better for project decoupling if you e. g. ever want to use configure-on-demand which would not be possible with your approach.