Custom gradle plugin with pmd, checkstyle and findbugs configured - android

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!

Related

Extension of type 'LibraryExtension' does not exist

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")

How to register generated resources in Android Gradle task?

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.

Mockk Missing calls inside every { ... } block

I'm stuck trying to mock some stuff with mockk:
I have the following setup on gradle
root:
|-- App (just a sample app for the SDK)
|-- SDK (SDK we develop) << apply plugin: 'com.android.library'
|-- SDKimpl.kt
|-- Foo (wrapper around a .jar library) << apply plugin: 'com.android.library'
|-- Foo.kt
So I'm writing an androidTest for the SDK and trying to mock Foo.kt.
There's nothing unusual about Foo class, just direct class Foo(private val someParams) {
So using androidTestImplementation "io.mockk:mockk-android:1.8.13" the mock goes:
val mock: Foo = mockk()
// val mock: Foo = mockkClass(Foo::class) // also tried this
every { mock.getData() } returns listOf("1", "2", "3")
I'm always getting the following crash:
io.mockk.MockKException: Missing calls inside every { ... } block.
at io.mockk.impl.recording.states.StubbingState.checkMissingCalls(StubbingState.kt:14)
at io.mockk.impl.recording.states.StubbingState.recordingDone(StubbingState.kt:8)
at io.mockk.impl.recording.CommonCallRecorder.done(CommonCallRecorder.kt:42)
Also tried just to gather information:
running inside JVM test folder. It gets mocked without issues, but I can't run my test as JVM
running androidTest inside Foo module. Got the same crash
using mockkClass(Foo::class). Got some crash
using annotation #MockK and MockKAnnotations.init(this). Got some crash.
added Log.d before every { line and inside getData() method and it seems the actual real method from the class is getting called during the mock setup. That seems super weird to me.
Any idea what's going wrong here?
edit:
as requested, full code. I'm current working on an isolated project to try to isolate the error, so Foo is just:
class Foo {
fun getData(): String {
Log.d(TAG, "invoked foo.getData()")
return "trolololo"
}
}
and then I have FooTest in androidTest:
class FooTest {
#Test
fun mock_foo() {
val foo = mockk<Foo>()
every { foo.getData() } returns "zero"
assertEquals("zero", foo.getData())
}
}
It seems to be a Mockk opened issue: https://github.com/mockk/mockk/issues/182
2 possible quick fixes (pick one):
Run the Instrumented Tests in an emulator >= Android-P
Set Foo class as open (and the method(s) you want to mock too)
Try to check the official guide and see what is missing.
In my case, I tried to mock an extension in Kotlin but missed the mockkStatic
fun Date.asMyTime() : DateTime = DateTime(this, DateTimeZone.getDefault())
mockkStatic("packageName.FileNameKt") // This is what I was missing
every {
DateTime().asMyTime()
} returns mock(DateTime::class.java)
In my case I forgot to spyk the class I was applying every {...} to. 😳
val presenter = spyk(MyPresenter())
every { view.myFun(any()) } returns Unit
In my case, I've missed
#Before
fun setUp() {
MockKAnnotations.init(this)
}
In my case I tried to mock using mock() function instead mockk() (double k)
Make sure the object is really a mock, not the real object.
For instance:
- Sdk sdk = Sdk()
+ Sdk sdk = mockk()
every { sdk.crypto } returns mockk()
My problem was that I used a java class without getters
public class KeyStorePasswordPair {
public KeyStore keyStore;
public String keyPassword;
public KeyStorePasswordPair(KeyStore keyStore, String keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
}
}
I needed to add getters for the variables to make mockking work:
public class KeyStorePasswordPair {
public KeyStore getKeyStore() {
return keyStore;
}
public String getKeyPassword() {
return keyPassword;
}
private KeyStore keyStore;
private String keyPassword;
public KeyStorePasswordPair(KeyStore keyStore, String keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
}
}
Mockk Missing calls inside every { ... } block
may also be thrown when you have an every block defined on an object that is not a mockk, for example:
You define stub behavior like this
every { foo.getData() } returns DATA
and then try to:
every { DATA.getVersion() } returns VERSION
Where MY_THING and VERSION objects are declared (and instantiated) in the test class.
The error message is not very informative and a bit misleading in that case.
try like this
`when`(mock.getData()).thenReturn(listOf("1", "2", "3"))

Gradle: Can't pass argument to class in plugin

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)
}

Remove regex string from project using Gradle

I would like to remove all my logs commands from my java (android) project on Release build using Gradle.
I'm using regex (which is correct):
'/Logger.log[^,]+[^L]+Logger.DEBUG[^;];/g'
and the following gradle script:
task dropJavaDebugLogs << {
FileTree javaFiles = fileTree("src/main/java") {
include "**/*.java"
}
String regex = "Logger.log[^,]+[^L]+Logger.DEBUG[^;];";
javaFiles.each { File javaFile ->
content = content.removeAll(regex);
}
}
Can you tell me what i doing wrong?

Categories

Resources