Cannot declare task as input to maven publication

I’m using Gradle 8.7 and the Kotlin DSL. I’m using a custom plugin to create an RPM that is the input to the maven-publish plugin. There’s one small twist in that the generated rpm file needs to be renamed prior to publication.

I’ve followed the example here regarding the declaration of a configuration, rpmFile, and rpmArtifact and using that artifact as an argument to the maven publication. However, because I have a task that renames the rpm file, if I try to run the full build that creates the rpm, renames it, and publishes it, I get an error that says:

Reason: Task ':my-rpm-subproject:publishMavenPublicationToMavenRepository' uses this output of task ':my-rpm-subproject:renameRpm' without declaring an implicit or explicit dependency.

If I attempt to declare a dependency as follows:

tasks.findByName("publishMavenPublicationToMavenRepository")!!.apply {
  dependsOn("renameRpm")
}

The build fails with effectively an NPE, because the task doesn’t appear to exist at the time of evaluation (replacing !! with ? is a no-op, again, because the task doesn’t exist, so I end up with the original error, saying that I need to declare the dependency).

I can run the build in two commands to get everything working (1) create the rpm first, and then 2) publish it), but that seems like a cheap hack. Is there a better solution?

I wanted to correct something I said in my prior message regarding a workaround. I cannot work around the issue using the approach of declaring a configuration and artifact as recommended in the official documentation and then running two gradle commands (one to create the RPM and the other to publish). I have to take a different approach to avoid the dependency error. Instead of calling artifacts.add, I do something like the following:

fun rpmFile(): File = tasks.findByName("rpmTaskName")!!.outputs.files.singleFile
fun fixRpmSuffix(filename: String): String = 
    filename.replace("-1.noarch.rpm", ".noarch.rpm")
fun renamedRpmFileName(): String = 
    fixRpmSuffix(rpmFile().absolutePath)

...

publishing {
    ...
    publications {
        ...
        artifact(renamedRpmFileName())
...

This still requires me to run the command twice to fool gradle into not recognizing that there is an undeclared dependency between the publishing task and the renameRpm task, but at least it works.

There are multiple problems.

  • the advice to add an explicit dependency is imho bad in almost all cases. If that error appears then it is almost always just a symptom of misconfiguration
  • in your case the misconfiguration is, that you do not add the task producing the final rpm as artifact, but a path as string which then of course does not carry any task dependency information. The task building the rpm is most probably not directly supported to be configured as artifact, but that is exactly what the builtBy in the example you linked mitigates. This is extremely important and what you are missing.
  • never use tasks.findByName, that completely breaks task configuration avoidance for all tasks in the build and also has timing problems as you have observed
  • also remaining is most probably not a good idea, because then the original rpm task is always out of date and needs to rerun, better configure the original task to generate the right name right away or at least copy the file to the new name

Björn,

Thank you very much for the reply.

  1. “add an explicit dependency is imho bad in almost all cases”. I’m very confused by this. If it’s a bad idea to declare dependencies, why do I see it almost everywhere in gradle files where tasks declare what dependencies they have? It seems quite normal to say task A depends on task B.
  2. Yes, I understand that’s what I’m missing, but I have no control over the plugin or the name of the file it generates.
  3. Why does tasks.findByName exist if it should never be used?
  4. Do I need to write my own plugin that uses the original plugin and then produces its own artifact that is the renamed file? This seems like an awful lot of flaming hoops to jump through for something seemingly as simple as renaming a file that needs to be published.

If it’s a bad idea to declare dependencies, why do I see it almost everywhere in gradle files where tasks declare what dependencies they have? It seems quite normal to say task A depends on task B.

Just that it’s discouraged bad practice does not mean that people are not using it. And then it spreads like a virus. You see someone doing it and then do it yourself. Then someone else sees you two do it, so also does it. But that doesn’t make it any better. :wink:

Explicit task dependencies are fine and expected if the left-hand side is a lifecycle task like tasks.check { dependsOn(my custom verification task) } as that is exactly the purpose of such lifecycle tasks.

But almost any other explicit task dependency is a code smell. Usually you should wire task outputs to task inputs (directly or transitively) and by that get the necessary task dependency automatically implicitly.

If you for example have a task that generates some source code, you might be tempted to configure it’s output directory as source directory. But then the compile tasks misses a task dependency so that the code gets generated. So you add a task dependency from the compile task to the generate task. But then you execute the sources jar task and recognize you get an error by Gradle, or the generated sources missing or stake ones packed up, so you add a task dependency there too, and so on. And additionally you have to remember removing the according task dependency when the reason vanishes. A much better idea is to register the task as source directory which then means that and consumer of the sources gets all outputs of that task as source and automatically has the necessary task dependency.

Yes, I understand that’s what I’m missing, but I have no control over the plugin or the name of the file it generates.

So? Why does that hinder you in using builtBy? Can you maybe show in an MCVE what you struggle with?

Why does tasks.findByName exist if it should never be used?

Well, in rare cases it might be appropriate. But mainly it is there for backwards compatibility as task configuration avoidance did not always exist.

Do I need to write my own plugin that uses the original plugin and then produces its own artifact that is the renamed file? This seems like an awful lot of flaming hoops to jump through for something seemingly as simple as renaming a file that needs to be published.

Why would you need an own plugin? You can surely do it, but a simple task that uses a copy closure at execution time should probably be enough. But again, an MCVE might clear things up.