swift_package/
lib.rs

1mod bindings;
2mod conf;
3mod swift_package_file;
4mod swift_resources_ext;
5
6use anyhow::{anyhow, bail, Context, Result};
7use camino_fs::{Utf8Path, Utf8PathExt};
8pub use conf::{CliArgs, Configuration, SwiftPackageConfiguration};
9use fs_extra::dir::CopyOptions;
10use log::LevelFilter;
11use simplelog::{
12    format_description, ColorChoice, Config as LogConfig, ConfigBuilder, TermLogger, TerminalMode,
13};
14use xcframework::Produced;
15pub use xcframework::{
16    CliArgs as XCCliArgs, Configuration as XCConfig, LibType, XCFrameworkConfiguration,
17};
18
19const SWIFT_PACKAGE_UNIFFY_VERSION: &str = env!("UNIFFY_BINDGEN_VERSION");
20
21#[allow(unused_imports)]
22#[cfg(not(test))]
23use log::{debug, info, warn};
24
25use std::env;
26#[allow(unused_imports)]
27#[cfg(test)]
28use std::{println as info, println as warn, println as debug}; // Workaround to use prinltn! for logs.
29
30pub fn build_cli(cli: CliArgs) -> Result<()> {
31    setup_logging(cli.verbose);
32    let conf = Configuration::load(cli)?;
33    build(conf)
34}
35
36pub fn build(conf: Configuration) -> Result<()> {
37    conf.framework_build_dir.rm()?;
38    conf.framework_build_dir.mkdirs()?;
39
40    info!("generating bindings...");
41    bindings::generate(&conf).context("generating bindings")?;
42
43    let produced = xcframework::build(&conf.xcframework).context("building with xcframework")?;
44
45    let resource_dirs = copy_resources(&conf)?;
46    if !resource_dirs.is_empty() {
47        swift_resources_ext::generate(&conf, &resource_dirs)?;
48    }
49    swift_package_file::generate(&conf, &produced, &resource_dirs)
50        .context("generate swift package file")?;
51
52    move_framework(&conf, &produced)?;
53    copy_swift_sources(&conf)?;
54    Ok(())
55}
56
57fn copy_resources(conf: &Configuration) -> Result<Vec<&str>> {
58    let mut names = vec![];
59    for dir in &conf.cargo_section.resource_dirs {
60        if !dir.is_dir() {
61            bail!("Expected a resource dir: {dir} but that's not a directory");
62        }
63
64        let name = dir.iter().last().ok_or(anyhow!("Empty dir: {dir}"))?;
65        let to = conf
66            .framework_build_dir
67            .join("Sources")
68            .join(&conf.cargo_section.package_name);
69
70        copy_dir(dir, &to)?;
71        names.push(name);
72    }
73    Ok(names)
74}
75
76fn copy_swift_sources(conf: &Configuration) -> Result<()> {
77    let to = conf
78        .framework_build_dir
79        .join("Sources")
80        .join(&conf.cargo_section.package_name);
81    to.mkdirs()?;
82    let from = &conf.bindings_build_dir;
83
84    from.ls()
85        .files()
86        .relative_paths()
87        .filter(|f| f.extension() == Some("swift"))
88        .try_for_each(|f| from.join(&f).cp(to.join(f)))?;
89    Ok(())
90}
91
92fn move_framework(conf: &Configuration, produced: &Produced) -> Result<()> {
93    conf.framework_build_dir.mkdirs()?;
94
95    produced.path.mv(conf
96        .framework_build_dir
97        .join(produced.path.file_name().unwrap()))?;
98    Ok(())
99}
100
101pub fn copy_dir(from: &Utf8Path, to: &Utf8Path) -> Result<()> {
102    to.mkdirs()?;
103
104    fs_extra::dir::copy(from, to, &CopyOptions::new()).context(format!(
105        "Could not recursively copy the directory {from} to {to}"
106    ))?;
107    Ok(())
108}
109
110fn setup_logging(verbose: u32) {
111    let log_level = match verbose {
112        0 => LevelFilter::Warn,
113        1 => LevelFilter::Info,
114        2 => LevelFilter::Debug,
115        _ => LevelFilter::Trace,
116    };
117
118    let log_config = if env::var("CARGO").is_ok() && verbose != 0 {
119        ConfigBuilder::new()
120            .set_time_format_custom(format_description!(
121                "cargo::warning=[hour]:[minute]:[second]"
122            ))
123            .build()
124    } else {
125        LogConfig::default()
126    };
127
128    let _ = TermLogger::init(
129        log_level,
130        log_config,
131        TerminalMode::Mixed,
132        ColorChoice::Auto,
133    );
134}