Catching up on project isolation. If I have a conv...
# community-support
m
Catching up on project isolation. If I have a convention plugin that creates an outgoingVariant and I want to aggregate all the outgoingVariants in the root project. Do I still have to list all the subprojects in the root project? Or is there a way to register a root dependency directly from a subproject?
Copy code
// root/build.gradle
        dependencies {
            // Do I have to list all my subprojects here ? 
            // It feels a bit awkward because I have 2 places to edit when I'm adding a module
            add("aggregatingConfiguration", project(":module1"))
            add("aggregatingConfiguration", project(":module2"))
            // ...
        }
p
Yes, but can use `subprojects`:
Copy code
for (subproject in subprojects) {
  aggregatingConfiguration(subproject)
}
m
Is that project isolation compatible? I’m never sure... And follow up question is I need to do this conditionally depending on the presence of my convention plugin:
Copy code
for (subproject in subprojects) {
  if (subproject.pluginManager.hasPlugin("com.myconvention")) {
    aggregatingConfiguration(subproject)
  }
}
That last thing sounds like it’s not really project isolation compatible but maybe it is?
p
Yes,
subprojects
, NOT
subprojects {}
, is compatible. But accessing any state, like
pluginManager
is not.
👀 1
But yeah, if you want to depend only on some subprojects, I think, you have to write them down explictly.
👍 1
with Gradle 8.8, there is
project.isolated
, but it does not provide many information
👍 1
m
I might depend on all of the subprojects and ignore the dependency resolution failures but that feels a bit weird
p
Yeah, this is also a workaround, but only works for your own configurations
m
yep, exactly
And so just to make sure I understand, mutating
project.configuration["foo"].dependencies
is ok but accessing
project.pluginManager
is not?
p
(well, you can extend any predefined configurations but its annoying)
IP is about configuration of others! projects. It is totally fine to add a dependency on another project. But you can't configure another (sub-) project from a parent. So we do the opposite: adding a sub project as dependency to the root project.
m
Ah yes!
Makes sense, thanks!
🙌 1
Would be nice to be able to add a dependency to another project without configuring it though
Have the “project” take some kind of “input”
p
But how? To add dependencies, the project needs the dependency scope configuration (confusing term), that will be added to the project by configuring it.
m
Yea, I have no idea 🙃
v
Mutating is even worse than querying. Both are even without project isolation discouraged bad practice. With project isolation neither should be allowed, but only actions doable through the
IsolatedProject
.
👍 1
And for "ignoring the resolution errors", that's exactly what a lenient artifact view is for. This is for example used for test report aggregation and jacoco report aggregation. So just add a dependency to all subprojects and use a lenient artifact view for resolution.
m
Follow up question. Lenient artifact view is good for project isolation . Projects can be configured in parallel. But doesn’t that defeat configure on demand? Because now every project needs to be configured even those that do not contribute the task graph
v
No idea, I never used configure on demand. But I guess so. Only if the project gets configured Gradle can know which variants are there. But even if querying the model of another project would be ok and you would check which plugins are applied on it like indicated above, you would also require the other project to be configured and furthermore you would need to make sure it is already configured when you try to request that information.
m
even if querying the model of another project would be ok and you would check which plugins are applied on it like indicated above, you would also require the other project to be configured
That why I’d love to have it “the other way around”. The leaf project produces artifacts and has a way to tell it to the aggregating project in a project isolation compatible way.
Could be entirely string based. The aggregating project would be configured in all cases but that’s probably ok (or maybe it’s not even configured and just keeps that “serialized” dependency information around until when it’s required)
There would be a project graph, like there is a task graph and every project could take inputs, be up-to-date etc... Huge rabbit hole I guess. Definitely not saying this is easy but on paper at least it’d be cool. Currently looking at publishing and documentation plugins and most of them use
allprojects {}
to some extent
v
Maybe you can knit something with a shared build service. That's "the other way" to share "things" between projects.
👍 1
But the lenient artifact view is probably the most idiomatic way.
m
Build service is the other route I was thinking about indeed. Will think about all of this, thanks!
👌 1
a
But doesn’t that defeat configure on demand? Because now every project needs to be configured even those that do not contribute the task graph
Only if the aggregating configuration is actually used during the build. Isolated projects also short-circuits the configuration of any project that it already has the dependency resolution metadata for, so it will configure each project the first time the configuration is used, and then only configure those projects that have changed after that.
Maybe you can knit something with a shared build service.
Using a build service will not work for this use case when Isolated projects is enabled, as Gradle will skip the configuration of projects that have not changed, so the build service will not be able to collect any data from them.
👀 1
That why I’d love to have it “the other way around”. The leaf project produces artifacts and has a way to tell it to the aggregating project in a project isolation compatible way.
This is exactly what you're doing by declaring some outgoing artifacts and then asking Gradle to find them all.
m
> This is exactly what you’re doing by declaring some outgoing artifacts and then asking Gradle to find them all. > Thanks for the details! I’ll let that soak a little bit. Having to rely on lenient artifact view for this still itches me a bit
it already has the dependency resolution metadata for, so it will configure each project the first time the configuration is used, and then only configure those projects that have changed after that.
This actually sounds like I was thinking about when talking about “project graph”. So the project edges are modeled with configurations, right?
I guess it still over-configures though? Let’s say I have this
Copy code
root-project -> has configuration "aggregatingConsumer". "aggregatingConsumer" depends on `project(module1)` and `project(module2)` 
module1 -> has matching outgoingVariant
module2 -> doesn't have matching outgoingVariant
The first time “aggregatingConsumer” is used, module1 and module2 are configured (module2 technically doesn’t need to be configured here). Any time module2 configuration changes, I need to re-configure root-project?
Might not be such a big issue in practice but still feels conceptually off?
Just bumped into an example where leniency could be problematic:
Copy code
dokkatooHtmlModuleOutputDirectoriesResolver - Resolves Dokkatoo html ModuleOutputDirectories files.
+--- project :apollo-runtime FAILED
+--- project :apollo-testing-support
+--- project :apollo-tooling
\--- project :intellij-plugin
     +--- project :apollo-gradle-plugin-external
     +--- project :apollo-ast
     +--- project :apollo-tooling
     +--- project :apollo-normalized-cache-sqlite
     +--- org.xerial:sqlite-jdbc:3.43.2.0
     |    \--- org.slf4j:slf4j-api:2.0.9
     \--- com.apollographql.apollo3:apollo-runtime:4.0.0-beta.3 FAILED
For some reason
apollo-runtime
could not be resolved up there (while it technically should). I’m guessing some conflict or so but because the error is ignored, debugging is not easy...
a
Any time module2 configuration changes, I need to re-configure root-project?
Yes and no. "No", because the isolated projects implementation will configure a project
a
that has a project dependency on another project
b
only if the outgoing dependency management data for
b
changes. So if
b
changes, but its dependency information does not change, then
a
will not be configured. The implementation can be made finer-grained in the future, but this is already not too bad. "Yes", however, because currently the root project is treated specially and is always configured if one of its subprojects changes. This is to help catch couplings, because typically they live in the root project. We want to improve this so that the root project is not treated specially, but haven't yet.
Just bumped into an example where leniency could be problematic
Yes, that's true. It would be good to tweak the API to separate out "please don't fail if there's no matching variant" from "please don't fail if anything goes wrong".
👍 2
m
> Just bumped into an example where leniency could be problematic Another issue with leniency is that it works well if you’re collecting across all projects (
gradle.lifecycle.allProjects { /* add dependency here */ }
) but it’s not great to express more granular dependencies (or more precisely leniency could work with more granular dependencies but doesn’t solve the issue of defining those cross project dependencies in the first place)