Reference function on Gradle extension plugin configuration - gradle

I am currently writing a Gradle plugin on Java.
I've successfully written a plugin able to receive String parameters con its configuration. However, I would like to pass instead of the value of the arguments a reference to a function on a gradle file so to execute it for getting the String argument. Is it possible? How should I performe it?
My current code is the following:
public class DemoPluginExtension {
private String commitId = "";
public String getCommitId() {
return commitId;
}
public void setCommitId(String commitId) {
this.commitId = commitId;
}
}
I have a gradle.build file with a code able to extract the commitId, lets call it getGitCommitIdInfo. So, I am able to use the plugin as follows:
demoSetting {
def commitIdfInfo = getGitCommitIdInfo()
commitId = commitIdfInfo
}
What I would like to perform is to use the plugin like follows:
demoSetting {
commitIdfunc = this.&getGitCommitIdInfo
}
But I do not know how to write the DemoPluginExtension code.
Any suggestion is going to be welcomed.
Thanks a lot!

Related

Applying Gradle plugin from local file

I have the following gradle plugin that does the job of starting up a java process. The code for this lives under a file named startAppServerPlugin.gradle under the project's buildSrc directory.
The code of the plugin looks like this:
repositories.jcenter()
dependencies {
localGroovy()
gradleApi()
}
}
public class StartAppServer implements Plugin<Project> {
#Override
void apply(Project project) {
project.task('startServer', type: StartServerTask)
}
}
public class StartServerTask extends DefaultTask {
String command
String ready
String directory = '.'
StartServerTask(){
description = "Spawn a new server process in the background."
}
#TaskAction
void spawn(){
if(!(command && ready)) {
throw new GradleException("Ensure that mandatory fields command and ready are set.")
}
Process process = buildProcess(directory, command)
waitFor(process)
}
private waitFor(Process process) {
def line
def reader = new BufferedReader(new InputStreamReader(process.getInputStream()))
while ((line = reader.readLine()) != null) {
logger.quiet line
if (line.contains(ready)) {
logger.quiet "$command is ready."
break
}
}
}
private static Process buildProcess(String directory, String command) {
def builder = new ProcessBuilder(command.split(' '))
builder.redirectErrorStream(true)
builder.directory(new File(directory))
def process = builder.start()
process
}
}
I'm trying to figure out a way of having this imported into my main build.gradle file due everything I tried so far has been unsuccessful.
So far I have tried this:
apply from: 'startAppServerPlugin.gradle'
apply plugin: 'fts.gradle.plugins'
But it has been failing. I've tried searching online for examples of doing what I need to do but so far I've been unsuccessful. Can anyone please provide a hint as to how I'm supposed to do so?
The buildSrc folder is treated as an included build, where the code is compiled and put on the classpath of the surrounding project. The actual build.gradle file in buildSrc is only used for compiling that project, and the things you put in it will not be available elsewhere.
You are supposed to create your classes as a normal Java/Groovy/Kotlin project under buildSrc. I don't know if you can use the default package, but it is generally best practice to have a package name anyway.
For example, your StartAppServer plugin should be in buildSrc/src/main/groovy/my/package/StartAppServer.groovy. Then you can apply it in your build scripts with apply plugin: my.package.StartAppServer.
There are a lot of good examples in the user guide.
You are on the right path. The first order of business is to import the external gradle build using:
apply from: 'startAppServerPlugin.gradle'
Then you can apply the plugin with:
apply plugin: StartAppServer
See Script Plugins and Applying Binary Plugins

completion Candidates for positional parameter in picocli

