As part of my build script, I would like to run some Java code before the APK is packaged.
When I try to build my project, I get the error:
Error: Could not find or load main class net.bgreco.myapp.build.BuildScript
BuildScript.java is located in app/src/main/java/net/bgreco/myapp/build/.
The BuildScript.class does get compiled successfully before I receive the error message above, and I can run it from the command line with no errors.
app/build.gradle :
task runBuildScript(type: JavaExec) {
main = 'net.bgreco.myapp.build.BuildScript'
}
tasks.whenTaskAdded { theTask ->
if (theTask.name.contains('package')) {
theTask.dependsOn 'runBuildScript'
}
}
With some help from this answer, I got it working by modifying the classpath as below. I also needed to reference some of the Android libraries so I included the path to android.jar.
app/build.gradle :
task runBuildScript(type: JavaExec) {
String flavor, buildType
(flavor, buildType) = getCurrentFlavorAndBuildType()
classpath files("build/intermediates/classes/" + flavor + "/" + buildType, "${android.getSdkDirectory().getAbsolutePath()}\\platforms\\${android.compileSdkVersion}\\android.jar")
main = 'net.bgreco.myapp.build.BuildScript'
args = [flavor] // Not needed unless you want to know the current flavor from within the BuildScript
}
tasks.whenTaskAdded { theTask ->
if (theTask.name.contains('package')) {
theTask.dependsOn 'runBuildScript'
}
}
def getCurrentFlavorAndBuildType() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern;
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find())
return [matcher.group(1).toLowerCase(), matcher.group(2).toLowerCase()]
else {
println "NO MATCH FOUND"
return ["", ""];
}
}
Related
My Question is very direct and easy to understand.
Question
In Gradle, is there any way I can get the current build type at runtime. For example, when running an assembleDebug task, can tasks within the build.gradle file make decisions based on the fact that this task is related to the debug build variant?
Sample Code
apply plugin: 'com.android.library'
ext.buildInProgress = ""
buildscript {
repositories {
maven {
url = url_here
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
configurations {
//get current build in progress here e.g buildInProgress = this.getBuildType()
}
android {
//Android build settings here
}
buildTypes {
release {
//release type details here
}
debug {
//debug type details here
}
anotherBuildType{
//another build type details here
}
}
}
dependencies {
//dependency list here
}
repositories{
maven(url=url2_here)
}
task myTask{
if(buildInProgress=='release'){
//do something this way
}
else if(buildInProgress=='debug'){
//do something this way
}
else if(buildInProgress=='anotherBuildType'){
//do it another way
}
}
In Summary
Is there a way for me to get exactly the build type in progress within myTask{}?
You can get the exact build type by parsing your applicationVariants:
applicationVariants.all { variant ->
buildType = variant.buildType.name // sets the current build type
}
A implementation could look like the following:
def buildType // Your variable
android {
applicationVariants.all { variant ->
buildType = variant.buildType.name // Sets the current build type
}
}
task myTask{
// Compare buildType here
}
Also you can check this and this similar answers.
Update
This answer by this question helped the questioner to settle the problem.
This worked for me
applicationVariants.all { variant ->
def variantType = variant.buildType.name
println "Variant type: $variantType"
if (variantType == "debug") {
// do stuff
}
}
You should getBuildConfigFields().get("MY_BUILD_TYPE").getValue())
https://stackoverflow.com/a/59994937/5279996
GL
If you want to suffix the buildtype name to the versionname (like me) just add this line to the version name:
debug {
versionNameSuffix "-debug"
}
This way you can identify the build type in the build name. And it works without declaring anything else.
Correct way for getting the current buildType being used during build in Kotlin programming language for android platform (logic is the same for Java)
project.afterEvaluate {
this.android().variants().all {
this.assembleProvider.configure {
this.doLast{
val variant = this#all
variant.outputs
.map
.forEach{
//do something with current buildType, or build flavor or whatever
println(variant.flavorName)
println(variant.buildType)
}
}
}
}
}
I'm getting build type in this way:
BuildConfig.BUILD_TYPE
If you need to check what is the current build type, create an enum class in your utils package and use it in your if statement:
enum class Environment(val value: String) {
RELEASE("release"),
LOCAL("local"),
STAGING("staging"),
DEBUG("debug")
}
Your if/when statement:
if (BuildConfig.BUILD_TYPE == Environment.RELEASE.value) {
//TODO
} else if(...)
or through when:
when(BuildConfig.BUILD_TYPE) {
Environment.RELEASE.value -> { //TODO }
Environment.LOCAL.value -> { // TODO }
// etc.
}
I checked other answers, nothing works.
What's below will help.
In your build.gradle (:app):
tasks.all { Task task ->
if (task.name == "preDebugBuild") {
doFirst {
//for debug build
}
} else if (task.name == "preReleaseBuild") {
doFirst {
//for release build
}
}
}
dependencies {
...
}
Be aware, the code that you put inside will not be executed when you change the build variant, but when you build app.
Try like this in your gradle : It works fine for me
//get current build all params as string
def buildParams = getGradle().getStartParameter().toString().toLowerCase();
applicationVariants.all { variant ->
variant.outputs.all {
def typename = variant.buildType.name.toLowerCase();
//and check build state in all variants
if(buildParams.contains(typename)) {
// finally, you get what you want.
}
}
}
You can get the exact build type by parsing your applicationVariants:
applicationVariants.all { variant ->
buildType = variant.buildType.name // sets the current build type
}
Here's the approach I used to detect the runtime build type without declaring any variables at build time.
def isCurrentBuildType(buildType) {
return gradle.getStartParameter().taskNames.find({ it.endsWith(buildType) }) != null
}
print(isCurrentBuildType("Release")) //prints true if the current build type is release.
Note that the first letter should be capital (e.g. assembleRelease, bundleRelease)
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.
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
Is there a way we could get the selected build variant on the Gradle file when we compile?
I would like to use inside the dependencies task the following code:
buildVariant = awesome code to get the buildVariant selected flag
if (buildVariant == A)
compile project(':moduleA')
else
compile project(':moduleB')
Groovy supports java code so you can use something like this:
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern;
if( tskReqStr.contains( "assemble" ) )
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher( tskReqStr )
if( matcher.find() )
return matcher.group(1).toLowerCase()
else
{
println "NO MATCH FOUND"
return "";
}
}
source: How to get current flavor in gradle
Click on Build Variant which is at the left bottom side of Android Studio
It has a list of all build variants available
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.