diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs')
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs new file mode 100644 index 0000000000..c3b2f15277 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::{ + bindings::{RunScriptOptions, TargetLanguage}, + library_mode::generate_bindings, +}; +use anyhow::{bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +use std::ffi::OsStr; +use std::fs::{read_to_string, File}; +use std::io::Write; +use std::process::{Command, Stdio}; +use uniffi_testing::UniFFITestHelper; + +/// Run Swift tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + run_script( + tmp_dir, + fixture_name, + script_file, + vec![], + &RunScriptOptions::default(), + ) +} + +/// Run a Swift script +/// +/// This function will set things up so that the script can import the UniFFI bindings for a crate +pub fn run_script( + tmp_dir: &str, + crate_name: &str, + script_file: &str, + args: Vec<String>, + options: &RunScriptOptions, +) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let test_helper = UniFFITestHelper::new(crate_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + let generated_sources = GeneratedSources::new(crate_name, &cdylib_path, &out_dir)?; + + // Compile the generated sources together to create a single swift module + compile_swift_module( + &out_dir, + &generated_sources.main_module, + &generated_sources.generated_swift_files, + &generated_sources.module_map, + options, + )?; + + // Run the test script against compiled bindings + let mut command = create_command("swift", options); + command + .current_dir(&out_dir) + .arg("-I") + .arg(&out_dir) + .arg("-L") + .arg(&out_dir) + .args(calc_library_args(&out_dir)?) + .arg("-Xcc") + .arg(format!( + "-fmodule-map-file={}", + generated_sources.module_map + )) + .arg(&script_path) + .args(args); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when running test script")? + .wait() + .context("Failed to wait for `swiftc` when running test script")?; + if !status.success() { + bail!("running `swift` to run test script failed ({:?})", command) + } + Ok(()) +} + +fn compile_swift_module<T: AsRef<OsStr>>( + out_dir: &Utf8Path, + module_name: &str, + sources: impl IntoIterator<Item = T>, + module_map: &Utf8Path, + options: &RunScriptOptions, +) -> Result<()> { + let output_filename = format!("{DLL_PREFIX}testmod_{module_name}{DLL_SUFFIX}"); + let mut command = create_command("swiftc", options); + command + .current_dir(out_dir) + .arg("-emit-module") + .arg("-module-name") + .arg(module_name) + .arg("-o") + .arg(output_filename) + .arg("-emit-library") + .arg("-Xcc") + .arg(format!("-fmodule-map-file={module_map}")) + .arg("-I") + .arg(out_dir) + .arg("-L") + .arg(out_dir) + .args(calc_library_args(out_dir)?) + .args(sources); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when compiling bindings")? + .wait() + .context("Failed to wait for `swiftc` when compiling bindings")?; + if !status.success() { + bail!( + "running `swiftc` to compile bindings failed ({:?})", + command + ) + }; + Ok(()) +} + +// Stores sources generated by `uniffi-bindgen-swift` +struct GeneratedSources { + main_module: String, + generated_swift_files: Vec<Utf8PathBuf>, + module_map: Utf8PathBuf, +} + +impl GeneratedSources { + fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result<Self> { + let sources = + generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?; + let main_source = sources + .iter() + .find(|s| s.package.name == crate_name) + .unwrap(); + let main_module = main_source.config.bindings.swift.module_name(); + let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; + let module_map = match modulemap_glob.len() { + 0 => bail!("No modulemap files found in {out_dir}"), + // Normally we only generate 1 module map and can return it directly + 1 => modulemap_glob.into_iter().next().unwrap(), + // When we use multiple UDL files in a test, for example the ext-types fixture, + // then we get multiple module maps and need to combine them + _ => { + let path = out_dir.join("combined.modulemap"); + let mut f = File::create(&path)?; + write!( + f, + "{}", + modulemap_glob + .into_iter() + .map(|path| Ok(read_to_string(path)?)) + .collect::<Result<Vec<String>>>()? + .join("\n") + )?; + path + } + }; + + Ok(GeneratedSources { + main_module, + generated_swift_files: glob(&out_dir.join("*.swift"))?, + module_map, + }) + } +} + +fn create_command(program: &str, options: &RunScriptOptions) -> Command { + let mut command = Command::new(program); + if !options.show_compiler_messages { + // This prevents most compiler messages, but not remarks + command.arg("-suppress-warnings"); + // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument, + // maybe we can eventually move to that + command.stderr(Stdio::null()); + } + command +} + +// Wraps glob to use Utf8Paths and flattens errors +fn glob(globspec: &Utf8Path) -> Result<Vec<Utf8PathBuf>> { + glob::glob(globspec.as_str())? + .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?)) + .collect() +} + +fn calc_library_args(out_dir: &Utf8Path) -> Result<Vec<String>> { + let results = glob::glob(out_dir.join(format!("{DLL_PREFIX}*{DLL_SUFFIX}")).as_str())?; + results + .map(|globresult| { + let path = Utf8PathBuf::try_from(globresult.unwrap())?; + Ok(format!( + "-l{}", + path.file_name() + .unwrap() + .strip_prefix(DLL_PREFIX) + .unwrap() + .strip_suffix(DLL_SUFFIX) + .unwrap() + )) + }) + .collect() +} |