1use serde::{Deserialize, Serialize};
4
5use super::permissions::PermissionPreset;
6
7#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
9#[serde(untagged)]
10pub enum DependencySpec {
11 Version(String),
13 Detailed(DetailedDependency),
15}
16
17#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
19pub struct DetailedDependency {
20 pub version: Option<String>,
21 pub path: Option<String>,
22 pub git: Option<String>,
23 pub tag: Option<String>,
24 pub branch: Option<String>,
25 pub rev: Option<String>,
26 #[serde(default)]
29 pub permissions: Option<PermissionPreset>,
30}
31
32#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
34pub struct NativeTarget {
35 pub os: String,
36 pub arch: String,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub env: Option<String>,
39}
40
41impl NativeTarget {
42 pub fn current() -> Self {
44 let env = option_env!("CARGO_CFG_TARGET_ENV")
45 .map(str::trim)
46 .filter(|value| !value.is_empty())
47 .map(str::to_string);
48 Self {
49 os: std::env::consts::OS.to_string(),
50 arch: std::env::consts::ARCH.to_string(),
51 env,
52 }
53 }
54
55 pub fn id(&self) -> String {
57 match &self.env {
58 Some(env) => format!("{}-{}-{}", self.os, self.arch, env),
59 None => format!("{}-{}", self.os, self.arch),
60 }
61 }
62
63 pub(crate) fn fallback_ids(&self) -> impl Iterator<Item = String> {
64 let mut ids = Vec::with_capacity(3);
65 ids.push(self.id());
66 ids.push(format!("{}-{}", self.os, self.arch));
67 ids.push(self.os.clone());
68 ids.into_iter()
69 }
70}
71
72#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
74#[serde(untagged)]
75pub enum NativeTargetValue {
76 Simple(String),
77 Detailed(NativeTargetValueDetail),
78}
79
80impl NativeTargetValue {
81 pub fn resolve(&self) -> Option<String> {
82 match self {
83 NativeTargetValue::Simple(value) => Some(value.clone()),
84 NativeTargetValue::Detailed(detail) => {
85 detail.path.clone().or_else(|| detail.value.clone())
86 }
87 }
88 }
89}
90
91#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
93pub struct NativeTargetValueDetail {
94 #[serde(default)]
95 pub value: Option<String>,
96 #[serde(default)]
97 pub path: Option<String>,
98}
99
100#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
108#[serde(untagged)]
109pub enum NativeDependencySpec {
110 Simple(String),
111 Detailed(NativeDependencyDetail),
112}
113
114#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
116#[serde(rename_all = "lowercase")]
117pub enum NativeDependencyProvider {
118 System,
120 Path,
122 Vendored,
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
128pub struct NativeDependencyDetail {
129 #[serde(default)]
130 pub linux: Option<String>,
131 #[serde(default)]
132 pub macos: Option<String>,
133 #[serde(default)]
134 pub windows: Option<String>,
135 #[serde(default)]
136 pub path: Option<String>,
137 #[serde(default)]
140 pub targets: std::collections::HashMap<String, NativeTargetValue>,
141 #[serde(default)]
143 pub provider: Option<NativeDependencyProvider>,
144 #[serde(default)]
147 pub version: Option<String>,
148 #[serde(default)]
150 pub cache_key: Option<String>,
151}
152
153impl NativeDependencySpec {
154 pub fn resolve_for_target(&self, target: &NativeTarget) -> Option<String> {
156 match self {
157 NativeDependencySpec::Simple(value) => Some(value.clone()),
158 NativeDependencySpec::Detailed(detail) => {
159 for candidate in target.fallback_ids() {
160 if let Some(value) = detail
161 .targets
162 .get(&candidate)
163 .and_then(NativeTargetValue::resolve)
164 {
165 return Some(value);
166 }
167 }
168 match target.os.as_str() {
169 "linux" => detail
170 .linux
171 .clone()
172 .or_else(|| detail.path.clone())
173 .or_else(|| detail.macos.clone())
174 .or_else(|| detail.windows.clone()),
175 "macos" => detail
176 .macos
177 .clone()
178 .or_else(|| detail.path.clone())
179 .or_else(|| detail.linux.clone())
180 .or_else(|| detail.windows.clone()),
181 "windows" => detail
182 .windows
183 .clone()
184 .or_else(|| detail.path.clone())
185 .or_else(|| detail.linux.clone())
186 .or_else(|| detail.macos.clone()),
187 _ => detail
188 .path
189 .clone()
190 .or_else(|| detail.linux.clone())
191 .or_else(|| detail.macos.clone())
192 .or_else(|| detail.windows.clone()),
193 }
194 }
195 }
196 }
197
198 pub fn resolve_for_host(&self) -> Option<String> {
200 self.resolve_for_target(&NativeTarget::current())
201 }
202
203 pub fn provider_for_target(&self, target: &NativeTarget) -> NativeDependencyProvider {
205 match self {
206 NativeDependencySpec::Simple(value) => {
207 if native_dep_looks_path_like(value) {
208 NativeDependencyProvider::Path
209 } else {
210 NativeDependencyProvider::System
211 }
212 }
213 NativeDependencySpec::Detailed(detail) => {
214 if let Some(provider) = &detail.provider {
215 return provider.clone();
216 }
217 if self
218 .resolve_for_target(target)
219 .as_deref()
220 .is_some_and(native_dep_looks_path_like)
221 {
222 return NativeDependencyProvider::Path;
223 }
224 if detail
225 .path
226 .as_deref()
227 .is_some_and(native_dep_looks_path_like)
228 {
229 NativeDependencyProvider::Path
230 } else {
231 NativeDependencyProvider::System
232 }
233 }
234 }
235 }
236
237 pub fn provider_for_host(&self) -> NativeDependencyProvider {
239 self.provider_for_target(&NativeTarget::current())
240 }
241
242 pub fn declared_version(&self) -> Option<&str> {
244 match self {
245 NativeDependencySpec::Simple(_) => None,
246 NativeDependencySpec::Detailed(detail) => detail.version.as_deref(),
247 }
248 }
249
250 pub fn cache_key(&self) -> Option<&str> {
252 match self {
253 NativeDependencySpec::Simple(_) => None,
254 NativeDependencySpec::Detailed(detail) => detail.cache_key.as_deref(),
255 }
256 }
257}
258
259pub(crate) fn native_dep_looks_path_like(spec: &str) -> bool {
260 let path = std::path::Path::new(spec);
261 path.is_absolute()
262 || spec.starts_with("./")
263 || spec.starts_with("../")
264 || spec.contains('/')
265 || spec.contains('\\')
266 || (spec.len() >= 2 && spec.as_bytes()[1] == b':')
267}
268
269pub fn parse_native_dependencies_section(
271 section: &toml::Value,
272) -> Result<std::collections::HashMap<String, NativeDependencySpec>, String> {
273 let table = section
274 .as_table()
275 .ok_or_else(|| "native-dependencies section must be a table".to_string())?;
276
277 let mut out = std::collections::HashMap::new();
278 for (name, value) in table {
279 let spec: NativeDependencySpec =
280 value.clone().try_into().map_err(|e: toml::de::Error| {
281 format!("native-dependencies.{} has invalid format: {}", name, e)
282 })?;
283 out.insert(name.clone(), spec);
284 }
285 Ok(out)
286}