Implicit Dependencies Warning with SourcesJar

I’m using Gradle 7.4.2.

Setup:

  • multiple subprojects building libraries (i.e. A, B, C, then D that includes A+B+C as dependencies)
  • some generated sources in the subprojects

What I Want:

  • single library (i.e. D-library.jar) that includes most of the subprojects (to push to Artifactory)
  • single D-sources.jar that includes all the related sources

I kindof have this working by adding the following to my D subproject:

sourceSets {
    main {
        java {
            srcDirs += "$buildDir/generated"
        }
    }
}
dependencies {
    project(":A")
    project(":B")
    project(":C")
}
java {
    withSourcesJar()
}

But I get a warning at build time like:
“Execution optimizations have been disabled for task ‘:D:sourcesJar’ to ensure correctness due to the following reasons:
Task ‘:D:sourcesJar’ uses the output of task ‘:D:compileJava’ without declaring an explicit or implicit dependency…”

So I think I need to declare dependencies formally against the “generated” directories in the other subprojects, but I don’t see a syntax to do that.

I tried

java { 
    inputs "..."
}

and

java {
    withSourcesJar() {
        inputs "..."
    }
}

but not of that works.

Can someone point me in the right direction?

To start with, I strongly recommend to not build a fat jar.
If you see the need to separate the code into multiple projects, the easiest, safest, idiomatic, and recommended way is to simply publish all of them.

Having said this, what you showed does anyway not define in any way that the dependencies’ code is packaged into the D artifacts.

But your problem also does not sound to be related to this fat-jar building at all, even if you left out the relevant part.

What the error is telling you is, that compileJava task has a directory as output that your sourcesJar task uses as input without having a task dependency. This often is a sign that somewhere explicit paths were configured instead of wiring task outputs to task inputs which brings proper implicit task dependencies, for example using srcDir(sourceGeneratingTask) which will then add proper task dependencies to all tasks needing sources.

The correct solution to your task heavily depends on the concrete situation.
For example whether you have annotation processors that generate code, because that makes it a bit harder.
If you do not have annotation processors that generate code, but only other tasks that generate code, make sure those tasks properly declare their output files and then use those tasks as srcDir instead of the whole build/generated which contains the annotation processor output directory whether some code is generated or not.

If you do have annotation processors that generate code and you want that generated code in the sources jar, you probably need to declare it explicitly on the sourcesJar task, as you cannot define the compileJava task as srcDir as that would be a dependency cycle.

Thanks for the detailed response.

To clarify, I’m not building a fat-jar. I just want to publish the output of the subprojects as a single jar. I understand your point of “simply publish all of them” but I feel that complicates things for the other projects that are consumers of this project. A single jar keeps it simple.

As far as code generation, yes, I’m using other tasks (no special annotation, though now I’m very curious about the ‘proper’ way to do it in Gradle).

If you do not have annotation processors that generate code, but only other tasks that generate code, make sure those tasks properly declare their output files and then use those tasks as srcDir instead of the whole build/generated

My generation tasks declare outputs.dir(somepath). Your response states I can use the srcDir(taskname) syntax, which was not obvious to me. I found something in the docs that supports what you say, but even if I’d found it ahead of time I don’t think I would have interpreted it that way:
SourceDirectorySet - Gradle DSL Version 8.1.1

But hey, it works :slight_smile:

A couple of final comments:

  1. I had to move the sourceSets declaration to the bottom of the .groovy file so it was after the task definitions. I understand the dependency but am not used to the ordering requirement. Different languages, different requirements :person_shrugging:
  2. I subsequently found a response on Stack Overflow that uses the approach I did (stating the paths directly instead of referencing the tasks) gradle - How to add output of task to srcDir of SourceSet - Stack Overflow. I’ll post a link to this discussion on that one.

To clarify, I’m not building a fat-jar. I just want to publish the output of the subprojects as a single jar

That is a fat jar.
Or well, a half-fat jar as you don’t want to also put the dependencies in.
:slight_smile:

but I feel that complicates things for the other projects that are consumers of this project

Not at all, they just depend on D either way.
D has the dependency on A, B, and C and thus they get it automatically transitively,
as long as they use a proper build tool like Gradle or Maven.
That’s exactly what they are for. :slight_smile:

If they gather needed JARs manually, then maybe yes, but that’s a deserved pain for not using a proper build tool imho. :smiley:

I found something in the docs that supports what you say, but even if I’d found it ahead of time I don’t think I would have interpreted it that way:

Yeah, that’s not documented the best, you should open an improvement request ticket for that, so that it is better documented how to work with generated code properly. :smiley:
But if you look at the JavaDoc of the srcDir / srcDirs methods: SourceDirectorySet (Gradle API 8.4)
they say “This is evaluated as per Project.files(Object...)” which lists all the possible arguments, including Task and Provider<Task> (and thus TaskProvider), and also describes that it is going to be automatically executed if necessary in that case.

I understand the dependency but am not used to the ordering requirement.

Well, Gradle build scripts are code.
They are executed like written.
So unless you write something in a closure that is evaluated later, things already have to be defined to be able to be used.

I highly recommend using the Kotlin DSL instead of the Groovy DSL btw. You immediately get type-safe build scripts, much better and actually helpful error messages if you mess up the syntax, and amazingly better IDE support when using a proper IDE like IntelliJ IDEA.

I subsequently found a response on Stack Overflow

Well, you find many bad advices about Gradle out there. :smiley:
Either because people just don’t know better or also because things changed.
Gradle is evolving rather rapidly, so things that are recommended now can be bad-practice and legacy in some months.
So always keep that in mind when reading Gradle related recommendations out there or also in here. :slight_smile:

1 Like