Creating MavenPublication lazily - How?

Hi,

My Gradle plugin needs to create a “companion” POM whose groupId and version will match those in the “main” POM, but whose artifactId will be generated as function(main.artifactId). However, this raises a problem because the group, artifactId and version fields on MavenPublication are plain String rather than Property<String>. Not only that, but I am certain that people may also be tweaking the MavenPublication properties manually in their build.gradle scripts using whatever means they can.

There is also a general consensus that “Using afterEvaluate {} is a Bad Thing” anyway. (I believe someone here has referred to it as a “hack that should be avoided”…?)

So the question becomes: How can I capture all possible customisations of the group, artifactId and version values when they are not even available as Gradle “lazy” properties? My best candidate seems to be Gradle.projectsEvaluated(Action<Gradle>), but I am open to other ideas.

Does anyone have any suggestions please?
Thanks,
Chris

You have to know version during configuration phase.

Gradle.projectsEvaluated is a path to hell. Gradle 7.x forbids projectsEvaluated from many contexts.

So the option is:

to know version during configuration phase

Redesign your build process to evaluate version during configuration.

There is no deterministic order of projectsEvaluated. Plugins will fight who first run projectsEvaluated. It is dead end.

Thanks for replying. I am not sure why you have singled out the version property here because the property I am most concerned about is artifactId. However, my fundamental problem is that I am writing a plugin which can be applied by anyone else’s build, and it is those builds which may potentially be tweaking the publish properties via Project.afterEvaluate {} handlers.

I cannot possibly rewrite everyone else in the World’s Gradle projects!

Can you even suggest how people might rewrite their projects without using Project.afterEvaluate {}, when the group , artifactId and version fields on MavenPublication are not implemented using Property<String>?

I am fully aware of how toxic afterEvaluate {} is inside Gradle projects, and am only considering using projectsEvaluated() because I cannot find a working alternative!

Thanks,
Chris

P.S. I have checked Gradle’s current documentation, and it says to defer configuration via Project.afterEvaluate {} :man_facepalming:.

I am not sure why you have singled out the version property here because the property I am most concerned about is artifactId .

I had hard time with version and forgot that you are interested in artifactId.

Still the problem I dealt will hurt you too.

Basically apply plugin: 'com.jfrog.artifactory' captured publications at the configuration phase. So that why I recommend to forget about afterEvaluate.

You even have to order plugins and configuration code properly for data be available at the right time.

It is an “old” way. With Gradle 7.x game rules are changing. Please read:

tasks.register

  • Some APIs may be disallowed if you try to access them from the new API’s configuration blocks. For example, Project.afterEvaluate() cannot be called when configuring a task registered with the new API. Since afterEvaluate is used to delay configuring a Project , mixing delayed configuration with the new API can cause errors that are hard to diagnose because tasks registered with the new API are not always configured, but an afterEvaluate block may be expected to always execute.

Yes, I am already familiar with Task Configuration Avoidance. I am also aware that Gradle 6.x already forbids certain actions (e.g. creating new tasks) from within a lazy task configuration handler.

I should probably also point out that, since Gradle 7.x has not been released yet, using afterEvaluate {} with MavenPublication is the documented approach for the current Gradle version. I don’t think you can dismiss that as “an old way” unless you can also rewrite the documented example to use any “new way”.

For my purposes, I will need to create new MavenPublication objects from existing fully configured MavenPublication objects. These new MavenPublication objects will almost certainly need to be publishable by either com.jfrog.artifactory or com.jfrog.bintray plugins too - perhaps even by both. Do you know if Gradle is even capable of doing this please? As I have already mentioned, Gradle’s own documentation suggests that using afterEvaluate {} is required when publishing!

Thanks,
Chris

I have been experimenting with something like (probably not quite valid Java):

project.getPluginManager().withPlugin("maven-publish", () -> {
    TaskContainer tasks = project.getTasks();
    FunkyTask funky = tasks.register("funky", FunkyTask.class)
    tasks.withType(GenerateMavenPom.class).configureEach(task -> task.dependsOn(funky));
    tasks.withType(GenerateModuleMetadata.class).configureEach(task -> task.dependsOn(funky));
});

where FunkyTask will generate my extra MavenPublication objects based on the existing ones. However, while the existing objects that FunkyTask “sees” are indeed fully-evaluated, I am worried that it may already be too late for the maven-publish plugin to process my extra ones at this point.

