Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions examples/cross_compilation/android_app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,35 @@ load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@rules_android//rules:rules.bzl", "android_binary")
load("@rules_cc//cc:defs.bzl", "cc_library")
load("@rules_kotlin//kotlin:android.bzl", "kt_android_library")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
load("//swift:swift.bzl", "swift_binary", "swift_library")
load("//swift/toolchains:android_runtime_lib.bzl", "select_android_runtime_lib")

package(default_visibility = ["//visibility:public"])

# `bazel run //examples/cross_compilation/android_app:run` installs the APK on a
# connected device/emulator and launches it, booting a hermetic emulator first
# if nothing is connected (downloaded emulator + system image; adb from
# @androidsdk). macOS/arm64 only — see emulator.bzl.
sh_binary(
name = "run",
srcs = ["run.sh"],
args = [
"$(rlocationpath :app.apk)",
"$(rlocationpath @androidsdk//:adb)",
"$(rlocationpath @android_emulator//:emulator/emulator)",
"$(rlocationpath @android_system_image//:system.img)",
],
data = [
":app.apk",
"@android_emulator//:all",
"@android_emulator//:emulator/emulator",
"@android_system_image//:all",
"@android_system_image//:system.img",
"@androidsdk//:adb",
],
)

android_binary(
name = "app",
manifest = "AndroidManifest.xml",
Expand Down
11 changes: 11 additions & 0 deletions examples/cross_compilation/android_app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ adb install -r bazel-bin/examples/cross_compilation/android_app/app.apk
adb shell am start -n com.example.swiftjni/.MainActivity
```

Or, with nothing preinstalled (no Android Studio, no device), let Bazel boot a
hermetic emulator, install, and launch — macOS/arm64:

```sh
bazel run //examples/cross_compilation/android_app:run
```

This downloads the emulator + an AOSP system image (see `emulator.bzl`) and uses
adb from `@androidsdk`; if a device/emulator is already connected it just installs
and launches on that.

The screen shows **“Hello from Swift, Android!”** — computed by the `Greeter`
`swift_library`, returned through the `@_cdecl` JNI entry point in
`libSwiftJNI.so`, and displayed by Kotlin. (`//.bazelrc` sets
Expand Down
9 changes: 9 additions & 0 deletions examples/cross_compilation/android_app/android.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,12 @@ register_toolchains(
"@androidsdk//:all",
dev_dependency = True,
)

# A hermetic emulator + system image so `bazel run …:run` can launch the example
# with nothing preinstalled (adb comes from @androidsdk).
emulator = use_extension(
"//examples/cross_compilation/android_app:emulator.bzl",
"emulator",
dev_dependency = True,
)
use_repo(emulator, "android_emulator", "android_system_image")
45 changes: 45 additions & 0 deletions examples/cross_compilation/android_app/emulator.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""A dev module extension that downloads a hermetic Android emulator and a
system image, so `bazel run //examples/cross_compilation/android_app:run` can
boot an emulator and launch the example with nothing preinstalled.

macOS/arm64 only, to keep this illustrative delta small (it's the platform the
example's CI runs on). adb comes from `@androidsdk`; only the emulator and the
system image — which `@androidsdk` does not provide — are downloaded here.
"""

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

_EMULATOR_BUILD = """\
package(default_visibility = ["//visibility:public"])

exports_files(["emulator/emulator"])

filegroup(name = "all", srcs = glob(["emulator/**"]))
"""

_SYSTEM_IMAGE_BUILD = """\
package(default_visibility = ["//visibility:public"])

exports_files(["system.img"])

filegroup(name = "all", srcs = glob(["**"]))
"""

def _emulator_impl(_module_ctx):
http_archive(
name = "android_emulator",
build_file_content = _EMULATOR_BUILD,
sha256 = "25378c67fd5bd03178e3a478b866496da8545e969df3a7a26ce9167772ffc026",
url = "https://dl.google.com/android/repository/emulator-darwin_aarch64-15565763.zip",
)

# A regular GPU-enabled AOSP (default) image, not the headless ATD image.
http_archive(
name = "android_system_image",
build_file_content = _SYSTEM_IMAGE_BUILD,
sha256 = "1447958a4c6747c44390ac5f5f4c894be6d1dfce93868a0385a95c5f0ae4c339",
strip_prefix = "arm64-v8a",
url = "https://dl.google.com/android/repository/sys-img/android/arm64-v8a-34_r04.zip",
)

emulator = module_extension(implementation = _emulator_impl)
80 changes: 80 additions & 0 deletions examples/cross_compilation/android_app/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash
# `bazel run //examples/cross_compilation/android_app:run`: installs the example
# APK on a connected device/emulator and launches it. If nothing is connected,
# boots the hermetic emulator (downloaded emulator + AOSP system image) first.
set -euo pipefail

RUNFILES_ROOT="$PWD/.."
APK="$RUNFILES_ROOT/$1"
ADB="$RUNFILES_ROOT/$2"
EMULATOR_BIN="$RUNFILES_ROOT/$3"
SYSIMG_MARKER="$RUNFILES_ROOT/$4"

has_device() {
"$ADB" devices | awk 'NR>1 && $2 == "device"' | grep -q .
}

"$ADB" start-server >/dev/null 2>&1 || true

if ! has_device; then
echo "No device connected; booting the hermetic emulator..."
EMULATOR_DIR="$(cd "$(dirname "$EMULATOR_BIN")" && pwd)"
SYSIMG_DIR="$(cd "$(dirname "$SYSIMG_MARKER")" && pwd)"

STATE="${TMPDIR:-/tmp}/rules_swift_android_example_emulator"
SDK="$STATE/sdk"
AVD_HOME="$STATE/avd"
AVD_DIR="$AVD_HOME/example.avd"
mkdir -p "$SDK/system-images/android-34/default" "$AVD_DIR" "$SDK/platform-tools"

ln -sfn "$EMULATOR_DIR" "$SDK/emulator"
ln -sfn "$SYSIMG_DIR" "$SDK/system-images/android-34/default/arm64-v8a"
ln -sfn "$ADB" "$SDK/platform-tools/adb"

cat > "$AVD_HOME/example.ini" <<EOF
avd.ini.encoding=UTF-8
path=$AVD_DIR
target=android-34
EOF

if [[ ! -f "$AVD_DIR/config.ini" ]]; then
cat > "$AVD_DIR/config.ini" <<EOF
AvdId=example
avd.ini.displayname=rules_swift Android example
abi.type=arm64-v8a
hw.cpu.arch=arm64
image.sysdir.1=system-images/android-34/default/arm64-v8a/
tag.id=default
hw.gpu.enabled=yes
hw.gpu.mode=auto
hw.ramSize=2048
hw.lcd.density=420
hw.lcd.width=1080
hw.lcd.height=1920
PlayStore.enabled=false
EOF
fi

export ANDROID_SDK_ROOT="$SDK"
export ANDROID_AVD_HOME="$AVD_HOME"
EMULATOR_LOG="$STATE/emulator.log"
"$SDK/emulator/emulator" -avd example -no-audio -no-boot-anim -no-snapshot >"$EMULATOR_LOG" 2>&1 &
echo " (emulator log: $EMULATOR_LOG)"

"$ADB" wait-for-device
for _ in $(seq 1 120); do
# Wait for boot *and* the package manager (install races a "Broken pipe"
# otherwise, right after sys.boot_completed flips).
if [[ "$("$ADB" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" == "1" ]] &&
"$ADB" shell pm path android >/dev/null 2>&1; then
break
fi
sleep 2
done
has_device || { echo "error: emulator failed to boot; see $EMULATOR_LOG" >&2; exit 1; }
echo "Emulator booted."
fi

"$ADB" install -r "$APK"
"$ADB" shell am start -n "com.example.swiftjni/.MainActivity"
echo "Launched com.example.swiftjni/.MainActivity"
Loading