nandi/jolt-nativepublic Fork 0
14a30c0
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Cross-compile for Android, with an NDK nobody has to install

zig cc carries a glibc sysroot and no bionic, so there is nothing on this
side to link an Android object against. The NDK's clang is the driver that
knows where libc.so and the platform stubs are, and this puts one behind the
same hermetic_cxx_toolchain zig already sits behind: only the archive and the
wrapper change, because the toolchain asks a wrapper for a mode rather than
naming binaries itself.

The archive is the one thing here buck does not carry. Unpacked the NDK is
three gigabytes, and an input that size reaches a remote worker only by being
uploaded to the CAS first — 276MiB on the wire before a single action runs.
What travels instead is DotSlash and the manifest naming the archive, both
small, and whatever machine runs the action fetches the same bytes under the
same digest from Google. A worker with a warm cache does nothing at all.

The cost is stated in ndk.sh: what the compiler *is* stops being part of the
action's digest. The manifest is an input, so a version bump still
invalidates, and DotSlash keys its cache on the digest, so a machine cannot
be holding something else under that name.

The Rust half needed the device's libstd, which no single dist tarball
carries: rustc resolves a target's std from sysroot/lib/rustlib/<triple> and
accepts --sysroot once, so cross-compiling wants two triples in one
directory. scripts/rustc has always stitched that by hand into a cache under
$HOME; toolchains//dist:android-sysroot does it as an artifact instead, which
is a thing an action can depend on and a worker can be handed.

