Different ways to apply plugins ? (Gradle Kotlin DSL) - gradle

Trying to migrate this one project's build to GSK.
We have this in Groovy:
allprojects {
apply plugin: 'java'
...
sourceSets {
...
}
sourceCompatibility = ...
}
So while figuring out how to access the plugin convention in Kotlin, I found out that:
allprojects {
plugins {
java apply true
}
...
println("Project $name, plugins: ${plugins}") // empty list
val java = the<JavaPluginConvention>() // throws exception
}
but if you do it like this:
allprojects {
apply {
plugin(JavaPlugin::class.java)
}
}
plugin is applied and convention becomes accessible
WTH?

This issue isn't specific to Kotlin, and is due to a race condition. While the script is being evaluated, it may not have added the plugin to the classpath yet. This is one of many reasons why the plugins block was created, as it's specifically evaluated prior to the rest of the scripts evaluation during a buildscript phase. That said however, this special treatment is only done if this block is at the top of the script, and not when it's within a subprojects or allprojects block, as those blocks are technically arbitrary and are evaluated later to ensure the buildscript is idempotent. In your case, you are just moving up the race by placing it in allprojects block, and are getting lucky.
When dealing with multi-project builds, this is problematic, however if possible, the best is to declare the plugin in the plugins block with the apply false constrained syntax to add it to your build's classpath in the buildscript phase. You will then be able to apply the plugin later via the plugin's id during script evaluation (version isn't necessary, as it's used for fetching the dependency only).
An example:
plugins {
id("org.gradle.sample.hello") version "1.0.0" apply false
}
subprojects {
apply(plugin = "org.gradle.sample.hello")
}
The Gradle User Guide does a great job at explaining how these should be used, and the balance you will need to consider in multi-module projects.
Due to the nature of how some plugins are written, there may be cases where other issues will arise, but if plugin authors are following best practice guidelines, you'll be fine.

If your plugins are for Kotlin, then the best way for now is:
plugins {
kotlin("jvm") version "1.7.21"
kotlin("plugin.serialization") version "1.7.21"
}

Related

Why dont I always see `buildscript` in gradle files? [duplicate]

