use serde::{Deserialize, Serialize};
use super::permissions::PermissionPreset;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum DependencySpec {
Version(String),
Detailed(DetailedDependency),
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct DetailedDependency {
pub version: Option<String>,
pub path: Option<String>,
pub git: Option<String>,
pub tag: Option<String>,
pub branch: Option<String>,
pub rev: Option<String>,
#[serde(default)]
pub permissions: Option<PermissionPreset>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct NativeTarget {
pub os: String,
pub arch: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub env: Option<String>,
}
impl NativeTarget {
pub fn current() -> Self {
let env = option_env!("CARGO_CFG_TARGET_ENV")
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string);
Self {
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
env,
}
}
pub fn id(&self) -> String {
match &self.env {
Some(env) => format!("{}-{}-{}", self.os, self.arch, env),
None => format!("{}-{}", self.os, self.arch),
}
}
pub(crate) fn fallback_ids(&self) -> impl Iterator<Item = String> {
let mut ids = Vec::with_capacity(3);
ids.push(self.id());
ids.push(format!("{}-{}", self.os, self.arch));
ids.push(self.os.clone());
ids.into_iter()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum NativeTargetValue {
Simple(String),
Detailed(NativeTargetValueDetail),
}
impl NativeTargetValue {
pub fn resolve(&self) -> Option<String> {
match self {
NativeTargetValue::Simple(value) => Some(value.clone()),
NativeTargetValue::Detailed(detail) => {
detail.path.clone().or_else(|| detail.value.clone())
}
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
pub struct NativeTargetValueDetail {
#[serde(default)]
pub value: Option<String>,
#[serde(default)]
pub path: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum NativeDependencySpec {
Simple(String),
Detailed(NativeDependencyDetail),
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum NativeDependencyProvider {
System,
Path,
Vendored,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
pub struct NativeDependencyDetail {
#[serde(default)]
pub linux: Option<String>,
#[serde(default)]
pub macos: Option<String>,
#[serde(default)]
pub windows: Option<String>,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub targets: std::collections::HashMap<String, NativeTargetValue>,
#[serde(default)]
pub provider: Option<NativeDependencyProvider>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub cache_key: Option<String>,
}
impl NativeDependencySpec {
pub fn resolve_for_target(&self, target: &NativeTarget) -> Option<String> {
match self {
NativeDependencySpec::Simple(value) => Some(value.clone()),
NativeDependencySpec::Detailed(detail) => {
for candidate in target.fallback_ids() {
if let Some(value) = detail
.targets
.get(&candidate)
.and_then(NativeTargetValue::resolve)
{
return Some(value);
}
}
match target.os.as_str() {
"linux" => detail
.linux
.clone()
.or_else(|| detail.path.clone())
.or_else(|| detail.macos.clone())
.or_else(|| detail.windows.clone()),
"macos" => detail
.macos
.clone()
.or_else(|| detail.path.clone())
.or_else(|| detail.linux.clone())
.or_else(|| detail.windows.clone()),
"windows" => detail
.windows
.clone()
.or_else(|| detail.path.clone())
.or_else(|| detail.linux.clone())
.or_else(|| detail.macos.clone()),
_ => detail
.path
.clone()
.or_else(|| detail.linux.clone())
.or_else(|| detail.macos.clone())
.or_else(|| detail.windows.clone()),
}
}
}
}
pub fn resolve_for_host(&self) -> Option<String> {
self.resolve_for_target(&NativeTarget::current())
}
pub fn provider_for_target(&self, target: &NativeTarget) -> NativeDependencyProvider {
match self {
NativeDependencySpec::Simple(value) => {
if native_dep_looks_path_like(value) {
NativeDependencyProvider::Path
} else {
NativeDependencyProvider::System
}
}
NativeDependencySpec::Detailed(detail) => {
if let Some(provider) = &detail.provider {
return provider.clone();
}
if self
.resolve_for_target(target)
.as_deref()
.is_some_and(native_dep_looks_path_like)
{
return NativeDependencyProvider::Path;
}
if detail
.path
.as_deref()
.is_some_and(native_dep_looks_path_like)
{
NativeDependencyProvider::Path
} else {
NativeDependencyProvider::System
}
}
}
}
pub fn provider_for_host(&self) -> NativeDependencyProvider {
self.provider_for_target(&NativeTarget::current())
}
pub fn declared_version(&self) -> Option<&str> {
match self {
NativeDependencySpec::Simple(_) => None,
NativeDependencySpec::Detailed(detail) => detail.version.as_deref(),
}
}
pub fn cache_key(&self) -> Option<&str> {
match self {
NativeDependencySpec::Simple(_) => None,
NativeDependencySpec::Detailed(detail) => detail.cache_key.as_deref(),
}
}
}
pub(crate) fn native_dep_looks_path_like(spec: &str) -> bool {
let path = std::path::Path::new(spec);
path.is_absolute()
|| spec.starts_with("./")
|| spec.starts_with("../")
|| spec.contains('/')
|| spec.contains('\\')
|| (spec.len() >= 2 && spec.as_bytes()[1] == b':')
}
pub fn parse_native_dependencies_section(
section: &toml::Value,
) -> Result<std::collections::HashMap<String, NativeDependencySpec>, String> {
let table = section
.as_table()
.ok_or_else(|| "native-dependencies section must be a table".to_string())?;
let mut out = std::collections::HashMap::new();
for (name, value) in table {
let spec: NativeDependencySpec =
value.clone().try_into().map_err(|e: toml::de::Error| {
format!("native-dependencies.{} has invalid format: {}", name, e)
})?;
out.insert(name.clone(), spec);
}
Ok(out)
}