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                ..SwiftBindingsOptions::default()
131            })?;
132        }
133        Command::Python { out_dir } => {
134            uniffi::generate_bindings_library_mode(
135                &megazord.library_path,
136                None,
137                &uniffi::PythonBindingGenerator,
138                &config_supplier,
139                None,
140                &out_dir,
141                false,
142            )?;
143        }
144    };
145    Ok(())
146}
147
148struct Megazord {
149    library_path: Utf8PathBuf,
150}
151
152impl Megazord {
153    fn new(arg: &MegazordArg, language: Language, workspace_root: &Utf8Path) -> Result<Self> {
154        if let Some(crate_name) = &arg.megazord {
155            // Build the megazord
156            process::Command::new("cargo")
157                .args(["build", "--release", "-p", crate_name])
158                .spawn()?
159                .wait()?;
160
161            let filename = match language {
162                // Swift uses static libs
163                Language::Swift => format!("lib{}.a", crate_name.replace('-', "_")),
164                // Everything else uses dynamic libraries
165                _ => format!(
166                    "{}{}{}",
167                    DLL_PREFIX,
168                    crate_name.replace('-', "_"),
169                    DLL_SUFFIX
170                ),
171            };
172            let library_path = workspace_root.join("target").join("release").join(filename);
173            Ok(Self { library_path })
174        } else if let Some(library_path) = &arg.library {
175            Ok(Self {
176                library_path: library_path.clone(),
177            })
178        } else {
179            bail!("Neither megazord nor library specified")
180        }
181    }
182}
183
184impl fmt::Display for Language {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        let name = match self {
187            Self::Swift => "swift",
188            Self::Kotlin => "kotlin",
189            Self::Python => "python",
190        };
191        write!(f, "{}", name)
192    }
193}
194
195impl Command {
196    fn language(&self) -> Language {
197        match self {
198            Self::Kotlin { .. } => Language::Kotlin,
199            Self::Swift { .. } => Language::Swift,
200            Self::Python { .. } => Language::Python,
201        }
202    }
203}