use cargo_metadata::{Package, Target};
use getopts::Options;
use std::env;
use std::path::{PathBuf, Path};
use std::process::ExitCode;
fn main() -> ExitCode {
let mut opts = Options::new();
opts.optopt("", "manifest-path", "Location of the Rust/Cargo project to convert", "Cargo.toml");
opts.optopt("o", "output-dir", "Where to write xcodeproj to (default: same directory as the crate)", "");
opts.optopt("", "features", "Default Cargo features to always enable", "");
opts.optflag("", "no-default-features", "Don't enable the `default` Cargo feature");
opts.optflag("", "skip-install", "Set SKIP_INSTALL=YES for embedding of bins and dylibs");
opts.optopt("", "platforms", "Space-separated list of supported Apple SDKs", "\"macosx iphoneos iphonesimulator watchos watchsimulator xros\"");
opts.optmulti("", "xcconfig", "Base configuration file. Specify twice for separate debug and release configs", "base.xcconfig");
opts.optopt("", "project-name", "Override crate name to use a differnet name in Xcode", "");
opts.optflag("", "nightly", "Use nightly rustup toolchain to build");
opts.optflag("h", "help", "This help.");
let matches = match opts.parse(env::args().skip(1)) {
Ok(m) => m,
Err(f) => {
eprintln!("error: {f}");
return ExitCode::FAILURE;
},
};
if matches.opt_present("help") {
println!("{}", opts.usage("cargo-xcode generates Xcode project files for Cargo crates"));
return ExitCode::SUCCESS;
}
let mut path = matches.opt_str("manifest-path").map(PathBuf::from);
let output_dir = matches.opt_str("output-dir");
let custom_project_name = matches.opt_str("project-name");
let skip_install = matches.opt_present("skip-install");
let features = matches.opt_str("features");
let default_features = !matches.opt_present("no-default-features");
let nightly = matches.opt_present("nightly");
let xcconfigs = matches.opt_strs("xcconfig");
let platforms = matches.opt_str("platforms").map(|p| {
p.split(|c: char| c.is_ascii_whitespace() || c == ',').filter(|s| !s.is_empty()).map(Into::into).collect()
});
let skip_cargo_arg = if matches.free.first().map(|s| s.as_str()) == Some("xcode") { 1 } else { 0 };
for arg in matches.free.into_iter().skip(skip_cargo_arg) {
if path.is_some() {
eprintln!("{}", opts.usage(&format!("error: unexpected argument '{arg}'")));
return ExitCode::FAILURE;
}
let arg_path = PathBuf::from(arg);
path = Some(if arg_path.is_file() { arg_path } else { arg_path.join("Cargo.toml") });
}
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.no_deps();
if let Some(ref path) = path {
cmd.manifest_path(path);
}
let meta = match cmd.exec() {
Ok(m) => m,
Err(cargo_metadata::Error::Io(e)) => {
eprintln!("error: Can't run `cargo metadata` for '{}'\n{e}", path.unwrap_or_else(|| env::current_dir().unwrap()).display());
if e.kind() == std::io::ErrorKind::NotFound {
eprintln!(" make sure `cargo` is in your PATH ({})", std::env::var("PATH").unwrap_or_default());
}
return ExitCode::FAILURE;
},
Err(e) => {
eprintln!("error: Can't parse cargo metadata in '{}'\n{e}", path.unwrap_or_else(|| env::current_dir().unwrap()).display());
return ExitCode::FAILURE;
},
};
let ok = meta.packages
.into_iter()
.filter_map(filter_package)
.map(move |p| {
let mut g = cargo_xcode::Generator::new(p, output_dir.as_ref().map(Path::new), custom_project_name.clone()).expect("Manifest path");
g.skip_install(skip_install);
g.nightly(nightly);
g.xcconfigs(xcconfigs.clone());
g.platforms(platforms.clone());
if let Some(f) = &features { g.features(f, default_features); }
let p = g.write_pbxproj().expect("Project writing");
println!("OK:\n{}", p.display());
})
.count();
if ok == 0 {
eprintln!(r#"error: No Rust crates found with crate-type "staticlib" or "cdylib"\nAdd `[lib] crate-type = ["lib", "cdylib"]` or add binaries"#);
return ExitCode::FAILURE;
}
ExitCode::SUCCESS
}
fn filter_package(mut package: Package) -> Option<Package> {
package.targets.retain(is_relevant_target);
if package.targets.is_empty() {
None
} else {
Some(package)
}
}
fn is_relevant_target(target: &Target) -> bool {
target.kind.iter().any(|k| k == "bin" || k == "staticlib" || k == "cdylib")
}