How to add to a plugin task the dependencies comming from build.grade script? - gradle

I Have a task from my plugin that need mysql or postgres drivers.
currently I hardcoded into FooPlugin::apply method this:
configuration.dependencies.add(project.dependencies.create('mysql:mysql-connector-java:5.1.34'))
But I would like to let users, to choose their drivers.
So for this I would like to grab all dependencies from gradle build script (build.gradle) which applying my plugin to inject these dependencies to the task dynamically.
Resolved: add a piece of code
I tried this:
class FooPlugin implements Plugin<Project>{
#Override
void apply(Project project) {
project.afterEvaluate {
def configuration = project.configurations.create('bar')
configuration.extendsFrom(project.configurations.findByName('compile'))
…
}
}
}
If you do not put into project.afterEvaluate block below error is raised:
Cannot change dependencies of configuration ':bar' after it has been resolved.

I'm not sure exactly what your trying to accomplish so I'm going to guess at a couple things.
Looks like your trying to add a dependency or react based on a dependency added. I think you can accomplish either through the resolutionStrategy
project.configurations {
compile.resolutionStrategy {
// adds a dependency to a project, always.
force 'commons-io:commons-io:2.5'
// loop through all the dependencies to modify before resolution
eachDependency { DependencyResolveDetails details ->
// here you can change details about a dependency or respond however needed
if (details.requested.group == 'mysql' && details.requested.name == 'mysql-connector-java') {
// for example we can force a specific version
details.useVersion '5.1.34'
}
// you could also add a if block for postgres if needed
}
}
}

Related

Module replacement when there is no conflict

Module replacement works well in Gradle, however it only applies when there is a conflict.
Although I understand the reason, it breaks my use-case where there is extension of configurations and the conflict happens in some but not others that I need to consume.
I have two special configurations and some module replacement:
configurations {
lib // what should be bundled
provided // what should not be bundled
implementation.extendsFrom(lib)
implementation.extendsFrom(provided)
}
dependencies {
modules {
module('javax.annotation:javax.annotation-api') {
replacedBy('jakarta.annotation:jakarta.annotation-api', 'Javax to Jakarta')
}
}
}
task collectLibs(type: Copy) {
// bundle everything from lib which is not provided (not even transitively)
from configurations.lib - configurations.provided
into "$buildDir/lib"
}
I also use company BOM, here for example: api platform('org.springframework.boot:spring-boot-dependencies:2.5.4') and so I don't want to specify versions anywhere in my project.
Let's assume these dependencies:
dependencies {
lib 'javax.annotation:javax.annotation-api'
provided 'jakarta.annotation:jakarta.annotation-api'
}
the task dependencies then correctly resolves compileClasspath and runtimeClasspath to jakarta.annotation-api, however the collected files in build/lib contain javax.annotation-api-1.3.2.jar even though it "should have been replaced and subtracted"
If I use module substitution instead, it works:
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module('javax.annotation:javax.annotation-api') using module('jakarta.annotation:jakarta.annotation-api:1.3.5')
}
}
However there I must specify version. Is there any possibility to force module replacement to always act?
My problem is caused by the subtraction, maybe there is a better way to find all dependencies that come from provided but not lib by looking at runtimeClasspath?
I tried something but it gets too complicated very quickly.
I found a solution. Instead of subtracting provided configuration, I can exclude everything from resolved provided configuration. The tricky part is to exclude not too much and not too little:
platform must remain otherwise resolution of versions will fail
both requested and selected must be excluded
This is not a general solution; it still requires some fiddling with configurations (provided must declare both javax and jakarta) but it works for me.
private static excludeFromConfiguration(Configuration configuration, Configuration toExclude) {
toExclude.incoming.resolutionResult.allDependencies.each { dep ->
if (dep instanceof ResolvedDependencyResult && dep.requested instanceof ModuleComponentSelector) {
def isPlatform = dep.requested.attributes.keySet().any {
// asking for org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE does not work
def attribute = dep.requested.attributes.getAttribute(it)
return attribute == org.gradle.api.attributes.Category.ENFORCED_PLATFORM ||
attribute == org.gradle.api.attributes.Category.REGULAR_PLATFORM
}
if (!isPlatform) {
// we exclude both - the requested and selected because there could have been some:
// module replacement, dependency substitution, capability matching
configuration.exclude(group: dep.requested.group, module: dep.requested.module)
configuration.exclude(group: dep.selected.moduleVersion.group, module: dep.selected.moduleVersion.name)
}
}
}
}

