boltffi_cli 0.27.5

CLI for BoltFFI - generate Swift, Kotlin, and WASM bindings from Rust
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;

use boltffi_binding::{
    BINDING_EXPANSION_BUILD_ENV, BINDING_EXPANSION_ROOT_ENV, BINDING_EXPANSION_SOURCE_ENV,
    BINDING_EXPANSION_SURFACE_ENV, BindingMetadataSurface,
};

use crate::cargo::Cargo;
use crate::cli::{CliError, Result};
use crate::config::Config;

const BINDING_EXPANSION_CFG: &str = "boltffi_binding_expansion";

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BindingExpansion {
    manifest_path: PathBuf,
    source_path: PathBuf,
    cargo_args: Vec<String>,
}

impl BindingExpansion {
    pub fn resolve(config: &Config, build_cargo_args: &[String]) -> Result<Self> {
        let cargo = Cargo::current(build_cargo_args)?;
        let metadata = cargo.metadata()?;
        let cargo_manifest_path = cargo.manifest_path()?;
        let package_selector =
            cargo.effective_package_selector(config, &metadata, &cargo_manifest_path);
        let package = metadata.find_package(&cargo_manifest_path, package_selector.as_deref())?;
        let library_target =
            package.resolve_library_target(&config.crate_artifact_name(), &cargo_manifest_path)?;

        Ok(Self {
            manifest_path: package.manifest_path.clone(),
            source_path: library_target.src_path.clone(),
            cargo_args: cargo.probe_command_arguments(),
        })
    }

    pub fn manifest_path(&self) -> PathBuf {
        self.manifest_path.clone()
    }

    pub fn cargo_args(&self) -> Vec<String> {
        self.cargo_args.clone()
    }

    pub fn env(&self) -> Result<Vec<(OsString, OsString)>> {
        let root = self
            .manifest_path
            .parent()
            .ok_or_else(|| CliError::CommandFailed {
                command: format!(
                    "manifest path '{}' has no parent directory",
                    self.manifest_path.display()
                ),
                status: None,
            })?;

        Ok(vec![
            (BINDING_EXPANSION_BUILD_ENV.into(), "1".into()),
            (
                BINDING_EXPANSION_ROOT_ENV.into(),
                root.as_os_str().to_owned(),
            ),
            (
                BINDING_EXPANSION_SOURCE_ENV.into(),
                self.source_path.as_os_str().to_owned(),
            ),
            (
                BINDING_EXPANSION_SURFACE_ENV.into(),
                BindingMetadataSurface::Native.as_str().into(),
            ),
            ExpansionRustflags::from_env().into_env(),
        ])
    }
}

enum ExpansionRustflags {
    Encoded(OsString),
    Plain(OsString),
}

impl ExpansionRustflags {
    fn from_env() -> Self {
        std::env::var_os("CARGO_ENCODED_RUSTFLAGS")
            .map(Self::encoded)
            .unwrap_or_else(|| Self::plain(std::env::var_os("RUSTFLAGS")))
    }

    fn encoded(existing: OsString) -> Self {
        Self::Encoded(Self::append_encoded(existing))
    }

    fn plain(existing: Option<OsString>) -> Self {
        Self::Plain(match existing.filter(|value| !value.is_empty()) {
            Some(mut value) => {
                value.push(" --cfg ");
                value.push(BINDING_EXPANSION_CFG);
                value
            }
            None => OsString::from(format!("--cfg {BINDING_EXPANSION_CFG}")),
        })
    }

    fn append_encoded(mut existing: OsString) -> OsString {
        if !existing.is_empty() {
            existing.push(OsStr::new("\u{1f}"));
        }
        existing.push(OsStr::new("--cfg"));
        existing.push(OsStr::new("\u{1f}"));
        existing.push(OsStr::new(BINDING_EXPANSION_CFG));
        existing
    }

    fn into_env(self) -> (OsString, OsString) {
        match self {
            Self::Encoded(value) => ("CARGO_ENCODED_RUSTFLAGS".into(), value),
            Self::Plain(value) => ("RUSTFLAGS".into(), value),
        }
    }
}