swift_package/conf/
configuration.rs

1use crate::SWIFT_PACKAGE_UNIFFY_VERSION;
2
3use super::{CliArgs, SwiftPackageConfiguration};
4use anyhow::{anyhow, bail, Context, Result};
5use camino_fs::Utf8PathBuf;
6use cargo_metadata::{Metadata, MetadataCommand, Package, TargetKind};
7use xcframework::{Configuration as XCMainConfig, XCFrameworkConfiguration};
8
9#[derive(Debug)]
10pub struct Configuration {
11    pub target_name: String,
12    pub cargo_section: SwiftPackageConfiguration,
13    pub xcframework: XCMainConfig,
14    pub cli: CliArgs,
15
16    /// When in a workspace configuration, this is different
17    /// from the package dir.
18    pub manifest_dir: Utf8PathBuf,
19    pub target_dir: Utf8PathBuf,
20    pub framework_build_dir: Utf8PathBuf,
21    pub bindings_build_dir: Utf8PathBuf,
22}
23
24impl Configuration {
25    pub fn load(cli: CliArgs) -> Result<Self> {
26        let manifest_path = cli
27            .manifest_path
28            .clone()
29            .unwrap_or_else(|| Utf8PathBuf::from("Cargo.toml"));
30        let mut manifest_dir = manifest_path.clone();
31        manifest_dir.pop();
32
33        let metadata = MetadataCommand::new().manifest_path(manifest_path).exec()?;
34
35        let target_dir = cli
36            .target_dir
37            .clone()
38            .unwrap_or_else(|| metadata.target_directory.clone());
39
40        let package = if let Some(package) = &cli.package {
41            metadata
42                .workspace_packages()
43                .iter()
44                .find(|p| &p.name == package)
45                .ok_or(anyhow!("Could not find package '{package}'"))?
46        } else {
47            metadata
48                .root_package()
49                .ok_or(anyhow!("Could not find root package in metadata"))?
50        };
51        let package_dir = package.manifest_path.parent().unwrap();
52
53        uniffi_version_check(&package, &metadata)?;
54        let Some(section) = package.metadata.get("swift-package") else {
55            bail!("Missing '[package.metadata.swift-package]' section in Cargo.toml")
56        };
57        let sp_conf = SwiftPackageConfiguration::parse(&section, &package_dir).context(
58            "Error when creating swift package configuration by parsing \
59                Cargo.toml section [package.metadata.swift-package]",
60        )?;
61        let target_name = find_target_name(&package)?;
62        let build_dir = target_dir.join(format!("lib{target_name}"));
63        let framework_build_dir = build_dir.join(format!("{}.package", sp_conf.package_name));
64        let bindings_build_dir = build_dir.join("bindings");
65
66        let mut xc_conf = XCFrameworkConfiguration::parse(&section, &package_dir, false).context(
67            "Error when creating xcframework configuration by parsing \
68                Cargo.toml section [package.metadata.swift-package]",
69        )?;
70        xc_conf.include_dir = bindings_build_dir.clone();
71        let xc_cli = cli.to_xc_cli();
72        let xcframework = XCMainConfig::new(&metadata, package, xc_cli, xc_conf)?;
73
74        Ok(Self {
75            cargo_section: sp_conf,
76            xcframework,
77            cli,
78            manifest_dir,
79            framework_build_dir,
80            bindings_build_dir,
81            target_name,
82            target_dir,
83        })
84    }
85
86    pub fn dylib_file(&self) -> Utf8PathBuf {
87        let profile = if self.cli.release { "release" } else { "debug" };
88        self.target_dir
89            .join(profile)
90            .join(format!("lib{}.dylib", self.target_name))
91    }
92}
93
94fn find_target_name(package: &Package) -> Result<String> {
95    let target = package
96        .targets
97        .iter()
98        .find(|t| t.kind.iter().any(|k| *k == TargetKind::CDyLib))
99        .ok_or(anyhow!(
100            "Could not find a cdylib target in package {}",
101            package.name
102        ))?;
103    Ok(target.name.clone())
104}
105
106fn uniffi_version_check(package: &Package, metadata: &Metadata) -> Result<()> {
107    package
108        .dependencies
109        .iter()
110        .find(|dep| dep.name == "uniffi")
111        .ok_or(anyhow!(
112            "The package {} should have a uniffi dependency",
113            package.name
114        ))?;
115    let uniffi_bindgen_version = metadata
116        .packages
117        .iter()
118        .find(|pack| pack.name == "uniffi")
119        .unwrap()
120        .version
121        .to_string();
122
123    let expected = major_and_minor(SWIFT_PACKAGE_UNIFFY_VERSION);
124    let found = major_and_minor(&uniffi_bindgen_version);
125    if expected != found {
126        bail!(
127            "uniffi_bindgen version mismatch: \
128            swift-package is build with {SWIFT_PACKAGE_UNIFFY_VERSION} \
129            but the project uses {uniffi_bindgen_version}"
130        );
131    }
132    Ok(())
133}
134
135fn major_and_minor(semver: &str) -> String {
136    let mut parts = semver.split('.');
137    let major = parts.next().unwrap();
138    let minor = parts.next().unwrap();
139    format!("{}.{}", major, minor)
140}