How to add a method to every `repositories` block, or every RepositoryHandler?

A large project with many developers and gradle projects uses a private maven repository for plugins, dependencies, and publication.
I would like to define a privateMaven() method, just like the built-in jcenter(), mavenCentral(), and google() methods. Currently we write a maven block anywhere we need to use the repository - repositories, publishing.repositories, pluginManagement.repositories, ...
repositories {
maven {
url "..."
credentials { ... }
}
}
which I would rather be
repositories {
private()
}
This answer explains how to extend repositories and buildscript.repositories but it doesn't work for publishing.repositories because publishing is provided by a plugin and doesn't work for pluginManagement.repositories. Also I would also have to enumerate every repositories configuration and developers can't use privateMaven() in any block we don't extend.
Is there a way for an init script to add a method to every repositories block, or every RepositoryHandler?
Assuming you're using the maven-publish plugin, you can define an extension like so:
private.gradle
apply plugin: 'maven-publish'
publishing.repositories.ext.privateRepo = {
publishing.repositories.maven {
url "https://artifactory.website.com/private-repo"
credentials {...}
}
}
build.gradle
apply from: 'private.gradle' // or you can just include the script in here
afterEvaluate {
publishing {
publications {...}
repositories {
privateRepo()
}
}
}
You can also create a plugin if you'd like to distribute the script, the usage would remain exactly the same.
https://guides.gradle.org/implementing-gradle-plugins/
PrivatePlugin.groovy
class PrivatePlugin implements Plugin<Project> {
#Override
void apply(Project project) {
// check if the publishing extension exists first
final publishing = project.extensions.findByType(PublishingExtension.class)
if (publishing != null) {
publishing.repositories.ext.privateRepo = {
publishing.repositories.maven {
url "https://artifactory.website.com/private-repo"
credentials {...}
}
}
}
}
}
Just be sure that if you distribute it as a plugin that you don't ship it with the credentials hardcoded.
My goal is that any repositories block can use privateMaven. I don't want to explicitly extend publishing.repositories.ext, repositories.ext, buildscript.repositories.ext. In addition to being tedious, if I miss one repositories or gradle adds a new repositories then privateMaven will not be available.
One solution which is close, but not perfect, was to create an extension on with a method that takes a reference to RepositoryHandler.
Any repository block and now use the extension as so
repositories {
custom.privateMaven(it)
}
beforeSettings {
extensions.create("custom", PrivateMavenExtension::class)
}
allprojects {
extensions.create("custom", PrivateMavenExtension::class)
}
open class PrivateMavenExtension {
fun privateMaven(handler: RepositoryHandler): MavenArtifactRepository {
return handler.maven {
// implementation
}
}
}
One major hurdle I haven't solved is that classes defined in an init.d script cannot be loaded in build.gradle. If PrivateMavenExtensions is defined in an init.d script I can't reference the extension in a type-safe way from a build.gradle.kts because the<PrivateMavenExtension>() cannot be resolved.

Read build script blocks from gradle plugin

There is a gradle plugin with id ("com.my.plugin").
The project using this plugin has the following build.gradle file:
...
apply plugin: 'com.my.plugin'
...
android {
    ...
    defaultConfig {
        ...
        testInstrumentationRunner "com.my.plugin.junit4.MyCustomRunner"
        ...
    }
    ...
}
...
dependencies {
    ...
    androidTestImplementation com.my:plugin-junit4:1.0.0-alpha04
    ...
}
...
The class implementing the plugin is as follows:
class MyPlugin: Plugin <Project> {
    override fun apply (project: Project) {
        project.afterEvaluate {
            // here I need to read testInstrumentationRunner value declared
            // in the defaultConfig block of the build.gradle file
            // also here I need to read androidTestImplementation value declared
            // in the dependencies block of the build.gradle file
        }
    }
}
In the project.afterEvaluate {...} block of the plugin I need to check for the values ​​of testInstrumentationRunner and androidTestImplementation declared in the build.gradle file of the project using this plugin. How to do it?
Since you're using Kotlin for your plugin implementation, you'll need know the type of the android { } extension. Otherwise you will run into compilation errors.
Essentially, you need to retrieve a reference of the android extension in your plugin like so:
project.afterEvaluate {
// we don't know the concrete type so this will be `Object` or `Any`
val android = project.extensions.getByName("android")
println(android::class.java) // figure out the type
// assume we know the type now
val typedAndroid = project.extensions.getByType(WhateverTheType::class.java)
// Ok now Kotlin knows of the type and its properties
println(typedAndroid.defaultConfig.testInstrumentationRunner)
}
I'm not familar with Android or its Gradle plugin. Google only led me to its Javadocs here which didn't help. So the above may or may not work.

