Android Studio / Gradle: run external tool before compilation - android

I have an Android Studio project which consists of several modules.
Some of these modules need to run an external pre-processing tool before they're compiled. Plus, I also need to run another tool project-wide every time ANY module in the project is compiled
Unfortunately I'm new to Gradle and I'm being a bit overwhelmed by it. I've succesfully included a new Gradle script in my project by editing settings.gradle and I've written in it some Gradle tasks that run the tools I want to run, and that is fine.
But the problem is I don't understand how to hook them up so they get executed at the right moment.
Basically, I want to know how to edit my build scripts in order to:
A) Always run a certain gradle task before any module in the project is built. (If possible, I'd want this to run only once, in the sense that if I rebuild the entire project, which is composed of 20+ modules, I don't want it to run 20 times, just once. This is secondary tho, the main thing is that it needs to run every time any module in the project is built)
B) Always run a certain gradle task before a certain module is built. In other words: how do I edit the build script of a single, specific module in order to run a certain task before compilation? Note that in this case the invoked task needs to be able to know which gradle project invoked it, ie: which module is being compiled.
Hope the questions are clear, I'll clarify if necessary (as I said, I'm new to Gradle, hope I didn't mess up the terminology too much)

Answer to A is task dependency, I'm assuming when you say modules you mean subprojects, the ones you included in your settings.gradle. If all these sub projects requires this task to run first, I would define the subprojects:
subprojects {
apply plugin: 'java'
task precompiletask() {
println "Executing pre-compile task"
}
compileJava.dependsOn precompiletask
}
The second part of A you might get for free if you setup your task correctly and put inputs/outputs. This is what gradle checks if it needs to rerun the task again or not. If nothing change in inputs/outputs then it won't run the precompiletask and would skip it.
For part B, what I would do with this is find a common attributes between these projects and configure them:
configure(someProjects()) {
// do whatever you want here to those projects
// for example, set up pre compile task like the one above
}
def someProjects() {
ext.someProjects = [] as Set
ext.someProjects.addAll subprojects.findAll { Project aProject ->
// filter here what's common with those projects
// for example, all projects that have yml file
}
logger.debug("Some projects [{}]", ext.someProjects)
ext.someProjects
}
Hope this helps, have fun :)

Related

How can I view the CLI command executed by a Gradle task in Android Studio?

I'm trying to get a better picture of what happens behind the scenes in Android Studio when building an Android application. I've been reading up on Gradle, but one thing I cannot figure out is how to see the respective CLI command and arguments that is being invoked by Gradle. It seems to be abstracted and not logged to the Gradle Console or Event Log.
The closest I've gotten to seeing what's going on inside Gradle is the AOSP code.
2.2.2 Source:
https://android.googlesource.com/platform/tools/base/+/gradle_2.2.2/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks
Goals
I want to be able to see the respective CLI command that is generated by the Gradle tasks inside Android Studio.
Use Case Example
I want to view the Legacy Android Build Process in depth. This includes going through the following:
Source Code / Library Code -> javac -> Java bytecode (.class) -> proguard -> minimized bytecode (.class) -> dex -> DEX bytecode (.dex)
For example I would want to see the respective javac command invoked by AndroidJavaCompile. https://android.googlesource.com/platform/tools/base/+/gradle_2.2.2/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/factory/AndroidJavaCompile.java
I fear that the only way to do this is to look directly through source code or even build directly from source.
Due Diligence
I've done quite a bit of searching on Google, Android blogs, Google I/O talks, Android books, and much more. I haven't been able to find a straight-forward answer.
That's not possible. Simply, because most of the Gradle tasks do not invoke CLI commands.
Every Gradle build file is a piece of Groovy code that gets executed in a JVM along with the Gradle API (written in Java). Therefor, you can implement any task or configuration functionality directly in any JVM language, from which most plugins make use of instead of executing command line tools. Nevertheless, this is possible by using or extending the Exec task.
The compilation step is handled by a AndroidJavaCompile task, which extends the common JavaCompile Gradle task by some version checks and the Instant Run feature. However, you don't know how Gradle actually compiles the .java files. In the internal source files for the JavaCompile task of the Gradle API, there seem to be various implementations (DaemonJavaCompiler, JdkJavaCompiler and even CommandLineJavaCompiler). Since you can specify CompilerOptions with your task, Gradle seems to choose the real compiler based on these options. Please note, that even if a CommandLineJavaCompiler exists, it is also possible (and highly likely), that Gradle prefers to use the javax.tools package and its JavaCompiler implementation to compile the source files instead of invoking a command line tool.
I also took a look on the ProGuard step in your example build process: ProGuard can be used as command line tool, where you can specify arguments to define how it'll work. But ProGuard also provides a Gradle task (ProGuardTask), that executes without invoking ProGuard from command line. The ProGuard Java code will be executed in the Gradle JVM.
As you can see, even if each Gradle task may be replaced by one (or multiple) CLI command(s), Gradle does not execute these commands. Instead, the functionality is called directly in the Gradle JVM. If you want to get a better insight, you can increase the Gradle log level. Good implementations of Gradle tasks should provide all necessary information in logs.

what exactly is "configuration on demand" in Gradle?

