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}; pub 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}