Hey folks, Have you migrated to Isolated Projects?...
# community-news
a
Hey folks, Have you migrated to Isolated Projects? If not, please share in the thread, what's your main blocker ๐Ÿ’ญ - thinking about it โณ - in progress ๐Ÿง‘โ€๐Ÿ’ป - using for local workflows (IDE sync) ๐Ÿšฆ - using on CI
๐Ÿง‘โ€๐Ÿ’ป 8
โŒ› 4
๐Ÿ’ญ 2
๐Ÿšฆ 9
p
My main blocker is Kotlin/JS/Wasm and Spring Boot support. I already use IP for Kotlin/JVM only builds, but my builds are pretty straightforward and IMHO well written ๐Ÿ˜…
b
None of the above. Like @Philip W, my main blocker is Isolated Projects support by other Gradle components that I use. My main project's own build logic is isolation-ready. Most of my isolation fixes went in over a year ago. But I'm still waiting for isolation support in third-party Gradle plugins that I use, specifically Goomph's Eclipse MavenCentral plugin. {friendly nudge}
p
Yeah, 3rd party plugins are often not compatible because Gradle is not a priority (when supporting maven and a cli and a library too, looking at you Google jib). In the past, as a plugin author, you were free to use any api without having real problems. With IP, you often need to redesign the plugin code. And you still cannot pass configuration data across projects, IMHO accessing settings extension should be accessible as readonly (via Providers) from a project: https://github.com/gradle/gradle/issues/25179#issuecomment-4441085870
d
Just peeked at this, Detekt is our blocker for now. github.com/detekt/detekt/issues/9642
b
Still caught in having to handle migrating away from legacy scripts.... It's tough because these script were not designed as conventions. While they are usually named after what they do, the legacy script often share many concerns that do not fit well a convention plugin transition. For isolated project, while thinking about it, it's a bit harder to see how to think about project needing other projects, how to configure the projects, or even how would aggregation tasks work with isolated projects. And of course, third party plugins may not help there.
j
Same as @Philip W. Spring boot is blocking us. Tried to work around it, but did not find a solution. github.com/spring-projects/spring-boot/issues/43755
y
Part of what makes the migration hard is that it is all-or-nothing. WIth configuration cache one can configure some tasks not to partake, but you can still benefit for all the other tasks. Even if a 3rdParty plugin is not compatible one can usually mark them with ``notCompatibleWithConfigurationCache`` and run the build with ``configuration-cache-problems=warn` . This is not the case for IP. You either have it or you don't. ``org.gradle.isolated-projects.dangerously-ignore-problems=true` is not a saviour. There are many (popular) 3rdParty plugins out there which are not IP-compatible, so it will take a long time. I think a lot of plugin authors do not know an easy way to test their plugins. One can start by passing
--isolated-projects
as an argument of GradleRunner, but you also need to test with Gradle 9.7+ which is another parameter required. In reality one also needs to write a multi-project test scenario which once again is more work. There is a lot of scaffolding involved. Using GradleTest 5.1.2 (or later) will simplify the scaffolding, but still requires a multi-project test scenario written by the plugin author.
nod 2
โž• 1
BTW I suspect the popular JReleaser plugin will cause problems as well.
t
Is it right that we cannot point to a file relative to the root project with
Copy code
rootProject.getLayout().getProjectDirectory().file("gradle/config/checkstyle/checkstyle.xml")
if we turn on Isolated Projects?
t
You can use
rootProject.getIsolated().getProjectDirectory().file("โ€ฆ")
(or
project.getIsolated().getRootProject().โ€ฆ
)
j
Or you can do
layout.settingsDirectory
instead since 8.13
t
thank you
y
Can't you just use
${rootDir}/my/path
in the Groovy DSL ? (If that is no longer available it sure would make
apply from:
a pain to use).
@Alex Semin Another reason is that for certain changes people need to now do things in
settings.gradle
. This has an implication for plugin authors. By example
ProjectEvaluationListener
: If someone used this in their Project plugin, they now have to move that functionality into a Settings plugin. They then have to tell their users to also apply the new Settings plugin. If another plugin author, uses the above plugin as a dependency of their plugin, they now either have to create a Settings plugin which also applies the above Settings plugin. They then have to tell their users to apply the new Settings plugin, OR they have to tell them to use the upstream Settings plugin. This leads to both some confusion for users, and to resistance from plugin authors.
p
So? Change is constant. Of course the migration will have some impact but many requirements of IP are best practices.
t
We have a multi-module spring app where we test each module with the testRuntime classpath of all runtime dependencies from all the modules (as this allows us to check that a transitive dependency hasn't snuck in to one module and changed behaviour when we join all the modules together) I assume this is a complete no-go for isolated projects?
j
Not sure I fully understand. Do you mean something like this: 1. I want the list of all (sub)projects 2. I want a complete runtime classpath for consistency If I have (1) I can create (2) by something like this
listOfAllSubprojectsPaths.forEach { p -> dependencies(testImplementation(project(p))) }
(the in this example
testRuntimeClasspath
would depends on "join" subprojects)
It would be great if Gradle had something like this built in:
isolated.rootProject.subProjectPaths
(this does not exist right now)
But you have access to all project paths in "settings" and can inject them into all projects using
gradle.lifecycle.beforeProject
. In early Gradle days you would do that via
ext
. But a better modeling is to: 1. Create an extension (Can be just and interface with a list of strings) 2. Instantiate that in settings 3. register it in all projects via
gradle.lifecycle.beforeProject
That's isolated project compatible because you do the common part in settings which always runs before the parallel project configuration.
t
So we had a module which used JaxB to deserialize XML from a RestTemplate A different module added the Google Cloud storage libs. These have a transitive dependency on jackson-dataformat-xml This meant that the tests for the xml module still passed, but when all the modules combined, spring picked Jackson for XML deserialization and the xml module @XmlElement annotations no longer worked So we added a plugin to do this;
Copy code
gradle.projectsEvaluated {
    val currentProject = project
    currentProject.rootProject.subprojects.forEach { otherProject ->
        if (otherProject != currentProject) {
            val targetConfigs = listOf("api", "implementation", "runtimeOnly")
            targetConfigs.forEach { configName ->
                otherProject.configurations.findByName(configName)
                    ?.dependencies
                    ?.withType(ExternalModuleDependency::class.java)
                    ?.forEach { extDep ->
                        currentProject.dependencies.add("testRuntimeOnly", extDep.copy())
                    }
            }
        }
    }
}
So at test time, the XML module breaks because the runtime external dependencies of the GCS module are present on the testRuntime classpath...
This is probably a horrible thing to do, and I'm sorry if I've made you feel ill ๐Ÿ˜„
j
I see. Conceptually, I think that's a pretty good approach. ๐Ÿ˜ƒ I am thinking a lot about "consistent resolution", which is an area where Gradle could offer more. These are the kind of solutions to work it out. I think for IP, you need to solve this somehow through dependency resolution, instead of reaching into the projects. All the dependencies you collect โ€“
("api", "implementation", "runtimeOnly")
โ€“ form the runtime classpath. So something like this should be similar
Copy code
listOfAllSubprojectsPaths.forEach { p -> currentProject.dependencies.add("testRuntimeOnly", project(p)) }
The
testRuntimeOnly(project(p))
gives you all the runtime dependencies of that project. Plus that project itself (the Jar) which is the difference you probably do not want.
To avoid that additional effect, you would need to register an additional variant in each of your projects that has all the dependencies, but not the artifact:
Copy code
// in all projects:
configurations.consumable("runtimeElementsTransitive") {
    extendsFrom(configurations.runtimeElements) // dependencies are inherited, artifact not (I think)
    attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named("java-runtime-depenedencies"))
}
t
Ooooh... I'll give that a whirl... SO this
listOfAllSubprojectsPaths.forEach
can be done in a settings plugin?
j
And the code then needs to select the different variant.
Copy code
listOfAllSubprojectsPaths.forEach { p -> currentProject.dependencies{
    testRuntimeOnly(project(p)) {
        attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named("java-runtime-depenedencies"))
    }
}
I haven't tested this, and there is maybe an easier/more compact way. But the solution would be to do it through dependency resolution somehow.
t
Thanks Jendrik I'll take a look ๐Ÿ™‚๐Ÿ‘
๐Ÿ‘ 1
j
> SO this
listOfAllSubprojectsPaths.forEach
can be done in a settings plugin? Yes something like this (just tried out in Kotlin code):
Copy code
interface AllProjectsExtension {
    val allProjectPaths: ListProperty<String>
}

let { // this is here because there is weird behavior that top-level variables can't be cached in config cache in Kotlin DSL
    val projectPaths = rootProject.children.map { it.path }
    gradle.lifecycle.beforeProject {
        extensions.create("allProjectPaths", AllProjectsExtension::class).allProjectPaths.addAll(projectPaths)
    }
}
Then there is an
allProjectPaths
extension in all projects to get the paths of all subprojects.
t
Got it working (in a Java plugin for the classic touch) ๐Ÿ™‚ As you said, I added a settings plugin to capture the list of included modules (used the same list for
include
), then when applied the plugin finds all non-project runtime dependencies for each project in the list and applied it to the target project's testRuntime classpath ๐Ÿ‘ Thanks again for all the help ๐Ÿ™‚
party gradlephant 1
y
Funky!