1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use anyhow::{bail, Result};
use cargo_metadata::Message;
use lazy_static::lazy_static;
use std::{
collections::HashMap,
path::Path,
process::{Command, Stdio},
sync::Mutex,
};
lazy_static! {
static ref COMPILED_COMPONENTS: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
static ref UNIFFI_BINDGEN: Mutex<i32> = Mutex::new(0);
}
pub fn run_foreign_language_testcase(pkg_dir: &str, udl_file: &str, test_file: &str) -> Result<()> {
let cdylib_file = ensure_compiled_cdylib(pkg_dir)?;
let out_dir = Path::new(cdylib_file.as_str())
.parent()
.ok_or_else(|| anyhow::anyhow!("Generated cdylib has no parent directory"))?
.to_str()
.unwrap();
let _lock = UNIFFI_BINDGEN.lock();
run_uniffi_bindgen_test(out_dir, udl_file, test_file)?;
Ok(())
}
pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result<String> {
let mut compiled_components = COMPILED_COMPONENTS.lock().unwrap();
if let Some(cdylib_file) = compiled_components.get(pkg_dir) {
return Ok(cdylib_file.to_string());
}
let mut cmd = Command::new("cargo");
cmd.arg("build").arg("--message-format=json").arg("--lib");
cmd.current_dir(pkg_dir);
cmd.stdout(Stdio::piped());
let mut child = cmd.spawn()?;
let output = std::io::BufReader::new(child.stdout.take().unwrap());
let cdylibs = Message::parse_stream(output)
.filter_map(|message| match message {
Err(e) => Some(Err(e.into())),
Ok(Message::CompilerArtifact(artifact)) => {
if artifact.target.kind.iter().any(|item| item == "cdylib") {
Some(Ok(artifact))
} else {
None
}
}
_ => None,
})
.collect::<Result<Vec<_>>>()?;
if !child.wait()?.success() {
bail!("Failed to execute `cargo build`");
}
let cdylib = match cdylibs.len() {
0 => bail!("Crate did not produce any cdylibs, it must not be a uniffi component"),
1 => &cdylibs[0],
_ => {
let package_name = Path::new(pkg_dir)
.file_name()
.and_then(|s| s.to_str())
.unwrap();
match cdylibs.iter().find(|cdylib| {
cdylib
.package_id
.repr
.starts_with(&format!("{:} ", package_name))
}) {
Some(cdylib) => {
log::warn!(
"Crate produced multiple cdylibs, using the one produced by {}",
pkg_dir
);
cdylib
}
None => {
bail!(
"Crate produced multiple cdylibs, none of which is produced by {}",
pkg_dir
);
}
}
}
};
let cdylib_files: Vec<_> = cdylib
.filenames
.iter()
.filter(|nm| matches!(nm.extension(), Some(std::env::consts::DLL_EXTENSION)))
.collect();
if cdylib_files.len() != 1 {
bail!("Failed to build exactly one cdylib file, it must not be a uniffi component");
}
let cdylib_file = cdylib_files[0].to_string();
compiled_components.insert(pkg_dir.to_string(), cdylib_file.clone());
Ok(cdylib_file)
}
#[cfg(not(feature = "builtin-bindgen"))]
fn run_uniffi_bindgen_test(out_dir: &str, udl_file: &str, test_file: &str) -> Result<()> {
let status = Command::new("uniffi-bindgen")
.args(&["test", out_dir, udl_file, test_file])
.status()?;
if !status.success() {
bail!("Error while running tests: {}",);
}
Ok(())
}
#[cfg(feature = "builtin-bindgen")]
fn run_uniffi_bindgen_test(out_dir: &str, udl_file: &str, test_file: &str) -> Result<()> {
uniffi_bindgen::run_tests(out_dir, udl_file, vec![test_file], None)
}