Title
#community-support
Martin

Martin

11/18/2022, 11:43 AM
Are the rules for how Gradle parses/compiles
buildscript {}
and
pluginManagement {}
blocks (and maybe others?) written somewhere? Or does anyone have a pointer where this is happening in the source?
11:46 AM
Adding to that, it looks like top level functions are not recognized in scripts that are applied? This fails to compile:
fun RepositoryHandler.addRepos() {
  mavenCentral()
  google()
  // more
}

pluginManagement {
  repositories {
    // Unresolved reference: addRepos
    addRepos()
  }
}

dependencyResolutionManagement {
  repositories {
    addRepos()
  }
}
ephemient

ephemient

11/18/2022, 11:55 AM
the blocks that affect the script's build classpath need to be executed outside of the context of the whole script
v

Vampire

11/18/2022, 11:56 AM
The
pluginManagement
block is extracted and evaluated separately before the settings script itself is compiled, otherwise its content could not influence the plugins that you can apply to the settings script itself. Due to that you of course cannot reference anything outside that block. Same for
plugins
blocks or
buildscript
blocks. But you can on the other hand inside the
pluginManagement
block configure other objects. So what I sometimes did is:
// the plugin management block is evaluated first separately, do not change this to
// listOf(pluginManagement.repositories, dependencyResolutionManagement.repositories)
// instead that would change the semantics
pluginManagement {
   listOf(repositories, dependencyResolutionManagement.repositories).forEach { repositories ->
      repositories.apply {
         mavenCentral()
         google()
      }
   }
}

dependencyResolutionManagement {
   repositoriesMode.set(FAIL_ON_PROJECT_REPOS)
}
Martin

Martin

11/18/2022, 11:58 AM
Nice! I didn't realize
pluginManagement{}
was the same as
buildscript{}
but it makes a lot of sense 👍
v

Vampire

11/18/2022, 11:59 AM
It's probably not the same, but at least similar in that regard 😄
Martin

Martin

11/18/2022, 11:59 AM
yea, that's what I meant 🙂
12:01 PM
How does it work for scripts that are applied? If I do
apply(from = "other.gradle.kts")
in
settings.gradle.kts
? Is Gradle smart enough to know that the script is applied in the context of settings and "extract" the
pluginManagement
block?
grossws

grossws

11/18/2022, 12:02 PM
It shouldn't work, same as with plugins block and apply from
Martin

Martin

11/18/2022, 12:02 PM
What do you mean be "shouldn't work"? I get the feeling that it somewhat works at the moment
12:03 PM
(but maybe not the "changing classpath" thing)
12:04 PM
More specifically, this seems to "work":
//other.gradle.kts
pluginManagement {
  repositories {
    mavenCentral() 
  }
}

//settings.gradle.kts
apply(from = "other.gradle.kts")
ephemient

ephemient

11/18/2022, 12:04 PM
https://github.com/gradle/gradle/blob/master/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/Lexer.kt#L223 are the top-level blocks that (depending on script type) are extracted and run independently of the rest of the script
Martin

Martin

11/18/2022, 12:05 PM
Nice, thanks!
v

Vampire

11/18/2022, 12:06 PM
Whether it works or not, I'd anyway recommend not to use "regular" script plugins ever. They usually just produce strange things and have issues like classpath nonsensen and so on.
grossws

grossws

11/18/2022, 12:06 PM
I'm afk right now but it's easy to check. IIRC plugins block the script script applied failed (either to build or to execute)
v

Vampire

11/18/2022, 12:06 PM
I'd guess it would not be extracted but just executed like if you do
pluginManagement.repositories { ... }
12:06 PM
But I didn't test it
ephemient

ephemient

11/18/2022, 12:07 PM
if you do that, it definitely won't affect plugins applied to the settings script itself
12:08 PM
I'm not sure about the projects within the build
Martin

Martin

11/18/2022, 12:08 PM
I'd guess it would not be extracted but just executed like if you do
pluginManagement.repositories { ... }
The "// Unresolved reference: addRepos" above seems to indicate it's still evaluated separately
I'd anyway recommend not to use "regular" script plugins ever. They usually just produce strange things and have issues like classpath nonsensen and so on.
Yea, it's just convenient. I've tried to "extract" that logic to an included build but it's just included builds all the way down from there 😅 . There's some kind of "bootstrapping" needed. Including a script makes it easy to factor some logic/definitions once, in a sense, similar to version catalogs for versions
ephemient

ephemient

11/18/2022, 12:09 PM
I would kind of expect that to be totally ignored
12:10 PM
since the block is "pre-executed", I believe it does nothing at script execution time
12:10 PM
but haven't tested so dunno
v