I am new to Gradle and I am reading the documentation but I don't understand some parts of it. One of these parts is connected with buildscript block. What is its purpose?
If your build script needs to use external libraries, you can add them to the script's classpath in the build script itself. You do this using the buildscript() method, passing in a closure which declares the build script classpath.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
}
Ok but what is the difference with:
repositories {
mavenCentral()
}
dependencies {
compile group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
For example, why it is necessary to use buildscript?
The buildScript block determines which plugins, task classes, and other classes are available for use in the rest of the build script. Without a buildScript block, you can use everything that ships with Gradle out-of-the-box. If you additionally want to use third-party plugins, task classes, or other classes (in the build script!), you have to specify the corresponding dependencies in the buildScript block.
The global level dependencies and repositories sections list dependencies that required for building your source and running your source etc.
The buildscript is for the build.gradle file itself. So, this would contain dependencies for say creating RPMs, Dockerfile, and any other dependencies for running the tasks in all the dependent build.gradle
I appreciate Peter's answer... but it was not immediately obvious to me what the rest of the build script meant as emphasized in the answer and in the documentation.
Usually bringing in dependent functionality is for use in the Java program or whatever other program you might be writing. Bringing in Spring say, is not to be used in the build script, but in the Java program. Putting it in the buildscript closure ensures that the dependencies are available for use within the gradle build itself. Not the output program.
A bit more explanation by demonstrating Android top-level gradle file.
buildscript {
// this is where we are going to find the libraries defined in "dependencies block" at below
repositories {
google()
jcenter()
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
}
// everything listed in the dependencies is actually a plugin, which we'll do "apply plugin" in our module level gradle file.
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2' // this is android gradle plugin
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // kotlin gradle plugin
}
}
module level gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
What is "plugin"? They are just java classes, which implement Plugin interface. Under the interface, it has a method "apply" to add multiple task objects with different names. Task is a class where we can implement the workflow. For instance, the build task consists of the flow of building the app.
So, what does buildscript do? It defines where to find the plugins. What does plugin do? It encompasses multiple tasks. What does task do? It provides us with the build, install, lint, etc.
My understanding might be wrong. Please don't hesitate to correct me if you find anything is misleading.
The "buildscript" configuration section is for gradle itself (i.e. changes to how gradle is able to perform the build). So this section will usually include the Android Gradle plugin.
It's a bit high level but hope helps.
For me, clear distinction started to shape once I start to understand what is a building block, method, and task. How the syntax looks like, how you can configure them etc. So I suggest you go through all these. After that, you can begin to make sense out of this syntax.
Then it's very important to know what's the type of the object build.gradle (an instance of Project class) so to know what can you have inside a build.gradle file. That would answer where that 'buildScript' and others come from. And to extend your capabilities/features (let's say android), see how plugins can help.
Last but not least, there's a very good tutorial here that talks about closures, delegates which are the concepts essential to understand the script.
buildscript block is used for the build script, NOT for the gradle build output (for example, an Android app apk). In the following code example, the encoding code is used in build script, not in the gradle build output program; so the dependecies should be added to buildscript block.
https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
External dependencies for the build script
Instead of manipulating the
script classpath directly, it is recommended to apply plugins that
come with their own classpath. For custom build logic, the
recommendation is to use a custom plugin. If your build script needs
to use external libraries, you can add them to the script’s classpath
in the build script itself. You do this using the buildscript()
method, passing in a block which declares the build script classpath.
The block passed to the buildscript() method configures a ScriptHandler instance.
You declare the build script classpath by adding dependencies to the
classpath configuration. This is the same way you declare, for
example, the Java compilation classpath. You can use any of the
dependency types except project dependencies.
Having declared the build script classpath, you can use the classes in
your build script as you would any other classes on the classpath. The
following example adds to the previous example, and uses classes from
the build script classpath.
import org.apache.commons.codec.binary.Base64
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
}
tasks.register('encode') {
doLast {
def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
println new String(encodedString)
}
}
You can imagine the buildScript block as contents from the Gradle core, like plugins{} block that already goes into Gradle internally.
So all plugins from buildScript of parent build.gradle will be available in all nested build.gradle modules.
I believe that everything in the buildscript {} will be available for the current build script itself and then the all its subprojects.
And for the properties declared in the file itself outside of the buildscript {} it will not become immediately available to for buildscript of the given project itself, but to all its subprojects.
So if you want to declare something and use it for the buildscript itself right away (current buildscript and not just subproject's buildscript), declare them in the buildscript {} for the current project and it also has the side effect to let subproject use it later on.
If you just want to declare something globally (for sub-projects's buildscripts) you can declare them directly as ext {} in parent project. The parent project won't be able to use them for itself's buildscript but it will be available all the subproject to use, in or out of the buildscript clause.
For example in parent project:
ext {
kotlin_version_XX = '1.7.10'
}
buildscript {
ext {
kotlin_version = '1.7.10'
}
// kotlin_version will be available here since declared in buildscript{}
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// will NOT be available here -- error due to kotlin_version_XX declared in project
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version_XX"
}
And if you have a subproject:
dependencies {
// both kotlin_version and kotlin_version_XX can be used here, since it was declared in parent project
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version_XX"
}
buildscript {
// both kotlin_version and kotlin_version_XX can even be used here for subproject's script's use, since it was already declared in parent project
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version_XX"
}

Gradle : Something like Maven Parent POM

In our company, many of the different projects use similar technology stack and will have many common features.
So, we want to maintain the common features, dependencies etc. in one common file and refer it in the other projects.
In maven, it is something like creating a separate maven project with the common dependency information and refer that in the other projects as .
I want to do something similar to the maven parent project in gradle, which can be used by all different projects.
I googled for that, but could not find a concise information on how to do that.
We are not allowed to use external thirdparty plugins.
It would be great if someone could explain it how to do that.
in gradle you can do that, but for it you need to have external plugin, otherwise it is not possible at least for now. I have achieved it in this way:
buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.netflix.nebula:nebula-dependency-recommender:4.3.0'
}
}
allprojects {
apply plugin: 'nebula.dependency-recommender'
apply plugin: 'groovy'
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
jcenter()
maven { url "http://repo1.maven.org/maven2/" }
maven { url "REPOSITORY_OF_YOUR_PARENT_POM.XML" }
}
dependencyRecommendations {
mavenBom module: 'YOUR_PARENT_POM_GROUP:YOUR_PARENT_POM_ID:YOUR_PARENT_POM_VERSION'
}
}
where:
REPOSITORY_OF_YOUR_PARENT_POM.XML - any system like nexus or something else accessible for maven
YOUR_PARENT_POM_GROUP - your parent pom project group (e.g. com.foo.bar.parent)
YOUR_PARENT_POM_ID - your parent pom id (e.g. projects-parent)
YOUR_PARENT_POM_VERSION - your parent pom project version (e.g. 1.0.1)
so, if the external dependency to netflix.nebula is fine , than you can go in this way
Gradle has many extension mechanisms for leveraging build logic located outside of the main script.
A simple thing that can be done is to use an external build script, which can be sourced from the local file system or through an URL, see the documentation on this topic.
If that solution gets too problematic, then you can move to packaging a real plugin that others can apply and potentially configure.
This will allow you to configure much more than dependencies for example.

