configure custom plugin using params from extension - gradle

Hi i'm trying to dynamically create and configure task based on plugin extension values, problem seems to be evaluation order, is there any way to work around it?
apply plugin: SetupPlugin
setup {
destDir = 'some directory set per project in build.gradle'
sourceFile = 'some file set per project in build.gradle'
}
class PluginExtension {
String destDir
String sourceFile
}
class SetupPlugin implements Plugin<Project> {
def placeholders
void apply(Project project) {
project.extensions.create("setup", PluginExtension)
project.task ("setupEnvironment", type: Copy) {
doFirst() {
//computes placeholders <-- project.setup has value here
}
into (project.setup.destDir){ //<-- project.setup is null
from project.setup.sourceFile
}
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: placeholders)
}
}
}

Moving this into the doFirst block can cause some sideeffects, as the gradle up to date task might run into problems as reconfigure the parameters of your copy task at execution time instead of configuration time. A quickfix which should do the trick is to defer the evaluation by using closures:
...
void apply(Project project) {
project.extensions.create("setup", PluginExtension)
project.task ("setupEnvironment", type: Copy) {
doFirst() {
//computes placeholders <-- project.setup has value here
}
into {project.setup.destDir}
from { project.setup.sourceFile }
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: placeholders)
}
}
...
hope that helped!
cheers,
René

It is because apply is called before applying setup settings.
It works for doFirst because it called after apply during build.
Maybe you may wrap your copy into doLast?

It turns out I asked a question that I think is very similar to this one. I may be missing a subtle difference, but in case it helps: here it is.

Related

GString lazy evaluation in Kotlin DSL using gradle-git-properties plugin

I'm using Gradle 6.2.2 with this plugin: com.gorylenko.gradle-git-properties (version 2.2.2). I'm trying to "translate" the following snippet into Kotlin DSL:
gitProperties {
extProperty = "gitProps" // git properties will be put in a map at project.ext.gitProps
}
shadowJar {
manifest {
attributes(
"Build-Revision": "${ -> project.ext.gitProps["git.commit.id"]}" // Uses GString lazy evaluation to delay until git properties are populated
)
}
}
...but this what I've come up with so far:
gitProperties {
extProperty = "gitProps"
keys = listOf("git.branch", "git.build.host", "git.build.version", "git.commit.id", "git.commit.id.abbrev",
"git.commit.time", "git.remote.origin.url", "git.tags", "git.total.commit.count")
}
tasks {
withType<ShadowJar> {
manifest.attributes.apply {
put("Build-Revision", "${project.ext.properties["git.commit.id"]}")
}
}
}
I can't figure out to make the "GString lazy evaluation" part working in Kotlin DSL, nor how the gitProps map fits on here; eventually that approach (which I know it's partially wrong) is returning null. Any ideas?
The below Kotlin syntax worked for me:
put("Build-Revision", object {
override fun toString():String = (project.extra["gitProps"] as Map<String, String>)["git.commit.id"]!!
})
I think you have some confusion over where and how the data is being stored, and in particular when it's available.
I just got hold of this plugin and had a look at it: it supplies a project extension, which you're configuring to specify why extras property to populate, and a task: "generateGitProperties". This task is added as a dependency for the "classes" task, so it's already run once you get to "shadowJar"
The issue is that figuring out the git properties and populating the extra properties only happens when that task is executed, so they're not available when the build is configured, hence the need for the lazy GString shenanigans to pass a lazy value down into the shadowJar configuration that will only be evaluated once shadowJar executes.
You can get hold of the extra properties like this:
tasks.register("example") {
dependsOn("generateGitProperties")
doFirst {
val gitProps: Map<String, String> by project.ext
for ((name, value) in gitProps) {
println("GIT: $name -> $value")
}
}
}
That works because it's in a "doFirst" block, so it's happening at task execution time, not configuration time. So essentially, you could emulate the "lazy GString" stuff. Something like this:
withType<Jar>().configureEach {
val lazyCommitId = object {
override fun toString(): String {
val gitProps: Map<String, String> by project.ext
return gitProps["git.commit.id"] ?: ""
}
}
manifest {
attributes["Git-Commit-Id"] = lazyCommitId
}
}
I did this just for "jar", but "shadowJar" is just a subtype of a Jar task anyway.