How to declare project artifacts in non-Java build?

I have multi-project Gradle build that contains also non-Java projects.
I want to declare the artifacts create by one such project in a way that I can use project/configuration dependencies to get them, e.g.
consumer:
dependencies {
myConf project(path: ':producer', configuration: 'myConf')
}
What I currently have is this:
producer:
configurations {
myConf
}
task produceFile {
//... somehow create the file...
outputs.file file('path/to/file')
}
artifacts.add('myConf', produceFile.outputs.files.singleFile, { builtBy produceFile })
Is there a better way to declare the artifact than my clumsy version?
I couldn't figure out a way to pass the task dependency from the artifact to the producing task in one go.
According to the documentation article on Legacy publishing and the javadoc on the ArtifactHandler, for your simple example it should be sufficient to just pass the task, as long as the task type extends AbstractArchiveTask (e.g. Zip or Jar):
artifacts.add('myConf', produceFile)
... or in the more Gradle-ish way:
artifacts {
myConf produceFile
}
The article mentioned above has another example, where a File is passed directly to the add method, which requires you to specify the task to build the file in the way you did in your example.
However, let me propose other ideas for syntax that may be experienced more 'lightweight':
artifacts {
myConf files(produceFile).singleFile { buildBy produceFile }
// or
myConf file: files(produceFile).singleFile, buildBy: [produceFile]
}
These two examples use the Project.files(...) method to resolve the output(s) of the task instead of accessing them manually. The second example makes use of the map syntax often provided by Gradle.
If you want to somehow standardize your way to publish your custom artifacts, I would propose to create a custom task type that offers any of the different arguments the ArtifactHandler can process as a method or property:
class MyTaskType extends DefaultTask {
// ... other stuff ... of course this should be part of a plugin
def getArtifact() {
return ... // either a (Configurable)PublishArtifact (if constructor is available) or a map representation
}
}
task produceFile(type: MyTaskType) {
// configure somehow
}
artifacts {
myConf produceFile.artifact
}

Are pmd, repositories etc tasks in Gradle

I am creating basic custom tasks in Gradle and learning how to extend them to do more complicated actions (Learning from here: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html).
One of my reference projects, which I am extending to learn Gradle looks something like this
// pmd config
pmd {
ignoreFailures = false
reportsDir = file("$globalOutputDir/pmd")
toolVersion = toolVersions.pmdVersion
}
repositories {
mavenCentral()
}
task listSubProjects{
doLast{
println 'Searching in root dir `'
}
}
My question is around the pmd and repositories sections and why they don't have a clear qualifier like "task" on them but my listSubProjects requires a task qualifier? Are these inherited tasks from plugins and don't need a task qualifier?
The blocks that you see are task extensions, also discussed here.
A plugin creator can define extensions to allow users to configure a plugin:
// plugin code
class GreetingPluginExtension {
// default value
String message = 'Hello from GreetingPlugin'
}
// plugin code
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// Add a task that uses configuration from the extension object
...
}
}
In project.extensions.create('greeting',... the greeting block to be used later in build.gradle files is defined.
Then in user build.gradle files
apply plugin: GreetingPlugin
// Configure the extension
greeting.message = 'Hi from Gradle'
// Same effect as previous lines but with different syntax
greeting {
message = 'Hi from Gradle'
}
Often the name of the extension is chosen to be the same as the plugin and/or the task, which can make things confusing.

Resources