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
Related
Answered here.
I have more than one flavor in my app and I want to use the same google-service.json for all of them, so I've thought about set the value of the attribute package_name as a regular expression and replace it using a task in my build.gradle (app module).
My flavors are defined on this way:
android {
productFlavors {
FirstFlavor {
applicationId "com.thisapp.first"
versionCode = 1
versionName "1.0"
}
SecondFlavor {
applicationId "com.myapp.second"
versionCode = 1
versionName "1.0"
}
}
}
My idea was something like:
task runBeforeBuild(type: Exec) {
def google_json = file('./google-services.json')
google_json.getText().replace('${package_name_value}', myPackageName)
}
The problem is I don't know how to access to the PackageName (myPackageName in the code) or if is even possible.
Maybe I have to use another task instead of runBeforeBuild, I'm not very familiar with Gradle.
I've found another way to do it, and wanted to share it.
Please note that this is my first time writing some tasks using gradle, so the code is not optimal at all (and I can't spend more time on it to improve it for now).
Explanation
What I'm doing is pretty simple.
1) Just before the task processFlavorBuildTypeGoogleServices, that is the task from Google Services that will read the google-services.json file, I trigger some code that will update the google-services.json file.
In order to do that :
gradle.taskGraph.beforeTask { Task task ->
if (task.name.startsWith("process") && task.name.endsWith("GoogleServices")) {
}
}
2) Retrieve the current flavor and buildType from the task name (example of a task name: processProdReleaseGoogleServices in the form of process'Flavor''BuildType'GoogleServices)
String currentFlavor = task.name.replace("process", "").replace("GoogleServices", "")
currentFlavor = currentFlavor.toLowerCase()
3) Remove the buildType from the currentFlavor variable. In order to do that, I simply loop through all the buildTypes in my project, and remove them from the currentFlavor variable
android.applicationVariants.all { variant ->
currentFlavor = currentFlavor.replace(variant.buildType.name, "")
}
At this point, the variable currentFlavor has the currentFlavor (for example "prod")
4) Retrieve the package name from the flavors defined in my build.gradle
In my build.gradle, I specify the packageName for each flavor:
productFlavors {
prod {
applicationId 'packageName1'
}
rec {
applicationId 'packageName2'
}
}
And I retrieve it like this:
(The package name is returned with [], so I have to remove them. For example I would retrieve [packageName1])
String currentApplicationId;
android.applicationVariants.all { variant ->
if (variant.flavorName == currentFlavor) {
currentApplicationId = variant.productFlavors.applicationId.toString().replace("[", "").replace("]", "")
}
}
5) Now that I have the package name of the current build, I just have to open the current google-services.json file, and update the package name inside. For that I added a method updateGoogleServicesJsonFile.
Be careful to change the filePath on the second line to point to your location.
def updateGoogleServicesJsonFile(applicationId) {
File file = new File(getProjectDir(), "/google-services.json")
if (!file.exists())
{
project.logger.log(LogLevel.ERROR, "Error updating the google-services.json because the file doesn't exists...")
return
}
List<String> lineList = file.readLines()
for (int i = 0; i < lineList.size(); i++)
{
if (lineList.get(i).trim().startsWith("\"package_name\": \""))
{
String line = lineList.get(i)
line = line.substring(0, line.indexOf(":") + 1)
line += " \"" + applicationId + "\""
lineList.set(i, line)
}
}
file.write(lineList.join("\n"))
}
And there you have it, some code to update the google-services.json file just before the task to read it is executed.
Code
def updateGoogleServicesJsonFile(applicationId) {
File file = new File(getProjectDir(), "/google-services.json")
if (!file.exists())
{
project.logger.log(LogLevel.ERROR, "Error updating the google-services.json because the file doesn't exists...")
return
}
List<String> lineList = file.readLines()
for (int i = 0; i < lineList.size(); i++)
{
if (lineList.get(i).trim().startsWith("\"package_name\": \""))
{
String line = lineList.get(i)
line = line.substring(0, line.indexOf(":") + 1)
line += " \"" + applicationId + "\""
lineList.set(i, line)
}
}
file.write(lineList.join("\n"))
}
gradle.taskGraph.beforeTask { Task task ->
// Before the task processFlavorBuildTypeGoogleServices (such as processProdReleaseGoogleServices), we update the google-services.json
if (task.name.startsWith("process") && task.name.endsWith("GoogleServices")) {
// Getting current flavor name out of the task name
String currentFlavor = task.name.replace("process", "").replace("GoogleServices", "")
currentFlavor = currentFlavor.toLowerCase()
android.applicationVariants.all { variant ->
currentFlavor = currentFlavor.replace(variant.buildType.name, "")
}
// Getting current application id that are defined in the productFlavors
String currentApplicationId;
android.applicationVariants.all { variant ->
if (variant.flavorName == currentFlavor) {
currentApplicationId = variant.productFlavors.applicationId.toString().replace("[", "").replace("]", "")
}
}
updateGoogleServicesJsonFile(currentApplicationId)
}
}
Answer updated
First of all I must explain I'm using Jenkins to compile my application, so the build process is not exactly the same than in Android Studio. In my case Jenkins only build the release version and is not getting the flavors on the same way than the IDE. I'll explain both solutions:
In the build.gradle (Module: app)
Mine
buildscript{
...
}
android {
...
}
afterEvaluate {
android.applicationVariants.all { variant ->
preBuild.doLast {
setGoogleServicesJson(variant)
}
}
// Only for Jenkins
assembleRelease.doFirst {
deleteGoogleServicesJson()
}
}
def setGoogleServicesJson(variant) {
def originalFileName = "google-services.bak"
def newFileName = "google-services.json"
def originalFile = "./$originalFileName"
def newFile = "./$newFileName"
def applicationId = variant.applicationId
def regularExpression = "\\\"package_name\\\" : \\\"(\\w(\\.\\w)?)+\\\""
def packageName = "\\\"package_name\\\" : \\\"$applicationId\\\""
copy {
from (originalFile)
into ("./")
rename (originalFileName, newFileName)
}
ant.replaceregexp(
file: newFile,
match: regularExpression,
replace: packageName,
byLine: true)
}
def deleteGoogleServicesJson() {
file("./google-services.json").delete()
}
apply plugin: 'com.google.gms.google-services'
Jenkins is getting the google-services.json located in the 'Project/app/' folder and it doesn't use the flavor ones, so for each variant and as soon as possible (after the preBuild task) I'm creating a new JSON from my *.bak file, overriding the package_name and letting Gradle continues with the building.
When everything is done and before it release the app (assembleRelease.doFirst) I delete the google-services.json and I keep the *.bak.
In my case I only want to change the package_name value of my JSON, but this solution won't work if I want to change another value as the project_number, the client_id or whatever else depending on the flavor.
Alternative solution (using flavors)
afterEvaluate {
android.applicationVariants.all { variant ->
def fileName = "google-services.json"
def originalFile = "./$fileName"
def flavorName = variant.flavorName
def destinationPath = "."
// If there is no flavor we use the original path
if (!flavorName.empty) {
destinationPath = "$destinationPath/src/$flavorName/"
copy {
from file(originalFile)
into destinationPath
}
}
def regularExpression = "\\\"package_name\\\" : \\\"(\\w(\\.\\w)?)+\\\""
def packageName = "\\\"package_name\\\" : \\\"$variant.applicationId\\\""
ant.replaceregexp(
file: "./$destinationPath/$fileName",
match: regularExpression,
replace: packageName,
byLine: true)
}
}
In this solution I have the google-services.json in the 'Project/app/' folder and I make a copy of it in each flavor folder. Then I override the package_name. In case you are working without flavors, the app will use the original JSON to compile.
You can check if another JSON exists in the flavor folder before override it, in case you have different values for the rest of the values.
Old solution
I've found a solution mixing this and this answers.
This is my build.gradle (Module: app) right now:
afterEvaluate {
android.applicationVariants.all { variant ->
def applicationId = variant.applicationId
ant.replaceregexp(file: './google-services.json', match:'package_name_value', replace: applicationId, byLine: true)
}
}
where package_name_value is the "regular expression" I've defined to be replaced.
The location of the google-services.json is "MyProject/ppp/google-services.json", and I've tested that if you put another googler-services.json inside your flavor folder, it overrides the first one.
*There is (at least) one problem when you have more than one flavor defined at the same time, because this task is always overriding the same file, so the final application id will be the last you have defined.
If you have another way, feel free to post it.
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.
I would like to create dynamic flavors from the directory tree.
It works great!
But Android Studio uses gradle in its tmp file like:
/home/svirch_n/.IntelliJIdea14/system/compile-server
and my script doesn't work anymore because it uses relative paths like this:
Closure getFlavors = { rootDir, basePackage ->
def result = [:]
new File("$rootDir").eachDir() { dir ->
def name = dir.getName()
if ("$name" != "main")
result.put("$name", "$basePackage.$name")
}
return result
}
// This is an ugly closure.
// If I can get rid of this, my problem will be solved
Closure getSrcPath = {
if (System.getProperty("user.dir").split("/").last() == "app") {
return "src"
} else {
return "app/src"
}
}
android {
...
def myFlavors = getFlavors(getSrcPath(), "com.example.app")
productFlavors {
myFlavors.each { flavorName, flavorPackage ->
"$flavorName" {
applicationId "$flavorPackage"
}
}
}
}
Do you have an idea how to solve this?
Thanks in advance for your help
P.S: I want dynamic flavors cause my git project has public and private repositories and not everyone can have all the flavors but I want them to compile anyway.
Assuming I am in the subproject 'app', I can use:
project(":app").getProjectDir().getPath()
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?
I have two flavors of my project:
flavor1 -> packagename: com.example.flavor1
flavor2 -> packagename: com.example.flavor2
Now I want to build a buildvariant of flavor1 and flavor2.
The only difference of the buildvariant is another packagename.
My project uses MapFragments and has just one Manifest - so I put the the permission name of MAPS_RECEIVE in my string resource files of the respective flavors.
The question is: how can I replace a string resource of a buildvariant?
I tried the following approach (described in this post):
buildTypes{
flavor1Rev{
packageName 'com.example.rev.flavor1'
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: ['package_permission' : 'com.example.rev.flavor1.permission.MAPS_RECEIVE'])
}
}
But using this I got this error:
Could not find method filter() for arguments
[{tokens={package_permission=com.example.rev.flavor1.permission.MAPS_RECEIVE}},
BuildTypeDsl_D ecorated{name=ReplaceTokens, debuggable=false,
jniDebugBuild=false, renderscript DebugBuild=false,
renderscriptOptimLevel=3, packageNameSuffix=null, versionNameS
uffix=null, runProguard=false, zipAlign=true, signingConfig=null}] on
BuildTypeD sl_Decorated{name=buderusFinal, debuggable=false,
jniDebugBuild=false, renderscr iptDebugBuild=false,
renderscriptOptimLevel=3, packageNameSuffix=null, versionNa
meSuffix=null, runProguard=false, zipAlign=true, signingConfig=null}.
Do I have to define an own task for the filter method?
EDIT [2013_07_09]:
String in src/flavor1/res:
<string name="package_permission">package_permission</string>
Code in build.gradle to replace the string:
buildTypes{
flavor1Rev{
copy{
from('src/res/'){
include '**/*.xml'
filter{String line -> line.replaceAll(package_permission, 'com.example.rev.flavor1.permission.MAPS_RECEIVE')}
}
into '$buildDir/res'
}
}
}
I solved the problem on my own, so here is the solution "step by step" - perhaps it will help some other newbies to gradle :)
Copy Task in General:
copy{
from("pathToMyFolder"){
include "my.file"
}
// you have to use a new path for youre modified file
into("pathToFolderWhereToCopyMyNewFile")
}
Replace a line in General:
copy {
...
filter{
String line -> line.replaceAll("<complete line of regular expression>",
"<complete line of modified expression>")
}
}
I think the biggest problem was to get the right paths, because I had to make this dynamically (this link was very helpful for me). I solved my problem by replacing the special lines in the manifest and not in the String-file.
The following example shows how to replace the "meta-data"-tag in the manifest to use youre google-maps-api-key (in my case there are different flavors that use different keys ):
android.applicationVariants.each{ variant ->
variant.processManifest.doLast{
copy{
from("${buildDir}/manifests"){
include "${variant.dirName}/AndroidManifest.xml"
}
into("${buildDir}/manifests/$variant.name")
// define a variable for your key:
def gmaps_key = "<your-key>"
filter{
String line -> line.replaceAll("<meta-data android:name=\"com.google.android.maps.v2.API_KEY\" android:value=\"\"/>",
"<meta-data android:name=\"com.google.android.maps.v2.API_KEY\" android:value=\"" + gmaps_key + "\"/>")
}
// set the path to the modified Manifest:
variant.processResources.manifestFile = file("${buildDir}/manifests/${variant.name}/${variant.dirName}/AndroidManifest.xml")
}
}
}
I use almost exactly the approach you wanted to. The replaceInManfest is also generic and can be used for other placeholders as well. The getGMapsKey() method just returns the appropriate key based on the buildType.
applicationVariants.all { variant ->
def flavor = variant.productFlavors.get(0)
def buildType = variant.buildType
variant.processManifest.doLast {
replaceInManifest(variant,
'GMAPS_KEY',
getGMapsKey(buildType))
}
}
def replaceInManifest(variant, fromString, toString) {
def flavor = variant.productFlavors.get(0)
def buildtype = variant.buildType
def manifestFile = "$buildDir/manifests/${flavor.name}/${buildtype.name}/AndroidManifest.xml"
def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
new File(manifestFile).write(updatedContent, 'UTF-8')
}
I have it up on a gist too if you want to see if it evolves later.
I found this to be a more elegant and generalizable approach than the others (although the token replacement just working would have been nicer).
The answers are quite outdated, there are better ways now to archive it. You can use the command in your build.gradle:
manifestPlaceholders = [
myPlaceholder: "placeholder",
]
and in your manifest:
android:someManifestAttribute="${myPlaceholder}"
more information can be found here:
https://developer.android.com/studio/build/manifest-merge.html
In the current Android Gradle DSL, the ApplicationVariant class has changed and Saad's approach has to be rewritten e.g. as follows:
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.processManifest.doLast {
replaceInManifest(output,
'GMAPS_KEY',
getGmapsKey(buildType))
}
}
}
def replaceInManifest(output, fromString, toString) {
def updatedContent = output.processManifest.manifestOutputFile.getText('UTF-8')
.replaceAll(fromString, toString)
output.processManifest.manifestOutputFile.write(updatedContent, 'UTF-8')
}
The new DSL also offers a cleaner approach to get directly to the manifest file.