My thinking here is that Gradle must already have finalised the task execution graph in order to execute FunkyTask, which by definition must make it too late to register any more GenerateMavenPom or GenerateModuleMetadata tasks.

Any thoughts on how to modify the task execution graph after all MavenPublication objects have been fully evaluated but without using any kind of EVIL “after evaluation” handler gratefully received.

Cheers,
Chris

gradle, and assorted plugin authors, don’t make it so easy to write and share logic that configures other plugins, partly because there is no way to turn lifecycle actions into a graph that can execute in a reliable order. It is possible to do when you can put bounds on what you support but doing this in the completely general sense reliably may well be impossible. I don’t see that much in the new APIs that help solve this problem.

where FunkyTask will generate my extra MavenPublication objects based on the existing ones

seems like a circular dependency (a task which is generated in response to creation of a MavenPublication attempts to create a new MavenPublication)

you can react to the creation of new publications though given that project.publications is a NamedDomainObjectContainer so it may be possible to use this to do what you need. Are you attempting to react to any artifact or some specific one?

These new MavenPublication objects will almost certainly need to be publishable by either com.jfrog.artifactory or com.jfrog.bintray plugins too - perhaps even by both.

if the artifactory plugin still configures things extremely eagerly (via afterEvaluate) then that is one of those things that make generic meta plugins nigh on impossible to write unfortunately

1 Like

seems like a circular dependency (a task which is generated in response to creation of a MavenPublication attempts to create a new MavenPublication )

I was creating FunkyTask when my plugin sees that “maven-publish” has been applied:

project.getPluginManager().withPlugin("maven-publish", () -> {
   // register funky task
});

so there shouldn’t be any circular dependency here. FunkyTask's action would be to iterate through all existing MavenPublication objects and to create a “companion” MavenPublication for each one.

the steps are

  1. apply maven-publish
  2. create a publication
  3. plugin create tasks which generate the actual physical artifacts and publish that publication
  4. tasks execute to actually publish

your code above is trying to insert some action to execute before 4 which will create something (another publication) which depends on step 3, this is the circular dependency I’m referring to.

the gradle-esque way to do this is likely to create your own publication plugin which then configures the underlying publish plugins for you rather than attempting to intercept the behaviour of existing plugins

the gradle-esque way to do this is likely to create your own publication plugin which then configures the underlying publish plugins for you rather than attempting to intercept the behaviour of existing plugins

Let’s put the com.jfrog.artifactory and com.jfrog.bintray plugins aside for a moment, because I can’t even see how to integrate with maven-publish without using afterEvaluate {} right now. (And neither can Gradle’s own developers, by the looks of things.)

To put it simply:

  • My plugin creates an artifact.
  • It is reasonable to assume that someone will want to publish that artifact.
  • Gradle publication is built on the maven-publish plugin.
  • If someone creates a MavenPublication for the artifact then I also need to create a “companion” MavenPublication for it, based upon the original’s fully-evaluated property values.
  • Gradle allows MavenPublication objects to be tweaked from afterEvaluate {} handlers. My “companion” MavenPublication would need to be created based on those tweaks too.
  • Conversely, if tweaking MavenPublication via afterEvaluate {} should be FORBIDDEN, then I would need an alternative method of achieving the same goal given that MavenPublication fields group, artifactId and version are not implemented as Property<String>.

Cheers,
Chris

In my experience it’s best to be pragmatic when it comes to writing gradle plugins which means using whatever APIs are available now to do the job & being ready to change the implementation as quickly as you need to support new gradle versions. This is particularly true when it comes to features which are reportedly going to be deprecated and/or removed as features can change significantly while incubating or just never make it out of incubation.

However I wouldn’t use afterEvaluate in this case as that is likely to be v brittle, I would expect you to have to use gradle.projectsEvaluated as the most reliable way to ensure your publication is created after any user customisation is completed. However I can’t see a way to make this bullet proof as actions are fired in registration order so you have no way of be certain your action/closure is registered after anything registered by the end user.

This means you probably need two things

  1. create your companion in gradle.projectsEvaluated to pick up anything registered “normally”
  2. also register a listener on the publications which can sweep up anything registered late by the user (i.e. after your projectsEvaluated listener has fired), note that this listener can reasonably assume there is no further configuration as the creation of the publication itself has been deferred
1 Like