Parallelize some Android Gradle build actions with the `compile` tier
Categories
(Firefox Build System :: Android Studio and Gradle Integration, enhancement)
Tracking
(Not tracked)
People
(Reporter: nalexander, Unassigned)
Details
Right now, we invoke Gradle twice during the build: once in the export tier to produce various inputs to the compile
tier (here) and again after the compile
tier to produce APKs here.
Most of the compile
tier is heavily concurrent, save for the final link, which is essentially single threaded. If we could execute the Gradle tasks that don't depend on the compiled library inputs into the compile
tier, we could achieve a significant wall-time savings.
In theory, this is not difficult: invoke Gradle as part of the compile
tier and use Gradle's input/output tracking to ensure that we don't do anything that requires the compiled libraries. In practice, this will not work well, because Gradle manages parallelism independently of Make and we should expect twice the desired number of concurrent jobs (one set of jobs from Make, one set of jobs from Gradle).
I have found a way to work around this, but it's complicated and delicate. I only pursued it because it was a fun challenge. What we want is for the Gradle build to use the Make jobserver when appropriate. Concurrency in the Gradle build is managed by this very simple lease holder. It's possible to use a Java Agent to replace that lease holder class with a more complicated implementation that gets tokens from the Make jobserver.
I ran into 2 wrinkles:
- It's important that Gradle always yield a lease for at least one task. I think that Gradle only tries to get leases (jobserver tokens) when tasks complete, so if no task ever completes, we never "go back to the well".
- This would be best implemented within Java, but I simply could not find a way to read from the Make jobserver without blocking. That is, we want
try_acquire
and all of my investigation suggests it's just not possible in the JVM. At least, not with Make jobserver using FDs, as is the norm at this time.
Working around 1. isn't particularly hard. But working around 2. might require compiling Rust into a JNI library, and across platforms (and early in the build!) that's pretty hard. (Locally, I just did this "by hand" on my macOS box, but in general that doesn't really fit with the build system.) Also hard in our build system: compiling a Java Agent and the replacement class before invoking Gradle for the Android build.
Reporter | ||
Comment 1•11 days ago
|
||
florian: aaditya: my thoughts and discoveries about parallelizing parts of the Android compilation tier with native code compilation. tl;dr: it's hard enough that I doubt we can do it in practice.
Reporter | ||
Comment 2•20 hours ago
|
||
So, I didn't stop churning on this in the background, and I have a solution for 2. Python can do non-blocking reads (and interact with Windows semaphores), so it's feasible to have Python run a simple socket server that the Java side connects to (that itself can be blocking).
I don't have a way to get a Java Agent attached to Gradle itself without using Gradle to produce the agent -- a tricky chicken-and-egg problem -- but we might be able to build the agent as a toolchain and get it onto devices that way.
Description
•