I recently changed some settings in Gradle to speed up its process and one of them was changing this: org.gradle.configureondemand=true property in gradle.properties file.
I know you can guess a lot from the words "configuration on demand", but I wanna know the exact impact of this feature? Do I have to do something to trigger configuration if I set this argument as true?
Can something go wrong if I set it as true ?
What configuration phase exactly is?
This setting is relevant only for multiple modules projects. Basically, it tells Gradle to configure modules that only are relevant to the requested tasks instead of configuring all of them, which is a default behaviour.
To answer more precisely to your questions:
No, you don't have to trigger configuration manually.
Yes, something could go wrong as stated in the documentation. The
feature should work very well for multi-project builds that have
decoupled projects.
In “configuration on demand” mode, projects are configured as follows:
The root project is always configured. This way the typical common configuration is supported (allprojects or subprojects script blocks).
The project in the directory where the build is executed is also configured, but only when Gradle is executed without any tasks. This way the default tasks behave correctly when projects are configured on demand.
The standard project dependencies are supported and makes relevant projects configured. If project A has a compile dependency on project B then building A causes configuration of both projects.
The task dependencies declared via task path are supported and cause relevant projects to be configured. Example: someTask.dependsOn(:someOtherProject:someOtherTask)
A task requested via task path from the command line (or Tooling API) causes the relevant project to be configured. For example, building projectA:projectB:someTask causes configuration of projectB.
Here is the full documentation.

android gradle run assemble after tests pass

I'm trying to fire off various android based gradle tasks e.g. assemble, after 'gradle clean test' is run.
Some background...
My company has jenkins and it is managed by a separate team so I don't have access to configure it myself. On any changes to the remote repo (git) a jenkins job will fire, running gradle clean test and using a build.gradle file that we have inside our repo.
I'm told that this is the only command that the build team will provide and if I want any further actions running, I'll have to configure them inside the build.gradle script.
I am imagining that I can possibly do something like afterTest(:assemble) or maybe addTestListener() but I can't seem to find any examples on google.
Can anyone here help me? Is this even possible or should I ask my build team to allow me to run a diff gradle task depending on what I want?
Configuring CI jobs uniformly is a good idea. However, there is no good way to have additional independent tasks executed when gradle clean test is run. They'd have to at least run gradle clean build so that you can add tasks with build.dependsOn(myTask). (However, keep in mind that build already depends on assemble.) Or they run a custom task such as gradle (clean) ciBuild which by default only depends on test, and to which further task dependencies can be added as necessary.

Gradle Android - Override standard tasks

I'm trying to customize the behavior of my Gradle build to be Android-Wear friendly.
I am bundling manually my wear apk in my handled apk (because i didnt managed to do it automagically).
This means that if I want to build a new version of the handled apk, i have to manually build my wear apk, copy/past the generated wear-apk insinde my res/raw of the handled project then build the new handled apk.
I want all this to be automatized.
So, what I need to do is :
Launch app:assembleRelease from cmd line
Gradle first do a wear:assembleRelease
At the end, Gradle take the apk from wear/output/apk/wear-apk.apk and copy it in app/src/main/res/raw
Then Gradle can procede to do app:assembleRelease
I dont find how to launch a task (wear:assembleRelease) from another task.
Any help is welcome !
I found a solution that may not be optimal but it is working for what I need.
In my handled app, i first have to say that the assembleRelease depends on my wear:assembleRelease:
app/build.gradle
project.afterEvaluate {
preReleaseBuild.dependsOn(':wear:assembleRelease')
}
preReleaseBuildis one of the very first task of the build but this task is created dynamically, that's why you have to wrap it after the project is evaluated.
Then, in my wear build.gradle, I have to specify the copy at the end of the build:
wear/build.gradle
assembleRelease << {
println "Copying the Wear APK"
copy {
from 'build/outputs/apk'
into '../app/src/main/assets'
include '**/wear-release.apk'
}
}
With only theses modifications, i managed to have the workflow explained in the question.
This could be enhanced because it is only working for the release build but it's a good first step.
Feel free to comment this solution.

In a multi-project setup, Gradle evaluates EVERY sub-project. Can I ignore some of the subprojects?

I have the following multi-project setup in Gradle for building multiple Android apps which use a few libraries:
Let's say my root dir is /workspace
The directory structure is the following (it's incomplete):
/workspace/ --> root dir for the gradle build
/workspace/settings.gradle
...
/workspace/myapp1/Android/ --> contains the 1st app (has a build.gradle)
/workspace/myapp2/Android/ --> contains the 2nd app (has a build.gradle)
...
/market_licensing/library/ ---> contains a lib (has a build.gradle)
/play_apk_expansion/downloader_library/ ---> another lib (has a build.gradle)
settings.gradle in /workspace/ looks like this:
include ':market_licensing:library'
include ':play_apk_expansion:downloader_library'
include ':myapp1:Android'
include ':myapp2:Android'
When I run a gradle build in one of the app directories, it works:
For example, /workspace/myapp1/Android>gradle clean runs fine.
However, this will EVALUATE also the build.gradle project for myapp2.
This is expected according to the Gradle manual. In http://www.gradle.org/docs/current/userguide/multi_project_builds.html, in part 56.3. Execution rules for multi-project builds it says "Gradle always evaluates every project of the multi-project build and creates all existing task objects.".
My problem with this is that this slows down my builds unnecessarily. Unnecessarily because, when I'm building myapp1, I don't care about myapp2. I want the libraries to be in a good state, but I don't care about myapp2. This is not terrible for 2 apps, but imagine having 20 apps. I want to be able to build the app that I'm currently working on as fast as possible.
Is there a way to ignore other subprojects , even for the evaluation stage?
Or is there an alternative way to set up multiple projects that depend on the same libraries but don't depend on each other?
as you pointed out the default behaviour in gradle is to evaluate all projects. There are plans (and also initial work started) to make this more fine grained in the future. For now you might checkout the incubating feature "configuration on demand". Maybe this helps you. have a look at http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:cross_project_configuration for details
cheers,
René

Categories

Resources