Applying platform constraints to all configurations - gradle

How do I inherit constraints from BOMs for all configurations in an ergonomic way ? The following is how I am currently doing it. I am on Gradle 6.6.1.
dependencies {
compileOnly(platform('x:y:z'))
implementation(platform('x:y:z'))
annotationProcessor(platform('x:y:z'))
testAnnotationProcessor(platform('x:y:z'))
testImplementation(platform('x:y:z'))
testCompileOnly(platform('x:y:z'))
}

Well, you could do it by abusing the configurations.all method like this:
// Groovy DSL
configurations.all { config ->
project.dependencies.add(config.name, project.dependencies.platform('x:y:z'))
}
But you don't need to add the platform to all those configurations in the first place. Because most of them are resolvable and extend both api and implementation, you typically only need to add it to one of those. The only exception is annotationProcessor, which is isolated (but is extended by testAnnotationProcessor). So you can still reduce it to:
// Groovy DSL
dependencies {
implementation platform('x:y:z') // or api
annotationProcessor platform('x:y:z')
}
This is in my opinion more readable and more precise.
A common use case is for Spring Boot. It could look like this:
// Groovy DSL
import org.springframework.boot.gradle.plugin.SpringBootPlugin
plugins {
id 'java'
id 'org.springframework.boot' version '2.4.2'
}
dependencies {
// BOMS (Note that using the "BOM_COORDINATES" variable makes it match the version of the plugin)
implementation platform(SpringBootPlugin.BOM_COORDINATES)
annotationProcessor platform(SpringBootPlugin.BOM_COORDINATES)
// Actual dependencies
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}
Interestingly, there is a Gradle issue on this exact use case. Here they explained that typically you don't need this functionality, and where you do it is better to be explicit about it rather than just "hammer" a set of dependency versions onto everything.

Related

Moving transitive dependency from implementation to testImplementation

I have few dependencies, that have the same transitive dependency for tests, resulting in test dependencies ending up in my 'implementation'.
Can I somehow smoothly redirect said dependency to 'testImplementation' or do I have to perform something like:
implementation(A){exclude T}
implementation(B){exclude T}
implementation(C){exclude T}
testImplementation(T)
Something a bit better would be:
configurations {
implementation {
exclude(T)
}
}
testImplementation(T)
Then you won't need to explicitly exclude T for each implementation dependency that might pull it in.

How to add dependencies (and repositories) dynamically in a custom Gradle plugin task using Kotlin dsl

The title basically says it all.
I am trying to create a plugin that can be configured and depending on the configuration, the task provided by the plugin adds compileOnly or implementation deppendencies to the project.
The resources on writing custom Gradle plugins are abhorrent (especially in Kotlin instead of Groovy) and I can't figure out how to do this myself.
This is where I'm at with my custom plugin code:
class SpigotVersioner: Plugin<Project> {
override fun apply(project: Project) {
println("Latest spigot version: ${WebScraper.getLatestVersion()}")
val extension = project.extensions.create("spigot", SpigotExtension::class.java)
extension.apiVersion.set("latest")
extension.bukkitVersion.set("latest")
project.task("compileSpigotAPI") {
it.group = "spigot"
it.description = "Adds the spigot api implementation to the project."
it.doLast {
val apiVersion = extension.apiVersion.get()
val dependency = deriveDependencyStr(apiVersion)
//DOESN'T WORK!
project.dependencies {
compileOnly(dependency)
}
//WHAT ARE THESE PARAMETERS SUPPOSED TO BE?
project.dependencies.add(configurationName: String, dependencyNotation: Any)
}
}
}
}
This is supposed to mimic something like
dependencies {
compileOnly 'my.derived.dependency.str:apiVersion:xy'
}
only the dependency being added is supposed to be configurable via an extension.
If possible, I'd like to extend this to also add the appropriate repository as well but the dependency issue is more important.
Bit of an old question now, but I too struggled with this so hopefully this answer is of use to someone.
//WHAT ARE THESE PARAMETERS SUPPOSED TO BE?
project.dependencies.add(configurationName: String, dependencyNotation: Any)
The configurationName is the configuration that you wish to add the dependency to e.g. implementation, testImplementation or api etc.
The dependencyNotation can be any of the following:
String Notation: Simply a String written using Gradle dependency notation e.g. com.mycompany:my-awesome-dependency:1.2.3. There are ways to also specify things like strictness when using these 'simple' declarations, this is somewhat documented here.
Map Notation: This is where you pass a Map<String, String> containing key-value pairs representing the dependency. The documentation on this is either non existent or elusive, but for example: "group": "com.mycompany", "name": "my-awesome-dependency", "version": "1.2.3".
Dependency Interface: This is where you pass in an object that implements one of the Dependency interfaces that the Gradle API provides. The most basic being org.gradle.api.artifacts.Dependency. The main issue with this method is again that the documentation is either elusive or non-existent. I cannot see a way to have Gradle create one of these objects (or see any pre implemented classes in the public API). You could always just implement the interface but there are some methods on there like contentEquals and copy() which seem overkill to implement.
My recommendation if it suits your use case would be to use the first option above.

