Are the rules for how Gradle parses/compiles `buil...
# community-support
m
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?
Adding to that, it looks like top level functions are not recognized in scripts that are applied? This fails to compile:
Copy code
fun RepositoryHandler.addRepos() {
  mavenCentral()
  google()
  // more
}

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

dependencyResolutionManagement {
  repositories {
    addRepos()
  }
}
e
the blocks that affect the script's build classpath need to be executed outside of the context of the whole script
v
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:
Copy code
// 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)
}
m
Nice! I didn't realize
pluginManagement{}
was the same as
buildscript{}
but it makes a lot of sense 👍
v
It's probably not the same, but at least similar in that regard 😄
m
yea, that's what I meant 🙂
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?
g
It shouldn't work, same as with plugins block and apply from
m
What do you mean be "shouldn't work"? I get the feeling that it somewhat works at the moment
(but maybe not the "changing classpath" thing)
More specifically, this seems to "work":
Copy code
//other.gradle.kts
pluginManagement {
  repositories {
    mavenCentral() 
  }
}

//settings.gradle.kts
apply(from = "other.gradle.kts")
e
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
m
Nice, thanks!
v
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.
g
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
I'd guess it would not be extracted but just executed like if you do
pluginManagement.repositories { ... }
But I didn't test it
e
if you do that, it definitely won't affect plugins applied to the settings script itself
I'm not sure about the projects within the build
m
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
e
I would kind of expect that to be totally ignored
since the block is "pre-executed", I believe it does nothing at script execution time
but haven't tested so dunno
v
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
m
The behaviour I have is:
Copy code
// 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")
}
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
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.
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.
e
if you want this to apply to all of your projects then a custom gradle distribution could make sense
m
Thanks all for the insights!
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
Actually it is not only for Kotlin, but also for Groovy, but yeah, maybe IntelliSense could be improved there
m
Good point. I'll add a note to that issue about Groovy too
e
also
initscript
is a special block in
*.init.gradle.kts
, etc. basically there's some logic in that file I linked previously
m
Ah yea, I've never used those, sorry I forgot them. Adding to the list
And also wrote about this so that I can go back to this next time https://blog.mbonnin.net/preview/63778db503a790859a3f3d12
e
this is nicer than explicitly writing
properties[...]
IMO,
Copy code
pluginManagement {
    val kotlinVersion: String by settings
    plugins {
        resolutionStrategy {
            eachPlugin {
                if (requested.id.id.startsWith("org.jetbrains.kotlin.")) useVersion(kotlinVersion)
            }
        }
    }
}
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)
m
Yea, I'm in the "no-delegate" clan 🙂
I wanted to give a solution that wasn't using the version catalogs but TBH it might be more confusing than anything else
Moved to a gist so as to keep the initial article to the point and used the delegate syntax there.
e
or, a terrible hack to read the version catalog 😆 (it's very much not public api at this point)
Copy code
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"]
}