From 1c464912a1198cb2fdde6b1ccc937f97d770715a Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 27 Aug 2024 17:13:00 +0200 Subject: [PATCH] Fix `wireguard-go-rs` build for different Android targets --- wireguard-go-rs/build.rs | 148 ++++++++++++++++++++++--- wireguard-go-rs/libwg/Android.mk | 4 +- wireguard-go-rs/libwg/build-android.sh | 63 ----------- 3 files changed, 136 insertions(+), 79 deletions(-) delete mode 100755 wireguard-go-rs/libwg/build-android.sh diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 08905f5e16c7..29759417e941 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -1,4 +1,10 @@ -use std::{borrow::BorrowMut, env, path::PathBuf, process::Command, str}; +use std::{ + borrow::BorrowMut, + env, + path::{Path, PathBuf}, + process::Command, + str, +}; use anyhow::{anyhow, bail, Context}; @@ -14,7 +20,7 @@ fn main() -> anyhow::Result<()> { match target_os.as_str() { "linux" => build_static_lib(Os::Linux, true)?, "macos" => build_static_lib(Os::MacOs, true)?, - "android" => build_android_dynamic_lib()?, + "android" => build_android_dynamic_lib(false)?, // building wireguard-go-rs for windows is not implemented _ => {} } @@ -34,6 +40,27 @@ enum Arch { Arm64, } +#[derive(PartialEq, Eq, Clone, Copy)] +enum AndroidTarget { + Aarch64, // "aarch64" + X86, // "x86_64" + Armv7, // "armv7" + I686, // "i686" +} + +impl AndroidTarget { + fn from_str(input: &str) -> anyhow::Result { + use AndroidTarget::*; + match input { + "aarch64-linux-android" => Ok(Aarch64), + "x86_64-linux-android" => Ok(X86), + "armv7-linux-androideabi" => Ok(Armv7), + "i686-linux-android" => Ok(I686), + _ => bail!("{input} is not a supported android target!"), + } + } +} + fn host_os() -> anyhow::Result { // this ugliness is a limitation of rust, where we can't directly // access the target triple of the build script. @@ -134,26 +161,117 @@ fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { Ok(()) } -/// Compile libwg as a dynamic library for android and place it in `../build/lib/$TARGET`. +/// Compile libwg as a dynamic library for android and place it in [`android_output_path`]. // NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. -fn build_android_dynamic_lib() -> anyhow::Result<()> { - let target_triplet = env::var("TARGET").context("TARGET is not set")?; +fn build_android_dynamic_lib(daita: bool) -> anyhow::Result<()> { + let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; + let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; + let target = AndroidTarget::from_str(&target_triple)?; + + // TODO: Since `libwg.so` is always copied to `android_output_path`, this rerun-directive will + // always trigger cargo to rebuild this crate. Some mechanism to detected changes to `android_output_path` + // is needed, because some external program may clean it at any time (e.g. gradle). + println!( + "cargo::rerun-if-changed={}", + android_output_path(target)?.display() + ); + + // Before calling `canonicalize`, the directory we're referring to actually has to exist. + std::fs::create_dir_all("../build")?; + let tmp_build_dir = Path::new("../build").canonicalize()?; + let go_path = tmp_build_dir.join("android-go-path"); + // Invoke the Makefile in wireguard-go-rs/libwg + let mut build_command = Command::new("make"); + build_command + .args(["-C", "./libwg"]) + .args(["-f", "Android.mk"]); + // Set up the correct Android toolchain for building libwg + build_command + .env("ANDROID_C_COMPILER", android_c_compiler(target)?) + .env("ANDROID_ABI", android_abi(target)) + .env("ANDROID_ARCH_NAME", android_arch_name(target)) + .env("GOPATH", &go_path) + // Note: -w -s results in a stripped binary + .env("LDFLAGS", format!("-L{out_dir} -w -s")); + + exec(build_command)?; + + // Move the resulting binary to the path where the Android project expects it to be + let binary = Path::new(&out_dir).join("libwg.so"); + let android_output_path = android_output_path(target)?; + let output = android_output_path.join("libwg.so"); + android_move_binary(&binary, &output)?; + + // Tell linker to check android_output_path for the dynamic library. + println!("cargo::rustc-link-search={}", android_output_path.display()); + println!("cargo::rustc-link-lib=dylib=wg"); - exec(Command::new("./libwg/build-android.sh"))?; + // If daita is enabled, also enable the corresponding rust feature flag + if daita { + println!(r#"cargo::rustc-cfg=daita"#); + } - // Tell linker to check `base`/$TARGET for the dynamic library. - let lib_dir = manifest_dir()?.join("../build/lib").join(target_triplet); - println!("cargo::rustc-link-search={}", lib_dir.display()); - println!("cargo::rustc-link-lib=dylib=wg"); + Ok(()) +} + +/// Copy `binary` to `output`. +/// +/// Note: This function will create the parent directory/directories to `output` if necessary. +fn android_move_binary(binary: &Path, output: &Path) -> anyhow::Result<()> { + let parent_of_output = output.parent().context(format!( + "Could not find parent directory of {}", + output.display() + ))?; + std::fs::create_dir_all(parent_of_output)?; + + let mut move_command = Command::new("mv"); + move_command + .arg(binary.to_str().unwrap()) + .arg(output.to_str().unwrap()); + + exec(&mut move_command)?; Ok(()) } -/// Get the directory containing `Cargo.toml` -fn manifest_dir() -> anyhow::Result { - env::var("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .context("CARGO_MANIFEST_DIR env var not set") +fn android_c_compiler(target: AndroidTarget) -> anyhow::Result { + let toolchain = env::var("NDK_TOOLCHAIN_DIR").context("Missing 'NDK_TOOLCHAIN_DIR")?; + let ccompiler = match target { + AndroidTarget::Aarch64 => "aarch64-linux-android26-clang", + AndroidTarget::X86 => "x86_64-linux-android26-clang", + AndroidTarget::Armv7 => "armv7a-linux-androideabi26-clang", + AndroidTarget::I686 => "i686-linux-android26-clang", + }; + let compiler = Path::new(&toolchain).join(ccompiler); + Ok(compiler) +} + +fn android_abi(target: AndroidTarget) -> String { + match target { + AndroidTarget::Aarch64 => "arm64-v8a", + AndroidTarget::X86 => "x86_64", + AndroidTarget::Armv7 => "armeabi-v7a", + AndroidTarget::I686 => "x86", + } + .to_string() +} + +fn android_arch_name(target: AndroidTarget) -> String { + match target { + AndroidTarget::Aarch64 => "arm64", + AndroidTarget::X86 => "x86_64", + AndroidTarget::Armv7 => "arm", + AndroidTarget::I686 => "x86", + } + .to_string() +} + +// Returns the path where the Android project expects Rust binaries to be +fn android_output_path(target: AndroidTarget) -> anyhow::Result { + let relative_output_path = Path::new("../android/app/build/extraJni").join(android_abi(target)); + std::fs::create_dir_all(relative_output_path.clone())?; + let output_path = relative_output_path.canonicalize()?; + Ok(output_path) } /// Execute a command, assert that it succeeds, and return stdout as a string. diff --git a/wireguard-go-rs/libwg/Android.mk b/wireguard-go-rs/libwg/Android.mk index acf2f6fe88c1..9cb87b24717b 100644 --- a/wireguard-go-rs/libwg/Android.mk +++ b/wireguard-go-rs/libwg/Android.mk @@ -2,7 +2,9 @@ # # Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. -DESTDIR ?= $(CURDIR)/../../build/lib/$(RUST_TARGET_TRIPLE) +DESTDIR ?= $(OUT_DIR) +CARGO_TARGET_DIR ?= +TARGET ?= NDK_GO_ARCH_MAP_x86 := 386 NDK_GO_ARCH_MAP_x86_64 := amd64 diff --git a/wireguard-go-rs/libwg/build-android.sh b/wireguard-go-rs/libwg/build-android.sh deleted file mode 100755 index 0c2336185ada..000000000000 --- a/wireguard-go-rs/libwg/build-android.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -# Ensure we are in the correct directory for the execution of this script -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd "$script_dir" - -# Keep a GOPATH in the build directory to maintain a cache of downloaded libraries -export GOPATH=$script_dir/../../build/android-go-path/ -mkdir -p "$GOPATH" - -ANDROID_STRIP_TOOL="${NDK_TOOLCHAIN_DIR}/llvm-strip" - -for arch in ${ARCHITECTURES:-armv7 aarch64 x86_64 i686}; do - case "$arch" in - "aarch64") - export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/aarch64-linux-android26-clang" - export RUST_TARGET_TRIPLE="aarch64-linux-android" - export ANDROID_ABI="arm64-v8a" - export ANDROID_ARCH_NAME="arm64" - ;; - "x86_64") - export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/x86_64-linux-android26-clang" - export RUST_TARGET_TRIPLE="x86_64-linux-android" - export ANDROID_ABI="x86_64" - export ANDROID_ARCH_NAME="x86_64" - ;; - "armv7") - export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/armv7a-linux-androideabi26-clang" - export RUST_TARGET_TRIPLE="armv7-linux-androideabi" - export ANDROID_ABI="armeabi-v7a" - export ANDROID_ARCH_NAME="arm" - ;; - "i686") - export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/i686-linux-android26-clang" - export RUST_TARGET_TRIPLE="i686-linux-android" - export ANDROID_ABI="x86" - export ANDROID_ARCH_NAME="x86" - ;; - esac - - # Build Wireguard-Go - pwd - make -f Android.mk clean - make -f Android.mk - - # Strip and copy the library to `android/build/extraJni/$ANDROID_ABI` to be able to build the APK - UNSTRIPPED_LIB_PATH="../../build/lib/$RUST_TARGET_TRIPLE/libwg.so" - STRIPPED_LIB_PATH="../../android/app/build/extraJni/$ANDROID_ABI/libwg.so" - - # Create the directories with RWX permissions for all users so that the build server can clean - # the directories afterwards - (umask ugo=rwx; mkdir -p "$(dirname "$STRIPPED_LIB_PATH")") - - $ANDROID_STRIP_TOOL --strip-unneeded --strip-debug -o "$STRIPPED_LIB_PATH" "$UNSTRIPPED_LIB_PATH" - - # Set permissions so that the build server can clean the outputs afterwards - chmod 777 "$STRIPPED_LIB_PATH" -done - -# ensure `git clean -fd` does not require root permissions -find "$GOPATH" -exec chmod +rw {} \;