I'm currently trying to improve the way our projects share their configuration. We have lots of different multi-module gradle projects for all of our libraries and microservices (i.e. many git repos).
My main goals are:
My current solution is a custom gradle distribution with an init script that:
mavenLocal()
and our Nexus repository to the project repos (very similar to the Gradle init script documentation example, except it adds repos as well as validating them)build.gradle
files are pretty much just for dependencies.Here is the init script (sanitised):
/** * Gradle extension applied to all projects to allow automatic configuration of Corporate plugins. */ class CorporatePlugins { public static final String NEXUS_URL = "https://example.com/repository/maven-public" public static final String CORPORATE_PLUGINS = "com.example:corporate-gradle-plugins" def buildscript CorporatePlugins(buildscript) { this.buildscript = buildscript } void version(String corporatePluginsVersion) { buildscript.repositories { maven { url NEXUS_URL } } buildscript.dependencies { classpath "$CORPORATE_PLUGINS:$corporatePluginsVersion" } } } allprojects { extensions.create('corporatePlugins', CorporatePlugins, buildscript) } apply plugin: CorporateInitPlugin class CorporateInitPlugin implements Plugin<Gradle> { void apply(Gradle gradle) { gradle.allprojects { project -> project.repositories { all { ArtifactRepository repo -> if (!(repo instanceof MavenArtifactRepository)) { project.logger.warn "Non-maven repository ${repo.name} detected in project ${project.name}. What are you doing???" } else if(repo.url.toString() == CorporatePlugins.NEXUS_URL || repo.name == "MavenLocal") { // Nexus and local maven are good! } else if (repo.name.startsWith("MavenLocal") && repo.url.toString().startsWith("file:")){ // Duplicate local maven - remove it! project.logger.warn("Duplicate mavenLocal() repo detected in project ${project.name} - the corporate gradle distribution has already configured it, so you should remove this!") remove repo } else { project.logger.warn "External repository ${repo.url} detected in project ${project.name}. You should only be using Nexus!" } } mavenLocal() // define Nexus repo for downloads maven { name "CorporateNexus" url CorporatePlugins.NEXUS_URL } } } } }
Then I configure each new project by adding the following to the root build.gradle file:
buildscript { // makes our plugins (and any others in Nexus) available to all build scripts in the project allprojects { corporatePlugins.version "1.2.3" } } allprojects { // apply plugins relevant to all projects (other plugins are applied where required) apply plugin: 'corporate.project' group = 'com.example' // allows quickly updating the wrapper for our custom distribution task wrapper(type: Wrapper) { distributionUrl = 'https://com.example/repository/maven-public/com/example/corporate-gradle/3.5/corporate-gradle-3.5.zip' } }
While this approach works, allows reproducible builds (unlike our previous setup which applied a build script from a URL - which at the time wasn't cacheable), and allows working offline, it does make it a little magical and I was wondering if I could do things better.
This was all triggered by reading a comment on Github by Gradle dev Stefan Oehme stating that a build should work without relying on an init script, i.e. init scripts should just be decorative and do things like the documented example - preventing unauthorised repos, etc.
My idea was to write some extension functions that would allow me to add our Nexus repo and plugins to a build in a way that looked like they were built into gradle (similar to the extension functions gradleScriptKotlin()
and kotlin-dsl()
provided by the Gradle Kotlin DSL.
So I created my extension functions in a kotlin gradle project:
package com.example import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.artifacts.dsl.RepositoryHandler import org.gradle.api.artifacts.repositories.MavenArtifactRepository fun RepositoryHandler.corporateNexus(): MavenArtifactRepository { return maven { with(it) { name = "Nexus" setUrl("https://example.com/repository/maven-public") } } } fun DependencyHandler.corporatePlugins(version: String) : Any { return "com.example:corporate-gradle-plugins:$version" }
With the plan to use them in my project's build.gradle.kts
as follows:
import com.example.corporateNexus import com.example.corporatePlugins buildscript { repositories { corporateNexus() } dependencies { classpath(corporatePlugins(version = "1.2.3")) } }
However, Gradle was unable to see my functions when used in the buildscript
block (unable to compile script). Using them in the normal project repos/dependencies worked fine though (they are visible and work as expected).
If this worked, I was hoping to bundle the jar into my custom distribution , meaning my init script could just do simple validation instead of hiding away the magical plugin and repo configuration. The extension functions wouldn't need to change, so it wouldn't require releasing a new Gradle distribution when plugins change.
What I tried:
buildscript.dependencies
) - doesn't work (maybe this doesn't work by design as it doesn't seem right to be adding a dependency to buildscript
that's referred to in the same block)buildSrc
(which works for normal project deps/repos but not buildscript
, but is not a real solution as it just moves the boilerplate)lib
folder of the distributionSo my question really boils down to:
buildScript
block)? Simply, it stands for 'Domain Specific Language'. IMO, in gradle context, DSL gives you a gradle specific way to form your build scripts. More precisely, it's a plugin-based build system that defines a way of setting up your build script using (mainly) building blocks defined in various plugins.
Gradle's Kotlin DSL provides an alternative syntax to the traditional Groovy DSL with an enhanced editing experience in supported IDEs, with superior content assist, refactoring, documentation, and more.
Gradle is a build system that is very commonly used in the Java, Android, and other ecosystems. It is the default choice for Kotlin/Native and Multiplatform when it comes to build systems.
The Kotlin DSL provides built-in support for three destination types: Fragment , Activity , and NavGraph destinations, each of which has its own inline extension function available for building and configuring the destination.
If you want to benefit from all the Gradle Kotlin DSL goodness you should strive to apply all plugins using the plugins {}
block. See https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md
You can manage plugin repositories and resolution strategies (e.g. their version) in your settings files. Starting with Gradle 4.4 this file can be written using the Kotlin DSL, aka settings.gradle.kts
. See https://docs.gradle.org/4.4-rc-1/release-notes.html.
With this in mind you could then have a centralized Settings
script plugin that sets things up and apply it in your builds settings.gradle.kts
files:
// corporate-settings.gradle.kts pluginManagement { repositories { maven { name = "Corporate Nexus" url = uri("https://example.com/repository/maven-public") } gradlePluginPortal() } }
and:
// settings.gradle.kts apply(from = "https://url.to/corporate-settings.gradle.kts")
Then in your project build scripts you can simply request plugins from your corporate repository:
// build.gradle.kts plugins { id("my-corporate-plugin") version "1.2.3" }
If you want your project build scripts in a multi-project build to not repeat the plugin version you can do so with Gradle 4.3 by declaring versions in your root project. Note that you also could set the versions in settings.gradle.kts
using pluginManagement.resolutionStrategy
if having all builds use the same plugins version is what you need.
Also note that for all this to work, your plugins must be published with their plugin marker artifact. This is easily done by using the java-gradle-plugin
plugin.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With