Using the new PI-isolation `project.gradle.lifecyc...
# community-support
m
Using the new PI-isolation
project.gradle.lifecycle.beforeProject {}
is there a way to do the usual trick of aggregating all projects into the root project? I can add a plugin to every project. But how do I collect the list of all projects to add as dependencies to the root project?
a
j
I am also wondering about this. I was thinking that there shouldn't be something like
isolated.subProjects
or
isolated.subProjectPaths
or/and
isolated.rootProject.subProjects
. The information provided should be the project names/paths as these are fixed after the initialization phase. The article uses the traditional
subprojects { }
block, which I assume is still allowed as long as you do not call any methods on the subproject object (
this
in the example in the article). But it does not feel like this is a good solution on the long run (?)
👍 1
m
This is what I ended up doing and I think it's working:
Copy code
override fun apply(target: Settings) {
        target.gradle.lifecycle.beforeProject { project ->
            if (project.rootProject == project) {
                project.pluginManager.apply("com.gradleup.nmcp.aggregation")

                project.subprojects {
                    project.dependencies.add(nmcpConsumerConfigurationName, project.dependencies.create(it))
                }
            } else {
                project.pluginManager.apply("com.gradleup.nmcp")
            }
        }
    }
I think it's all based on the fact that
subprojects { it.path }
is PI-isolation compatible
a
The general guideline is that anything that’s immutable after settings have executed is safe to access even across projects. This is true for things related to project identity, such as name/path and even the project directory. This is also true for the project hierarchy structure as a whole — it’s safe to iterate over the projects via
subprojects
and such, as long as you only touch immutable project state of other projects. Establishing dependencies between projects also does not require anything except project identity, so it’s safe too.
Having said that, we’ll still look into the use case of cross-project aggregation and potentially come up with a more first-class solution. https://github.com/gradle/gradle/issues/29403
The article uses the traditional
subprojects { }
block, which I assume is still allowed as long as you do not call any methods on the subproject object (
this
in the example in the article).
As explained above, you can still call some methods on the other project instance. But I agree that there is no easy way to tell what is allowed and what will be treated as a violation. On the one hand, the
subprojects
calls are ubiquitous in the wild, so we want to support them as much as we can. But maybe introducing a new “definitely-safe” API for the same thing is still warranted, if only to avoid mental exercises of figuring out what is allowed and what is not.
m
> the use case of cross-project aggregation and potentially come up with a more first-class solution. I'm concerned that we are focusing too much on the
subprojects {}
aggregations and potentially missing a more flexible cross project API. I managed to do what I want with the
subprojects {}
block above but this comes with a big caveat that my aggregation now needs to use
lenient(true)
. This is making me quite uncomfortable. I've been hit a couple of times already with resolution errors (bad artifact being resolved, undetected misconfiguration, etc...).
In my case, what I'd like is that the subproject plugin can add its own project to a known-in-advance root project (doesn't have to be the root project but root is probably the simplest mental model). I don't think this is currently possible?
a
This is making me quite uncomfortable. I’ve been hit a couple of times already with resolution errors (bad artifact being resolved, undetected misconfiguration, etc...)
Can you describe your use case from the user experience you expect? For instance, does it mean, you’d like a mechanism that fails fast, instead of silently skipping “non-producing” projects?
m
Yes, fail fast is almost a requirement IMO. Also listing dependencies shouldn't list irrellevant projects, etc...
It's all about modeling (and editing) project dependencies without having to rely on a "global state"
If I were to attempt an analogy, I can code 2 different UI views that have their own state and interact with each other without using
Application
or
AppDelegate
. Just like my UI is a graph of views that can interact with each others, I'd expect my build system to be a graph of tasks and projects that can interact with each other.
Of course we don't want the internal state of each view (or project) to be publicly mutable but having a very fixed defined API to mutate dependencies would be cool
v
Why do your need to use
lenient
? It ignores any error that could arise. Without
lenient
, the artifact view should still only give you those that have the variant but not fail on non-producing projects, at least it you set any attributes. (https://github.com/gradle/gradle/issues/27773 / https://github.com/gradle/gradle/issues/30314)
a
In my case, what I’d like is that the subproject plugin can add its own project to a known-in-advance root project (doesn’t have to be the root project but root is probably the simplest mental model). I don’t think this is currently possible?
If by “add” you mean adding a dependency, then it’s not possible. Isolated Projects come with a promise of safe configure-on-demand and incrementality. If a subproject was allowed to contribute anything to a mutable container of a parent (e.g. a dependency on itself into the root project), then the build result could be different depending on whether that subproject was scheduled for configuration or not (e.g. when running a qualified task like
:myRootTask
). In other words, unless the root project (or any project with the aggregating logic) does not explicitly request something from the subprojects, those might not be configured at all.
❤️ 1
m
Without
lenient
, the artifact view should still only give you those that have the variant but not fail on non-producing projects, at least it you set any attributes.
TIL, thanks @Vampire! This is good to know but also reinforces my impression that these APIs are more complicated than need be.
unless the root project does not explicitly request something from the subprojects, those might not be configured at all.
Ah, Yes! I remember this discussion now, Thanks! But then isn't what I'm doing with
subprojects {}
and adding all the projects to the root dependencies essentially just killing any configure-on-demand benefits?
v
Maybe you then need to register the dependencies manually and have some verification that you didn't forget any? For example the sub-project writing to some shared build service that they should be included in the root project and then in the root project check whether all in the service are added, which will then at least be detected in a full build on CI?
m
That would be better than no verification (at least the user would get a nice error). But still not the endgame I'm looking for (the user would have to configure things in only a single place and the project graph doesn't contain unused/wrong edges)
Tasks have 2 steps: • Registration is lightweight, just builds the task graph • Then the tasks are configured, inputs are computed, etc... which is more expensive> Would be cool to have something like this for the project graph as well where building the project graph would be relatively lightweight
a
But then isn’t what I’m doing with
subprojects {}
and adding all the projects to the root dependencies essentially just killing any configure-on-demand benefits?
Possibly, but this would be only for this case, when you do expect to aggregate things across all projects. It doesn’t mean that other builds that don’t do a similar thing shouldn’t benefit from configure-on-demand. Moreover, even in that case you should still be able to run a task only in a subproject (aka you don’t need the aggregation for that invocation), and not pay the price of configuring all sibling subprojects.
👍 1