What is the difference between simple and source set dependencies when working with the Kotlin MPP plugin?

I'm using the Kotlin MPP plugin (with .kts support) and while I've been reading some code I came upon build.gradle.kts files like this:
kotlin {
sourceSets {
commonMain {
dependencies {
api(kotlinxCollectionsImmutable)
}
}
}
dependencies {
with(Libs) {
commonMainApi(kotlinStdLibCommon)
commonMainApi(kotlinxCoroutinesCommon)
}
}
}
What is the difference between declaring an api dependency within a sourceSet compared to declaring a commonMainApi dependency? Is there any?
No difference. The commonMainApi is just an alternative way of doing the same, and doesn't look to be recommended any more. Link - https://kotlinlang.ru/docs/reference/building-mpp-with-gradle.html
Альтернативным способом указания зависимостей является использование
встроенного DSL Gradle на верхнем уровне с именами конфигурации,
следующими за шаблоном : [translation:
Alternatively, dependencies can be declared by specifying
configuration names at the top level using the built-in Gradle DSL]
dependencies {
commonMainApi 'com.example:foo-common:1.0'
jvm6MainApi 'com.example:foo-jvm6:1.0'
}
Interestingly, this document is described as a translation of https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html where this paragraph (about the alternative syntax) is completely missing, so one can only deduce that the English version has been updated and the alternative syntax removed as either not recommended or obsolete at this point.

How can I decompose a Gradle build into multiple files?

I'm writing a Gradle plugin which contains a collection of multiple chunks of independent configuration which will be applied to any project applying the plugin.
I want to keep the fragments very separate to discourage other people from adding unrelated logic to an existing place, and to improve visibility of what the plugin is actually configuring.
So I thought I could do this:
class CommonChecksPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
project.apply plugin: 'base'
def resolveFile = { filename ->
new URL(buildscript.sourceURI.toURL(), filename)
}
project.apply from: resolveFile('configuration1.gradle')
project.apply from: resolveFile('configuration2.gradle')
}
}
Example configurationN.gradle:
task 'checkSomething', type: CheckSomething
Problem is, this Java class CheckSomething cannot be resolved.
Is there a sensible way to do this other than just giving up and moving all the sub-scripts in as full Groovy classes? I'm reluctant to move them to classes, because I want to apply the same checks to the plugin project itself, and it seems difficult to apply them if they require compilation.
The applied script has a different classloader to the plugin, it doesn't inherit the buildscript classloader so therefore the task isn't on the classpath. You can do the following:
project.ext.CheckSomething = CheckSomething
project.apply from: resolveFile('configuration1.gradle')
See https://discuss.gradle.org/t/buildscript-configurations-classpath-not-available-in-apply-from-scripts/1391

What order does Grails evaluate its dependency repositories?

Does Grails evaluate its Maven repos top-down or bottom-up?
I would assume top-down but some manual testing might indicate bottom-up. I can provide test results if need be.
grails.project.dependency.resolution = {
repositories {
grailsPlugins()
grailsHome()
mavenLocal()
grailsCentral()
mavenCentral()
}
}

Resources