I'm trying to provide completion for positional parameters.
Somewhere I found note that they are not very well supported, but currently I'm not able to find exact place in spec and I'm not sure what that really means.
In meantime I found CompletionCandidatesTest.java in sources which would suggest that they're supported in some fashion or at least prepared to support it.
That's why I would like to know if they work and if yes what I'm doing wrong.
Currently my code in Groovy looks like this:
package com.some.package
import picocli.CommandLine
import picocli.CommandLine.Command
#Command
class TjTest implements Runnable {
static class TjTestCandidates implements Iterable<String> {
#Override
Iterator<String> iterator() {
return Arrays.asList("aaaa", "bbbb", "cccc", "dddd", "eeeee", "ffff").iterator()
}
}
#CommandLine.Option(names = "-x", completionCandidates = TjTestCandidates)
String x;
#CommandLine.Parameters(completionCandidates = TjTestCandidates)
String param;
#Override
public void run() {
println "Start"
println x
println param;
println "Stop"
}
public static void main(String[] args) {
CommandLine.run(new TjTest(), args);
}
}
I performed required bash commands like this:
java -cp "picocli-3.9.5.jar;tj.jar" picocli.AutoComplete -f -n tjtest com.some.package.TjTest
. tjtest_completion
It works like a charm for an option. Unfortunately I was not able to make it work for parameter.
I was also trying to:
remove #Option and leave only #Parameters
put index in parameter
Your code looks fine. The current state (picocli 3.9.5) is that positional parameter completion works in JLine, but not in bash/zsh.
There is an outstanding todo item to fix this. Someone contributed a pull request to address this but it had an issue and has not been merged.
Contributions are welcome!

Gradle how to change version number in source code

Java code:
public static String VERSION = "version_number";
Gradle build.gradle
version = '1.0'
How to set the version in java code from grade? The version must be in source code.
Is there a convenient way? A not-so-nice way:
copy the java file to another location, e.g. build/changed-source
change the version in the source, by replacing token
add the build/changed-source in main source set.
I'd do similar to Michael Easter but with these differences
Store generated sources separately from main sources (src/main/java and $buildDir/generated/java). This has the added benefit of not needing custom gitignore
Generate in a subdirectory of $buildDir so that clean task will delete the generated sources
Use a separate task for code generation with proper up-to-date & skip support
Use Copy.expand(Map) to do the token replacement
Since its directory based, everything in src/template/java will have tokens replaced. You can easily add more templates in future
src/template/java/com/foo/BuildInfo.java
package com.foo;
public class BuildInfo {
public static String getVersion() {
return "${version}";
}
}
build.gradle
task generateJava(type:Copy) {
def templateContext = [version: project.version]
inputs.properties templateContext // for gradle up-to-date check
from 'src/template/java'
into "$buildDir/generated/java"
expand templateContext
}
sourceSets.main.java.srcDir "$buildDir/generated/java" // add the extra source dir
compileJava.dependsOn generateJava // wire the generateJava task into the DAG
One method is to similar to your not-so-nice way, but slightly easier. Consider a file in templates/BuildInfo.java:
package __PACKAGE;
public class BuildInfo {
private static final String version = "__VERSION";
private static final String buildTimestamp = "__BUILD_TIMESTAMP";
public String toString() {
return "version : " + version + "\n" +
"build timestamp : " + buildTimestamp + "\n";
}
}
This file can then be "stamped" with information as first thing in the compileJava task and written to src/main/java/your/package/BuildInfo.java:
def targetPackage = 'net/codetojoy/util'
def targetPackageJava = 'net.codetojoy.util'
def appVersion = project.appVersion // from gradle.properties
def buildTimeStamp = new Date().toString()
compileJava {
doFirst {
ant.mkdir(dir: "${projectDir}/src/main/java/${targetPackage}")
def newBuildInfo = new File("${projectDir}/src/main/java/${targetPackage}/BuildInfo.java")
def templateBuildInfo = new File("${projectDir}/templates/TemplateBuildInfo.java")
newBuildInfo.withWriter { def writer ->
templateBuildInfo.eachLine { def line ->
def newLine = line.replace("__PACKAGE", targetPackageJava)
.replace("__VERSION", appVersion)
.replace("__BUILD_TIMESTAMP", buildTimeStamp)
writer.write(newLine + "\n");
}
}
}
}
A working example is provided here. Everything would be stored in source-control except the src/main/java/your/package/BuildInfo.java file. Note the version would be stored in gradle.properties.

How to access variant.outputFileName in Kotlin