selftest/ is what keeps both honest — one C object and one Rust one that
compile only for the device, and fail loudly if the sysroot, the API level or
the libstd is not what it should be. Nothing ships them.
nandi committed 2026-08-31T00:31:17-07:00 Browse files
14a30c0 parent: 5a37b4b
modified platforms/BUCK +13 -0
@@ -28,3 +28,16 @@ execution_platform(
2828 use_windows_path_separators = host_info().os.is_windows,
2929 visibility = ["PUBLIC"],
3030 )
31+
32+# A *target* platform, unlike the two above: this is what a graph is built
33+# for, not where the building runs. Naming it on a configured_alias moves the
34+# target configuration and leaves the execution platform alone, so build
35+# scripts and proc macros still compile and run on the host.
36+platform(
37+ name = "android-arm64",
38+ constraint_values = [
39+ "prelude//os/constraints:os[android]",
40+ "prelude//cpu/constraints:cpu[arm64]",
41+ ],
42+ visibility = ["PUBLIC"],
43+)
@@ -28,3 +28,16 @@ execution_platform(
28 use_windows_path_separators = host_info().os.is_windows,28 use_windows_path_separators = host_info().os.is_windows,
29 visibility = ["PUBLIC"],29 visibility = ["PUBLIC"],
30 )30 )
31+
32+# A *target* platform, unlike the two above: this is what a graph is built
33+# for, not where the building runs. Naming it on a configured_alias moves the
34+# target configuration and leaves the execution platform alone, so build
35+# scripts and proc macros still compile and run on the host.
36+platform(
37+ name = "android-arm64",
38+ constraint_values = [
39+ "prelude//os/constraints:os[android]",
40+ "prelude//cpu/constraints:cpu[arm64]",
41+ ],
42+ visibility = ["PUBLIC"],
43+)
added scripts/BUCK +11 -0
new file mode 100644
@@ -0,0 +1,11 @@
1+# The pins, as artifacts.
2+#
3+# A manifest that a *build action* resolves has to be an input to it, so that
4+# bumping the version it names invalidates what was built with the old one.
5+# See toolchains/ndk.sh, which is handed this file and a DotSlash to read it
6+# with.
7+export_file(
8+ name = "android-ndk.dotslash",
9+ mode = "reference",
10+ visibility = ["PUBLIC"],
11+)
new file mode 100644
@@ -0,0 +1,11 @@
1+# The pins, as artifacts.
2+#
3+# A manifest that a *build action* resolves has to be an input to it, so that
4+# bumping the version it names invalidates what was built with the old one.
5+# See toolchains/ndk.sh, which is handed this file and a DotSlash to read it
6+# with.
7+export_file(
8+ name = "android-ndk.dotslash",
9+ mode = "reference",
10+ visibility = ["PUBLIC"],
11+)
added scripts/android-cc +20 -0
new file mode 100755
@@ -0,0 +1,20 @@
1+#!/bin/sh
2+# The NDK's clang, as the linker rustc runs for an Android target.
3+#
4+# The buck-side twin of this is toolchains/ndk.sh, which the C toolchain gets
5+# as a dependency; both resolve the same archive, pinned in one place —
6+# android-ndk.dotslash beside this script. rustc reaches it through a shim
7+# instead because -Clinker takes a plain string: rustc_flags cannot carry an
8+# artifact path, so the linker has to be something with a stable name. The cfg
9+# in toolchains/BUCK is what keeps two NDKs from sharing a cache entry.
10+#
11+# ANDROID_NDK_HOME wins if it is set, so a machine that already has an NDK
12+# skips the download.
13+set -e
14+dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
15+api=${JOLT_ANDROID_API:-28}
16+target=${JOLT_ANDROID_TARGET:-aarch64-linux-android}
17+
18+# android-ndk-bin is where the lookup lives, because an APK build wants the
19+# other binaries out of the same archive.
20+exec "$("$dir/android-ndk-bin")/clang" --target="$target$api" "$@"
new file mode 100755
@@ -0,0 +1,20 @@
1+#!/bin/sh
2+# The NDK's clang, as the linker rustc runs for an Android target.
3+#
4+# The buck-side twin of this is toolchains/ndk.sh, which the C toolchain gets
5+# as a dependency; both resolve the same archive, pinned in one place —
6+# android-ndk.dotslash beside this script. rustc reaches it through a shim
7+# instead because -Clinker takes a plain string: rustc_flags cannot carry an
8+# artifact path, so the linker has to be something with a stable name. The cfg
9+# in toolchains/BUCK is what keeps two NDKs from sharing a cache entry.
10+#
11+# ANDROID_NDK_HOME wins if it is set, so a machine that already has an NDK
12+# skips the download.
13+set -e
14+dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
15+api=${JOLT_ANDROID_API:-28}
16+target=${JOLT_ANDROID_TARGET:-aarch64-linux-android}
17+
18+# android-ndk-bin is where the lookup lives, because an APK build wants the
19+# other binaries out of the same archive.
20+exec "$("$dir/android-ndk-bin")/clang" --target="$target$api" "$@"
added scripts/android-ndk-bin +23 -0
new file mode 100755
@@ -0,0 +1,23 @@
1+#!/bin/sh
2+# Print the directory holding the NDK's clang and LLVM binutils.
3+#
4+# android-cc needs one of those binaries and an APK build needs others —
5+# llvm-objcopy, for the boot image blob — so the lookup is here rather than
6+# duplicated at each caller. ANDROID_NDK_HOME wins if it is set; otherwise the
7+# pin in android-ndk.dotslash beside this script answers, downloading once.
8+set -e
9+dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
10+
11+ndk=${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}
12+if [ -n "$ndk" ]; then
13+ for host in linux-x86_64 darwin-x86_64; do
14+ if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then
15+ echo "$ndk/toolchains/llvm/prebuilt/$host/bin"
16+ exit 0
17+ fi
18+ done
19+ echo "scripts/android-ndk-bin: no llvm prebuilt under $ndk" >&2
20+ exit 1
21+fi
22+
23+dirname -- "$(dotslash -- fetch "$dir/android-ndk.dotslash")"
new file mode 100755
@@ -0,0 +1,23 @@
1+#!/bin/sh
2+# Print the directory holding the NDK's clang and LLVM binutils.
3+#
4+# android-cc needs one of those binaries and an APK build needs others —
5+# llvm-objcopy, for the boot image blob — so the lookup is here rather than
6+# duplicated at each caller. ANDROID_NDK_HOME wins if it is set; otherwise the
7+# pin in android-ndk.dotslash beside this script answers, downloading once.
8+set -e
9+dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
10+
11+ndk=${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}
12+if [ -n "$ndk" ]; then
13+ for host in linux-x86_64 darwin-x86_64; do
14+ if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then
15+ echo "$ndk/toolchains/llvm/prebuilt/$host/bin"
16+ exit 0
17+ fi
18+ done
19+ echo "scripts/android-ndk-bin: no llvm prebuilt under $ndk" >&2
20+ exit 1
21+fi
22+
23+dirname -- "$(dotslash -- fetch "$dir/android-ndk.dotslash")"
added scripts/android-ndk.dotslash +55 -0
new file mode 100755
@@ -0,0 +1,55 @@
1+#!/usr/bin/env dotslash
2+
3+// The NDK, pinned the way every other tool here is.
4+//
5+// bin/clang is the path named, but the whole archive is what matters: the
6+// driver finds its sysroot, its resource directory and the rest of the LLVM
7+// binaries relative to itself, so the tree has to travel intact. The name is
8+// only what makes this a runnable shim as well as a manifest.
9+//
10+// Google publishes SHA-1 for these and nothing else, so the digests below were
11+// taken from the archives and checked against the SHA-1 in repository2-3.xml.
12+//
13+// There is no Linux/arm64 NDK. Both macOS entries are the x86_64 prebuilt,
14+// which is what Google ships an arm64 host runs it under Rosetta.
15+{
16+ "name": "android-ndk-r29",
17+ "platforms": {
18+ "linux-x86_64": {
19+ "size": 783549481,
20+ "hash": "sha256",
21+ "digest": "4abbbcdc842f3d4879206e9695d52709603e52dd68d3c1fff04b3b5e7a308ecf",
22+ "format": "zip",
23+ "path": "android-ndk-r29/toolchains/llvm/prebuilt/linux-x86_64/bin/clang",
24+ "providers": [
25+ {
26+ "url": "https://dl.google.com/android/repository/android-ndk-r29-linux.zip"
27+ }
28+ ]
29+ },
30+ "macos-x86_64": {
31+ "size": 1049519838,
32+ "hash": "sha256",
33+ "digest": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
34+ "format": "zip",
35+ "path": "android-ndk-r29/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang",
36+ "providers": [
37+ {
38+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip"
39+ }
40+ ]
41+ },
42+ "macos-aarch64": {
43+ "size": 1049519838,
44+ "hash": "sha256",
45+ "digest": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
46+ "format": "zip",
47+ "path": "android-ndk-r29/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang",
48+ "providers": [
49+ {
50+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip"
51+ }
52+ ]
53+ }
54+ }
55+}
new file mode 100755
@@ -0,0 +1,55 @@
1+#!/usr/bin/env dotslash
2+
3+// The NDK, pinned the way every other tool here is.
4+//
5+// bin/clang is the path named, but the whole archive is what matters: the
6+// driver finds its sysroot, its resource directory and the rest of the LLVM
7+// binaries relative to itself, so the tree has to travel intact. The name is
8+// only what makes this a runnable shim as well as a manifest.
9+//
10+// Google publishes SHA-1 for these and nothing else, so the digests below were
11+// taken from the archives and checked against the SHA-1 in repository2-3.xml.
12+//
13+// There is no Linux/arm64 NDK. Both macOS entries are the x86_64 prebuilt,
14+// which is what Google ships an arm64 host runs it under Rosetta.
15+{
16+ "name": "android-ndk-r29",
17+ "platforms": {
18+ "linux-x86_64": {
19+ "size": 783549481,
20+ "hash": "sha256",
21+ "digest": "4abbbcdc842f3d4879206e9695d52709603e52dd68d3c1fff04b3b5e7a308ecf",
22+ "format": "zip",
23+ "path": "android-ndk-r29/toolchains/llvm/prebuilt/linux-x86_64/bin/clang",
24+ "providers": [
25+ {
26+ "url": "https://dl.google.com/android/repository/android-ndk-r29-linux.zip"
27+ }
28+ ]
29+ },
30+ "macos-x86_64": {
31+ "size": 1049519838,
32+ "hash": "sha256",
33+ "digest": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
34+ "format": "zip",
35+ "path": "android-ndk-r29/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang",
36+ "providers": [
37+ {
38+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip"
39+ }
40+ ]
41+ },
42+ "macos-aarch64": {
43+ "size": 1049519838,
44+ "hash": "sha256",
45+ "digest": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
46+ "format": "zip",
47+ "path": "android-ndk-r29/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang",
48+ "providers": [
49+ {
50+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip"
51+ }
52+ ]
53+ }
54+ }
55+}
modified scripts/dotslash-to-buck +16 -2
@@ -24,7 +24,17 @@ OUT = HERE.parent / "toolchains" / "dist" / "generated.bzl"
2424
2525 # The tools buck fetches for itself. buck2 is not among them: it is the thing
2626 # doing the fetching, and dotslash is how a human gets it.
27-TOOLS = {"zig": "zig", "rustc": "rust"}
27+TOOLS = {
28+ "zig": "zig",
29+ "rustc": "rust",
30+ "android-ndk": "android-ndk",
31+ # Target-only: the same archive whatever the host, which is why
32+ # every platform in that manifest carries the same digest.
33+ "rust-std-android": "rust-std-android",
34+ # Fetched so it can do the fetching, on a machine this one cannot upload
35+ # three gigabytes to. See the manifest.
36+ "dotslash": "dotslash",
37+}
2838
2939
3040 def load(path):
@@ -50,10 +60,14 @@ def main():
5060 url = entry["providers"][0]["url"]
5161 # The path of the binary inside the archive starts with the
5262 # directory the tarball unpacks into, which is what to strip.
63+ # A flat archive — one binary, no directory around it — has
64+ # nothing to strip, and naming the binary would strip the whole
65+ # payload.
66+ head, _, rest = entry["path"].partition("/")
5367 platforms[platform] = {
5468 "url": url,
5569 "sha256": entry["digest"],
56- "strip_prefix": entry["path"].split("/")[0],
70+ "strip_prefix": head if rest else "",
5771 "type": entry["format"],
5872 }
5973 entries[name] = platforms
@@ -24,7 +24,17 @@ OUT = HERE.parent / "toolchains" / "dist" / "generated.bzl"
24 24
25 # The tools buck fetches for itself. buck2 is not among them: it is the thing25 # The tools buck fetches for itself. buck2 is not among them: it is the thing
26 # doing the fetching, and dotslash is how a human gets it.26 # doing the fetching, and dotslash is how a human gets it.
27-TOOLS = {"zig": "zig", "rustc": "rust"}27+TOOLS = {
28+ "zig": "zig",
29+ "rustc": "rust",
30+ "android-ndk": "android-ndk",
31+ # Target-only: the same archive whatever the host, which is why
32+ # every platform in that manifest carries the same digest.
33+ "rust-std-android": "rust-std-android",
34+ # Fetched so it can do the fetching, on a machine this one cannot upload
35+ # three gigabytes to. See the manifest.
36+ "dotslash": "dotslash",
37+}
28 38
29 39
30 def load(path):40 def load(path):
@@ -50,10 +60,14 @@ def main():
50 url = entry["providers"][0]["url"]60 url = entry["providers"][0]["url"]
51 # The path of the binary inside the archive starts with the61 # The path of the binary inside the archive starts with the
52 # directory the tarball unpacks into, which is what to strip.62 # directory the tarball unpacks into, which is what to strip.
63+ # A flat archive — one binary, no directory around it — has
64+ # nothing to strip, and naming the binary would strip the whole
65+ # payload.
66+ head, _, rest = entry["path"].partition("/")
53 platforms[platform] = {67 platforms[platform] = {
54 "url": url,68 "url": url,
55 "sha256": entry["digest"],69 "sha256": entry["digest"],
56- "strip_prefix": entry["path"].split("/")[0],70+ "strip_prefix": head if rest else "",
57 "type": entry["format"],71 "type": entry["format"],
58 }72 }
59 entries[name] = platforms73 entries[name] = platforms
added scripts/dotslash.dotslash +66 -0
new file mode 100644
@@ -0,0 +1,66 @@
1+#!/usr/bin/env dotslash
2+
3+// DotSlash itself, so that a remote worker can fetch a toolchain the way this
4+// machine does.
5+//
6+// The other manifests here are pins buck reads and then fetches for itself.
7+// This one is different: it is fetched so that it can do the fetching. An NDK
8+// is three gigabytes unpacked, and shipping that to a worker as an action
9+// input means uploading it to the CAS first; a worker that runs this instead
10+// downloads the same pinned archive, by the same digest, from Google.
11+//
12+// Not runnable as a shim, and nothing tries: a DotSlash file cannot bootstrap
13+// the program that reads it.
14+{
15+ "name": "dotslash",
16+ "platforms": {
17+ "linux-x86_64": {
18+ "size": 842816,
19+ "hash": "sha256",
20+ "digest": "4c75c6eb7890ae35993b962073f6d9bbe78b42b81a5691303ad70f63bfbf7196",
21+ "format": "tar.gz",
22+ "path": "dotslash",
23+ "providers": [
24+ {
25+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.x86_64.v0.5.9.tar.gz"
26+ }
27+ ]
28+ },
29+ "linux-aarch64": {
30+ "size": 754251,
31+ "hash": "sha256",
32+ "digest": "e58374446dacd4a228388cd275d974bbd8f946916b513cf0eb86bdff5d9d675e",
33+ "format": "tar.gz",
34+ "path": "dotslash",
35+ "providers": [
36+ {
37+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.arm64.v0.5.9.tar.gz"
38+ }
39+ ]
40+ },
41+ "macos-x86_64": {
42+ "size": 716580,
43+ "hash": "sha256",
44+ "digest": "51446593596ffe4cd22bc43a0fe6d82dd367758eb53cb4ade36d6a684ed08469",
45+ "format": "tar.gz",
46+ "path": "dotslash",
47+ "providers": [
48+ {
49+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-amd64.v0.5.9.tar.gz"
50+ }
51+ ]
52+ },
53+ "macos-aarch64": {
54+ "size": 600404,
55+ "hash": "sha256",
56+ "digest": "eef15be1b554de7b1409cb0c69c24f2cd04f19c0f101ad69fcdf9d66d4d51639",
57+ "format": "tar.gz",
58+ "path": "dotslash",
59+ "providers": [
60+ {
61+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-arm64.v0.5.9.tar.gz"
62+ }
63+ ]
64+ }
65+ }
66+}
new file mode 100644
@@ -0,0 +1,66 @@
1+#!/usr/bin/env dotslash
2+
3+// DotSlash itself, so that a remote worker can fetch a toolchain the way this
4+// machine does.
5+//
6+// The other manifests here are pins buck reads and then fetches for itself.
7+// This one is different: it is fetched so that it can do the fetching. An NDK
8+// is three gigabytes unpacked, and shipping that to a worker as an action
9+// input means uploading it to the CAS first; a worker that runs this instead
10+// downloads the same pinned archive, by the same digest, from Google.
11+//
12+// Not runnable as a shim, and nothing tries: a DotSlash file cannot bootstrap
13+// the program that reads it.
14+{
15+ "name": "dotslash",
16+ "platforms": {
17+ "linux-x86_64": {
18+ "size": 842816,
19+ "hash": "sha256",
20+ "digest": "4c75c6eb7890ae35993b962073f6d9bbe78b42b81a5691303ad70f63bfbf7196",
21+ "format": "tar.gz",
22+ "path": "dotslash",
23+ "providers": [
24+ {
25+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.x86_64.v0.5.9.tar.gz"
26+ }
27+ ]
28+ },
29+ "linux-aarch64": {
30+ "size": 754251,
31+ "hash": "sha256",
32+ "digest": "e58374446dacd4a228388cd275d974bbd8f946916b513cf0eb86bdff5d9d675e",
33+ "format": "tar.gz",
34+ "path": "dotslash",
35+ "providers": [
36+ {
37+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.arm64.v0.5.9.tar.gz"
38+ }
39+ ]
40+ },
41+ "macos-x86_64": {
42+ "size": 716580,
43+ "hash": "sha256",
44+ "digest": "51446593596ffe4cd22bc43a0fe6d82dd367758eb53cb4ade36d6a684ed08469",
45+ "format": "tar.gz",
46+ "path": "dotslash",
47+ "providers": [
48+ {
49+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-amd64.v0.5.9.tar.gz"
50+ }
51+ ]
52+ },
53+ "macos-aarch64": {
54+ "size": 600404,
55+ "hash": "sha256",
56+ "digest": "eef15be1b554de7b1409cb0c69c24f2cd04f19c0f101ad69fcdf9d66d4d51639",
57+ "format": "tar.gz",
58+ "path": "dotslash",
59+ "providers": [
60+ {
61+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-arm64.v0.5.9.tar.gz"
62+ }
63+ ]
64+ }
65+ }
66+}
modified toolchains/BUCK +74 -11
@@ -1,3 +1,4 @@
1+load("//dist:generated.bzl", "DIST")
12 load(":defs.bzl", "hermetic_cxx_toolchain", "hermetic_rust_toolchain")
23 load("@prelude//toolchains:genrule.bzl", "system_genrule_toolchain")
34 load("@prelude//toolchains:python.bzl", "system_python_bootstrap_toolchain")
@@ -34,16 +35,45 @@ _SCRIPTS = read_root_config("jolt", "scripts", "scripts")
3435 # by the old one — the E0514 failure, one toolchain over. Naming the version in
3536 # a cfg nothing reads keeps the digests apart until zig is a dependency too.
3637 _ZIG_VERSION = read_root_config("jolt", "zig_version", "unknown")
38+
39+# The NDK's, for the same reason and read from the pin rather than the machine.
40+# The C toolchain does not need it — the archive is a dependency there, so it
41+# is in every cxx action's digest already. rustc does: it links an Android
42+# target through scripts/android-cc, an absolute path that does not change when
43+# the NDK behind it does.
44+_NDK_VERSION = DIST["android-ndk"]["linux-x86_64"]["strip_prefix"]
3745 #
3846 # This is the same arrangement .cargo/config.toml used to describe: zig cc
3947 # carries its own glibc sysroot, which is why this repo builds on a machine
40-# with no `cc` at all. Unlike vidya there is no Android row here — both shared
41-# objects this repo ships are desktop, and the APK takes its libvidya from
42-# vidya's own buck2 build.
48+# with no `cc` at all.
49+#
50+# Android is the one target it cannot serve. zig brings a glibc sysroot and no
51+# bionic, so there is nothing there to link an Android object against; the
52+# NDK's own clang is the driver that knows where libc.so and the platform stubs
53+# live. Only the archive and the wrapper change — the toolchain either side of
54+# them is the same construction, which is the point of asking a wrapper for a
55+# mode rather than naming binaries here.
4356 hermetic_cxx_toolchain(
4457 name = "cxx",
45- dist = "toolchains//dist:zig",
46- wrapper = "toolchains//:zcc.sh",
58+ dist = select({
59+ "DEFAULT": "toolchains//dist:zig",
60+ # DotSlash, not the NDK: ndk.sh fetches the NDK where it runs, which is
61+ # the difference between a worker downloading three gigabytes from
62+ # Google and this machine uploading them to the CAS first.
63+ "prelude//os:android": "toolchains//dist:dotslash",
64+ }),
65+ manifest = select({
66+ "DEFAULT": None,
67+ "prelude//os:android": "root//scripts:android-ndk.dotslash",
68+ }),
69+ wrapper = select({
70+ "DEFAULT": "toolchains//:zcc.sh",
71+ "prelude//os:android": "toolchains//:ndk.sh",
72+ }),
73+ platform_name = select({
74+ "DEFAULT": "x86_64",
75+ "prelude//os:android": "arm64",
76+ }),
4777 visibility = ["PUBLIC"],
4878 )
4979
@@ -65,8 +95,32 @@ hermetic_rust_toolchain(
6595 dist = "toolchains//dist:rust",
6696 wrapper = "toolchains//:rustc.sh",
6797 default_edition = "2021",
68- rustc_flags = [
69- "-Clinker=" + _SCRIPTS + "/zcc",
98+ # Pipelined builds compile each crate against its dependencies'
99+ # `-Zno-codegen` metadata rather than their rlibs. On the Android graph the
100+ # two disagree for android-activity, and what comes out the far side is a
101+ # link that cannot find winit — or glutin-winit, or anything else that
102+ # reaches the event loop through it. Build real rlibs there. The desktop
103+ # graph pipelines fine and keeps the parallelism.
104+ nightly_features = select({
105+ "DEFAULT": True,
106+ "prelude//os:android": False,
107+ }),
108+ # Which libstd rustc is given. The desktop configuration takes the
109+ # component the dist tarball carries for its own host; Android needs two
110+ # triples in one directory, so it takes the stitched one.
111+ sysroot = select({
112+ "DEFAULT": None,
113+ "prelude//os:android": "toolchains//dist:android-sysroot",
114+ }),
115+ rustc_flags = select({
116+ "DEFAULT": ["-Clinker=" + _SCRIPTS + "/zcc"],
117+ # zig has no bionic to link against; the NDK's clang does. The cfg
118+ # nothing reads is what keeps the digests apart, as above.
119+ "prelude//os:android": [
120+ "-Clinker=" + _SCRIPTS + "/android-cc",
121+ "--cfg=jolt_ndk=\"" + _NDK_VERSION + "\"",
122+ ],
123+ }) + [
70124 # What [profile.release] in Cargo.toml says, and for the reason given
71125 # there: the tree walk and the texture uploads are hot every frame, and
72126 # the media plane decodes H.264. buck2 optimises nothing by default, so
@@ -75,10 +129,13 @@ hermetic_rust_toolchain(
75129 "-Copt-level=2",
76130 "--cfg=jolt_cc=\"" + _ZIG_VERSION + "\"",
77131 ],
78- # A plain string rather than a select: the sysroot path inside the dist
79- # tarball is built from this, and only the triples dist.bzl fetches an
80- # archive for can be named here.
81- rustc_target_triple = "x86_64-unknown-linux-gnu",
132+ # Only triples there is an archive for can be named here: the desktop one
133+ # comes out of the dist tarball, the Android one out of the libstd fetched
134+ # beside it.
135+ rustc_target_triple = select({
136+ "DEFAULT": "x86_64-unknown-linux-gnu",
137+ "prelude//os:android": "aarch64-linux-android",
138+ }),
82139 visibility = ["PUBLIC"],
83140 )
84141
@@ -90,6 +147,12 @@ export_file(
90147 visibility = ["PUBLIC"],
91148 )
92149
150+export_file(
151+ name = "ndk.sh",
152+ mode = "reference",
153+ visibility = ["PUBLIC"],
154+)
155+
93156 export_file(
94157 name = "rustc.sh",
95158 mode = "reference",
@@ -1,3 +1,4 @@
1+load("//dist:generated.bzl", "DIST")
1 load(":defs.bzl", "hermetic_cxx_toolchain", "hermetic_rust_toolchain")2 load(":defs.bzl", "hermetic_cxx_toolchain", "hermetic_rust_toolchain")
2 load("@prelude//toolchains:genrule.bzl", "system_genrule_toolchain")3 load("@prelude//toolchains:genrule.bzl", "system_genrule_toolchain")
3 load("@prelude//toolchains:python.bzl", "system_python_bootstrap_toolchain")4 load("@prelude//toolchains:python.bzl", "system_python_bootstrap_toolchain")
@@ -34,16 +35,45 @@ _SCRIPTS = read_root_config("jolt", "scripts", "scripts")
34 # by the old one — the E0514 failure, one toolchain over. Naming the version in35 # by the old one — the E0514 failure, one toolchain over. Naming the version in
35 # a cfg nothing reads keeps the digests apart until zig is a dependency too.36 # a cfg nothing reads keeps the digests apart until zig is a dependency too.
36 _ZIG_VERSION = read_root_config("jolt", "zig_version", "unknown")37 _ZIG_VERSION = read_root_config("jolt", "zig_version", "unknown")
38+
39+# The NDK's, for the same reason and read from the pin rather than the machine.
40+# The C toolchain does not need it — the archive is a dependency there, so it
41+# is in every cxx action's digest already. rustc does: it links an Android
42+# target through scripts/android-cc, an absolute path that does not change when
43+# the NDK behind it does.
44+_NDK_VERSION = DIST["android-ndk"]["linux-x86_64"]["strip_prefix"]
37 #45 #
38 # This is the same arrangement .cargo/config.toml used to describe: zig cc46 # This is the same arrangement .cargo/config.toml used to describe: zig cc
39 # carries its own glibc sysroot, which is why this repo builds on a machine47 # carries its own glibc sysroot, which is why this repo builds on a machine
40-# with no `cc` at all. Unlike vidya there is no Android row here — both shared48+# with no `cc` at all.
41-# objects this repo ships are desktop, and the APK takes its libvidya from49+#
42-# vidya's own buck2 build.50+# Android is the one target it cannot serve. zig brings a glibc sysroot and no
51+# bionic, so there is nothing there to link an Android object against; the
52+# NDK's own clang is the driver that knows where libc.so and the platform stubs
53+# live. Only the archive and the wrapper change — the toolchain either side of
54+# them is the same construction, which is the point of asking a wrapper for a
55+# mode rather than naming binaries here.
43 hermetic_cxx_toolchain(56 hermetic_cxx_toolchain(
44 name = "cxx",57 name = "cxx",
45- dist = "toolchains//dist:zig",58+ dist = select({
46- wrapper = "toolchains//:zcc.sh",59+ "DEFAULT": "toolchains//dist:zig",
60+ # DotSlash, not the NDK: ndk.sh fetches the NDK where it runs, which is
61+ # the difference between a worker downloading three gigabytes from
62+ # Google and this machine uploading them to the CAS first.
63+ "prelude//os:android": "toolchains//dist:dotslash",
64+ }),
65+ manifest = select({
66+ "DEFAULT": None,
67+ "prelude//os:android": "root//scripts:android-ndk.dotslash",
68+ }),
69+ wrapper = select({
70+ "DEFAULT": "toolchains//:zcc.sh",
71+ "prelude//os:android": "toolchains//:ndk.sh",
72+ }),
73+ platform_name = select({
74+ "DEFAULT": "x86_64",
75+ "prelude//os:android": "arm64",
76+ }),
47 visibility = ["PUBLIC"],77 visibility = ["PUBLIC"],
48 )78 )
49 79
@@ -65,8 +95,32 @@ hermetic_rust_toolchain(
65 dist = "toolchains//dist:rust",95 dist = "toolchains//dist:rust",
66 wrapper = "toolchains//:rustc.sh",96 wrapper = "toolchains//:rustc.sh",
67 default_edition = "2021",97 default_edition = "2021",
68- rustc_flags = [98+ # Pipelined builds compile each crate against its dependencies'
69- "-Clinker=" + _SCRIPTS + "/zcc",99+ # `-Zno-codegen` metadata rather than their rlibs. On the Android graph the
100+ # two disagree for android-activity, and what comes out the far side is a
101+ # link that cannot find winit — or glutin-winit, or anything else that
102+ # reaches the event loop through it. Build real rlibs there. The desktop
103+ # graph pipelines fine and keeps the parallelism.
104+ nightly_features = select({
105+ "DEFAULT": True,
106+ "prelude//os:android": False,
107+ }),
108+ # Which libstd rustc is given. The desktop configuration takes the
109+ # component the dist tarball carries for its own host; Android needs two
110+ # triples in one directory, so it takes the stitched one.
111+ sysroot = select({
112+ "DEFAULT": None,
113+ "prelude//os:android": "toolchains//dist:android-sysroot",
114+ }),
115+ rustc_flags = select({
116+ "DEFAULT": ["-Clinker=" + _SCRIPTS + "/zcc"],
117+ # zig has no bionic to link against; the NDK's clang does. The cfg
118+ # nothing reads is what keeps the digests apart, as above.
119+ "prelude//os:android": [
120+ "-Clinker=" + _SCRIPTS + "/android-cc",
121+ "--cfg=jolt_ndk=\"" + _NDK_VERSION + "\"",
122+ ],
123+ }) + [
70 # What [profile.release] in Cargo.toml says, and for the reason given124 # What [profile.release] in Cargo.toml says, and for the reason given
71 # there: the tree walk and the texture uploads are hot every frame, and125 # there: the tree walk and the texture uploads are hot every frame, and
72 # the media plane decodes H.264. buck2 optimises nothing by default, so126 # the media plane decodes H.264. buck2 optimises nothing by default, so
@@ -75,10 +129,13 @@ hermetic_rust_toolchain(
75 "-Copt-level=2",129 "-Copt-level=2",
76 "--cfg=jolt_cc=\"" + _ZIG_VERSION + "\"",130 "--cfg=jolt_cc=\"" + _ZIG_VERSION + "\"",
77 ],131 ],
78- # A plain string rather than a select: the sysroot path inside the dist132+ # Only triples there is an archive for can be named here: the desktop one
79- # tarball is built from this, and only the triples dist.bzl fetches an133+ # comes out of the dist tarball, the Android one out of the libstd fetched
80- # archive for can be named here.134+ # beside it.
81- rustc_target_triple = "x86_64-unknown-linux-gnu",135+ rustc_target_triple = select({
136+ "DEFAULT": "x86_64-unknown-linux-gnu",
137+ "prelude//os:android": "aarch64-linux-android",
138+ }),
82 visibility = ["PUBLIC"],139 visibility = ["PUBLIC"],
83 )140 )
84 141
@@ -90,6 +147,12 @@ export_file(
90 visibility = ["PUBLIC"],147 visibility = ["PUBLIC"],
91 )148 )
92 149
150+export_file(
151+ name = "ndk.sh",
152+ mode = "reference",
153+ visibility = ["PUBLIC"],
154+)
155+
93 export_file(156 export_file(
94 name = "rustc.sh",157 name = "rustc.sh",
95 mode = "reference",158 mode = "reference",
modified toolchains/defs.bzl +32 -6
@@ -22,10 +22,19 @@ def _hermetic_rust_toolchain_impl(ctx):
2222 # rustc goes through a wrapper because the sysroot cannot travel any other
2323 # way: rustc_flags takes plain strings, so an artifact path cannot be put
2424 # there, and putting --sysroot on the compiler unconditionally breaks
25- # buck's own rustc_cfg action, which passes an empty one. Both the wrapper
26- # and the tarball are inputs, which is the point.
25+ # buck's own rustc_cfg action, which passes an empty one. The wrapper, the
26+ # tarball and the sysroot are all inputs, which is the point.
2727 wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0]
28- compiler = cmd_args(wrapper, dist, ctx.attrs.rustc_target_triple)
28+
29+ # Cross-compiling needs a sysroot holding two triples, which no single
30+ # archive carries, so the Android configuration passes one stitched
31+ # together in toolchains//dist. Everything else takes the component the
32+ # dist tarball already has for the host it runs on.
33+ if ctx.attrs.sysroot != None:
34+ sysroot = ctx.attrs.sysroot[DefaultInfo].default_outputs[0]
35+ else:
36+ sysroot = cmd_args(dist, format = "{}/rust-std-" + ctx.attrs.rustc_target_triple)
37+ compiler = cmd_args(wrapper, dist, sysroot)
2938 rustdoc = cmd_args(dist, format = "{}/rustc/bin/rustdoc")
3039
3140 return [
@@ -65,6 +74,7 @@ hermetic_rust_toolchain = rule(
6574 "rustc_binary_flags": attrs.list(attrs.arg(), default = []),
6675 "rustc_flags": attrs.list(attrs.arg(), default = []),
6776 "rustc_target_triple": attrs.string(),
77+ "sysroot": attrs.option(attrs.dep(providers = [DefaultInfo]), default = None),
6878 "rustc_test_flags": attrs.list(attrs.arg(), default = []),
6979 "rustdoc_flags": attrs.list(attrs.arg(), default = []),
7080 "warn_lints": attrs.list(attrs.string(), default = []),
@@ -77,8 +87,12 @@ hermetic_rust_toolchain = rule(
7787 # The prelude's system_cxx_toolchain cannot be made hermetic: it takes its
7888 # tools as plain strings, so nothing it names becomes an input, and it
7989 # hardcodes nm/objcopy/objdump/ranlib/strip as bare PATH lookups regardless of
80-# what is passed. This is the same construction with zig behind every tool that
81-# this graph actually runs.
90+# what is passed. This is the same construction with a fetched archive behind
91+# every tool that this graph actually runs.
92+#
93+# Which archive is the caller's business: the wrapper is asked for a mode — cc,
94+# c++, ar, ranlib or objcopy — and what it runs for that mode is between it and
95+# the dist beside it. zcc.sh answers with zig, ndk.sh with the NDK's clang.
8296 load("@prelude//cxx:cxx_toolchain_types.bzl", "BinaryUtilitiesInfo", "CCompilerInfo", "CxxCompilerInfo", "CxxInternalTools", "CxxPlatformInfo", "CxxToolchainInfo", "DepTrackingMode", "LinkerInfo", "LinkerType", "PicBehavior", "RuntimeDependencyHandling", "ShlibInterfacesMode")
8397 load("@prelude//cxx:headers.bzl", "HeaderMode")
8498 load("@prelude//linking:link_info.bzl", "LinkOrdering", "LinkStyle")
@@ -88,7 +102,14 @@ def _hermetic_cxx_toolchain_impl(ctx: AnalysisContext):
88102 dist = ctx.attrs.dist[DefaultInfo].default_outputs[0]
89103 wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0]
90104
105+ # A wrapper takes the directory it was given, then whatever else it needs
106+ # to find its tools, then the mode. zcc.sh needs nothing else — zig is in
107+ # the directory. ndk.sh is handed a manifest, because what it runs is not
108+ # an input at all: see the note there.
91109 def zig(mode):
110+ if ctx.attrs.manifest != None:
111+ manifest = ctx.attrs.manifest[DefaultInfo].default_outputs[0]
112+ return RunInfo(args = cmd_args(wrapper, dist, manifest, mode))
92113 return RunInfo(args = cmd_args(wrapper, dist, mode))
93114
94115 cc = zig("cc")
@@ -170,7 +191,10 @@ def _hermetic_cxx_toolchain_impl(ctx: AnalysisContext):
170191 use_dep_files = True,
171192 runtime_dependency_handling = RuntimeDependencyHandling("no_symlink"),
172193 ),
173- CxxPlatformInfo(name = "x86_64"),
194+ # Names the target architecture, not the host's: this is what the
195+ # prelude stamps into per-platform output paths, so an Android build
196+ # and a desktop one cannot land on the same one.
197+ CxxPlatformInfo(name = ctx.attrs.platform_name),
174198 ]
175199
176200 hermetic_cxx_toolchain = rule(
@@ -183,6 +207,8 @@ hermetic_cxx_toolchain = rule(
183207 "link_flags": attrs.list(attrs.arg(), default = []),
184208 "link_ordering": attrs.option(attrs.enum(LinkOrdering.values()), default = None),
185209 "link_style": attrs.string(default = "shared"),
210+ "manifest": attrs.option(attrs.dep(providers = [DefaultInfo]), default = None),
211+ "platform_name": attrs.string(default = "x86_64"),
186212 "wrapper": attrs.dep(providers = [DefaultInfo]),
187213 },
188214 is_toolchain_rule = True,
@@ -22,10 +22,19 @@ def _hermetic_rust_toolchain_impl(ctx):
22 # rustc goes through a wrapper because the sysroot cannot travel any other22 # rustc goes through a wrapper because the sysroot cannot travel any other
23 # way: rustc_flags takes plain strings, so an artifact path cannot be put23 # way: rustc_flags takes plain strings, so an artifact path cannot be put
24 # there, and putting --sysroot on the compiler unconditionally breaks24 # there, and putting --sysroot on the compiler unconditionally breaks
25- # buck's own rustc_cfg action, which passes an empty one. Both the wrapper25+ # buck's own rustc_cfg action, which passes an empty one. The wrapper, the
26- # and the tarball are inputs, which is the point.26+ # tarball and the sysroot are all inputs, which is the point.
27 wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0]27 wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0]
28- compiler = cmd_args(wrapper, dist, ctx.attrs.rustc_target_triple)28+
29+ # Cross-compiling needs a sysroot holding two triples, which no single
30+ # archive carries, so the Android configuration passes one stitched
31+ # together in toolchains//dist. Everything else takes the component the
32+ # dist tarball already has for the host it runs on.
33+ if ctx.attrs.sysroot != None:
34+ sysroot = ctx.attrs.sysroot[DefaultInfo].default_outputs[0]
35+ else:
36+ sysroot = cmd_args(dist, format = "{}/rust-std-" + ctx.attrs.rustc_target_triple)
37+ compiler = cmd_args(wrapper, dist, sysroot)
29 rustdoc = cmd_args(dist, format = "{}/rustc/bin/rustdoc")38 rustdoc = cmd_args(dist, format = "{}/rustc/bin/rustdoc")
30 39
31 return [40 return [
@@ -65,6 +74,7 @@ hermetic_rust_toolchain = rule(
65 "rustc_binary_flags": attrs.list(attrs.arg(), default = []),74 "rustc_binary_flags": attrs.list(attrs.arg(), default = []),
66 "rustc_flags": attrs.list(attrs.arg(), default = []),75 "rustc_flags": attrs.list(attrs.arg(), default = []),
67 "rustc_target_triple": attrs.string(),76 "rustc_target_triple": attrs.string(),
77+ "sysroot": attrs.option(attrs.dep(providers = [DefaultInfo]), default = None),
68 "rustc_test_flags": attrs.list(attrs.arg(), default = []),78 "rustc_test_flags": attrs.list(attrs.arg(), default = []),
69 "rustdoc_flags": attrs.list(attrs.arg(), default = []),79 "rustdoc_flags": attrs.list(attrs.arg(), default = []),
70 "warn_lints": attrs.list(attrs.string(), default = []),80 "warn_lints": attrs.list(attrs.string(), default = []),
@@ -77,8 +87,12 @@ hermetic_rust_toolchain = rule(
77 # The prelude's system_cxx_toolchain cannot be made hermetic: it takes its87 # The prelude's system_cxx_toolchain cannot be made hermetic: it takes its
78 # tools as plain strings, so nothing it names becomes an input, and it88 # tools as plain strings, so nothing it names becomes an input, and it
79 # hardcodes nm/objcopy/objdump/ranlib/strip as bare PATH lookups regardless of89 # hardcodes nm/objcopy/objdump/ranlib/strip as bare PATH lookups regardless of
80-# what is passed. This is the same construction with zig behind every tool that90+# what is passed. This is the same construction with a fetched archive behind
81-# this graph actually runs.91+# every tool that this graph actually runs.
92+#
93+# Which archive is the caller's business: the wrapper is asked for a mode — cc,
94+# c++, ar, ranlib or objcopy — and what it runs for that mode is between it and
95+# the dist beside it. zcc.sh answers with zig, ndk.sh with the NDK's clang.
82 load("@prelude//cxx:cxx_toolchain_types.bzl", "BinaryUtilitiesInfo", "CCompilerInfo", "CxxCompilerInfo", "CxxInternalTools", "CxxPlatformInfo", "CxxToolchainInfo", "DepTrackingMode", "LinkerInfo", "LinkerType", "PicBehavior", "RuntimeDependencyHandling", "ShlibInterfacesMode")96 load("@prelude//cxx:cxx_toolchain_types.bzl", "BinaryUtilitiesInfo", "CCompilerInfo", "CxxCompilerInfo", "CxxInternalTools", "CxxPlatformInfo", "CxxToolchainInfo", "DepTrackingMode", "LinkerInfo", "LinkerType", "PicBehavior", "RuntimeDependencyHandling", "ShlibInterfacesMode")
83 load("@prelude//cxx:headers.bzl", "HeaderMode")97 load("@prelude//cxx:headers.bzl", "HeaderMode")
84 load("@prelude//linking:link_info.bzl", "LinkOrdering", "LinkStyle")98 load("@prelude//linking:link_info.bzl", "LinkOrdering", "LinkStyle")
@@ -88,7 +102,14 @@ def _hermetic_cxx_toolchain_impl(ctx: AnalysisContext):
88 dist = ctx.attrs.dist[DefaultInfo].default_outputs[0]102 dist = ctx.attrs.dist[DefaultInfo].default_outputs[0]
89 wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0]103 wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0]
90 104
105+ # A wrapper takes the directory it was given, then whatever else it needs
106+ # to find its tools, then the mode. zcc.sh needs nothing else — zig is in
107+ # the directory. ndk.sh is handed a manifest, because what it runs is not
108+ # an input at all: see the note there.
91 def zig(mode):109 def zig(mode):
110+ if ctx.attrs.manifest != None:
111+ manifest = ctx.attrs.manifest[DefaultInfo].default_outputs[0]
112+ return RunInfo(args = cmd_args(wrapper, dist, manifest, mode))
92 return RunInfo(args = cmd_args(wrapper, dist, mode))113 return RunInfo(args = cmd_args(wrapper, dist, mode))
93 114
94 cc = zig("cc")115 cc = zig("cc")
@@ -170,7 +191,10 @@ def _hermetic_cxx_toolchain_impl(ctx: AnalysisContext):
170 use_dep_files = True,191 use_dep_files = True,
171 runtime_dependency_handling = RuntimeDependencyHandling("no_symlink"),192 runtime_dependency_handling = RuntimeDependencyHandling("no_symlink"),
172 ),193 ),
173- CxxPlatformInfo(name = "x86_64"),194+ # Names the target architecture, not the host's: this is what the
195+ # prelude stamps into per-platform output paths, so an Android build
196+ # and a desktop one cannot land on the same one.
197+ CxxPlatformInfo(name = ctx.attrs.platform_name),
174 ]198 ]
175 199
176 hermetic_cxx_toolchain = rule(200 hermetic_cxx_toolchain = rule(
@@ -183,6 +207,8 @@ hermetic_cxx_toolchain = rule(
183 "link_flags": attrs.list(attrs.arg(), default = []),207 "link_flags": attrs.list(attrs.arg(), default = []),
184 "link_ordering": attrs.option(attrs.enum(LinkOrdering.values()), default = None),208 "link_ordering": attrs.option(attrs.enum(LinkOrdering.values()), default = None),
185 "link_style": attrs.string(default = "shared"),209 "link_style": attrs.string(default = "shared"),
210+ "manifest": attrs.option(attrs.dep(providers = [DefaultInfo]), default = None),
211+ "platform_name": attrs.string(default = "x86_64"),
186 "wrapper": attrs.dep(providers = [DefaultInfo]),212 "wrapper": attrs.dep(providers = [DefaultInfo]),
187 },213 },
188 is_toolchain_rule = True,214 is_toolchain_rule = True,
modified toolchains/dist.bzl +56 -1
@@ -19,7 +19,7 @@ def _archive(name, tool, platform):
1919 name = name,
2020 urls = [entry["url"]],
2121 sha256 = entry["sha256"],
22- strip_prefix = entry["strip_prefix"],
22+ strip_prefix = entry["strip_prefix"] or None,
2323 type = entry["type"],
2424 visibility = ["PUBLIC"],
2525 )
@@ -31,3 +31,58 @@ def rust_dist(name, platform):
3131 def zig_dist(name, platform):
3232 """zig, which is the C and C++ compiler, the linker and the archiver."""
3333 _archive(name, "zig", platform)
34+
35+def rust_std_android_dist(name, platform):
36+ """The Rust standard library, built for the device.
37+
38+ A component rather than a toolchain: the dist tarball above carries a
39+ libstd for the host it runs on and no other, so cross-compiling needs this
40+ fetched beside it.
41+ """
42+ _archive(name, "rust-std-android", platform)
43+
44+def rust_android_sysroot(name, rust, rust_std, host_triple, target_triple):
45+ """The two libstds above, as the one sysroot rustc will take.
46+
47+ rustc resolves a target's libstd from sysroot/lib/rustlib/<triple>, and
48+ accepts --sysroot once. Cross-compiling therefore needs a sysroot holding
49+ both triples: the host's, for build scripts and proc macros, and the
50+ target's, for the crate being built. The components ship separately, so
51+ they are stitched together here.
52+
53+ scripts/rustc does this at run time, in a cache under the user's home. As
54+ an artifact instead it is an input like anything else — hashed into the
55+ actions that use it, and shippable to a worker.
56+ """
57+ native.genrule(
58+ name = name,
59+ out = "sysroot",
60+ cmd = " && ".join([
61+ "mkdir -p $OUT/lib",
62+ "cp -a $(location {})/rust-std-{}/lib/. $OUT/lib/".format(rust, host_triple),
63+ "cp -a $(location {})/rust-std-{}/lib/rustlib/{} $OUT/lib/rustlib/".format(
64+ rust_std,
65+ target_triple,
66+ target_triple,
67+ ),
68+ ]),
69+ visibility = ["PUBLIC"],
70+ )
71+
72+def dotslash_dist(name, platform):
73+ """DotSlash, as an input, so an action can fetch what it needs where it runs.
74+
75+ The NDK is the one archive this repo does not hand to buck: unpacked it is
76+ three gigabytes, and an input that size has to reach a worker through the
77+ CAS. Fetching it on the worker instead moves the same pinned bytes over the
78+ same digest, from Google rather than from here.
79+ """
80+ _archive(name, "dotslash", platform)
81+
82+def android_ndk_dist(name, platform):
83+ """The NDK: clang and the LLVM binutils, over a bionic sysroot.
84+
85+ The one archive here that is not a compiler for the host — it is fetched
86+ for the host that runs it and builds only for Android.
87+ """
88+ _archive(name, "android-ndk", platform)
@@ -19,7 +19,7 @@ def _archive(name, tool, platform):
19 name = name,19 name = name,
20 urls = [entry["url"]],20 urls = [entry["url"]],
21 sha256 = entry["sha256"],21 sha256 = entry["sha256"],
22- strip_prefix = entry["strip_prefix"],22+ strip_prefix = entry["strip_prefix"] or None,
23 type = entry["type"],23 type = entry["type"],
24 visibility = ["PUBLIC"],24 visibility = ["PUBLIC"],
25 )25 )
@@ -31,3 +31,58 @@ def rust_dist(name, platform):
31 def zig_dist(name, platform):31 def zig_dist(name, platform):
32 """zig, which is the C and C++ compiler, the linker and the archiver."""32 """zig, which is the C and C++ compiler, the linker and the archiver."""
33 _archive(name, "zig", platform)33 _archive(name, "zig", platform)
34+
35+def rust_std_android_dist(name, platform):
36+ """The Rust standard library, built for the device.
37+
38+ A component rather than a toolchain: the dist tarball above carries a
39+ libstd for the host it runs on and no other, so cross-compiling needs this
40+ fetched beside it.
41+ """
42+ _archive(name, "rust-std-android", platform)
43+
44+def rust_android_sysroot(name, rust, rust_std, host_triple, target_triple):
45+ """The two libstds above, as the one sysroot rustc will take.
46+
47+ rustc resolves a target's libstd from sysroot/lib/rustlib/<triple>, and
48+ accepts --sysroot once. Cross-compiling therefore needs a sysroot holding
49+ both triples: the host's, for build scripts and proc macros, and the
50+ target's, for the crate being built. The components ship separately, so
51+ they are stitched together here.
52+
53+ scripts/rustc does this at run time, in a cache under the user's home. As
54+ an artifact instead it is an input like anything else — hashed into the
55+ actions that use it, and shippable to a worker.
56+ """
57+ native.genrule(
58+ name = name,
59+ out = "sysroot",
60+ cmd = " && ".join([
61+ "mkdir -p $OUT/lib",
62+ "cp -a $(location {})/rust-std-{}/lib/. $OUT/lib/".format(rust, host_triple),
63+ "cp -a $(location {})/rust-std-{}/lib/rustlib/{} $OUT/lib/rustlib/".format(
64+ rust_std,
65+ target_triple,
66+ target_triple,
67+ ),
68+ ]),
69+ visibility = ["PUBLIC"],
70+ )
71+
72+def dotslash_dist(name, platform):
73+ """DotSlash, as an input, so an action can fetch what it needs where it runs.
74+
75+ The NDK is the one archive this repo does not hand to buck: unpacked it is
76+ three gigabytes, and an input that size has to reach a worker through the
77+ CAS. Fetching it on the worker instead moves the same pinned bytes over the
78+ same digest, from Google rather than from here.
79+ """
80+ _archive(name, "dotslash", platform)
81+
82+def android_ndk_dist(name, platform):
83+ """The NDK: clang and the LLVM binutils, over a bionic sysroot.
84+
85+ The one archive here that is not a compiler for the host — it is fetched
86+ for the host that runs it and builds only for Android.
87+ """
88+ _archive(name, "android-ndk", platform)
modified toolchains/dist/BUCK +28 -1
@@ -1,7 +1,34 @@
11 # The pinned toolchains, fetched by buck rather than by the shims. See
22 # ../dist.bzl; the versions and digests come from scripts/*.dotslash.
3-load("//:dist.bzl", "rust_dist", "zig_dist")
3+load(
4+ "//:dist.bzl",
5+ "android_ndk_dist",
6+ "dotslash_dist",
7+ "rust_android_sysroot",
8+ "rust_dist",
9+ "rust_std_android_dist",
10+ "zig_dist",
11+)
412
513 rust_dist(name = "rust", platform = "linux-x86_64")
614
715 zig_dist(name = "zig", platform = "linux-x86_64")
16+
17+# What fetches the NDK, rather than the NDK. See dist.bzl, and ../ndk.sh for
18+# why the big archive is the one thing here buck does not carry.
19+dotslash_dist(name = "dotslash", platform = "linux-x86_64")
20+
21+# Kept for a build that would rather have the NDK as a real input than fetch it
22+# where it runs — nothing depends on this today.
23+android_ndk_dist(name = "android-ndk", platform = "linux-x86_64")
24+
25+rust_std_android_dist(name = "rust-std-android", platform = "linux-x86_64")
26+
27+# What the Rust toolchain hands rustc as --sysroot when the target is Android.
28+rust_android_sysroot(
29+ name = "android-sysroot",
30+ rust = ":rust",
31+ rust_std = ":rust-std-android",
32+ host_triple = "x86_64-unknown-linux-gnu",
33+ target_triple = "aarch64-linux-android",
34+)
@@ -1,7 +1,34 @@
1 # The pinned toolchains, fetched by buck rather than by the shims. See1 # The pinned toolchains, fetched by buck rather than by the shims. See
2 # ../dist.bzl; the versions and digests come from scripts/*.dotslash.2 # ../dist.bzl; the versions and digests come from scripts/*.dotslash.
3-load("//:dist.bzl", "rust_dist", "zig_dist")3+load(
4+ "//:dist.bzl",
5+ "android_ndk_dist",
6+ "dotslash_dist",
7+ "rust_android_sysroot",
8+ "rust_dist",
9+ "rust_std_android_dist",
10+ "zig_dist",
11+)
4 12
5 rust_dist(name = "rust", platform = "linux-x86_64")13 rust_dist(name = "rust", platform = "linux-x86_64")
6 14
7 zig_dist(name = "zig", platform = "linux-x86_64")15 zig_dist(name = "zig", platform = "linux-x86_64")
16+
17+# What fetches the NDK, rather than the NDK. See dist.bzl, and ../ndk.sh for
18+# why the big archive is the one thing here buck does not carry.
19+dotslash_dist(name = "dotslash", platform = "linux-x86_64")
20+
21+# Kept for a build that would rather have the NDK as a real input than fetch it
22+# where it runs — nothing depends on this today.
23+android_ndk_dist(name = "android-ndk", platform = "linux-x86_64")
24+
25+rust_std_android_dist(name = "rust-std-android", platform = "linux-x86_64")
26+
27+# What the Rust toolchain hands rustc as --sysroot when the target is Android.
28+rust_android_sysroot(
29+ name = "android-sysroot",
30+ rust = ":rust",
31+ rust_std = ":rust-std-android",
32+ host_triple = "x86_64-unknown-linux-gnu",
33+ target_triple = "aarch64-linux-android",
34+)
modified toolchains/dist/generated.bzl +72 -0
@@ -4,6 +4,78 @@
44 # Bump the DotSlash file and run `just sync-dist`.
55
66 DIST = {
7+ "android-ndk": {
8+ "linux-x86_64": {
9+ "url": "https://dl.google.com/android/repository/android-ndk-r29-linux.zip",
10+ "sha256": "4abbbcdc842f3d4879206e9695d52709603e52dd68d3c1fff04b3b5e7a308ecf",
11+ "strip_prefix": "android-ndk-r29",
12+ "type": "zip"
13+ },
14+ "macos-aarch64": {
15+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip",
16+ "sha256": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
17+ "strip_prefix": "android-ndk-r29",
18+ "type": "zip"
19+ },
20+ "macos-x86_64": {
21+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip",
22+ "sha256": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
23+ "strip_prefix": "android-ndk-r29",
24+ "type": "zip"
25+ }
26+ },
27+ "dotslash": {
28+ "linux-aarch64": {
29+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.arm64.v0.5.9.tar.gz",
30+ "sha256": "e58374446dacd4a228388cd275d974bbd8f946916b513cf0eb86bdff5d9d675e",
31+ "strip_prefix": "",
32+ "type": "tar.gz"
33+ },
34+ "linux-x86_64": {
35+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.x86_64.v0.5.9.tar.gz",
36+ "sha256": "4c75c6eb7890ae35993b962073f6d9bbe78b42b81a5691303ad70f63bfbf7196",
37+ "strip_prefix": "",
38+ "type": "tar.gz"
39+ },
40+ "macos-aarch64": {
41+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-arm64.v0.5.9.tar.gz",
42+ "sha256": "eef15be1b554de7b1409cb0c69c24f2cd04f19c0f101ad69fcdf9d66d4d51639",
43+ "strip_prefix": "",
44+ "type": "tar.gz"
45+ },
46+ "macos-x86_64": {
47+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-amd64.v0.5.9.tar.gz",
48+ "sha256": "51446593596ffe4cd22bc43a0fe6d82dd367758eb53cb4ade36d6a684ed08469",
49+ "strip_prefix": "",
50+ "type": "tar.gz"
51+ }
52+ },
53+ "rust-std-android": {
54+ "linux-aarch64": {
55+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
56+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
57+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
58+ "type": "tar.gz"
59+ },
60+ "linux-x86_64": {
61+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
62+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
63+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
64+ "type": "tar.gz"
65+ },
66+ "macos-aarch64": {
67+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
68+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
69+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
70+ "type": "tar.gz"
71+ },
72+ "macos-x86_64": {
73+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
74+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
75+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
76+ "type": "tar.gz"
77+ }
78+ },
779 "rust": {
880 "linux-aarch64": {
981 "url": "https://static.rust-lang.org/dist/2026-08-20/rust-1.98.0-aarch64-unknown-linux-gnu.tar.gz",
@@ -4,6 +4,78 @@
4 # Bump the DotSlash file and run `just sync-dist`.4 # Bump the DotSlash file and run `just sync-dist`.
5 5
6 DIST = {6 DIST = {
7+ "android-ndk": {
8+ "linux-x86_64": {
9+ "url": "https://dl.google.com/android/repository/android-ndk-r29-linux.zip",
10+ "sha256": "4abbbcdc842f3d4879206e9695d52709603e52dd68d3c1fff04b3b5e7a308ecf",
11+ "strip_prefix": "android-ndk-r29",
12+ "type": "zip"
13+ },
14+ "macos-aarch64": {
15+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip",
16+ "sha256": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
17+ "strip_prefix": "android-ndk-r29",
18+ "type": "zip"
19+ },
20+ "macos-x86_64": {
21+ "url": "https://dl.google.com/android/repository/android-ndk-r29-darwin.zip",
22+ "sha256": "ce5e4b100ec5fe5be4eb3edcb2c02528824ff9cda3860f5304619be6c3da34d3",
23+ "strip_prefix": "android-ndk-r29",
24+ "type": "zip"
25+ }
26+ },
27+ "dotslash": {
28+ "linux-aarch64": {
29+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.arm64.v0.5.9.tar.gz",
30+ "sha256": "e58374446dacd4a228388cd275d974bbd8f946916b513cf0eb86bdff5d9d675e",
31+ "strip_prefix": "",
32+ "type": "tar.gz"
33+ },
34+ "linux-x86_64": {
35+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-linux-musl.x86_64.v0.5.9.tar.gz",
36+ "sha256": "4c75c6eb7890ae35993b962073f6d9bbe78b42b81a5691303ad70f63bfbf7196",
37+ "strip_prefix": "",
38+ "type": "tar.gz"
39+ },
40+ "macos-aarch64": {
41+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-arm64.v0.5.9.tar.gz",
42+ "sha256": "eef15be1b554de7b1409cb0c69c24f2cd04f19c0f101ad69fcdf9d66d4d51639",
43+ "strip_prefix": "",
44+ "type": "tar.gz"
45+ },
46+ "macos-x86_64": {
47+ "url": "https://github.com/facebook/dotslash/releases/download/v0.5.9/dotslash-macos-amd64.v0.5.9.tar.gz",
48+ "sha256": "51446593596ffe4cd22bc43a0fe6d82dd367758eb53cb4ade36d6a684ed08469",
49+ "strip_prefix": "",
50+ "type": "tar.gz"
51+ }
52+ },
53+ "rust-std-android": {
54+ "linux-aarch64": {
55+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
56+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
57+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
58+ "type": "tar.gz"
59+ },
60+ "linux-x86_64": {
61+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
62+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
63+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
64+ "type": "tar.gz"
65+ },
66+ "macos-aarch64": {
67+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
68+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
69+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
70+ "type": "tar.gz"
71+ },
72+ "macos-x86_64": {
73+ "url": "https://static.rust-lang.org/dist/2026-08-20/rust-std-1.98.0-aarch64-linux-android.tar.gz",
74+ "sha256": "ac1be298e0500438090baa492ac8858473466e386f780878d65d42e575679514",
75+ "strip_prefix": "rust-std-1.98.0-aarch64-linux-android",
76+ "type": "tar.gz"
77+ }
78+ },
7 "rust": {79 "rust": {
8 "linux-aarch64": {80 "linux-aarch64": {
9 "url": "https://static.rust-lang.org/dist/2026-08-20/rust-1.98.0-aarch64-unknown-linux-gnu.tar.gz",81 "url": "https://static.rust-lang.org/dist/2026-08-20/rust-1.98.0-aarch64-unknown-linux-gnu.tar.gz",
added toolchains/ndk.sh +54 -0
new file mode 100755
@@ -0,0 +1,54 @@
1+#!/bin/sh
2+# The NDK, as the C toolchain for Android, fetched where the action runs.
3+#
4+# Unlike zcc.sh, the archive behind this is not an input. That is deliberate:
5+# unpacked it is three gigabytes, and an input has to travel to a worker
6+# through the CAS — uploaded from here first, then materialised there. What
7+# travels instead is DotSlash (under a megabyte) and the manifest naming the
8+# archive, and the worker fetches the same bytes under the same digest from
9+# Google. A machine that has already fetched it does nothing.
10+#
11+# The cost of that is honest to state: what the compiler *is* stops being part
12+# of the action's cache digest. The manifest is an input, so a version bump
13+# still invalidates; a machine whose DotSlash cache holds something else under
14+# that digest cannot happen, because the digest is what the cache is keyed on.
15+#
16+# $1 is the directory holding the DotSlash binary, $2 the manifest to resolve
17+# and $3 the mode — cc, c++, ar, ranlib or objcopy. buck passes all three.
18+set -e
19+dotslash=$1/dotslash
20+manifest=$2
21+mode=$3
22+shift 3
23+
24+# ANDROID_NDK_HOME still wins, for a machine that has one installed.
25+ndk=${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}
26+if [ -n "$ndk" ]; then
27+ for host in linux-x86_64 darwin-x86_64; do
28+ if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then
29+ bin=$ndk/toolchains/llvm/prebuilt/$host/bin
30+ break
31+ fi
32+ done
33+ [ -n "${bin:-}" ] || { echo "toolchains/ndk.sh: no llvm prebuilt under $ndk" >&2; exit 1; }
34+else
35+ # The manifest names bin/clang inside the archive; DotSlash unpacks the
36+ # whole NDK around it, which is what the driver needs — its sysroot and its
37+ # resource directory are found relative to the binary.
38+ bin=$(dirname "$("$dotslash" -- fetch "$manifest")")
39+fi
40+
41+# API 28 is frq's min_sdk_version, and the floor for what the app needs from
42+# bionic. The NDK ships a wrapper script per API level that adds this flag and
43+# nothing else; passing it here keeps this to one executable out of the
44+# archive, so nothing depends on which permissions survived the unpacking.
45+target=${JOLT_ANDROID_TARGET:-aarch64-linux-android}${JOLT_ANDROID_API:-28}
46+
47+case $mode in
48+ cc) exec "$bin/clang" --target="$target" "$@" ;;
49+ c++) exec "$bin/clang++" --target="$target" "$@" ;;
50+ ar) exec "$bin/llvm-ar" "$@" ;;
51+ ranlib) exec "$bin/llvm-ranlib" "$@" ;;
52+ objcopy) exec "$bin/llvm-objcopy" "$@" ;;
53+ *) echo "toolchains/ndk.sh: unknown mode $mode" >&2; exit 1 ;;
54+esac
new file mode 100755
@@ -0,0 +1,54 @@
1+#!/bin/sh
2+# The NDK, as the C toolchain for Android, fetched where the action runs.
3+#
4+# Unlike zcc.sh, the archive behind this is not an input. That is deliberate:
5+# unpacked it is three gigabytes, and an input has to travel to a worker
6+# through the CAS — uploaded from here first, then materialised there. What
7+# travels instead is DotSlash (under a megabyte) and the manifest naming the
8+# archive, and the worker fetches the same bytes under the same digest from
9+# Google. A machine that has already fetched it does nothing.
10+#
11+# The cost of that is honest to state: what the compiler *is* stops being part
12+# of the action's cache digest. The manifest is an input, so a version bump
13+# still invalidates; a machine whose DotSlash cache holds something else under
14+# that digest cannot happen, because the digest is what the cache is keyed on.
15+#
16+# $1 is the directory holding the DotSlash binary, $2 the manifest to resolve
17+# and $3 the mode — cc, c++, ar, ranlib or objcopy. buck passes all three.
18+set -e
19+dotslash=$1/dotslash
20+manifest=$2
21+mode=$3
22+shift 3
23+
24+# ANDROID_NDK_HOME still wins, for a machine that has one installed.
25+ndk=${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}
26+if [ -n "$ndk" ]; then
27+ for host in linux-x86_64 darwin-x86_64; do
28+ if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then
29+ bin=$ndk/toolchains/llvm/prebuilt/$host/bin
30+ break
31+ fi
32+ done
33+ [ -n "${bin:-}" ] || { echo "toolchains/ndk.sh: no llvm prebuilt under $ndk" >&2; exit 1; }
34+else
35+ # The manifest names bin/clang inside the archive; DotSlash unpacks the
36+ # whole NDK around it, which is what the driver needs — its sysroot and its
37+ # resource directory are found relative to the binary.
38+ bin=$(dirname "$("$dotslash" -- fetch "$manifest")")
39+fi
40+
41+# API 28 is frq's min_sdk_version, and the floor for what the app needs from
42+# bionic. The NDK ships a wrapper script per API level that adds this flag and
43+# nothing else; passing it here keeps this to one executable out of the
44+# archive, so nothing depends on which permissions survived the unpacking.
45+target=${JOLT_ANDROID_TARGET:-aarch64-linux-android}${JOLT_ANDROID_API:-28}
46+
47+case $mode in
48+ cc) exec "$bin/clang" --target="$target" "$@" ;;
49+ c++) exec "$bin/clang++" --target="$target" "$@" ;;
50+ ar) exec "$bin/llvm-ar" "$@" ;;
51+ ranlib) exec "$bin/llvm-ranlib" "$@" ;;
52+ objcopy) exec "$bin/llvm-objcopy" "$@" ;;
53+ *) echo "toolchains/ndk.sh: unknown mode $mode" >&2; exit 1 ;;
54+esac
modified toolchains/rustc.sh +9 -6
@@ -6,17 +6,20 @@
66 # identity is part of the action's cache digest and a remote worker can be
77 # handed the whole thing.
88 #
9-# $1 is the directory the tarball unpacked into, $2 the target triple; buck
10-# passes both because neither is knowable from inside the script.
9+# $1 is the directory the tarball unpacked into and $2 the sysroot to use;
10+# buck passes both because neither is knowable from inside the script. The
11+# sysroot is a separate argument rather than something built from a triple
12+# here: cross-compiling needs two libstds stitched into one directory, and
13+# that stitching is an artifact of its own (toolchains//dist:android-sysroot).
1114 set -e
1215 dist=$1
13-triple=$2
16+sysroot=$2
1417 shift 2
1518
1619 # The dist tarball keeps its components in sibling directories, so the
1720 # compiler's own default sysroot (rustc/) holds no libstd — it lives under
18-# rust-std-<triple>/. The codegen libraries are found by rpath relative to the
19-# binary, so only the sysroot has to be named.
21+# rust-std-<triple>/, which is what buck names above. The codegen libraries are
22+# found by rpath relative to the binary, so only the sysroot has to be named.
2023 #
2124 # Callers that set their own win: buck's rustc_cfg action passes an empty
2225 # --sysroot, and rustc refuses the option twice.
@@ -26,4 +29,4 @@ for arg do
2629 esac
2730 done
2831
29-exec "$dist/rustc/bin/rustc" --sysroot "$dist/rust-std-$triple" "$@"
32+exec "$dist/rustc/bin/rustc" --sysroot "$sysroot" "$@"
@@ -6,17 +6,20 @@
6 # identity is part of the action's cache digest and a remote worker can be6 # identity is part of the action's cache digest and a remote worker can be
7 # handed the whole thing.7 # handed the whole thing.
8 #8 #
9-# $1 is the directory the tarball unpacked into, $2 the target triple; buck9+# $1 is the directory the tarball unpacked into and $2 the sysroot to use;
10-# passes both because neither is knowable from inside the script.10+# buck passes both because neither is knowable from inside the script. The
11+# sysroot is a separate argument rather than something built from a triple
12+# here: cross-compiling needs two libstds stitched into one directory, and
13+# that stitching is an artifact of its own (toolchains//dist:android-sysroot).
11 set -e14 set -e
12 dist=$115 dist=$1
13-triple=$216+sysroot=$2
14 shift 217 shift 2
15 18
16 # The dist tarball keeps its components in sibling directories, so the19 # The dist tarball keeps its components in sibling directories, so the
17 # compiler's own default sysroot (rustc/) holds no libstd — it lives under20 # compiler's own default sysroot (rustc/) holds no libstd — it lives under
18-# rust-std-<triple>/. The codegen libraries are found by rpath relative to the21+# rust-std-<triple>/, which is what buck names above. The codegen libraries are
19-# binary, so only the sysroot has to be named.22+# found by rpath relative to the binary, so only the sysroot has to be named.
20 #23 #
21 # Callers that set their own win: buck's rustc_cfg action passes an empty24 # Callers that set their own win: buck's rustc_cfg action passes an empty
22 # --sysroot, and rustc refuses the option twice.25 # --sysroot, and rustc refuses the option twice.
@@ -26,4 +29,4 @@ for arg do
26 esac29 esac
27 done30 done
28 31
29-exec "$dist/rustc/bin/rustc" --sysroot "$dist/rust-std-$triple" "$@"32+exec "$dist/rustc/bin/rustc" --sysroot "$sysroot" "$@"
added toolchains/selftest/BUCK +43 -0
new file mode 100644
@@ -0,0 +1,43 @@
1+# The Android toolchain, built.
2+#
3+# There is no Android graph in this repo yet — the third-party crates have no
4+# Android platform in reindeer.toml, so nothing that depends on them can be
5+# configured for the device — so these are what keep the toolchain honest in
6+# the meantime. One C object, compiled by the NDK's clang against bionic's
7+# headers, and one Rust one, compiled against the device's libstd and linked
8+# by that same clang. Both configured for the device, neither shipping.
9+#
10+# just buck build toolchains//selftest:abi-android
11+# just buck build toolchains//selftest:std-android
12+cxx_library(
13+ name = "abi",
14+ srcs = ["abi.c"],
15+ visibility = ["PUBLIC"],
16+)
17+
18+# The target configuration moves; the execution platform does not. Building
19+# `:abi` directly would compile it for the host, where it is meant to fail.
20+configured_alias(
21+ name = "abi-android",
22+ actual = ":abi",
23+ platform = "root//platforms:android-arm64",
24+ visibility = ["PUBLIC"],
25+)
26+
27+rust_library(
28+ name = "std",
29+ srcs = ["abi.rs"],
30+ crate_root = "abi.rs",
31+ edition = "2021",
32+ # Shared, so the link runs: a rlib would prove rustc found the target
33+ # libstd and nothing about whether the linker can produce a device object.
34+ preferred_linkage = "shared",
35+ visibility = ["PUBLIC"],
36+)
37+
38+configured_alias(
39+ name = "std-android",
40+ actual = ":std",
41+ platform = "root//platforms:android-arm64",
42+ visibility = ["PUBLIC"],
43+)
new file mode 100644
@@ -0,0 +1,43 @@
1+# The Android toolchain, built.
2+#
3+# There is no Android graph in this repo yet — the third-party crates have no
4+# Android platform in reindeer.toml, so nothing that depends on them can be
5+# configured for the device — so these are what keep the toolchain honest in
6+# the meantime. One C object, compiled by the NDK's clang against bionic's
7+# headers, and one Rust one, compiled against the device's libstd and linked
8+# by that same clang. Both configured for the device, neither shipping.
9+#
10+# just buck build toolchains//selftest:abi-android
11+# just buck build toolchains//selftest:std-android
12+cxx_library(
13+ name = "abi",
14+ srcs = ["abi.c"],
15+ visibility = ["PUBLIC"],
16+)
17+
18+# The target configuration moves; the execution platform does not. Building
19+# `:abi` directly would compile it for the host, where it is meant to fail.
20+configured_alias(
21+ name = "abi-android",
22+ actual = ":abi",
23+ platform = "root//platforms:android-arm64",
24+ visibility = ["PUBLIC"],
25+)
26+
27+rust_library(
28+ name = "std",
29+ srcs = ["abi.rs"],
30+ crate_root = "abi.rs",
31+ edition = "2021",
32+ # Shared, so the link runs: a rlib would prove rustc found the target
33+ # libstd and nothing about whether the linker can produce a device object.
34+ preferred_linkage = "shared",
35+ visibility = ["PUBLIC"],
36+)
37+
38+configured_alias(
39+ name = "std-android",
40+ actual = ":std",
41+ platform = "root//platforms:android-arm64",
42+ visibility = ["PUBLIC"],
43+)
added toolchains/selftest/abi.c +26 -0
new file mode 100644
@@ -0,0 +1,26 @@
1+/* What the Android toolchain is asked to prove.
2+ *
3+ * Nothing ships this: it is the smallest object that can only compile if the
4+ * driver really is the NDK's, really is pointed at bionic, and really is
5+ * building for the device rather than for the host that invoked it. A desktop
6+ * clang answers every one of these the other way, and __ANDROID_API__ comes
7+ * from the --target= suffix, so a missing API level fails here too. */
8+#include <android/api-level.h>
9+#include <jni.h>
10+
11+#if !defined(__ANDROID__)
12+#error "not an Android target"
13+#endif
14+#if !defined(__aarch64__)
15+#error "not an arm64 target"
16+#endif
17+#if __ANDROID_API__ < 28
18+#error "API level below frq's min_sdk_version"
19+#endif
20+
21+/* jni.h is a platform header, so reaching it proves the sysroot is the NDK's
22+ * and not whatever the host has under /usr/include. */
23+jint jolt_android_abi_ok(void) {
24+ return __ANDROID_API__;
25+}
26+
new file mode 100644
@@ -0,0 +1,26 @@
1+/* What the Android toolchain is asked to prove.
2+ *
3+ * Nothing ships this: it is the smallest object that can only compile if the
4+ * driver really is the NDK's, really is pointed at bionic, and really is
5+ * building for the device rather than for the host that invoked it. A desktop
6+ * clang answers every one of these the other way, and __ANDROID_API__ comes
7+ * from the --target= suffix, so a missing API level fails here too. */
8+#include <android/api-level.h>
9+#include <jni.h>
10+
11+#if !defined(__ANDROID__)
12+#error "not an Android target"
13+#endif
14+#if !defined(__aarch64__)
15+#error "not an arm64 target"
16+#endif
17+#if __ANDROID_API__ < 28
18+#error "API level below frq's min_sdk_version"
19+#endif
20+
21+/* jni.h is a platform header, so reaching it proves the sysroot is the NDK's
22+ * and not whatever the host has under /usr/include. */
23+jint jolt_android_abi_ok(void) {
24+ return __ANDROID_API__;
25+}
26+
added toolchains/selftest/abi.rs +22 -0
new file mode 100644
@@ -0,0 +1,22 @@
1+//! The Rust half of what the Android toolchain is asked to prove.
2+//!
3+//! No dependencies, first-party or third: the third-party graph has no Android
4+//! platform yet, so this is what can cross-compile today. It fails to build
5+//! unless rustc was given the device's libstd — `std::os::android` exists on
6+//! no other target — and unless the sysroot also still holds the host's, which
7+//! is what the stitching in toolchains//dist is for.
8+
9+#[cfg(target_os = "android")]
10+pub use std::os::android as _platform;
11+
12+#[cfg(not(target_os = "android"))]
13+compile_error!("not an Android target");
14+
15+/// Named so the symbol is visible in the object; nothing calls it.
16+#[unsafe(no_mangle)]
17+pub extern "C" fn jolt_android_std_ok() -> u32 {
18+ // Reaching libstd's allocator and its formatting machinery is the point:
19+ // a target libstd that failed to load would not get this far.
20+ format!("{}", u32::from(cfg!(target_arch = "aarch64"))).len() as u32
21+}
22+
new file mode 100644
@@ -0,0 +1,22 @@
1+//! The Rust half of what the Android toolchain is asked to prove.
2+//!
3+//! No dependencies, first-party or third: the third-party graph has no Android
4+//! platform yet, so this is what can cross-compile today. It fails to build
5+//! unless rustc was given the device's libstd — `std::os::android` exists on
6+//! no other target — and unless the sysroot also still holds the host's, which
7+//! is what the stitching in toolchains//dist is for.
8+
9+#[cfg(target_os = "android")]
10+pub use std::os::android as _platform;
11+
12+#[cfg(not(target_os = "android"))]
13+compile_error!("not an Android target");
14+
15+/// Named so the symbol is visible in the object; nothing calls it.
16+#[unsafe(no_mangle)]
17+pub extern "C" fn jolt_android_std_ok() -> u32 {
18+ // Reaching libstd's allocator and its formatting machinery is the point:
19+ // a target libstd that failed to load would not get this far.
20+ format!("{}", u32::from(cfg!(target_arch = "aarch64"))).len() as u32
21+}
22+