cargo-xcodebuild 0.1.0

Helps cargo build and run apps for iOS
use anyhow::Context as _;
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
pub struct Root {
    pub package: Package,
    pub lib: Option<Lib>,
}

impl Root {
    pub fn validate_crate_type(self) -> anyhow::Result<Self> {
        let crate_types = self
            .lib
            .clone()
            .unwrap_or_default()
            .crate_type
            .unwrap_or_default()
            .join(" ");
        if !crate_types.contains("staticlib") {
            anyhow::bail!(
                "Missing `staticlib` crate-type in `lib` section. Please check Cargo.toml."
            )
        }

        Ok(self)
    }

    pub fn validate_build_targets(self) -> anyhow::Result<Self> {
        let ios_metadata = self.ios_metadata()?;
        let targets = ios_metadata.build_targets.unwrap_or_default();
        if targets.is_empty() {
            anyhow::bail!("Missing `build_targets` in `package.metadata.ios` section. Please check Cargo.toml.")
        }

        Ok(self)
    }

    pub fn ios_metadata(&self) -> anyhow::Result<IosMetadata> {
        self.package
            .metadata
            .clone()
            .with_context(|| "Missing `metadata` section. Please check Cargo.toml.".to_string())?
            .ios
            .with_context(|| "Missing `ios` section. Please check Cargo.toml.".to_string())
    }
}

#[derive(Debug, Clone, Deserialize)]
pub struct Package {
    pub name: String,
    pub version: String,
    pub metadata: Option<PackageMetadata>,
}

#[derive(Clone, Debug, Default, Deserialize)]
pub struct Lib {
    pub name: Option<String>,
    #[serde(rename = "crate-type")]
    pub crate_type: Option<Vec<String>>,
}

#[derive(Clone, Debug, Default, Deserialize)]
pub struct PackageMetadata {
    pub ios: Option<IosMetadata>,
}

#[derive(Clone, Debug, Default, Deserialize)]
pub struct IosMetadata {
    pub build_targets: Option<Vec<Target>>,
    pub deployment_target: Option<String>,
    pub dependencies: Option<Vec<String>>,
    //TODO: unused now
    pub supported_interface_orientations: Option<Vec<Orientation>>,
    pub bundle_id_prefix: Option<String>,
    pub code_sign_identity: Option<String>,
    pub development_team: Option<String>,
    pub device_id: Option<String>,
    pub device_type: Option<DeviceType>,
    pub assets: Option<Vec<String>>,
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DeviceType {
    Device,
    Simulator,
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub enum Orientation {
    #[serde(rename = "UIInterfaceOrientationUnknown")]
    Unknown,
    #[serde(rename = "UIInterfaceOrientationPortrait")]
    Portrait,
    #[serde(rename = "UIInterfaceOrientationPortraitUpsideDown")]
    PortraitUpsideDown,
    #[serde(rename = "UIInterfaceOrientationLandscapeLeft")]
    LandscapeLeft,
    #[serde(rename = "UIInterfaceOrientationLandscapeRight")]
    LandscapeRight,
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub enum Target {
    #[serde(rename = "aarch64-apple-ios")]
    Arm,
    #[serde(rename = "aarch64-apple-ios-sim")]
    ArmSim,
    #[serde(rename = "x86_64-apple-ios")]
    Sim,
}

impl Target {
    pub fn to_rustc_target(self) -> &'static str {
        match self {
            Target::Arm => "aarch64-apple-ios",
            Target::ArmSim => "aarch64-apple-ios-sim",
            Target::Sim => "x86_64-apple-ios",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from_toml() {
        let contents = r#"[package]
        name = "xcodebuild_examples"
        version = "0.1.0"
        edition = "2021"
        license = "MIT OR Apache-2.0"

        [lib]
        crate-type = ["staticlib", "cdylib"]

        [dependencies]


        [package.metadata.ios]
        build_targets = [ "aarch64-apple-ios", "aarch64-apple-ios-sim"]
        "#;
        let toml: Root = toml::from_str(&contents).unwrap();
        let ios_metadata = toml
            .package
            .metadata
            .unwrap_or_default()
            .ios
            .unwrap_or_default();

        assert_eq!(
            toml.lib.unwrap_or_default().crate_type.unwrap_or_default(),
            vec!["staticlib", "cdylib"]
        );
        assert_eq!(
            ios_metadata.build_targets.unwrap_or_default(),
            vec![Target::Arm, Target::ArmSim]
        );
    }
}