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?
Related
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.
As per https://developer.android.com/studio/write/lint.html#snapshot we can create a Lint warning baseline file.
The problem is that I have multiple flavors, each having their own sourceSets. Some files are used in a single flavor.
When I generate the baseline file, it's always specific to a variant. Which means that it's invalid for the other variants, ie it will miss some existing issues.
I have tried putting the
lintOptions {
baseline file("lint-baseline.xml")
}
in the build and flavor blocks, but it won't generate multiple baselines.
Has anyone managed to generate flavor specific lint baseline file? And if so how?
Thanks!
I was trying the same thing and found a way of doing it.This create diff file for release and debug.You can put your custom logic in getfileName
lintOptions {
baseline file(getFileName())
checkAllWarnings true
warningsAsErrors true
abortOnError true
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().args.toString()
println tskReqStr
if (tskReqStr.contains("Debug")) {
return "debug"
} else {
return "release"
}
}
private String getFileName(String command) {
return getCurrentFlavor() + "-lint-baseline.xml"
}
I couldn't make the above answer exactly work as I got errors when trying to define the method in the build.gradle file.
Using himanshu saluja's answer this is what worked for me.
My root project's gradle file has:
ext.getLintFileName = {
Gradle gradle = getGradle()
String taskReq = gradle.getStartParameter().getTaskRequests().args.toString()
if(taskReq.contains("Debug")) {
return "debug-lint-baseline.xml"
} else {
return "release-lint-baseline.xml"
}
}
And the sub project's gradle file inside the android block uses the value like this:
lintOptions {
baseline file(rootProject.ext.getLintFileName)
checkDependencies true
abortOnError true
absolutePaths false
}
Given that the baseline feature is on LintOptions and this one is AFAIK not capable of being variant aware, this will not work out of the box.
You could file a feature request on https://b.android.com though.
according to my GitHub sample code:
1- add the following function to your app-level build.gradle file:
def getPath() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern
String path
String fileName = "lint-baseline"
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else if (tskReqStr.contains("generate"))
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
else if (tskReqStr.contains("lint"))
pattern = Pattern.compile("lint(\\w+)(Release|Debug)")
if(pattern != null) {
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find()) {
path = matcher.group(1).toLowerCase() + matcher.group(2).toLowerCase()
return "lint-baselines/${path}-${fileName}.xml"
} else {
return "lint-baselines/${fileName}.xml"
}
}
return "lint-baselines/${fileName}.xml"
}
this function creates a specific path for each build variants. you can customize the file name by changing the "fileName" variable.
2- add the following line to lintOption scop of your app-level build.gradle file:
lintOptions {
...
// Use (or create) a baseline file for issues that should not be reported
baseline file("${getPath()}")
...
}
3- run linter for each of build varients.
for example type the following command in the terminal tab of Android studio in the root of project:
gradlew app:lintMyAppRelease
app = your module name
MyAppRelease = your build varient
4- Done
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)
}
My project depends on a NDK. But my NDK build is different for my projectFlavors.
I like to build and pack my dependent NDK with -DFLAVOR1 compile option defined if app's flavor1 is selected for my app. -DFLAVOR2 when flavor2 is selected and etc.
My whole app will not work correctly if app is on flavor1 and incorrectly use a NDK built on -DFLAVOR2, so the correct selection is important.
Now how we can write our build.gradle to solve this special conditional build?
I finally found a hacky approach for my problem, then I though it is better to share it here for everyone and improvement.
Step 1:
You need to provide flag from app and depend it on preBuild. Here is a sample code for doing it.Thanks How to get current flavor in gradle for function. I just modified it a little.
import java.util.regex.Matcher
import java.util.regex.Pattern
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern;
if( tskReqStr.contains( ":app:assemble" ) )
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else if( tskReqStr.contains( ":app:generate" ) )
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("incremental(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher( tskReqStr )
if( matcher.find() )
return matcher.group(1).toLowerCase()
else
{
println "NO MATCH FOUND"
return "";
}
}
task setFlavorFlag() {
def flavorName = getCurrentFlavor();
if (!flavorName.equals("")) {
printf("Setting flag from app...\n")
def f = file("../build/conf.tmp")
if (!f.exists()) f.createNewFile()
f.write("-D${flavorName.toUpperCase()}")
}
}
preBuild.dependsOn setFlavorFlag
dependencies {
compile project(path: ':mylibrary')
}
Step 2:
Access flag from library. I did it in ndk section:
ndk {
...
def f = file("../build/conf.tmp")
if (f.exists()) {
printf("Building library for Flavor:%s\n", f.text)
ndk.CFlags.add(f.text);
f.delete()
}
}
ok. Now your app Flavor is passed with a -D option to compile of library. Here was the approach I found after 2-3 days. I'm open for all improvements for this approach.
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!