How to get build variant output directory - android

I need to run some tasks that occur after an Android project's assemble* task finishes. In particular, these tasks need to know what was the output directory for all the compiled classes for a particular build variant. How do I retrieve the output directory for an assembleFlavor1Debug task?
My current workaround is something like this (although this workaround presents problems of its own, like not being able to find the assemble tasks even though it's been placed after the android configuration block):
android.buildTypes.all { theBuildType ->
android.productFlavors.all { theFlavor ->
String capitalizedType = ... //Type name with first letter capitalized
String capitalizedFlavor = ... //Flavor name with first letter capitalized
...
project.tasks["assemble${capitalizedType}${capitalizedFlavor}"].configure {
doLast {
project.ext.variantOutput = "build/intermediates/classes/${theFlavor.name}/${theBuildType.name}"
}
}
}
}
EDIT #1: I was able to fix my workaround. The major issue was that the Android assemble* tasks (assembleProdDebug, assembleProdRelease, etc.) were not yet created on the project, even though configuration was occurring after the Android configuration block. I was able to get the additional configuration on the assemble* tasks done by enclosing the entire code snippet above into a gradle.taskGraph.whenReady {...} block, but this did mean I lose out on the ability to continue configuring the dependency graph. Fortunately, not being to configure dependencies in my particular case was not a major loss; all I needed was the ability to record the last assembled build type and product flavor.
I'd also like to note that this behavior is with version 1.0.0 of the Android Gradle plugin. Although I have not checked, the absence of these Android tasks might not occur on newer versions of this plugin.
EDIT #2: So I've also tried version 1.3.0 of the Android Gradle plugin. I'd also like to note that this is the LIBRARY Android plugin, and not the application plugin (I suspect these missing assemble* tasks are not generated during project configuration for the application plugin as well, however).

You might want to try instead of wrapping the entire thing with gradle.taskGraph.whenReady try using afterEvaluate closure. The tasks should exist after the project is evaluated.
This means your closure would run at the end of the configuration phase and before the execution phase. At this time all tasks would have to be registered.
afterEvaluate { project ->
// do work on `Project` object just like normal
project.android.buildTypes.all { theBuildType ->
...
}
}
ref: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#afterEvaluate(groovy.lang.Closure)

Related

Gradle/Android: includeBuild sets build variant to null

Had no luck posting through the gradle community, so I thought I would reach out here.
In my root settings.gradle file, I use includeBuild to include the projects of interest. This works and I see that the builds have been included. Each one of these android libraries may contain a dependency on another module in a different project in the monorepo. All of these libraries produce artifacts that are published, so normally I would just target the recently released artifact. I want to use includeBuild with dependency substitution when developing locally. This way, if I make a change in a dependency I can make changes in all dependents immediately without having to release an artifact first.
Let me show you an example of one that I have working:
I have directory called base-implementation, this directory contains two gradle projects: base-api and base-ui-api. Each of these projects contains one singular android library module api and ui-api respectively. To further illustrate, one drilldown from a package structure would go base-implementation → base-api → api where api may be defined as an artifact dependency in other android libraries in this monorepo. For example ui-api defines a dependency on api.
Given this structure, in my base-ui-api project, I define an includeBuild on base-api and substitute out the dependency with the android library api. That looks like this:
def apiDependency = "com.myapp.example:api"
if(isIntegrationBuild.toBoolean()) {
includeBuild('../base-api') {
dependencySubstitution {
substitute module(apiDependency) using project(':api')
}
}
}
The isIntegrationBuild is just a gradle property I have set up as a development flag to use dependency substitution. I can run the gradle task provided by android called androidDependencies which will list all resolved dependencies for a given build. I can verify here that the dependency is indeed subbed out in ui-api by confirming this line “:api (variant: debug)”
It is important to note at this point, every single android library in this mono repo has only two build variants of debug and release there are no inconsistencies here with build variants.
The problem I am running into is this. I have a library that defines a dependency on another library which then defines a dependency on api. When going to build this library, a random nullPointerException is thrown without any message. What I have noticed however, is this; When I run androidDependencies task on the library that it is dependent on (the library that contains a dependency on api), the subbed out api dependency comes back with this “:api (variant: null)” and I cannot for the life of me figure out why. All includeBuilds and dependency substitution definitions are exactly the same. yet one resolves with variant debug and the other does not. They have next to identical build.gradle files at both the project and module level as well. There is nothing special happening to explicitly define a default variant implementation and all variants match across all libraries.
If what I explained above is unclear, let me drive home the issue with the project structure visual that doesn’t work. There is sensitive information in these library names so for sake of censoring, I will replace their names with A and B. Where A depends on B and B depends on api. A and B also share the same root dir but this root dir is just a container. Not a gradle project.
A depends on B so in the project level settings.gradle of A I define an includeBuild with a dependency substitution for B. This looks like the following:
def BDependency = "com.example.b:b"
if (isIntegrationBuild.toBoolean()) {
includeBuild('../project-b') {
dependencySubstitution {
substitute module(BDependency) using project(':b')
}
}
}
This block runs and works, I can see and confirm that the artifact is swapped out with the included build.
Now, B defines a dependency on api so its project level settings.gradle looks almost identical to the first case I stated where ui-api depends on api the only difference is relative pathing for includeBuild. This looks like the following:
def apiDependency = "com.myapp.example:api"
if(isIntegrationBuild.toBoolean()) {
includeBuild('../../base-api') {
dependencySubstitution {
substitute module(apiDependency) using project(':api')
}
}
}
Why is that when I build ui-api it pulls in api of variant debug, but when I build B it pulls in api of variant null? Is there a something I am missing about nested includedBuilds?

How to Run Custom Gradle Task In Gradle Plugin During Android Project Configuration Phase?

Okay, So I have been pulling my hair out trying to get this to work properly and I just want to write my tests and push this to maven already so my standup updates are not the same day after day.
Problem Statement: I have multiple android projects that all require the same pre-configuration. This includes declaring maven repositories, setting versionCodes and versionNames, and other misc configuration items. The same blocks are copy and pasted in every project or placed in a local common.gradle file and then applied and called at the project level build.gradle configuration stage.
My Solution: Extract all of this common logic into a standalone gradle plugin and let each projects build.gradle file apply this plugin to receive these configuration events.
I created a standalone plugin, did an override on plugin and it looks something like this:
class CommonManager: Plugin<Project> {
override fun apply(target: Project) {
println("APPLY FUNCTION HAS BEEN ENTERED")
target.tasks.create("myPluginTask", ReleaseManager::class.java)
}
The ReleaseManager class is a class that extends DefaultTask and creates a task to run. This is what is recommended in the gradle docs and it makes sense from a testability / reusability standpoint and that class looks something like this:
abstract class ReleaseManager: DefaultTask() {
#get:Input
abstract val versionHistoryPath: Property<String>
#TaskAction
fun configureVersionsAndReleaseMeta() { // do work... }
According to the docs I have read and other example projects I have thumbed through, this is set up correctly. I built the plugin using java-library instead of java just to have a local .jar file to point to in my android projects for quick testing. An example of one of these build.gradle files looks like the following:
buildscript {
repositories {
google()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
// misc dependencies ...
classpath files("libs/MyCustomPlugin-1.0-SNAPSHOT.jar")
}
}
apply plugin: "com.myplugin.common"
myPluginTask {
versionHistoryPath.set("sdk/version_history.txt")
}
subprojects {
apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.artifactory'
group = 'org.jfrog.test.gradle.publish'
}
I create a dependency on the .jar and apply the plugin, then configure the task with the property it needs to run. However, the task never runs during the configuration phase when the log outputs > Configure project I see my print statement from the apply function in my plugin log but no output or configuration happens in the task class that I defined. If I click the play arrow next to the myPluginTaks{} configuration definition, it does in fact run fine. At this point, I know I'm pointing to the .jar correctly, I can access the task that I create in my plugin class, and I can set its property respectively. I'm newer to doing this kind of custom work with gradle and want to understand what I am doing wrong. Let me outline my thought process and debugging steps I have tried.
My "Bug" Statement: I want the task "myPluginTask" to execute after I apply my plugin in my build.gradle script and I have configured the task with the proper input. It is currently not executing the task.
Thoughts and notes:
I used create over register in the plugin apply function when creating my task. My understanding is that by using create, I am saying "hey I want to create a task, and I want the build to know about it immediately" rather than "Hey here's a task definition, but lets lazy load it once it is actually invoked on the implementation end"
I moved the implementation of ReleaseManager into the plugin class as individual functions and called these functions inside the task creation closure. That works. To my understanding this is because anything defined in there is run during a builds configuration phase. I can defer this to the execution phase with a doFirst or doLast closure if I wanted to.
My hunch is, I've created a task and sure the build now knows about it immediately, but it has no plans on executing the task until at least the execution phase and needs a trigger. Whether that trigger is a direct invocation of the task or the task depends on a task that is going to run in the execution phase.
Any help would be greatly appreciated. Happy to provide any additional information if needed.
A user below provided an insightful link that helped me arrive at a solution. I also have a much better understanding of Gradle at this point and can better articulate my problem.
TLDR; My hunch was correct. I had successfully created a task, but did not provide a way to trigger such task. You do this by depending on a task provided by the target project.
To reiterate, my plugin class looked like the following:
class CommonManager: Plugin<Project> {
override fun apply(target: Project) {
println("APPLY FUNCTION HAS BEEN ENTERED")
target.tasks.create("myPluginTask", ReleaseManager::class.java)
}
My module level build.gradle file applying the plugin had the following:
apply plugin: "com.myplugin.common"
apply plugin: "com.android.library"
The apply function would trigger in my plugin and the task would be created. It would NOT run however. To accomplish this, I needed to depend on another task in my target build as a trigger. That effectively looked like the following:
class CommonManager: Plugin<Project> {
override fun apply(target: Project) {
println("APPLY FUNCTION HAS BEEN ENTERED")
target.tasks.create("myPluginTask", ReleaseManager::class.java) { task ->
// Have target project create a dependency between its task and mine
target.tasks.findByName("preBuild").dependsOn(task)
}
"preBuild" is a task provided by the android plugin. In my case com.android.library this lead to issue two. When building my project, the plugin would throw an error stating that "preBuild" is not a defined task. At first I thought I just couldn't reference tasks provided by the android library but then it hit me. If you look at my above code block for my modules build.gradle file, you will notice I define my common plugin first. This means at the time of my plugins apply block firing, the plugin was unaware of this task because the android plugin had yet to be applied.
You could define the android plugin first and document that order of definition is important but, that is not a pragmatic solution. What you can do however, is implement the afterEvaluate closure on the target project like so:
class CommonManager: Plugin<Project> {
override fun apply(target: Project) {
println("APPLY FUNCTION HAS BEEN ENTERED")
target.tasks.create("myPluginTask", ReleaseManager::class.java) { task ->
target.afterEvaluate {
// Have target project create a dependency between its task and mine
target.tasks.findByName("preBuild").dependsOn(task)
}
}
This will wait for evaluation of the modules build.gradle file to finish. Therefore, once my plugins apply block is triggered, my plugin will know about the "preBuild" task because the android plugin was applied during project evaluation.
Bonus tip: I am using create to make my task in the plugin. If you are using register, you do not need the afterEvaluate closure. If you define one, the plugin will throw a runtime error if running gradle 7.0 or higher. The gradle community considers that a redundancy.
Am not quite sure that this will help you or not .
did you try to
myPluginTask.configure {
mustRunAfter TASKNAME
}
I was looking around and this might be helpful as well .

Real time rules with ktlint

I am trying to use ktlint on my android project.
Currently I am using these gradle plugins:
org.jlleitschuh.gradle.ktlint
org.jlleitschuh.gradle.ktlint-idea
I can check the code using a manual gradle task, where the warnings will be displayed by the terminal.
Is it possible to achive something like eslint in javascript? Example:
Perhaps the following advice does not directly solve your requirement. But, what about making the auto format process part of the build process? I mean, your code will be auto formatted and then checked according to the ktlint rules and you only will be worried about to run the build command (i.e. ./gradlew clean build) before committing your code. This is how I have done that:
First of all, I am using Gradle 6.3 and I have implemented the version 9.1.2 of org.jlleitschuh.gradle.ktlint plugin.
Once the plugin has been imported to the project, some tasks are added as the plugin's documentation describes:
This plugin adds two maintasks to every source set: ktlint[source set name]SourceSetCheck and ktlint[source set name]SourceSetFormat
Usually the added tasks are:
ktlintMainSourceSetCheck and ktlintMainSourceSetFormat for src/kotlin/main source set
ktlintTestSourceSetCheck and ktlintTestSourceSetFormat for src/kotlin/test source set
In order to make ktlintMainSourceSetFormat and ktlintTestSourceSetFormat part of the build process, you should add the following gradle action inside subprojects { } section:
For kotlin-based gradle scripts (build.gradle.kts):
afterEvaluate {
tasks["ktlintMainSourceSetCheck"].dependsOn(tasks["ktlintMainSourceSetFormat"])
tasks["ktlintTestSourceSetCheck"].dependsOn(tasks["ktlintTestSourceSetFormat"])
}
For groovy-based gradle scripts (build.gradle):
afterEvaluate {
tasks.getByName('ktlintMainSourceSetCheck').dependsOn(tasks.getByName('ktlintMainSourceSetFormat'))
tasks.getByName('ktlintTestSourceSetCheck').dependsOn(tasks.getByName('ktlintTestSourceSetFormat'))
}
This makes the "format" tasks mentioned above run before the "check" tasks after the build process of the current sub project has succeeded. These "check" tasks are the ones that display formatting errors in the console whenever the build is run, but making them children of the "format" tasks will force the build process to format the code before checking it so that no formatting errors will show up in the console.
Additionally, you can also apply code checks and formats to build script files (build.gradle(.kts)) by adding the following code inside allprojects { } section:
afterEvaluate {
tasks["ktlintKotlinScriptCheck"].dependsOn(tasks["ktlintKotlinScriptFormat"])
}
For Git support:
If you want to add the auto formatting feature at pre-commit stage, you can use these tasks: addKtlintCheckGitPreCommitHook and addKtlintFormatGitPreCommitHook as explained here
For IntelliJ IDEA support:
If you want to apply the ktlint rules to your current project, just execute the following gradle command once you have imported the org.jlleitschuh.gradle.ktlint-idea plugin: ./gradlew ktlintApplyToIdea and this will replace the codeStyles file inside .idea/ folder as described here
Finally, here is an example of a build.gradle.kts where all the mentioned above is applied.
You will not get an error message like in eslint but using following method you won't need to deal with ktlint styling issues manually. Is not it much better than manually fixing things up?
Install ktlint commandline tools as described here.
Set IntelliJ IDEA's code styles to align with ktlint's code styles as described here.
So when you make a commit using IntelliJ IDEA's commit window (CTRL + K or Command + K) and you have reformat code before commit enabled then your commit will be made with correct code styles.
Try to install plugin Ktlint ​(unofficial)​ and follow setup instructions.
Go to Preferences > Tools > ktlint and make sure Enabled ktlint checkbox is selected.
In case of android project also select Android mode check box.

How to skip a specific gradle task

I want to skip one of the tasks of Android gradle build, so I tried something like
project.tasks.getByName("transformNativeLibsWithStripDebugSymbolForDebug").onlyIf {
println("Skipping")
return false
}
However, the task is not found, even though I can see it in the tasks that are executed...
Any idea how can I get this task? I guess it should be dependant of one of the tasks in the project.
Context - I have a shared library that I want to user the unstripped version of it, however gradle strips it anyway...
EDIT
After some digging, it seems that those tasks are added as part of the binary plugin (the apply plugin: 'com.android.library' line at the top of the gradle file).
This transform task is added using the transform API, which doesn't seems to have a way to unregister/modify existing transform...
It's dynamically generated task. Try to add next:
android {...}
afterEvaluate { project ->
project.tasks.transformNativeLibsWithStripDebugSymbolForDebug {
onlyIf {
println 'Skipping...'
return false
}
}
}
dependencies {...}
In Gradle Console you should see:
Skipping...
:app:transformNativeLibsWithStripDebugSymbolForDebug SKIPPED
Do not forget that transformNativeLibsWithStripDebugSymbolForDebug task is only executed, when you use assembleDebug task (or Shift+F10 combination in Android Studio).

Crashlytics NDK symbols and Gradle tasks

I have a question that mostly relates to gradle.
I'm using Crashlytics to report NDK crashes in my Android app.
I have a task in build.gradle that calls ndk-build and compiles the cpp files into an .so file.
At the end of this task I want to call a task that uploads generated symbols mapping to Crashlytics.
After installing the Fabric plugin in Android Studio, I saw there are some new tasks that were added to the Gradle tab. One of them is
crashlyticsUploadSymbols[buildType][flavour] where buildType and flavour indicate which buildtype and flavour is currently selected.
This task does seem to upload a symbols file.
My question is,
Is it possible to call this task from within build.gradle?
Currently I use a manual call in Android Studio's terminal tab in the form of:
./gradlew crashlyticsUploadSymbols[buildType][flavour]
Is it possible to call this task somehow from within build.gradle?
To call this task I use finalizedBy at the end of the buildNdk task, so once buildNdk has finished, the upload task will execute.
Also very important, how can I get the current buildType and flavour so I am able to add it to the crashlyticsUploadSymbols call?
Thank you!
Mike from Crashlytics and Fabric here.
This was also answered on the Twitter Community forum's, but sharing the same answer here.
Option 1:
If you only want or need to upload symbols for your release builds, then you can set crashlticsUploadSymbolsRelease as the finalizedBy task for your ndk-build task.
Option 2:
If you have multiple variant-based tasks, you can do something like:
android.applicationVariants.all { variant ->
def variantName = variant.name.capitalize()
def task = project.task ("ndkBuild${variantName}")
task.finalizedBy project.("crashlyticsUploadSymbols${variantName}")
}
The following did the job for me:
android {
...
afterEvaluate {
assembleDebug.finalizedBy(crashlyticsUploadSymbolsDebug)
assembleRelease.finalizedBy(crashlyticsUploadSymbolsRelease)
}
}

Categories

Resources