How to apply plugin to allprojects with new Gradle plugins mechanism?

Before Gradle 2.1 I could apply plugin to all projects by using allProjects closure (by prevoisly resolving the jar, of course):
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:3.0.1"
}
}
allprojects {
apply plugin: "com.jfrog.artifactory"
}
With new publishing mechanism it looks like the plugins closure can't be used inside allprojects:
allprojects {
plugins {
id "com.jfrog.artifactory" version "3.0.1"
}
}
fails with:
"Could not find method plugins() for arguments [build_xxxx_run_closure1_closure4#yyyyy] on root project"
What are the rules of using plugins closure? Is the plugin applied to current project only? If so, how can I apply it to all projects without repeating the plugins closure inside each build?
The new plugins {...} syntax cannot be used within a allprojects {...} or subprojects {...} closure. Additionally, it can only be used within build scripts (no script plugins, init scripts, etc). If you want to avoid having to apply the plugin to each project individually I'd suggest using the old notation. This is an issue the Gradle team is aware of and a solution will be introduced in future versions.
Update: Starting with Gradle 3.0 you can do this in a slightly modified way. You still have to explicitly use apply() but you no longer have to deal with all the buildscript { } nonsense to get the plugin on your classpath. This also allows you to conditionally apply plugins. Check out the Gradle 3.0 release notes for more information.
plugins {
id 'my.special.plugin' version '1.0' apply false
}
allprojects {
apply plugin: 'java'
apply plugin: 'my.special.plugin'
}

How to avoid duplicate plugin declarations in each build script?

I have a Gradle build that invokes various other gradle scripts, using apply from. Both the main build.gradle and each of the sub-scripts make use of the same build script plugin (the gradle-cargo-plugin, specifically).
The only way I've managed to get this to work is to repeat the declaration of the plugin in each script:
build.gradle:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.gradle.api.plugins:gradle-cargo-plugin:1.5.1'
}
}
apply from: 'other.gradle'
// do something with the cargo plugin
other.gradle:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.gradle.api.plugins:gradle-cargo-plugin:1.5.1'
}
}
// do something else with the cargo plugin
As you can see, the buildscript section is repeated in each script. Changing that dependency becomes tedious and error-prone, but the sub-script don't inherit the dependency from the main build.gradle.
Is there a way to clean this up, either by allowing the invoked scripts to inherit the buildscript dependency, or a different way to delegate to the sub-scripts instead of using apply from?
Works fine for me with Gradle 2.1. Build scripts declared in a build.gradle's buildscript block are visible in script plugins.
Plugins in the new plugin portal (http://plugins.gradle.org/) can be applied in a single line, and don't require a buildscript block (with Gradle 2.1 and higher).

Spring Boot Gradle Plugin "Blessed" Dependencies in a multi-project environment

In a multi-project Gradle environment, I have the usual buildscript block in my parent build.gradle:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.7.RELEASE")
}
}
However, the dependencies which are supposed to be "blessed" (See 54.2 Declaring dependencies without versions) does not seem to be, since Gradle does not search for the correct version (because there is not version at all). I wonder if I am missing some additional configuration or it's just not possible at this moment to have "blessed" dependencies for a multi-project Gradle environment.
Applying spring-boot plugin to all projects (not just parent project) should help.
Try:
allprojects {
apply plugin: 'spring-boot'
}
EDIT: Andy and I posted the answers almost at the same time. But yes, either allprojects or subprojects should work fine, depending whether you parent project needs the plugin, or not.
You need to apply the Spring Boot plugin to every project that you want to use the Boot-provided dependency versions. As it's a multi-project build (and assuming you want to apply the plugin to every subproject), add the following to your build.gradle:
subprojects {
apply plugin: 'spring-boot'
}

Resources