Creating a closure in ext

I am implementing the texturePacker task given in LibGDX's TexturePacker with gradle.
project.ext {
// ...
texturePacker = ["assets", "../android/assets", "texture"]
}
import com.badlogic.gdx.tools.texturepacker.TexturePacker
task texturePacker << {
if (project.ext.has('texturePacker')) {
logger.info "Calling TexturePacker: "+ texturePacker
TexturePacker.process(texturePacker[0], texturePacker[1], texturePacker[2])
}
}
I got it working with the suggested modifications for the classpath and added extension variable. Now I want to modify the textPacker extension variable to be a closure (Is that the right terminology?) with descriptive member names rather than an array. I tried doing this:
project.ext {
// ...
texturePacker {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
This gives the following error:
Error:Could not find method texturePacker() for arguments [build_4dusyb6n0t7j9dfuws8cc2jlu$_run_closure1$_closure7#6305684e] on project ':desktop' of type org.gradle.api.Project.
I am very new to gradle and groovy, so I have no idea what this error means. More importantly, what is the correct way to do what I want?
I suppose, closure is not the thing you need, since it's used not to store variables, but to store some executable code. By the way, if need to store it, you have to add = as follows:
project.ext {
texturePacker = {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
Anyway, if need to store variables within texturePacker variable, you rather have to use a Map type, then a Closure. This could be done like this:
project.ext {
texturePacker = [
inputDir : "assets",
outputDir : "../android/assets",
packFileName : "texture"
]
}
And then you can access this variable just by names, as:
println texturePacker.inputDir
Or, I think you can also go for implementing your own task with those properties. You can use DefaultTask which is a standard implementation of a regular task (and I'm sure it'd be enough for you);
class TexturePacker extends DefaultTask {
String inputDir; // a property - not a field!
String outputDir; // a property - not a field!
...
#TaskAction
void doSth(){
// do sth with properties above - that will be called automatically by gradle as a task-execution
}
}
task packer (type:TexturePacker) {
inputDir '<your-input-dir>'
outputDir '<your-output-dir>'
}
Syntax might not be super correct, but I think you get the idea.

having gradle.projectsEvaluated in custom plugin

How can I move the following code snippet to my custom gradle plugin?
gradle.projectsEvaluated {
assemble.dependsOn(myCustomPluginTask)
}
I tried the following in the custom gradle plugin, but got "No such property: assemble for class: CustomGradlePlugin".
void apply(Project project) {
project.apply(plugin: 'java')
project.gradle.projectsEvaluated {
assemble.dependsOn(myCustomPluginTask)
}
...
}
Is there a reason why you need to do it within projectsEvaluated?
The Gradle Life Cycle can be a bit confusing when starting out. I recommend you read and understand it thoroughly before attempting your own plugin.
You can probably ignore the projectsEvaluated part (because you're in a plugin and not your build.gradle) and just do this:
void apply(Project project) {
project.apply(plugin: 'java')
project.task('myCustomPluginTask') {
print "This is called in the Configuration Phase"
doLast {
print "This is called during the Execution phase"
}
}
project.tasks.assemble.dependsOn project.tasks.myCustomPluginTask
}

Gradle distribution - create an empty directory

Is there a way to add empty directories (e.g, "logs") when creating a distribution with the gradle distribution plugin?
I saw this JIRA, describing the exact same thing. It is still open https://issues.gradle.org/browse/GRADLE-1671
I wonder if there are any workarounds I can use. I don't quite understand the workarounds described in the jira.
Thank you.
So I managed to work around this by following the suggestion in the mentioned JIRA to create a dummy empty directory and then copy it to the distribution location.
It's ugly but works. I'm sure it can be written more efficiently though. This is the Copy block from inside distributions/main/contents:
into('') {
//create an empty 'logs' directory in distribution root
def logDirBase = new File('/tmp/app-dummy-dir')
logDirBase.mkdirs()
def logDir = new File(logDirBase.absolutePath + '/logs')
logDir.mkdirs()
from {logDirBase}
}
Based on Logato's own answer I've come up with the following code, which is more elegant and also closes the file pointer correctly (using the with context):
distributions {
main {
baseName = 'app'
contents {
into('') {
File.createTempDir().with {
def tmpLog = new File(absolutePath, 'logs')
println tmpLog.absolutePath
tmpLog.mkdirs()
from (absolutePath) {
includeEmptyDirs = true
}
}
// ...
}
// ...
}
}
}
This seems odd answering this so late. But, there are 2 issues here.
We should really avoid creating empty directories. But, if we must...we must.
The previous examples attempt to create empty directories outside of the current project, which seems to go against the goal of most builds. We can avoid this and work more naturally within gradle by adding a custom task.
plugins {
id 'java'
id 'distribution'
}
group 'org.example'
version '1.0-SNAPSHOT'
task createEmptyLogDir() {
ext {
buildDir = layout.buildDirectory.dir('empty_dirs')
}
doFirst {
File.createTempDir().with {
def dir = new File(buildDir.get().asFile, 'logs')
dir.mkdirs()
}
}
outputs.dir(buildDir)
}
tasks.assembleDist.configure {
dependsOn('createEmptyLogDir')
}
distributions {
main {
distributionBaseName = 'app'
contents {
into('lib') {
from tasks.jar
}
from tasks.createEmptyLogDir {
includeEmptyDirs = true
}
}
}
}
This has the advantage of building within the build directory, using task inputs/outputs for up-to-date checks, and cleaning up.
An important note is that we cannot just create the distribution with empty directories, alone. This will be seen as no source and up-to-date. So, I added the jar in this example. Tested with gradle 7.1.

How to use gradle extension correctly in plugins using GradleBuild task?

EDIT : I rephrased my question in taken the propositon of David M. Karr into account.
I am writing a gradle plugin. This plugin is launching a task extending GradleBuild. The external gradle build file needs some info as parameters. These parameters are given in project extension.
Plugin code
class MyPlugin implements Plugin<Project> {
def mExt
void apply(Project project) {
mExt = project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
def param = new StartParameter()
param.setProjectProperties([target:getTarget()])
// Problem here
startParameter = param
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
}
def getMyBuildPath(){
...
}
// Problem here
def getTarget(){
return {mExt.target}
}
}
class MyExt {
def String target = "uninitialised"
}
Gradle build file :
apply plugin : 'com.example.myplugin'
ext{
target = "myTarget"
}
External Gradle build file :
task build(){
println project.target
}
If I put a closure in getTarget(), println project.target shows the closure and not the string.
If I don't put the closure :
// Problem here
def getTarget(){
return mExt.target
}
Then I got "uninitialised" instead of "myTarget".
How can I get the value of myext.target here ?
I am using gradle 2.3
Try this:
Define an instance variable called "myext", of type "MyExt".
In the "apply" method, do this:
myext = project.extensions.create('myext',MyExt)
In the "getTarget" method, return "myext.target".
I have succeeded in getting what I wanted to in using project.afterEvaluate method. Thanks to this question
1) In gradle build task, startParameter.projectProperties is waiting for a map, not a closure. So the idea to put a closure for a lazy definition cannot work.
2) If I put directly in my plugin a reference to mExt.target or project.myext.target, then the initial value is set. The value put in my build.gradle file is not used because the plugin is already evaluated.
3) project.afterEvaluate() solve my problem. The closure ends configuring myTask in using the project's extension.
void apply(Project project) {
project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
project.afterEvaluate { proj ->
proj.myTask.startParameter.projectProperties = [target:proj.myext.target]
}
}

Resources