From d525e718d8e42cb306e1972d4546a1c814355c03 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Tue, 15 Mar 2022 11:53:05 -0400 Subject: [PATCH] Adding support for external fixture/example tests Added code to allow external bindings crates to find the UDL files and cdylibs for examples/fixtures. Made a new crate for this, since I didn't want to pull in `serde` as a dependency for `uniffi`. Eventually, this can replace `uniffi/testing.rs` and we can remove some other dependencies from `uniffi` like `cargo_metadata`. Standardized the crate names for fixtures/examples. I think we're going to need to publish these to crates.io eventually. Make all fixtures/examples specify `crate-type = ["lib", "cdylib"]`. The `lib` part is needed for moz-central to be able to import these crates and build them into `libxul`. Added `Cargo.toml` config section that lists external clates for `ext-types` We copy the dylibs for external crates as well as our own. I think this should fix #1183. --- Cargo.toml | 1 + examples/arithmetic/Cargo.toml | 2 +- examples/callbacks/Cargo.toml | 2 +- examples/custom-types/Cargo.toml | 2 +- examples/geometry/Cargo.toml | 2 +- examples/rondpoint/Cargo.toml | 2 +- examples/sprites/Cargo.toml | 2 +- examples/todolist/Cargo.toml | 2 +- fixtures/callbacks/Cargo.toml | 2 +- fixtures/coverall/Cargo.toml | 2 +- fixtures/ext-types/guid/Cargo.toml | 2 +- fixtures/ext-types/lib/Cargo.toml | 19 +- fixtures/ext-types/uniffi-one/Cargo.toml | 6 +- fixtures/external-types/lib/Cargo.toml | 4 +- .../reexport-scaffolding-macro/Cargo.toml | 6 +- .../cdylib-dependency/Cargo.toml | 4 +- .../ffi-crate/Cargo.toml | 6 +- .../enum-without-i32-helpers/Cargo.toml | 4 +- .../fully-qualified-types/Cargo.toml | 4 +- .../Cargo.toml | 4 +- fixtures/swift-omit-labels/Cargo.toml | 4 +- fixtures/uniffi-fixture-time/Cargo.toml | 2 +- uniffi_testing/Cargo.toml | 11 + uniffi_testing/README.md | 18 ++ uniffi_testing/src/lib.rs | 246 ++++++++++++++++++ 25 files changed, 322 insertions(+), 37 deletions(-) create mode 100644 uniffi_testing/Cargo.toml create mode 100644 uniffi_testing/README.md create mode 100644 uniffi_testing/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 26dd14093e..6462f7759c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "uniffi_bindgen", "uniffi_build", "uniffi_macros", + "uniffi_testing", "uniffi", "examples/arithmetic", "examples/callbacks", diff --git a/examples/arithmetic/Cargo.toml b/examples/arithmetic/Cargo.toml index 851c7fb882..b7a02f814c 100644 --- a/examples/arithmetic/Cargo.toml +++ b/examples/arithmetic/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["staticlib", "cdylib", "lib"] +crate-type = ["lib", "cdylib"] name = "arithmetical" [dependencies] diff --git a/examples/callbacks/Cargo.toml b/examples/callbacks/Cargo.toml index 693fb5783c..b5933c8982 100644 --- a/examples/callbacks/Cargo.toml +++ b/examples/callbacks/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["lib", "cdylib"] name = "callbacks" [dependencies] diff --git a/examples/custom-types/Cargo.toml b/examples/custom-types/Cargo.toml index 2ffbdf3c18..88e9fd1340 100644 --- a/examples/custom-types/Cargo.toml +++ b/examples/custom-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-types" +name = "uniffi-example-custom-types" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 81bca46d28..39f10c86a2 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_geometry" [dependencies] diff --git a/examples/rondpoint/Cargo.toml b/examples/rondpoint/Cargo.toml index 3817eb83cf..6f3b94aa75 100644 --- a/examples/rondpoint/Cargo.toml +++ b/examples/rondpoint/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["lib", "cdylib"] name = "uniffi_rondpoint" [dependencies] diff --git a/examples/sprites/Cargo.toml b/examples/sprites/Cargo.toml index 9bd2125006..7227d1bb04 100644 --- a/examples/sprites/Cargo.toml +++ b/examples/sprites/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_sprites" [dependencies] diff --git a/examples/todolist/Cargo.toml b/examples/todolist/Cargo.toml index dc0e864a2d..71ef40ee9b 100644 --- a/examples/todolist/Cargo.toml +++ b/examples/todolist/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["staticlib", "cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_todolist" [dependencies] diff --git a/fixtures/callbacks/Cargo.toml b/fixtures/callbacks/Cargo.toml index 548f94e574..066cf875c5 100644 --- a/fixtures/callbacks/Cargo.toml +++ b/fixtures/callbacks/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "callbacks" +name = "uniffi-fixture-callbacks" version = "0.17.0" authors = ["Firefox Sync Team "] edition = "2018" diff --git a/fixtures/coverall/Cargo.toml b/fixtures/coverall/Cargo.toml index 652cc1bfdb..ab79f95d07 100644 --- a/fixtures/coverall/Cargo.toml +++ b/fixtures/coverall/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coverall" +name = "uniffi-fixture-coverall" version = "0.17.0" authors = ["Firefox Sync Team "] edition = "2018" diff --git a/fixtures/ext-types/guid/Cargo.toml b/fixtures/ext-types/guid/Cargo.toml index 54e6c69f37..875516b47f 100644 --- a/fixtures/ext-types/guid/Cargo.toml +++ b/fixtures/ext-types/guid/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ext-types-guid" +name = "uniffi-fixture-ext-types-guid" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] diff --git a/fixtures/ext-types/lib/Cargo.toml b/fixtures/ext-types/lib/Cargo.toml index ade9d364f3..edcfc357dc 100644 --- a/fixtures/ext-types/lib/Cargo.toml +++ b/fixtures/ext-types/lib/Cargo.toml @@ -1,13 +1,20 @@ [package] -name = "ext-types-lib" +name = "uniffi-fixture-ext-types" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] license = "MPL-2.0" publish = false +[package.metadata.uniffi.testing] +external-crates = [ + "uniffi-fixture-ext-types-guid", + "uniffi-fixture-ext-types-lib-one", + "uniffi-example-custom-types", +] + [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_ext_types_lib" [dependencies] @@ -16,13 +23,15 @@ bytes = "1.0" uniffi_macros = {path = "../../../uniffi_macros"} uniffi = {path = "../../../uniffi", features=["builtin-bindgen"]} -uniffi-one = {path = "../uniffi-one"} -ext-types-guid = {path = "../guid"} +uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"} +uniffi-fixture-ext-types-guid = {path = "../guid"} # Reuse one of our examples. -custom-types = {path = "../../../examples/custom-types"} +uniffi-example-custom-types = {path = "../../../examples/custom-types"} url = "2.2" [build-dependencies] uniffi_build = {path = "../../../uniffi_build", features=["builtin-bindgen"]} + + diff --git a/fixtures/ext-types/uniffi-one/Cargo.toml b/fixtures/ext-types/uniffi-one/Cargo.toml index 70a4bea692..9911f1b3ee 100644 --- a/fixtures/ext-types/uniffi-one/Cargo.toml +++ b/fixtures/ext-types/uniffi-one/Cargo.toml @@ -1,6 +1,5 @@ [package] -# TODO: modify the crate name to test non-default names. -name = "uniffi-one" +name = "uniffi-fixture-ext-types-lib-one" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] @@ -8,7 +7,8 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["lib", "cdylib"] +name = "uniffi_one" [dependencies] anyhow = "1" diff --git a/fixtures/external-types/lib/Cargo.toml b/fixtures/external-types/lib/Cargo.toml index 66486ccf6b..7c891a61ab 100644 --- a/fixtures/external-types/lib/Cargo.toml +++ b/fixtures/external-types/lib/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "external_types_lib" +name = "uniffi-fixture-external-types" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["staticlib", "cdylib", "lib"] +crate-type = ["lib", "cdylib"] name = "uniffi_external_types_lib" [dependencies] diff --git a/fixtures/reexport-scaffolding-macro/Cargo.toml b/fixtures/reexport-scaffolding-macro/Cargo.toml index 1aa10e51bc..408736c163 100644 --- a/fixtures/reexport-scaffolding-macro/Cargo.toml +++ b/fixtures/reexport-scaffolding-macro/Cargo.toml @@ -7,11 +7,11 @@ edition = "2021" [lib] name = "reexport_scaffolding_macro" -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] [dependencies] -callbacks = { path = "../callbacks" } -coverall = { path = "../coverall" } +uniffi-fixture-callbacks = { path = "../callbacks" } +uniffi-fixture-coverall = { path = "../coverall" } uniffi = { path = "../../uniffi", features=["builtin-bindgen"] } [dev-dependencies] diff --git a/fixtures/regressions/cdylib-crate-type-dependency/cdylib-dependency/Cargo.toml b/fixtures/regressions/cdylib-crate-type-dependency/cdylib-dependency/Cargo.toml index cf09bfc0d5..09688407a5 100644 --- a/fixtures/regressions/cdylib-crate-type-dependency/cdylib-dependency/Cargo.toml +++ b/fixtures/regressions/cdylib-crate-type-dependency/cdylib-dependency/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "cdylib-dependency" +name = "uniffi-fixture-regression-cdylib-dependency" version = "0.17.0" authors = ["king6cong "] edition = "2018" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] diff --git a/fixtures/regressions/cdylib-crate-type-dependency/ffi-crate/Cargo.toml b/fixtures/regressions/cdylib-crate-type-dependency/ffi-crate/Cargo.toml index ad9eeea8e8..6f9a12026d 100644 --- a/fixtures/regressions/cdylib-crate-type-dependency/ffi-crate/Cargo.toml +++ b/fixtures/regressions/cdylib-crate-type-dependency/ffi-crate/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ffi-crate" +name = "uniffi-fixture-regression-cdylib-dependency-ffi-crate" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] @@ -7,13 +7,13 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_empty" [dependencies] uniffi_macros = {path = "../../../../uniffi_macros"} uniffi = {path = "../../../../uniffi", features=["builtin-bindgen"]} -cdylib-dependency = {path = "../cdylib-dependency"} +uniffi-fixture-regression-cdylib-dependency = {path = "../cdylib-dependency"} [build-dependencies] uniffi_build = {path = "../../../../uniffi_build", features=["builtin-bindgen"]} diff --git a/fixtures/regressions/enum-without-i32-helpers/Cargo.toml b/fixtures/regressions/enum-without-i32-helpers/Cargo.toml index e5b665e410..5c0a69880b 100644 --- a/fixtures/regressions/enum-without-i32-helpers/Cargo.toml +++ b/fixtures/regressions/enum-without-i32-helpers/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "i356-enum-without-int-helpers" +name = "uniffi-fixture-regression-i356-enum-without-int-helpers" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_regression_test_i356" [dependencies] diff --git a/fixtures/regressions/fully-qualified-types/Cargo.toml b/fixtures/regressions/fully-qualified-types/Cargo.toml index 9d0e4fec67..62b2af58bb 100644 --- a/fixtures/regressions/fully-qualified-types/Cargo.toml +++ b/fixtures/regressions/fully-qualified-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "i1015-fully-qualified-types" +name = "uniffi-fixture-regression-i1015-fully-qualified-types" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_regression_test_i1015" [dependencies] diff --git a/fixtures/regressions/kotlin-experimental-unsigned-types/Cargo.toml b/fixtures/regressions/kotlin-experimental-unsigned-types/Cargo.toml index d2e9a9f1cb..6206ae4bb3 100644 --- a/fixtures/regressions/kotlin-experimental-unsigned-types/Cargo.toml +++ b/fixtures/regressions/kotlin-experimental-unsigned-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kotlin-experimental-unsigned-types" +name = "uniffi-fixture-regression-kotlin-experimental-unsigned-types" edition = "2018" version = "0.17.0" authors = ["Firefox Sync Team "] @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_regression_test_kt_unsigned_types" [dependencies] diff --git a/fixtures/swift-omit-labels/Cargo.toml b/fixtures/swift-omit-labels/Cargo.toml index 1cb7174f5d..c1512f6184 100644 --- a/fixtures/swift-omit-labels/Cargo.toml +++ b/fixtures/swift-omit-labels/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "omit_argument_labels" +name = "uniffi-fixture-swift-omit-argument-labels" version = "0.17.0" authors = ["Firefox Sync Team "] edition = "2018" publish = false [lib] -crate-type = ["staticlib", "cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_omit_argument_labels" [dependencies] diff --git a/fixtures/uniffi-fixture-time/Cargo.toml b/fixtures/uniffi-fixture-time/Cargo.toml index ded85437d9..2a4fd5eb45 100644 --- a/fixtures/uniffi-fixture-time/Cargo.toml +++ b/fixtures/uniffi-fixture-time/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" publish = false [lib] -crate-type = ["staticlib", "cdylib"] +crate-type = ["lib", "cdylib"] name = "uniffi_chronological" [dependencies] diff --git a/uniffi_testing/Cargo.toml b/uniffi_testing/Cargo.toml new file mode 100644 index 0000000000..7258bff76f --- /dev/null +++ b/uniffi_testing/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "uniffi_testing" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +cargo_metadata = "0.13" +lazy_static = "1.4" +serde = "1" +serde_json = "1" diff --git a/uniffi_testing/README.md b/uniffi_testing/README.md new file mode 100644 index 0000000000..c171b26373 --- /dev/null +++ b/uniffi_testing/README.md @@ -0,0 +1,18 @@ +This crate contains helper code for testing bindings. Our general system is to +generate bindings for the libraries from the examples and fixtures +directories, then execute a script that tests the bindings. + +Each bindings crate can do this in a different way, but the typical system is: + + - Construct a `UniFFITestHelper` struct to assist the process + - Call `UniFFITestHelper.create_out_dir()` to create a temp directory to + store testing files + - Call `UniFFITestHelper.copy_cdylibs_to_out_dir()` to copy the dylib + artifacts for the example/fixture library to the `out_dir`. This is needed + because the bindings code dynamically links to or loads from this library. + - Call `UniFFITestHelper.get_compile_sources()` to iterate over (`udl_path`, + `uniffi_config_path`) pairs and generate the bindings from them. This step + is specific to the bindings language, it may mean creating a .jar file, + compiling a binary, or just copying script files over. + - Execute the test script and check if it succeeds. This step is also + specific to the bindings language. diff --git a/uniffi_testing/src/lib.rs b/uniffi_testing/src/lib.rs new file mode 100644 index 0000000000..74f6a0e917 --- /dev/null +++ b/uniffi_testing/src/lib.rs @@ -0,0 +1,246 @@ +/* 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 anyhow::{bail, Result}; +use cargo_metadata::{Artifact, Message, Metadata, MetadataCommand, Package, Target}; +use serde::Deserialize; +use std::{ + collections::hash_map::DefaultHasher, + env, + env::consts::DLL_EXTENSION, + fs::{copy, read_dir}, + hash::{Hash, Hasher}, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +#[derive(Deserialize)] +struct UniFFITestingMetadata { + #[serde(rename = "external-crates")] + external_crates: Option>, +} + +// A source to compile for a test +#[derive(Debug)] +pub struct CompileSource { + pub udl_path: PathBuf, + pub config_path: Option, +} + +// Store Cargo output to in a lazy_static to avoid calling it more than once. +lazy_static::lazy_static! { + static ref CARGO_METADATA: Metadata = get_cargo_metadata(); + static ref CARGO_BUILD_MESSAGES: Vec = get_cargo_build_messages(); +} + +/// Struct for running fixture and example tests for bindings generators +/// +/// Expectations: +/// - Used from a integration test (a `.rs` file in the tests/ directory) +/// - The working directory is the project root for the bindings crate. This is the normal case +/// for test code, just make sure you don't cd somewhere else. +/// - The bindings crate has a dev-dependency on the fixture crate +/// - The fixture crate produces a cdylib library +/// - The fixture crate, and any external-crates, has 1 UDL file in it's src/ directory +pub struct UniFFITestHelper { + name: String, + package: Package, + metadata: Option, +} + +impl UniFFITestHelper { + pub fn new(name: &str) -> Result { + let package = Self::find_package(name)?; + let metadata: Option = package + .metadata + .pointer("/uniffi/testing") + .cloned() + .map(serde_json::from_value) + .transpose()?; + Ok(Self { + name: name.to_string(), + package, + metadata, + }) + } + + fn find_package(name: &str) -> Result { + let matching: Vec<&Package> = CARGO_METADATA + .packages + .iter() + .filter(|p| p.name == name) + .collect(); + match matching.len() { + 1 => Ok(matching[0].clone()), + n => bail!("cargo metadata return {} packages named {}", n, name), + } + } + + fn find_packages_for_external_crates(&self) -> Result> { + // Add any external crates listed in `Cargo.toml` + match &self.metadata { + None => Ok(vec![]), + Some(metadata) => metadata + .external_crates + .iter() + .flatten() + .map(|name| Self::find_package(name)) + .collect(), + } + } + + fn find_cdylib_path(package: &Package) -> Result { + let cdylib_targets: Vec<&Target> = package + .targets + .iter() + .filter(|t| t.crate_types.iter().any(|t| t == "cdylib")) + .collect(); + let target = match cdylib_targets.len() { + 1 => cdylib_targets[0], + n => bail!("Found {} cdylib targets for {}", n, package.name), + }; + + let artifacts = CARGO_BUILD_MESSAGES + .iter() + .filter_map(|message| match message { + Message::CompilerArtifact(artifact) => { + if artifact.target == *target { + Some(artifact.clone()) + } else { + None + } + } + _ => None, + }) + .collect::>(); + let artifact = match artifacts.len() { + 1 => &artifacts[0], + n => bail!("Found {} artifacts for target {}", n, target.name), + }; + let cdylib_files: Vec<_> = artifact + .filenames + .iter() + .filter(|nm| matches!(nm.extension(), Some(DLL_EXTENSION))) + .collect(); + + match cdylib_files.len() { + 1 => Ok(cdylib_files[0].as_std_path().to_path_buf()), + n => bail!("Found {} cdylib files for {}", n, artifact.target.name), + } + } + + /// Create at `out_dir` for testing + /// + /// This directory can be used for: + /// - Generated bindings files (usually via the `--out-dir` param) + /// - cdylib libraries that the bindings depend on + /// - Anything else that's useful for testing + /// + /// This directory typically created as a subdirectory of `CARGO_TARGET_TMPDIR` when running an + /// integration test. + /// + /// We use the script path to create a hash included in the outpuit directory. This avoids + /// path collutions when 2 scripts run against the same fixture. + pub fn create_out_dir( + &self, + temp_dir: impl AsRef, + script_path: impl AsRef, + ) -> Result { + let dirname = format!("{}-{}", self.name, hash_path(script_path.as_ref())); + let out_dir = Path::new(temp_dir.as_ref()).join(dirname); + if out_dir.exists() { + // Clean out any files from previous runs + std::fs::remove_dir_all(&out_dir)?; + } + std::fs::create_dir(&out_dir)?; + Ok(out_dir) + } + + /// Copy the `cdylib` for a fixture into the out_dir + /// + /// This is typically needed for the bindings to open it when running the tests + /// + /// Returns the path to the copied library + pub fn copy_cdylibs_to_out_dir(&self, out_dir: impl AsRef) -> Result<()> { + let cdylib_paths = std::iter::once(self.package.clone()) + .chain(self.find_packages_for_external_crates()?) + .map(|p| Self::find_cdylib_path(&p)) + .collect::>>()?; + + for path in cdylib_paths.into_iter() { + let dest = out_dir.as_ref().join(path.file_name().unwrap()); + copy(&path, &dest)?; + } + Ok(()) + } + + /// Get paths to the UDL and config files for a fixture + pub fn get_compile_sources(&self) -> Result> { + std::iter::once(self.package.clone()) + .chain(self.find_packages_for_external_crates()?) + .map(|p| self.find_compile_source(&p)) + .collect() + } + + fn find_compile_source(&self, package: &Package) -> Result { + let crate_root = package.manifest_path.parent().unwrap().as_std_path(); + let src_dir = crate_root.join("src"); + let mut udl_paths = find_files( + &src_dir, + |path| matches!(path.extension(), Some(ext) if ext.to_ascii_lowercase() == "udl"), + )?; + let udl_path = match udl_paths.len() { + 1 => udl_paths.remove(0), + n => bail!("Found {} UDL files in {:?}", n, src_dir), + }; + let mut config_paths = find_files( + crate_root, + |path| matches!(path.file_name(), Some(name) if name == "uniffi.toml"), + )?; + let config_path = match config_paths.len() { + 0 => None, + 1 => Some(config_paths.remove(0)), + n => bail!("Found {} UDL files in {:?}", n, crate_root), + }; + + Ok(CompileSource { + udl_path, + config_path, + }) + } +} + +fn find_files bool>(dir: &Path, predicate: F) -> Result> { + Ok(read_dir(&dir)? + .flatten() + .map(|entry| entry.path()) + .filter(|p| predicate(p)) + .collect()) +} + +fn get_cargo_metadata() -> Metadata { + MetadataCommand::new() + .exec() + .expect("error running cargo metadata") +} + +fn get_cargo_build_messages() -> Vec { + let mut child = Command::new(env!("CARGO")) + .arg("build") + .arg("--message-format=json") + .arg("--tests") + .stdout(Stdio::piped()) + .spawn() + .expect("Error running cargo build"); + let output = std::io::BufReader::new(child.stdout.take().unwrap()); + Message::parse_stream(output) + .map(|m| m.expect("Error parsing cargo build messages")) + .collect() +} + +fn hash_path(path: &Path) -> String { + let mut hasher = DefaultHasher::new(); + path.hash(&mut hasher); + format!("{:x}", hasher.finish()) +}