In gradle, how to use a variable for a plugin version? - gradle

One of my build scripts imports that nebula plugin:
plugins {
id 'nebula.ospackage' version '3.5.0'
}
I've been moving all of my version info into a separate file that all projects have access to and am wondering what is the correct syntax to convert to something like:
plugins {
id 'nebula.ospackage' version "$versions.nebula_gradle_ospackage_plugin"
}
When I try running the above with "gradle clean build", I get the following error:
build file 'build.gradle': 2: argument list must be exactly 1 literal
non empty string
See
https://docs.gradle.org/2.7/userguide/plugins.html#sec:plugins_block
for information on the plugins {} block
# line 2, column 33.
id 'nebula.ospackage' version "$versions.nebula_gradle_ospackage_plugin"
The linked article shows how I could use the "buildscript" block, which works, but it seems like there must be a way to make this work in a single line?

As of Gradle 5.6, you can declare your plugin versions in the gradle.properties file, and reference these properties in plugins block.
For example, the gradle.properties file:
springBootVersion=2.2.0.RELEASE
the plugins block in build.gradle:
plugins {
id "org.springframework.boot" version "${springBootVersion}"
}
See: https://github.com/gradle/gradle/issues/1697#issuecomment-506910915.
See also:
https://docs.gradle.org/5.6/release-notes.html#central-management-of-plugin-versions-with-settings-script
https://docs.gradle.org/5.6/userguide/plugins.html#sec:plugin_management

You cannot use variable here:
Where «plugin version» and «plugin id» must be constant, literal,
strings. No other statements are allowed; their presence will cause a
compilation error.

This is an old post, but the bug is still open, and I found this post looking for workarounds.
It seems you were aware of this already, and actually have BoygeniusDexter's answer, but I think this may help others finding this post like I did. The following workaround is based on the Gradle docs and solved the problem for me:
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
plugins {
id 'java'
// and other plugins
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
}
// but the one with the variable version is applied the old way:
apply plugin: 'org.springframework.boot'
// We can use the variable in dependencies, too:
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion
// ...
}

In addition to #zhouji comment, you can not only specify the versions in gradle.properties, but you can also define it programmatically like:
buildscript {
ext {
kotlinVersion = "${JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16) ? '1.5.32' : '1.4.32'}"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}"
}

Gradle 7
Gradle 7 introduced version catalogs for declaring dependencies and sharing them between projects/modules.
Plugins can be declared in the catalog:
// settings.gradle
dependencyResolutionManagement {
versionCatalogs {
libs {
version('android-plugins', '7.3.0')
plugin('android-application', 'com.android.application').versionRef('android-plugins')
// A library declaration using the same version.
library('some-library', 'com.group', 'artifact').versionRef('android-plugins')
}
}
}
The declaration can be referred to in the plugins block:
// build.gradle
plugins {
alias(libs.plugins.android.application) apply false
}
The version can also be referred to in the plugins block:
// build.gradle
plugins {
id 'com.android.application' version libs.versions.android.plugins apply false
}
See the version catalogs documentation for more information.
Note: In the declaration, - is used as separator in the dependency name. On the consuming side . is used instead, as - is the sign for the minus operator of the groovy language and can't be used in property names.
So the separators are different on the declaring and consuming side - which threw me off at first.

