I tried as per example in, and simplified it to just "message".
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension, project)
project.tasks.create('hello', Greeting) {
message = extension.message
}
}
}
class GreetingPluginExtension {
def String message = "aa"
GreetingPluginExtension(Project project) {
message = project.property(String)
}
}
class Greeting extends DefaultTask {
#Input
def String message = project.property(String)
#TaskAction
def testCoverageVerification() {
logger.quiet("Message is")
logger.quiet(message)
}
}
apply plugin: GreetingPlugin
greeting {
message = 'Hi from Gradle'
}
When I run ./gradlew -q hello, I got
Message is
value: null
I expect it to be
Message is
value: Hi from Gradle
Why did my String from the greeting fail to pass in?
Actually, it's quite easy to explain. The plugin (hence the task creation and variable value assignment) is applied and evaluated before the extension block is evaluated. So the task is created even before the extension's value is set. From the link you provided:
The extension declaration in the build script as well as the mapping
between extension properties and custom task properties occurs during
Gradle's configuration phase of the build lifecycle. To avoid
evaluation order issues, the actual value of a mapped property has to
be resolved during the execution phase.
To make it work you can change the plugin declaration to this:
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension, project)
project.tasks.create('hello', Greeting) {
doFirst {
message = extension.message
}
}
}
}
However it doesn't make much sense - so don't use it ;)
I manage to resolve my issue by directly getting it from project.greeting.message
#TaskAction
def testCoverageVerification() {
message = project.greeting.message
logger.quiet("Message is")
logger.quiet(message)
}
Related
I created a gradle plugin to unify some settings between the various modules of the application. the summary of the error is this:
org.gradle.api.plugins.InvalidPluginException: An exception occurred applying plugin request [id: 'common.plugin']
Caused by: org.gradle.api.UnknownDomainObjectException: Extension of type 'LibraryExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension,....]
This is a summary image of the project architecture:
CommonPluginClass:
class CommonPluginClass : Plugin<Project> {
private val consumerProguardFileName = "consumer-rules.pro"
private val proguardFileName = "proguard-rules.pro"
private val sdkToCompile = 33
override fun apply(project: Project) {
println(">>> Adding sugar to gradle files!")
with(project)
{
applyPlugins(this)
androidConfig(this)
}
println(">>> Sugar added for core Module!")
}
private fun applyPlugins(project: Project) {
println(">>> apply plugins!")
project.pluginManager.run {
apply("com.android.application")
apply("kotlin-android")
apply("kotlin-kapt")
}
println(">>> end apply plugins!")
}
private fun androidConfig(project: Project) {
project.extensions.configure<LibraryExtension>{
defaultConfig.targetSdk = 33
}
}
}
the error occurs inside the androidConfig function when calling configure
I was inspired by now android, dependencies and imports are similar but not build. Can someone unlock it please.
build-logic:convention build.gradle
plugins {
alias(libs.plugins.kotlin.jvm) apply false
id "org.gradle.kotlin.kotlin-dsl" version "2.4.1"
}
dependencies {
compileOnly(libs.android.pluginGradle)
compileOnly(libs.kotlin.pluginGradle)
}
gradlePlugin {
plugins {
commonPlugin {
id = "common.plugin"
implementationClass = "CommonPluginClass"
}
}
}
LITTLE UPDATE:
I've noticed that any module I enter always identifies it to me as ApplicationExtension
I found the solution and as usual it's a stupid thing.
the application extension use plugin
apply("com.android.application")
the library or module use plugin
apply("com.android.library")
I am trying to build a custom Gradle Task to perform some app maintenance operations. I would like to pass argument(s) to the task to control operations. To illustrate my dilemma, consider to following two trivial custom task classes:
// Custom task with no arguments
public class HelloClassA extends DefaultTask {
#TaskAction
public void printHello()
{
println "Hello, World!"
}
}
// Custom task with one argument
public class HelloClassB extends DefaultTask {
#TaskAction
public void printMsg(String msg)
{
println msg
}
}
They are identical except that "A" prints a hard-coded string while "B" accepts a string as an argument.
Class A is used in the following task:
task helloA(type:HelloClassA) { }
and is invoked by, e.g.,
gradlew.bat helloA
It works fine and prints "Hello, World!" to the Build window. However, I can't figure out the syntax is for a task to invoke Class B.
How do I do that? (or am I just way off base trying to do it this way?)
Some rather Strange Things I've noticed...
The name of the method in the classes (e.g., "printHello") seems to be irrelevant: any reasonable name produces identical output (?).
When invoking by gradlew.bat, any unambiguous substring of the task name works the same, e.g., "GRADLEW helloA" or "GRADLEW hell". I guess this is just GRADLE trying to be helpful (?).
You can't directly pass it as an argument to the method. But, if you wish there is a way using #Input annotation.
Following is the example for you use case:
abstract class HelloClassB extends DefaultTask {
#Input
abstract Property<String> getMsg()//this is an abstract method to get values
#TaskAction
def printMsg() {
println msg.get() // here we get the value set from task.register
}
}
// printB is the task name
tasks.register('printB',HelloClassB) {
msg = 'Hello B' // here we set the Input value
}
Now in your terminal run: gradlew -q printB
If you want to read more about this then please have a look here: https://docs.gradle.org/current/userguide/custom_tasks.html
You can pass an argument(s) directly to a method without using properties. Here's how:
apply plugin: 'com.android.application'
import javax.inject.Inject
abstract class CustomTask extends DefaultTask {
#Inject
CustomTask(String message,int number) {
println "Processing \""+message+"\", "+number // process args
}
}
Create task 'mytask' as follows in the android section appending the arguments (which must agree in count and format with the task definitions):
tasks.register( 'mytask', CustomTask, 'Hello, World!', 123 )
Execute 'mytask' by
gradlew.bat mytask
which produces the output
Processing "Hello, World!", 123
I am trying to generate some Android resources in a Gradle task.
I've written a task which parses an input file, and writes out an XML file to a location under the app's build directory.
app/build.gradle
import groovy.xml.MarkupBuilder
task generateSomeAppResources {
ext.outputDir = new File(projectDir, "build/generated/res/values")
doFirst {
mkdir outputDir
new File(outputDir, "generated.xml").withWriter { writer ->
def destXml = new MarkupBuilder(new IndentPrinter(writer, " ", true, true))
destXml.setDoubleQuotes(true)
def destXmlMkp = destXml.getMkp()
destXmlMkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
destXmlMkp.comment("Generated at ${new Date()}")
destXmlMkp.yield "\r\n"
destXml.resources() {
"string"("name": "generated_app_resource") {
destXmlMkp.yield("Some generated value for the app")
}
}
}
}
}
This works fine, and the generated output looks like I expect.
generated.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated at Wed Feb 12 12:46:12 GMT 2020 -->
<resources>
<string name="generated_app_resource">Some generated value for the app</string>
</resources>
I am struggling to get the Android build system to detect the generated file, though. Google's advice is
to write a task that outputs a generated resource directory structure with whatever you need, use BaseVariant.registerGeneratedResFolders()
But documentation on registerGeneratedResFolders() is non-existent. After much tedious searching I found some example usages in the Play Services Plugin source, for example, so I tried to add something along those lines.
app/build.gradle
android.applicationVariants.all { variant ->
def files = project.files(generateSomeAppResources.outputDir)
files.builtBy(generateSomeAppResources)
variant.preBuildProvider.configure { dependsOn(generateSomeAppResources) }
variant.mergeResourcesProvider.configure { dependsOn(generateSomeAppResources) }
variant.registerGeneratedResFolders(files)
}
But I'm missing something. The generated resource shows up purple in Android Studio, meaning that the IDE thinks it exists...
...but the code fails to compile with an Unresolved reference: generated_app_resource error.
I don't know what magic incantations are needed to make the Android build system pick up these resources. How do I get this to build?
To create resources, android requires
1) A resource directory above values folder then you can add desired resources as per your requirement
2) Instruct the build process to add the generated resources while building R.java
So first configure your build resource task like:
task generateSomeAppResources {
ext.outputDir = new File(projectDir, "build/generated/res/custom/values")
print("path is "+projectDir)
doFirst {
mkdir outputDir
new File(outputDir, "strings.xml").withWriter { writer ->
def destXml = new MarkupBuilder(new IndentPrinter(writer, " ", true, true))
destXml.setDoubleQuotes(true)
def destXmlMkp = destXml.getMkp()
destXmlMkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
destXmlMkp.comment("Generated at ${new Date()}")
destXmlMkp.yield "\r\n"
destXml.resources() {
"string"("name": "generated_app_resource") {
destXmlMkp.yield("Some generated value for the app")
}
}
}
}
}
now add the path in the build process using sourceSets in build.gradle(app) like
android {
//....
sourceSets {
main {
res.srcDirs += [
'build/generated/res/custom',
]
}
}
}
Additionally, add the task in the current build process as
gradle.projectsEvaluated {
preBuild.dependsOn('generateSomeAppResources')
} // no need of `android.applicationVariants.all...`
Now sync the project and it will work as expected.
Result:
Example on how to generate resources using BaseVariant.registerGeneratedResFolders
def generateResourcesTask = tasks.register("generateResources", GenerateResourcesTask)
android.libraryVariants.all { variant ->
variant.registerGeneratedResFolders(
project.files(generateResourcesTask.map { it.outputDir })
)
}
Using tasks.register(...), project.files(...) and TaskProvider.map(...) utilizes task avoidance api, and automatically wires up tasks dependencies. registerGeneratedResFolders also automatically adds resources to sourceset, so that you can reference generated resources in your code.
abstract class GenerateResourcesTask : DefaultTask() {
// If you need inputs
// #get:InputFiles
// lateinit var inputs: FileCollection
#get:OutputDirectory
val outputDir = File(project.buildDir, "generated/res/regenerate/")
private val outputFile = File(outputDir, "values/missingRes.xml")
#TaskAction
fun action() {
val result = buildString {
appendln(
"""
<?xml version="1.0" encoding="utf-8"?>
<resources>
""".trimIndent()
)
appendln(""" <string name="test_string">Test string</string>""")
appendln("</resources>")
}
outputFile.parentFile.mkdirs()
outputFile.writeText(result)
}
}
You can define this task class in build.gradle file, or in buildSrc.
Using annotations like #OutputFile makes your task incremental.
For more info no what annotations do and how to use them check out gradle's documentation. You can also use Runtime API to create tasks with properties.
I want to run ./gradlew verifyProjectDebug to run a subset of verification tasks.
verifyProjectDebug tries to extract a subset of the tasks in the project and execute them.
static def isValidTask(String name) {
def isLint = name.matches("lint.*Debug")
def isKtlint = name.matches("ktlint.*Debug.*Check")
def isUnitTest = name.matches("test((?!Prod|Staging).)*DebugUnitTest")
return (isLint || isKtlint || isUnitTest) && !name.contains("Prod")
}
task verifyProjectDebug() {
group = "verification"
description = "Runs lint, ktlint and tests for all debug non-production variants"
doLast {
getSubprojects()
.collect { it.tasks }
.flatten()
.findAll { isValidTask(it.name) }
.each { it.execute() }
}
}
Unfortunately, calling .execute() on a task does not invoke its dependencies so some of the tasks fails because its dependencies were not invoked.
Is there any way in gradle I can achieve this. Thanks a ton!
execute is a method of the Task class. You're trying to bypass Gradle build system. Executing tasks is not a simple matter or creating an instance and calling execute. Gradle handles dependency injection, caching, input & output processing, all kinds of stuff. So leverage Gradle.
1)
Create one lifecycle task that is the parent task for everything you want to execute.
final def verifyProject = tasks.register("verifyProject")
Lifecycle task is a task that doesn't do any work, it only depends on other tasks.
2)
You can only reference tasks that are already created. For example you can't reference lint task of the debug variant until the debug variant is created.
Process each variant when it's created, find all tasks you want executed and connect them to the master task.
android.applicationVariants.all {
final def cappedVariantName = name.capitalize()
// For every build variant that has build type "debug"...
if (variant.buildType == "debug") {
verifyProject.configure {
dependsOn("lint$cappedVariantName")
dependsOn("ktlint$cappedVariantName")
dependsOn("test${cappedVariantName}UnitTest")
}
}
}
Please verify the names of tasks you want executed.
Now ehen you run gradlew verifyProject all the tasks this task depends on will get executed. You're in charge of the dependencies.
If you want to use this in an Android library module replace android.applicationVariants with android.libraryVariants.
The code follows Task Conviguration Avoidance. This means the tasks you defined won't be configured unless you specifically invoke them. This should save resources (CPU & memory) when running a build.
3)
To do this automatically for all modules pick one or both of the following, and put to to your root project build.gradle.
subprojects { project ->
project.plugins.whenPluginAdded { plugin ->
// For all libraries and only libraries:
if (plugin instanceof com.android.build.gradle.LibraryPlugin) {
project.android.libraryVariants.all { variant ->
// See above.
}
}
// For all apps and only apps:
if (plugin instanceof com.android.build.gradle.AppPlugin) {
project.android.applicationVariants.all { variant ->
// See above.
}
}
}
}
Putting this in the project level gradle file did the trick.
task verifyDebugProjects() {
group = "verification"
description = "Runs lint, ktlint and tests for all debug non-production variants"
}
static def isValidVerifyDebugTask(String name) {
def isLint = name.matches("lint.*Debug")
def isKtlint = name.matches("ktlint.*Debug.*Check")
def isUnitTest = name.matches("test((?!Prod|Staging).)*DebugUnitTest")
return (isLint || isKtlint || isUnitTest) && !name.contains("Prod")
}
gradle.projectsEvaluated {
getSubprojects()
.collect { it.tasks }
.flatten()
.findAll { isValidVerifyDebugTask(it.name) }
.each { verifyDebugProjects.dependsOn it }
}
Thanks for the advice, in the doc it says:
TextResource ruleSetConfig
Note: This property is incubating and may change in a future version of Gradle.
The custom rule set to be used (if any). Replaces ruleSetFiles, except that it does not currently support multiple rule sets. See the official documentation for how to author a rule set. Example: ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml"))
But if I try
setRuleSetConfig(project.resources.text.fromFile(project.resources.file("pmd.xml")))
it says:
Caused by: groovy.lang.MissingMethodException: No signature of method: org.gradle.api.internal.resources.DefaultResourceHandler.file() is applicable for argument types: (java.lang.String) values: [pmd.xml]
Possible solutions: find(), find(groovy.lang.Closure), use([Ljava.lang.Object;), is(java.lang.Object), with(groovy.lang.Closure), wait()
at com.barista_v.android_quality.MyPmdTask.<init>(MyPmdTask.groovy:20)
at com.barista_v.android_quality.MyPmdTask_Decorated.<init>(Unknown Source)
at org.gradle.api.internal.DependencyInjectingInstantiator.newInstance(DependencyInjectingInstantiator.java:48)
at org.gradle.api.internal.ClassGeneratorBackedInstantiator.newInstance(ClassGeneratorBackedInstantiator.java:36)
at org.gradle.api.internal.project.taskfactory.TaskFactory$1.call(TaskFactory.java:121)
... 82 more
Also I am trying in this way::smile:
class MyPmdTask extends Pmd {
MyPmdTask() {
project.extensions.pmd.with {
reportsDir = project.file("$project.buildDir/outputs/")
}
source = project.fileTree('src/main/java').toList()
ruleSets = []
reports {
xml.enabled = false
html.enabled = true
}
setIgnoreFailures(true)
setRuleSetConfig(ResourceUtils.readTextResource(project, getClass().getClassLoader(), "pmd.xml"))
}
}
class ResourceUtils {
static TextResource readTextResource(Project project, ClassLoader classLoader, String fileName) {
println "reading config file $fileName"
def inputStream = classLoader.getResourceAsStream(fileName)
project.resources.text.fromString inputStream.text
}
}
and it set the config file but it dont use my rules, dont crash but dont use my rules.
I tried setting in
project.extensions.pmd.with {
// HERE
}
when I try getRuleSetConfig() it returns null but with setRuleSetConfig() appears to be setted but not used.
I dont want to create another repo just for configurations like here https://github.com/MovingBlocks/TeraConfig.
Related to: https://discuss.gradle.org/t/how-can-i-read-setup-resources-from-plugin-jar/13274/4
what can I do?
I was able to create a custom plugin that adds a PMD task of the built-in type, using a custom ruleset packaged in the plugin's src/main/resources directory; I'm using kotlin, but you can probably get the idea:
class BuildPlugin: Plugin<Project> {
override fun apply(project: Project) {
val android = project.extensions.getByType(BaseExtension::class.java)
project.pluginManager.apply(PmdPlugin::class.java)
project.extensions.getByType(PmdExtension::class.java).toolVersion = "5.5.2"
project.tasks.register("pmd", Pmd::class.java) {
it.source(android.sourceSets.getByName("main").java.sourceFiles,
android.sourceSets.getByName("test").java.sourceFiles,
android.sourceSets.getByName("androidTest").java.sourceFiles)
it.ruleSetConfig = project.resources.text.fromUri(javaClass.classLoader.getResource("config/pmd.xml"))
}
}
}
It's still a work in progress (for instance, I'm unsure if there are implications to resolving the android extension directly in my apply), but it seems to use my ruleset as expected!