We've been using a snippet like this one to rename the APK file generated by our Gradle build:
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}-${variant.versionName}.apk"
}
}
Source: https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#variant_output
I am now in the process of converting my build.gradle to build.gradle.kts, i. e. to the Gradle Kotlin DSL. This is one of the last missing pieces: I can't figure out how to access outputFileName.
According to the API docs it does not even seem to exist:
BaseVariant.getOutputs() returns a DomainObjectCollection<BaseVariantOutput> which provides the all method used in the snippet.
BaseVariantOutput extends OutputFile which extends VariantOutput but none of these has an outputFileName or any getters or setters of a matching name.
So, I suspect there is some advanced Groovy magic at work to make this work - but how do I get there in Kotlin?
A little simplified version of #david.mihola answer:
android {
/**
* Notes Impl: Use DomainObjectCollection#all
*/
applicationVariants.all {
val variant = this
variant.outputs
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
val outputFileName = "YourAppName - ${variant.baseName} - ${variant.versionName} ${variant.versionCode}.apk"
println("OutputFileName: $outputFileName")
output.outputFileName = outputFileName
}
}
}
Browsing through the source code of the Android Gradle plugin, I think I found the answer - here we go:
We are actually dealing with objects of type BaseVariantOutputImpl and this class does have both these methods:
public String getOutputFileName() {
return apkData.getOutputFileName();
}
public void setOutputFileName(String outputFileName) {
if (new File(outputFileName).isAbsolute()) {
throw new GradleException("Absolute path are not supported when setting " +
"an output file name");
}
apkData.setOutputFileName(outputFileName);
}
Using this knowledge we can now:
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
and then cast our target objects like so:
applicationVariants.all(object : Action<ApplicationVariant> {
override fun execute(variant: ApplicationVariant) {
println("variant: ${variant}")
variant.outputs.all(object : Action<BaseVariantOutput> {
override fun execute(output: BaseVariantOutput) {
val outputImpl = output as BaseVariantOutputImpl
val fileName = output.outputFileName
.replace("-release", "-release-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
.replace("-debug", "-debug-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
println("output file name: ${fileName}")
outputImpl.outputFileName = fileName
}
})
}
})
So, I guess: Yes, there is some Groovy magic at work, namely that Groovy's dynamic type system allows you to just access getOutputFileName and setOutputFileName (by way of the abbreviated outputImpl.outputFileName syntax, as in Kotlin) from your code, hoping they will be there at runtime, even if the compile time interfaces that you know about don't have them.
Shorter version using lambdas:
applicationVariants.all{
outputs.all {
if(name.contains("release"))
(this as BaseVariantOutputImpl).outputFileName = "../../apk/$name-$versionName.apk"
}
}
This will place APK into app/apk folder with name made of variant name and version code.
You can change the format of filename as you wish.
Important: it must be done only on release builds, because ".." in path corrupts debug build process with strange errors.
For libraryVariants it is possible to change output file name without accessing internal api:
libraryVariants.all {
outputs.all {
packageLibraryProvider {
archiveFileName.set("yourlibrary-${buildType.name}.aar")
}
}
}
For Kotlin KTS.
NOTE: This is considered a temporal soluciĆ³n, until a proper way to do it in KTS is released by Android team.
Working in AGP v7.1.2 it might work also in lower versions of AGP.
:app build.gradle
android {
// ...
this.buildOutputs.all {
val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val variantName: String = variantOutputImpl.name
val outputFileName = "custom-name-${variantName}.apk"
variantOutputImpl.outputFileName = outputFileName
}
}

Creating custom gradle task inside Grails plugin

I am trying to create a Grails plugin that creates a custom Gradle Task which can be depended on by bootRun. I would like to do something like this:
#CompileStatic
static void configureProcessConfig(Project project) {
TaskContainer taskContainer = project.tasks
if(taskContainer.findByName('processConfig') == null) {
taskContainer.create("processConfig") {
List<File> testResources = [project.file("src/test/resources")]
for (t in testResources) {
if (t.name.contains('.properties') || t.name.contains('.groovy')) {
Path originFile = t.toPath()
Path destFile = Paths.get('build/classes/main/' + t.name)
Files.copy(originFile, destFile)
}
}
}
def processConfigTask = taskContainer.findByName('processConfig')
taskContainer.findByName("bootRun")?.dependsOn(processConfigTask)
}
}
However, I can't seem to get it to work in my xxxGrailsPlugin.groovy file. I don't know where to get the Project file to call this. It doesn't create the task. I am happy to do something different, but I can't figure out how to do it. I would prefer not to write to every build.gradle file where this plugin is used, but if that's the best option, I guess I will.
Any help is appreciated. Thanks!

Resources