uniffi_bindgen_library_mode/
main.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::{
6    env::consts::{DLL_PREFIX, DLL_SUFFIX},
7    fmt, process,
8};
9
10use anyhow::{bail, Result};
11use camino::{Utf8Path, Utf8PathBuf};
12use clap::{Args, Parser, Subcommand};
13use uniffi_bindgen::bindings::{generate_swift_bindings, SwiftBindingsOptions};
14
15#[derive(Parser)]
16#[command(version, about, long_about = None)]
17struct Cli {
18    #[command(flatten)]
19    megazord: MegazordArg,
20    #[command(subcommand)]
21    command: Command,
22}
23
24#[derive(Args)]
25#[group(required = true, multiple = false)]
26struct MegazordArg {
27    /// Name of the megazord to use
28    #[arg(short, long, value_parser=["megazord", "megazord_ios", "megazord_focus", "cirrus", "nimbus-experimenter"])]
29    megazord: Option<String>,
30
31    /// Path to a library file
32    #[arg(short, long)]
33    library: Option<Utf8PathBuf>,
34}
35
36#[derive(Subcommand)]
37enum Command {
38    Kotlin {
39        out_dir: Utf8PathBuf,
40    },
41    Swift {
42        out_dir: Utf8PathBuf,
43        /// Generate swift files
44        #[arg(long)]
45        swift_sources: bool,
46        /// Generate header files
47        #[arg(long)]
48        headers: bool,
49        /// Generate modulemap
50        #[arg(long)]
51        modulemap: bool,
52        // Generate an xcframework-compatible modulemap
53        #[arg(long)]
54        xcframework: bool,
55        /// module name for the generated modulemap
56        #[arg(long)]
57        module_name: Option<String>,
58        /// filename for the generate modulemap
59        #[arg(long)]
60        modulemap_filename: Option<String>,
61    },
62    Python {
63        out_dir: Utf8PathBuf,
64    },
65}
66
67enum Language {
68    Kotlin,
69    Swift,
70    Python,
71}
72
73fn main() {
74    if let Err(e) = run_uniffi_bindgen(Cli::parse()) {
75        eprintln!("{e}");
76        std::process::exit(1);
77    }
78}
79
80fn run_uniffi_bindgen(cli: Cli) -> Result<()> {
81    let metadata = cargo_metadata::MetadataCommand::new()
82        .exec()
83        .expect("error running cargo metadata");
84    let megazord = Megazord::new(
85        &cli.megazord,
86        cli.command.language(),
87        &metadata.workspace_root,
88    )?;
89    let config_supplier = uniffi::CargoMetadataConfigSupplier::from(metadata);
90
91    match cli.command {
92        Command::Kotlin { out_dir } => {
93            uniffi::generate_bindings_library_mode(
94                &megazord.library_path,
95                None,
96                &uniffi::KotlinBindingGenerator,
97                &config_supplier,
98                None,
99                &out_dir,
100                false,
101            )?;
102        }
103        Command::Swift {
104            out_dir,
105            mut swift_sources,
106            mut headers,
107            mut modulemap,
108            xcframework,
109            module_name,
110            modulemap_filename,
111        } => {
112            let module_name = module_name.unwrap_or_else(|| "MozillaRustComponents".to_owned());
113            // If no generate kinds were specified, generate them all
114            if !(swift_sources || headers || modulemap) {
115                swift_sources = true;
116                headers = true;
117                modulemap = true;
118            }
119
120            generate_swift_bindings(SwiftBindingsOptions {
121                out_dir,
122                generate_swift_sources: swift_sources,
123                generate_headers: headers,
124                generate_modulemap: modulemap,
125                library_path: megazord.library_path,
126                xcframework,
127                module_name: Some(module_name),
128                modulemap_filename,
129                metadata_no_deps: false,
130            })?;
131        }
132        Command::Python { out_dir } => {
133            uniffi::generate_bindings_library_mode(
134                &megazord.library_path,
135                None,
136                &uniffi::PythonBindingGenerator,
137                &config_supplier,
138                None,
139                &out_dir,
140                false,
141            )?;
142        }
143    };
144    Ok(())
145}
146
147struct Megazord {
148    library_path: Utf8PathBuf,
149}
150
151impl Megazord {
152    fn new(arg: &MegazordArg, language: Language, workspace_root: &Utf8Path) -> Result<Self> {
153        if let Some(crate_name) = &arg.megazord {
154            // Build the megazord
155            process::Command::new("cargo")
156                .args(["build", "--release", "-p", crate_name])
157                .spawn()?
158                .wait()?;
159
160            let filename = match language {
161                // Swift uses static libs
162                Language::Swift => format!("lib{}.a", crate_name.replace('-', "_")),
163                // Everything else uses dynamic libraries
164                _ => format!(
165                    "{}{}{}",
166                    DLL_PREFIX,
167                    crate_name.replace('-', "_"),
168                    DLL_SUFFIX
169                ),
170            };
171            let library_path = workspace_root.join("target").join("release").join(filename);
172            Ok(Self { library_path })
173        } else if let Some(library_path) = &arg.library {
174            Ok(Self {
175                library_path: library_path.clone(),
176            })
177        } else {
178            bail!("Neither megazord nor library specified")
179        }
180    }
181}
182
183impl fmt::Display for Language {
184    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185        let name = match self {
186            Self::Swift => "swift",
187            Self::Kotlin => "kotlin",
188            Self::Python => "python",
189        };
190        write!(f, "{}", name)
191    }
192}
193
194impl Command {
195    fn language(&self) -> Language {
196        match self {
197            Self::Kotlin { .. } => Language::Kotlin,
198            Self::Swift { .. } => Language::Swift,
199            Self::Python { .. } => Language::Python,
200        }
201    }
202}