Vampire

11/18/2022, 12:10 PM
The "// Unresolved reference: addRepos" above seems to indicate it's still evaluated separately.
No, if you would do
pluginManagement.repositories { ... }
it would not be extracted but then would also have the implicit
gradlePluginPortal()
(except they fixed that in the meantime) just like if you onyl define the plugin repositories in a settings script. With that syntax it is just executed in the normal settings script execution, hence the big header-comment in my hack-around
Martin

Martin

11/18/2022, 12:17 PM
The behaviour I have is:
// other.gradle.kts

fun addRepos() {
  // do stufff, add repos
}

pluginManagement {
  repositories {
    // undefined reference
    // addRepos()
    println("this code is executed")
  }
}

pluginManagement.repositories {
  // compiles
  addRepos()
  println("this code is also executed")
}
12:18 PM
So that'd imply that the
pluginManagement{}
is evaluated separately, right? Even if it's in a separate
other.gradle.kts
block. It's still executed like
pluginManagement.repositories {}
but somehow compiled separately
v

Vampire

11/18/2022, 12:46 PM
Ok, I quickly played around with it. It seems to still be extracted and evaluated separately, yes. But it has the same effect than doing
pluginManagement.repositories
. So if you do
pluginManagement { ... }
in a script plugin that you then apply in the settings script, it is still only executed when the script plugin is applied and thus has no effect on plugin resolution within the settings script.
1:08 PM
First the
pluginManagement
block directly within the settings script is evaluated. Then the
plugins
block direclty within the settings script is evaluated. Only then the remaining settings script is evaluated which includes application of regular script plugins. So to also affect the plugin resolution within the settings script, you have afaik no alternative to using the
pluginManagement
block directly in the settings script. Except if you would use an init script for example from a custom Gradle distribution. In that you could afair use
beforeSettings
to have the same effect.
ephemient

ephemient

11/18/2022, 1:10 PM
if you want this to apply to all of your projects then a custom gradle distribution could make sense
Martin

Martin

11/18/2022, 1:40 PM
Thanks all for the insights!
2:26 PM
I opened this issue for the Kotlin IntelliJ plugin (I think this is where it belongs?) to improve IDE support for this: https://youtrack.jetbrains.com/issue/KTIJ-23699/Special-handling-for-Gradle-buildscript-plugins-and-pluginManagement-blocks
v

Vampire

11/18/2022, 2:30 PM
Actually it is not only for Kotlin, but also for Groovy, but yeah, maybe IntelliSense could be improved there
Martin

Martin

11/18/2022, 2:31 PM
Good point. I'll add a note to that issue about Groovy too
ephemient

ephemient

11/18/2022, 2:32 PM
also
initscript
is a special block in
*.init.gradle.kts
, etc. basically there's some logic in that file I linked previously
Martin

Martin

11/18/2022, 2:32 PM
Ah yea, I've never used those, sorry I forgot them. Adding to the list
3:20 PM
And also wrote about this so that I can go back to this next time https://blog.mbonnin.net/preview/63778db503a790859a3f3d12
ephemient

ephemient

11/18/2022, 3:24 PM
this is nicer than explicitly writing
properties[...]
IMO,
pluginManagement {
    val kotlinVersion: String by settings
    plugins {
        resolutionStrategy {
            eachPlugin {
                if (requested.id.id.startsWith("org.jetbrains.kotlin.")) useVersion(kotlinVersion)
            }
        }
    }
}
3:25 PM
not everybody likes the delegate syntax, but it's kinda closer to the brevity of the Groovy DSL that way. (
by project
in a build script of course)
Martin

Martin

11/18/2022, 3:26 PM
Yea, I'm in the "no-delegate" clan 🙂
3:26 PM
I wanted to give a solution that wasn't using the version catalogs but TBH it might be more confusing than anything else
3:31 PM
Moved to a gist so as to keep the initial article to the point and used the delegate syntax there.
ephemient

ephemient

11/18/2022, 4:04 PM
or, a terrible hack to read the version catalog 😆 (it's very much not public api at this point)
pluginManagement {
    val parseToml = DefaultTask::class.java.classLoader.loadClass("org.tomlj.Toml")
        .getMethod("parse", java.io.InputStream::class.java)
    val toml = file("gradle/libs.versions.toml").inputStream().use { parseToml.invoke(null, it) }
    val versions = toml.withGroovyBuilder {
        "getTable"("versions")?.withGroovyBuilder {
            ("keySet"() as Set<*>).associateWith { "get"(listOf(it)) as? String }
        }
    }.orEmpty()
    val kotlinVersion = versions["kotlin"]
}