Summary
Omit the version from the plugin block
Move/specify the version in the buildscript.dependencies block, instead
Use a variable in that block that is a const variable
In short, plugin blocks require string literals or properties whereas dependency blocks permit varables. The original question asks for doing this in a "single line" and with this approach, your plugin block is shorter and all your dependencies live in one place for all modules. Depending on your goals, that might be better than doing everything in "one line."
Full Example
For Android, I was able to do this by omitting the version from the plugin block and then specifying it as a const in the buildscript dependencies, instead. That block allows variables, whereas the plugin block only allows string literals. From there, I use an object in buildSrc because that provides the most flexibility, while keeping all dependency info in one file for all modules. So my setup looks like this:
├── project/
│ ├── build.gradle
| └── app/
| └── build.gradle
| └── buildSrc/
| └── build.gradle.kts
| └── src/main/java/com/example/package/Deps.kt
From there, to specify any plugin version as a variable in one place, the app/build.gradle file omits the version (using kotlin as an example but the same approach works for any plugin):
app/build.gradle
plugins {
id 'kotlin-android' // omit version property here but provide it in buildscript dependencies
...
}
...
dependencies {
...
// Dagger
implementation Deps.Dagger.ANDROID_SUPPORT
kapt Deps.Dagger.ANDROID_PROCESSOR
kapt Deps.Dagger.COMPILER
}
Then the buildscript dependency section (usually in the parent build.gradle file but can also be in the same file) provides the version USING A VARIABLE!
project/build.gradle
import com.example.package.Deps // object in the buildSrc folder with all version info
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
}
}
Lastly, all the version information is managed in buildSrc, with an object in the Deps.kt file (there are many blog posts out there on using buildSrc with Android projects):
Deps.kt
object Deps {
// For use in the top-level buildscript dependency section which only works with a constant rather than `Deps.Kotlin.version`
const val kotlinVersion = "1.3.72"
object Kotlin : Version(kotlinVersion) {
val STDLIB = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
}
object Dagger : Version("2.25.2") {
val ANDROID_SUPPORT = "com.google.dagger:dagger-android-support:$version"
val ANDROID_PROCESSOR = "com.google.dagger:dagger-android-processor:$version"
val COMPILER = "com.google.dagger:dagger-compiler:$version"
}
}
open class Version(#JvmField val version: String)
Overall, I really like this setup. It's flexible, all dependency info is in one place, even across multiple modules and, best of all, it allows for code completion in the gradle files, when typing dependencies!

// gradle.properties
springBootVersion=2.6.7
// settings.gradle.kts
pluginManagement {
val springBootVersion: String by settings
plugins {
id("org.springframework.boot") version springBootVersion
}
}
// build.gradle.kts
plugins {
id("org.springframework.boot")
}

I hit this issue.
when I ran
gradle clean test build
instead of
./gradlew clean test build
and my "gradle" was at version 4.x
but my ./gradlew resolved to 6.9 or higher.
aka, "oops, I forgot my build.gradle files were coded to use later versions of gradle(w), but I snapfu-ed and executed the older 4.x version of regular "gradle"

Related

Gradle bundle import to module

I am trying to import base to a project, but I get an error.
settings.gradle
enableFeaturePreview('VERSION_CATALOGS')
dependencyResolutionManagement {
versionCatalogs {
libs {
// https://mvnrepository.com/artifact/org.springframework/spring-core
version('spring', '5.3.14')
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter
version('spring-boot', '2.6.2')
alias('spring-core').to('org.springframework', 'spring-core').versionRef('spring')
alias('spring-context').to('org.springframework', 'spring-context').versionRef('spring')
alias('spring-boot').to('org.springframework.boot', 'spring-boot-starter').versionRef('spring-boot')
bundle('base', ['spring-core', 'spring-context'])
}
}
}
module's build.gradle
dependencies {
implementation(libs.base)
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
refreshing gradle will produce the following error
Could not get unknown property 'base' for extension 'libs' of type org.gradle.accessors.dm.LibrariesForLibs.
How to import libs.base correctly?
The correct way of referencing a bundle from a version catalog is
libs.bundles.base
Similarly for plugins use:
libs.plugins.yourplugin
It is also possible to reference versions, but this works slightly different. For example:
implementation group: "org.springframework.boot", name: "spring-boot-starter-web-services", version: libs.versions.spring.boot.get()
This assumes a version definition in your catalog which looks like:
version('spring-boot', '2.6.7')
Note that bundles, versions and plugins are all spelled as plural.
Also note that as of Gradle 7 version catalogs are no longer a feature preview. They are available by default. Also, the notation for libraries has changed slightly. You have to use library instead of alias.
See Gradle's support site for more information.

Gradle7 Version Catalog: How to use it with buildSrc?

I am very excited about the incubating Gradle's version catalogs and have been experimenting with it. I’ve found that the information in my gradle/libs.versions.toml is accessible in the build.gradle.kts scripts for my app and utility-lib projects.
However, I am unable to use the content of the toml file for buildSrc/build.gradle.kts or the convention files.
The only way that I could build was to hard-code the dependencies into those files, as I did before the version catalog feature.
In the buildSrc folder, I created a settings.gradle.kts file and inserted the dependencyResolutionManagement code for versionCatalogs, which is pointing to the same file as for my app and utility-lib projects.
Based on the Gradle7 docs, it seems that sharing a version catalog with buildSrc and modules is possible… I’d appreciate a nudge into getting it to work with buildSrc, if possible.
Here is a simple sample project, which I created via gradle init: my-version-catalog
Thank you for your time and help,
Mike
With Gradle 7.3.3, it is possible. Note version catalogs are GA since Gradle 7.4
The code snippet assumes Gradle is at least 7.4, but if you need them prior that version, insert enableFeaturePreview("VERSION_CATALOGS") at the beginning of each settings.gradle.kts.
Using buildSrc
buildSrc/settings.gradle.kts
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
buildSrc/build.gradle.kts
dependencies {
implementation(libs.gradleplugin.intellij) // <- the lib reference
}
You can even use the version catalog for plugins
gradle/libs.versions.toml
...
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-changelog = { id = "org.jetbrains.changelog", version.ref = "changelog-plugin" }
jetbrains-intellij = { id = "org.jetbrains.intellij", version.ref = "intellij-plugin" }
hierynomus-license = { id = "com.github.hierynomus.license", version.ref = "license-plugin" }
nebula-integtest = { id = "nebula.integtest", version.ref = "nebula-integtest-plugin" }
build.gradle.kts
plugins {
id("java")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.nebula.integtest)
alias(libs.plugins.jetbrains.intellij)
alias(libs.plugins.jetbrains.changelog)
alias(libs.plugins.hierynomus.license)
}
Note for accessing the catalog within scripts, please refer to the below section, the trick is the same.
Using convention plugins and included build
In the main project include a the Gradle project that holds the convention plugins.
build.gradle.kts
includeBuild("convention-plugins") // here it's a subfolder
convention-plugins/settings.gradle.kts
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "convention-plugins"
The trick to enable convention plugins to access the version catalog is split in two part, add an ugly implementation dependency that locate where the version catalog generated classes are located.
libs.javaClass.superclass.protectionDomain.codeSource.location
Then in the convention plugin refer to the libs extension via Project::the.
val libs = the<LibrariesForLibs>()
This is tracked by gradle/gradle#15383.
convention-plugins/build.gradle.kts
plugins {
`kotlin-dsl`
}
dependencies {
implementation(libs.gradleplugin.kotlin.jvm)
// https://github.com/gradle/gradle/issues/15383
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
}
And in the actual convention plugin
import org.gradle.accessors.dm.LibrariesForLibs
plugins {
id("org.jetbrains.kotlin.jvm")
}
// https://github.com/gradle/gradle/issues/15383
val libs = the<LibrariesForLibs>()
dependencies {
detektPlugins(libs.bundles.kotlinStuff) // access catalog entries
}
The org.gradle.accessors.dm.LibrariesForLibs class is generated by gradle is somewhere in local gradle folder ./gradle/<version>/dependency-accessors/<hash>/classes
Quick note that older IntelliJ IDEA currently (2022.3) reports alias(libs.gradleplugin.thePlugin) as an error in the editor,
although the dependencies are correctly resolved.
This tracked by KTIJ-19369, the ticket indicates this is actually a bug in Gradle Kotlin DSL gradle/gradle#22797, and someone made a simple IntelliJ IDEA plugin to hide this error until resolved.
Brice, it looks like a can of worms to go down that path, particularly for my situation, where I'm trying to use a libs.version.toml file from an android project, but the custom plugin is of course from a java/kotlin project. I tried creating the libs file by hardwiring the path to the toml file in the custom plugin. It might work if both were java projects, but I never tried that since that's not what I'm after. The ideal solution would be for the plugin to use the libs file from the project it is applied to, but it looks like the version catalog needs to be created in the settings file, before you even have access to "Project", so that's why you would have to hardwire the path.
Short answer. No, but there are other techniques for a custom plugin to get project version data from the project it is applied to.

Gradle cannot find plugin declared in buildscript

I'm trying to factor out common Gradle tasks in a reusable file. Here is an excerpt of a build-root.gradle file:
buildscript {
// Repository declaration
ext {
isSnapshot = version.endsWith("-SNAPSHOT")
repos = {
def mavenRepo = { repoName, repoUrl ->
maven {
credentials {
username System.env.<some env var>
password System.env.<some env var>
}
name repoName
url repoUrl
}
}
mavenLocal()
mavenRepo('repo1', 'https://repo1.url')
mavenRepo('repo2', 'https://repo2.url')
mavenRepo('repo3', 'https://repo3.url')
}
}
// Versions and libraries declaration
ext {
versions = [
... some stuff
// Gradle
gradleRelease : '2.8.1',
... more stuff
]
libs = [
... some stuff
// Gradle
gradleRelease : "net.researchgate:gradle-release:$versions.gradleRelease",
... more stuff
]
}
repositories repos
dependencies {
classpath libs.gradleRelease
}
apply plugin: 'net.researchgate.release'
}
... more common stuff
The idea is for subprojects to apply from that file and get all the goodies from it.
On the "apply plugin" line I get the following error - > Plugin with id 'net.researchgate.release' not found.
I printed the libs.gradleRelease string, it looks fine: net.researchgate:gradle-release:2.8.1
We are currently using Gradle 5.2.1, but I also tried 6.0.1 - same error. Any ideas why it can't find the plugin? BTW, this is not exclusive to this particular plugin, I tried others and still get the same error.
After pulling whatever was left of my hair and banging my head against the wall, I came across this => https://discuss.gradle.org/t/how-do-i-include-buildscript-block-from-external-gradle-script/7016
Relevant comment from #Peter_Niederwieser:
"Secondly, externalizing a build script block into a script plugin isn’t supported. (It’s a tough problem, and can’t think of a good way to implement this.) You may have to live with some duplication, at least for the time being. Remember that dependencies specified in a project’s ‘buildscript’ block are visible to all subprojects. Hence, as long as you don’t need dependencies to be available in a script plugin, you just need to declare them in the root project’s build script."
Which is exactly what I was trying to do. I'm not going to curse here...

How to add string replacement to plugins block [duplicate]

One of my build scripts imports that nebula plugin:
plugins {
id 'nebula.ospackage' version '3.5.0'
}
I've been moving all of my version info into a separate file that all projects have access to and am wondering what is the correct syntax to convert to something like:
plugins {
id 'nebula.ospackage' version "$versions.nebula_gradle_ospackage_plugin"
}
When I try running the above with "gradle clean build", I get the following error:
build file 'build.gradle': 2: argument list must be exactly 1 literal
non empty string
See
https://docs.gradle.org/2.7/userguide/plugins.html#sec:plugins_block
for information on the plugins {} block
# line 2, column 33.
id 'nebula.ospackage' version "$versions.nebula_gradle_ospackage_plugin"
The linked article shows how I could use the "buildscript" block, which works, but it seems like there must be a way to make this work in a single line?
As of Gradle 5.6, you can declare your plugin versions in the gradle.properties file, and reference these properties in plugins block.
For example, the gradle.properties file:
springBootVersion=2.2.0.RELEASE
the plugins block in build.gradle:
plugins {
id "org.springframework.boot" version "${springBootVersion}"
}
See: https://github.com/gradle/gradle/issues/1697#issuecomment-506910915.
See also:
https://docs.gradle.org/5.6/release-notes.html#central-management-of-plugin-versions-with-settings-script
https://docs.gradle.org/5.6/userguide/plugins.html#sec:plugin_management
You cannot use variable here:
Where «plugin version» and «plugin id» must be constant, literal,
strings. No other statements are allowed; their presence will cause a
compilation error.
This is an old post, but the bug is still open, and I found this post looking for workarounds.
It seems you were aware of this already, and actually have BoygeniusDexter's answer, but I think this may help others finding this post like I did. The following workaround is based on the Gradle docs and solved the problem for me:
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
plugins {
id 'java'
// and other plugins
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
}
// but the one with the variable version is applied the old way:
apply plugin: 'org.springframework.boot'
// We can use the variable in dependencies, too:
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion
// ...
}
In addition to #zhouji comment, you can not only specify the versions in gradle.properties, but you can also define it programmatically like:
buildscript {
ext {
kotlinVersion = "${JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16) ? '1.5.32' : '1.4.32'}"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}"
}
Gradle 7
Gradle 7 introduced version catalogs for declaring dependencies and sharing them between projects/modules.
Plugins can be declared in the catalog:
// settings.gradle
dependencyResolutionManagement {
versionCatalogs {
libs {
version('android-plugins', '7.3.0')
plugin('android-application', 'com.android.application').versionRef('android-plugins')
// A library declaration using the same version.
library('some-library', 'com.group', 'artifact').versionRef('android-plugins')
}
}
}
The declaration can be referred to in the plugins block:
// build.gradle
plugins {
alias(libs.plugins.android.application) apply false
}
The version can also be referred to in the plugins block:
// build.gradle
plugins {
id 'com.android.application' version libs.versions.android.plugins apply false
}
See the version catalogs documentation for more information.
Note: In the declaration, - is used as separator in the dependency name. On the consuming side . is used instead, as - is the sign for the minus operator of the groovy language and can't be used in property names.
So the separators are different on the declaring and consuming side - which threw me off at first.
Summary
Omit the version from the plugin block
Move/specify the version in the buildscript.dependencies block, instead
Use a variable in that block that is a const variable
In short, plugin blocks require string literals or properties whereas dependency blocks permit varables. The original question asks for doing this in a "single line" and with this approach, your plugin block is shorter and all your dependencies live in one place for all modules. Depending on your goals, that might be better than doing everything in "one line."
Full Example
For Android, I was able to do this by omitting the version from the plugin block and then specifying it as a const in the buildscript dependencies, instead. That block allows variables, whereas the plugin block only allows string literals. From there, I use an object in buildSrc because that provides the most flexibility, while keeping all dependency info in one file for all modules. So my setup looks like this:
├── project/
│ ├── build.gradle
| └── app/
| └── build.gradle
| └── buildSrc/
| └── build.gradle.kts
| └── src/main/java/com/example/package/Deps.kt
From there, to specify any plugin version as a variable in one place, the app/build.gradle file omits the version (using kotlin as an example but the same approach works for any plugin):
app/build.gradle
plugins {
id 'kotlin-android' // omit version property here but provide it in buildscript dependencies
...
}
...
dependencies {
...
// Dagger
implementation Deps.Dagger.ANDROID_SUPPORT
kapt Deps.Dagger.ANDROID_PROCESSOR
kapt Deps.Dagger.COMPILER
}
Then the buildscript dependency section (usually in the parent build.gradle file but can also be in the same file) provides the version USING A VARIABLE!
project/build.gradle
import com.example.package.Deps // object in the buildSrc folder with all version info
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
}
}
Lastly, all the version information is managed in buildSrc, with an object in the Deps.kt file (there are many blog posts out there on using buildSrc with Android projects):
Deps.kt
object Deps {
// For use in the top-level buildscript dependency section which only works with a constant rather than `Deps.Kotlin.version`
const val kotlinVersion = "1.3.72"
object Kotlin : Version(kotlinVersion) {
val STDLIB = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
}
object Dagger : Version("2.25.2") {
val ANDROID_SUPPORT = "com.google.dagger:dagger-android-support:$version"
val ANDROID_PROCESSOR = "com.google.dagger:dagger-android-processor:$version"
val COMPILER = "com.google.dagger:dagger-compiler:$version"
}
}
open class Version(#JvmField val version: String)
Overall, I really like this setup. It's flexible, all dependency info is in one place, even across multiple modules and, best of all, it allows for code completion in the gradle files, when typing dependencies!
// gradle.properties
springBootVersion=2.6.7
// settings.gradle.kts
pluginManagement {
val springBootVersion: String by settings
plugins {
id("org.springframework.boot") version springBootVersion
}
}
// build.gradle.kts
plugins {
id("org.springframework.boot")
}
I hit this issue.
when I ran
gradle clean test build
instead of
./gradlew clean test build
and my "gradle" was at version 4.x
but my ./gradlew resolved to 6.9 or higher.
aka, "oops, I forgot my build.gradle files were coded to use later versions of gradle(w), but I snapfu-ed and executed the older 4.x version of regular "gradle"

Execute Gradle task after subprojects are configured

I have a multi-project Gradle build where subprojects are assigned version numbers independent of the root project. I'd like to inject this version number into a few resource files in each subproject. Normally, I'd do this by configuring the processResources task for each subproject in the root build. However, the problem is that Gradle appears to be executing the processResources task before loading the subprojects' build files and is injecting "unspecified" as the version.
Currently, my project looks like this:
/settings.gradle
include 'childA' // ... and many others
/build.gradle
subprojects {
apply plugin: 'java'
apply plugin: 'com.example.exampleplugin'
}
subprojects {
// This has to be configured before processResources
customPlugin {
baseDir = "../common"
}
processResources {
// PROBLEM: version is "unspecified" here
inputs.property "version", project.version
// Inject the version:
from(sourceSets.main.resources.srcDirs) {
include 'res1.txt', 'res2.txt', 'res3.txt'
expand 'version':project.version
}
// ...
}
}
/childA/build.gradle
version = "0.5.424"
I looked into adding evaluationDependsOnChildren() at the beginning of root's build.gradle, but that causes an error because childA/build.gradle runs before customPlugin { ... }. I've tried using dependsOn, mustRunAfter, and other techniques, but none seem have the desired effect. (Perhaps I don't fully understand the lifecycle, but it seems like the root project is configured and executed before the subprojects. Shouldn't it configure root, then configure subprojects, and then execute?)
How can I get inject the version of each subproject into the appropriate resource files without a lot of copy/paste or boilerplate?
You could try using this method, with a hook:
gradle.projectsEvaluated({
// your code
})
I got this figured out for myself. I'm using a init.gradle file to apply something to the rootProject, but I need data from a subproject.
First option was to evaluate each subproject before I modified it:
rootProject {
project.subprojects { sub ->
sub.evaluate()
//Put your code here
But I wasn't sure what side effects forcing the sub project to evaluate would have so I did the following:
allprojects {
afterEvaluate { project ->
//Put your code here
Try doing it like this:
subprojects { project ->
// your code
}
Otherwise project will refer to your root project where no version has been specified.

Resources