/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use camino::Utf8PathBuf; use clap::{Args, Parser, Subcommand, ValueEnum}; use std::fmt; // TODO: remove blanket import use uniffi_bindgen::bindings::*; use uniffi_bindgen::pipeline::initial; use uniffi_pipeline::PrintOptions; /// TargetLanguage uniffi_bindgen, with a `clap::ValueEnum` derive. #[derive(Copy, Clone, ValueEnum)] enum TargetLanguageArg { Kotlin, Swift, Python, Ruby, } impl fmt::Display for TargetLanguageArg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Kotlin => write!(f, "kotlin"), Self::Swift => write!(f, "swift"), Self::Python => write!(f, "python"), Self::Ruby => write!(f, "ruby"), } } } impl From for TargetLanguage { fn from(l: TargetLanguageArg) -> Self { match l { TargetLanguageArg::Kotlin => Self::Kotlin, TargetLanguageArg::Swift => Self::Swift, TargetLanguageArg::Python => Self::Python, TargetLanguageArg::Ruby => Self::Ruby, } } } // Structs to help our cmdline parsing. Note that docstrings below form part // of the "help" output. /// Scaffolding and bindings generator for Rust #[derive(Parser)] #[clap(name = "uniffi-bindgen")] #[clap(version = clap::crate_version!())] #[clap(propagate_version = true)] struct Cli { #[clap(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Generate foreign language bindings Generate { /// Foreign language(s) for which to build bindings. #[clap(long, short, value_enum)] language: Vec, /// Directory in which to write generated files. Default is same folder as .udl file. #[clap(long, short)] out_dir: Option, /// Do not try to format the generated bindings. #[clap(long, short)] no_format: bool, /// Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence. #[clap(long, short)] config: Option, /// Deprecated /// /// This used to signal that a source file is a library rather than a UDL file. /// Nowadays, UniFFI will auto-detect this. #[clap(long = "library")] _library_mode: bool, /// When `--library` is passed, only generate bindings for one crate. /// When `--library` is not passed, use this as the crate name instead of attempting to /// locate and parse Cargo.toml. #[clap(long = "crate")] crate_name: Option, /// Path to the UDL file, or cdylib if `library-mode` is specified source: Utf8PathBuf, /// Whether we should exclude dependencies when running "cargo metadata". /// This will mean external types may not be resolved if they are implemented in crates /// outside of this workspace. /// This can be used in environments when all types are in the namespace and fetching /// all sub-dependencies causes obscure platform specific problems. #[clap(long)] metadata_no_deps: bool, }, /// Generate Rust scaffolding code Scaffolding { /// Directory in which to write generated files. Default is same folder as .udl file. #[clap(long, short)] out_dir: Option, /// Do not try to format the generated bindings. #[clap(long, short)] no_format: bool, /// Path to the UDL file. udl_file: Utf8PathBuf, }, /// Inspect the bindings render pipeline Pipeline(PipelineArgs), } #[derive(Args)] struct PipelineArgs { /// Pass in a cdylib path rather than a UDL file #[clap(long = "library")] library_mode: bool, /// Path to the UDL file, or cdylib if `library-mode` is specified source: Utf8PathBuf, /// When `--library` is passed, only generate bindings for one crate. /// When `--library` is not passed, use this as the crate name instead of attempting to /// locate and parse Cargo.toml. #[clap(long = "crate")] crate_name: Option, /// Whether we should exclude dependencies when running "cargo metadata". /// This will mean external types may not be resolved if they are implemented in crates /// outside of this workspace. /// This can be used in environments when all types are in the namespace and fetching /// all sub-dependencies causes obscure platform specific problems. #[clap(long)] metadata_no_deps: bool, /// Bindings Language language: TargetLanguageArg, /// Only show passes that match /// /// Use `last` to only show the last pass, this can be useful when you're writing new pipelines #[clap(short, long)] pass: Option, /// Don't show diffs for middle passes #[clap(long)] no_diff: bool, /// Only show data for types with name #[clap(short = 't', long = "type")] filter_type: Option, /// Only show data for items with fields that match #[clap(short = 'n', long = "name")] filter_name: Option, } pub fn run_main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { Commands::Generate { language, out_dir, no_format, config, source, crate_name, metadata_no_deps, .. } => { if language.is_empty() { panic!("please specify at least one language with --language") } generate(GenerateOptions { languages: language.into_iter().map(TargetLanguage::from).collect(), out_dir: out_dir .expect("--out-dir is required when generating {language} bindings"), source, config_override: config, crate_filter: crate_name, metadata_no_deps, format: !no_format, })?; } Commands::Scaffolding { out_dir, no_format, udl_file, } => { uniffi_bindgen::generate_component_scaffolding( &udl_file, out_dir.as_deref(), !no_format, )?; } Commands::Pipeline(args) => { let mut paths = uniffi_bindgen::BindgenPaths::default(); #[cfg(feature = "cargo-metadata")] paths.add_cargo_metadata_layer(args.metadata_no_deps)?; let initial_root = if args.library_mode { initial::Root::from_library(paths, &args.source, args.crate_name)? } else { initial::Root::from_udl(paths, &args.source, args.crate_name)? }; let opts = PrintOptions { pass: args.pass, no_diff: args.no_diff, filter_type: args.filter_type, filter_name: args.filter_name, }; match args.language { TargetLanguageArg::Python => python::pipeline().print_passes(initial_root, opts)?, language => unimplemented!("{language} does not use the bindings IR pipeline yet"), }; } }; Ok(()) }