Development of the Android Rust toolchain can involve a large number of tools and technologies, many of which are open source and have better documentation elsewhere. This file will focus on describing the tools developed as part of this project. For a definitive list of the options supported by each tool please use the --help
flag.
The toools/audit.py
script can be used to check a prebuilt archive for any new shared library dependencies. New dependencies usually indicate a hermeticity issue and should be investigated to ensure that the new library originated from in-tree source or resolves to a prebuilt distributed as part of Android.
Usage:
./tools/audit.py <scandir>
To see a mapping between binaries and their dependencies you can pass the --map
flag.
If a new shared library is required by the toolchain its name should be added to the resources/shared_library_allow_list.txt
file. This will enable audit.py
to recognize the library and not fail the audit when it's present as a dependency.
One or more toolchain prebuilts can be benchmarked using the tools/benchmark.py
script. This script will build Android with each of the prebuilts some number of times (default of 5) and produce a CSV file containing timing values for various categories of targets such as C/C++, Java or Rust.
An example invocation of the benchmarking tool:
$ ./tools/benchmark.py --label example-invoke --iterations 10 ../../dist/rust-baseline.tar.xz ../../dist/rust-experiment.tar.xz
The bolt
command line tool can be a bit finnicky to work with and so we use the tools/boltifyer.py
script to automate optimizing an entire prebuilt at a time. By default the tool will apply peephole optimizations to all target binaries in the archive. The --profile-generate
and --profile-use
options can be set generate or use profiles in a fashion similar to how normal PGO profiles are handled. These flags take absolute paths as arguments. If you'd like the profiles to be generated into a path relative to the instrumented binary you can use the --profile-generate-relative
flag.
Most BOLT optimizations require relocation information to be useful. Make sure to pass the --emit-relocs
flag to the tools/build.py
script if you intend to BOLT the prebuilt later.
Example usage:
$ ./tools/boltifyer.py --bulid-name 1.77.1-bolted --profile-generate -- ../../dist/rust-1.77.1-relocs.tar.xz
As no value was passed to the --profile-generate
flag in the above command the tool will use the default location: out/profiles
.
Juggling the configuration values for each of the targets we support is too burdensome and error prone to do by hand. As such, the Android Rust toolchain uses the tools/build.py
script to set-up a new copy of the Rust source in the out/
directory, apply patches, emit compiler wrapper scripts, initialize environment variables, and invoke the Rust build system (x.py
).
For a comprehensive list of the options available please see the output from the --help
flag. Below are several example invocations of the script that can be used for common tasks.
Linux release:
$ ./tools/build.py --lto thin --llvm-linkage shared --cgu1
Fast testing of host targets:
$ ./tools/build.py --lto thin --llvm-linkage shared --host-only
You an re-use existing intermediate outputs by telling the script to avoid re-initializing the Rust out directory:
$ ./tools/build.py --lto thin --llvm-linkage shared --host-only --no-copy-and-patch
The flags controlling profiling instrumentation for the compiler take paths to where the profiles should be stored but will default to a value of /path/to/android/out/profiles
if the flag is set but no path is given. The same default paths are used by tools/test_compiler.py
.
The following artifacts will be placed into the distribution (--dist
) directory:
Upstream Rust source archives can be downloaded, extracted, and committed to Android using the tools/fetch_source.py
script. By default the tool will fetch the release archive for the provided version number if it exists. To fetch the beta or nightly versions of the Rust source use the --beta
and --nightly
flags respectively. Branch names and issue numbers can be provided using the --branch
and --issue
flags.
$ ./tools/fetch_source.py --issue 333887339 1.78.0
The tools/lto_check.py
script takes no argument and will simply check all .o
objects under out/rustc/build
and list any that do not contain LLVM IR.
To get the best performance out of Android's Rust toolchain we want to optimize it using profiles generated while it compiles for each of the backends we utilize for production code (ARM, RISC-V, and x86). While there exists tools for merging multiple PGO or BOLT profiles into a single, representative file (profdata
and merge_fdata
respectively), these tools can require multiple invocations and are generally a pain to use manually for a project this large.
The ./tools/merge_profiles.py
script takes the paths to multiple profile locations and produces a single profile for PGO or an archive of profiles for BOLT.
$ ./tools/merge_profiles.py ../../dist/run1 ../../dist/run2
The steps necessary to build a profiled and optimized version of the Rust toolchain are numerous and finnicky. To help avoid mistakes and enable rapid testing and debugging the tools/pipeline.py
script will execute all of the build stages locally. The --llvm-linkage
and --lto
flags are shared with the ./tools/build.py
script but all other options are determined by the stages in the pipeline. Artifacts generated by the pipeline script are placed into a build-name
specific subdirectory of the distribution directory. For example, if 1.78.0-pipeline
is provided as the build-name
artifacts will be found under /path/to/dist/rust-1.78.0-pipeline/stage{0-6}/
.
The stage at which the script starts, resumes, or stops compilation can be controlled with the --start
and --end
flags. The --overwrite
flag needs to be provided if you use the same build-name
and wish to re-run a stage that has already completed successfully.
For a full description of the optimization pipeline please see the BUILDS file.
Android maintains several patches against the upstream Rust toolchain source. These patches range from small changes that are needed locally but can't be upstreamed, patches that have been backported or already upstreamed, or patches that are under development and are not ready for external release. The tools/recontextualize_patches.py
script helps keep these patches up-to-date relative to the upstream source. When this tool is run it will re-apply the patches to the toolchain/rustc
source repository using the git am
command before re-emitting them into the toolchain/android_rust/patches
directory. If the patches apply successfully the result will be refreshed context information. By performing this step as part of every toolchain release we can avoid context drift and the eventual breaking of patches that that entails.
The tools/soong_trace.py
script is used to extract compiler statistics from a Soong trace file. It will print the number of targets, and time spent compiling them, for C/C++, Java, and Rust. The --csv
option can be used to cause to tool to generate a CSV file containing the same data.
Being able to compile a toolchain is of little use to us if it can't, in turn, compile Android. To help ensure that we ship a working toolchain we use the tools/test_compiler.py
script to verify the state of our prebuilts.
The --clean
flag can be used to ensure that the out/
directory is fully deleted before we attempt to build any Android targets. The following flags can be used to select which Android targets to build:
--all-rust
- Every Rust target defined for the lunch
ed device--image
- All targets necessary to generate a boot image--test-targets
- Android targets needed to run device tests--rustdoc
- All Rustdoc targets--custom-targets
- A set of user-specified Android targetsNote: not all Rust targets build for the RISC-V architecture. It is best to restrict the test targets to --image
until architecture support has matured.
This tool is also capable of collecting profiles from the test compiler. Use the following (mutually exclusive) flags to collect the relevant profiles:
--profile-generate
--cs-profile-generate
--bolt-profile-generate
Basic usage:
$ ./tools/test_compiler.py --prebuilt-path ../../dist/rust-1.77.1.tar.xz --clean --all-rust
Re-use the compiler in the test directory to build custom targets:
$ ./tools/test_compiler.py --reuse-prebuilt --clean --custom-targets keystore2 libtokio
Updating the Rust prebuilts used by Android involves commits to three separate projects:
build/soong
- Rust version numberprebuilts/rust
- Rust executables and librariestoolchain/android_rust
- Build command, profiles, and manifest used to build releaseThe tools/update_prebuilts.py
script will create the three Git commits but they will need to be uploaded manually.
Basic usage of the tools only requires the Rust version number (though an issue number is also strongly recommended):
$ ./tools/update_prebuilts.py --issue 333887339 1.78.0
The --bid
flag can be used to select a specific build, which can be helpful if “last known good build” detection feature encounters an error. The --bolt
/--no-bolt
, --musl
/--no-musl
, and --pgo
/--no-pgo
flags can be used to select which build targets to download artifacts from.
The Android Rust toolchain provides a Docker image that can be used when building the toolchain and running other tools. This Docker image is meant to be used on the Android build servers to provide a stable and reproducible build environment.
To use this Docker image you'll first need to build it. To do so enter the toolchain/android_rust/resources
directory and run the following command:
$ docker build -t android-rust:dev .
You may then use the tools/docker_run.py
script to execute a tool inside an instance of the docker image like so:
$ ./tools/docker_run.py ./tools/build.py --build-name docker-test
To run start an interactive Docker instance with the correct mount point for the Android source you can run this command (substituting in the correct path to your Android root):
$ sudo docker run --mount type=bind,source=/path/to/android/root,target=/android -it android-rust:dev /bin/bash
The Android Rust Toolchain Docker image is defined in resources/Dockerfile and modifications can be built and published to the gCloud artifact registry by a team member.
To update the Docker image you first need to configure your gCloud project and obtain credentials:
$ gcloud config set project android-native-docker $ gcloud auth login
Next, you can run the following command in the resources/
directory:
$ gcloud builds submit --region=us-west1 --tag us-west1-docker.pkg.dev/android-native-docker/rust/toolchain-build-image:v1