| 1 | use std::fs::create_dir_all; |
| 2 | |
| 3 | use camino::{Utf8Path, Utf8PathBuf}; |
| 4 | use clap::{Args, Subcommand, ValueEnum}; |
| 5 | use uniffi_bindgen::bindings::{GenerateOptions, TargetLanguage, generate}; |
| 6 | use xshell::cmd; |
| 7 | |
| 8 | use crate::{Result, sh, workspace}; |
| 9 | |
| 10 | struct PackageValues { |
| 11 | name: &'static str, |
| 12 | features: &'static str, |
| 13 | } |
| 14 | |
| 15 | #[derive(ValueEnum, Clone)] |
| 16 | enum Package { |
| 17 | CryptoSDK, |
| 18 | FullSDK, |
| 19 | } |
| 20 | |
| 21 | impl Package { |
| 22 | fn values(self) -> PackageValues { |
| 23 | match self { |
| 24 | Package::CryptoSDK => PackageValues { name: "matrix-sdk-crypto-ffi", features: "" }, |
| 25 | Package::FullSDK => PackageValues { name: "matrix-sdk-ffi", features: "sentry" }, |
| 26 | } |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | #[derive(Args)] |
| 31 | pub struct KotlinArgs { |
| 32 | #[clap(subcommand)] |
| 33 | cmd: KotlinCommand, |
| 34 | } |
| 35 | |
| 36 | #[derive(Subcommand)] |
| 37 | enum KotlinCommand { |
| 38 | /// Builds the SDK for Android as an AAR. |
| 39 | BuildAndroidLibrary { |
| 40 | #[clap(value_enum, long)] |
| 41 | package: Package, |
| 42 | |
| 43 | /// Build with the release profile |
| 44 | #[clap(long)] |
| 45 | release: bool, |
| 46 | |
| 47 | /// Build with a custom profile, takes precedence over `--release` |
| 48 | #[clap(long)] |
| 49 | profile: Option<String>, |
| 50 | |
| 51 | /// Build the given target only |
| 52 | #[clap(long)] |
| 53 | only_target: Option<String>, |
| 54 | |
| 55 | /// Move the generated files into the given src directory |
| 56 | #[clap(long)] |
| 57 | src_dir: Utf8PathBuf, |
| 58 | }, |
| 59 | } |
| 60 | |
| 61 | impl KotlinArgs { |
| 62 | pub fn run(self) -> Result<()> { |
| 63 | let sh = sh(); |
| 64 | let _p = sh.push_dir(workspace::root_path()?); |
| 65 | |
| 66 | match self.cmd { |
| 67 | KotlinCommand::BuildAndroidLibrary { |
| 68 | release, |
| 69 | profile, |
| 70 | src_dir, |
| 71 | only_target, |
| 72 | package, |
| 73 | } => { |
| 74 | let profile = profile.as_deref().unwrap_or(if release { "release" } else { "dev" }); |
| 75 | build_android_library(profile, only_target, &src_dir, package) |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | fn build_android_library( |
| 82 | profile: &str, |
| 83 | only_target: Option<String>, |
| 84 | src_dir: &Utf8Path, |
| 85 | package: Package, |
| 86 | ) -> Result<()> { |
| 87 | let package_values = package.values(); |
| 88 | let package_name = package_values.name; |
| 89 | let package_features = package_values.features; |
| 90 | |
| 91 | let jni_libs_dir = src_dir.join("jniLibs"); |
| 92 | let jni_libs_dir_str = jni_libs_dir.as_str(); |
| 93 | |
| 94 | let kotlin_generated_dir = src_dir.join("kotlin"); |
| 95 | create_dir_all(kotlin_generated_dir.clone())?; |
| 96 | |
| 97 | let uniffi_lib_path = if let Some(target) = only_target { |
| 98 | println!("-- Building for {target} [1/1]"); |
| 99 | build_for_android_target( |
| 100 | target.as_str(), |
| 101 | profile, |
| 102 | jni_libs_dir_str, |
| 103 | package_name, |
| 104 | package_features, |
| 105 | )? |
| 106 | } else { |
| 107 | println!("-- Building for x86_64-linux-android[1/4]"); |
| 108 | build_for_android_target( |
| 109 | "x86_64-linux-android", |
| 110 | profile, |
| 111 | jni_libs_dir_str, |
| 112 | package_name, |
| 113 | package_features, |
| 114 | )?; |
| 115 | println!("-- Building for aarch64-linux-android[2/4]"); |
| 116 | build_for_android_target( |
| 117 | "aarch64-linux-android", |
| 118 | profile, |
| 119 | jni_libs_dir_str, |
| 120 | package_name, |
| 121 | package_features, |
| 122 | )?; |
| 123 | println!("-- Building for armv7-linux-androideabi[3/4]"); |
| 124 | build_for_android_target( |
| 125 | "armv7-linux-androideabi", |
| 126 | profile, |
| 127 | jni_libs_dir_str, |
| 128 | package_name, |
| 129 | package_features, |
| 130 | )?; |
| 131 | println!("-- Building for i686-linux-android[4/4]"); |
| 132 | build_for_android_target( |
| 133 | "i686-linux-android", |
| 134 | profile, |
| 135 | jni_libs_dir_str, |
| 136 | package_name, |
| 137 | package_features, |
| 138 | )? |
| 139 | }; |
| 140 | |
| 141 | println!("-- Generate uniffi files"); |
| 142 | generate_uniffi_bindings(&uniffi_lib_path, &kotlin_generated_dir)?; |
| 143 | |
| 144 | println!("-- All done and hunky dory. Enjoy!"); |
| 145 | Ok(()) |
| 146 | } |
| 147 | |
| 148 | fn generate_uniffi_bindings(library_path: &Utf8Path, ffi_generated_dir: &Utf8Path) -> Result<()> { |
| 149 | println!("-- library_path = {library_path}"); |
| 150 | generate(GenerateOptions { |
| 151 | languages: vec![TargetLanguage::Kotlin], |
| 152 | source: library_path.to_path_buf(), |
| 153 | out_dir: ffi_generated_dir.to_path_buf(), |
| 154 | ..GenerateOptions::default() |
| 155 | })?; |
| 156 | Ok(()) |
| 157 | } |
| 158 | |
| 159 | fn build_for_android_target( |
| 160 | target: &str, |
| 161 | profile: &str, |
| 162 | dest_dir: &str, |
| 163 | package_name: &str, |
| 164 | features: &str, |
| 165 | ) -> Result<Utf8PathBuf> { |
| 166 | let sh = sh(); |
| 167 | cmd!( |
| 168 | sh, |
| 169 | "cargo ndk --target {target} -o {dest_dir} build --profile {profile} --package {package_name} --features {features}" |
| 170 | ) |
| 171 | .run()?; |
| 172 | |
| 173 | // The builtin dev profile has its files stored under target/debug, all |
| 174 | // other targets have matching directory names |
| 175 | let profile_dir_name = if profile == "dev" { "debug" } else { profile }; |
| 176 | let package_camel = package_name.replace('-', "_"); |
| 177 | let lib_name = format!("lib{package_camel}.so"); |
| 178 | Ok(workspace::target_path()?.join(target).join(profile_dir_name).join(lib_name)) |
| 179 | } |