uniffi_bindgen_java/
lib.rs

1use anyhow::Result;
2use camino::Utf8PathBuf;
3use clap::{Parser, Subcommand};
4use std::{
5    collections::HashMap,
6    fs::{self},
7};
8use uniffi_bindgen::{BindingGenerator, Component, ComponentInterface, GenerationSettings};
9
10mod gen_java;
11
12pub struct JavaBindingGenerator;
13impl BindingGenerator for JavaBindingGenerator {
14    type Config = gen_java::Config;
15
16    fn new_config(&self, root_toml: &toml::Value) -> Result<Self::Config> {
17        Ok(
18            match root_toml.get("bindings").and_then(|b| b.get("java")) {
19                Some(v) => v.clone().try_into()?,
20                None => Default::default(),
21            },
22        )
23    }
24
25    fn update_component_configs(
26        &self,
27        settings: &GenerationSettings,
28        components: &mut Vec<Component<Self::Config>>,
29    ) -> Result<()> {
30        for c in &mut *components {
31            c.config
32                .package_name
33                .get_or_insert_with(|| format!("uniffi.{}", c.ci.namespace()));
34            c.config.cdylib_name.get_or_insert_with(|| {
35                settings
36                    .cdylib
37                    .clone()
38                    .unwrap_or_else(|| format!("uniffi_{}", c.ci.namespace()))
39            });
40        }
41        // We need to update package names
42        let packages = HashMap::<String, String>::from_iter(
43            components
44                .iter()
45                .map(|c| (c.ci.crate_name().to_string(), c.config.package_name())),
46        );
47        for c in components {
48            for (ext_crate, ext_package) in &packages {
49                if ext_crate != c.ci.crate_name()
50                    && !c.config.external_packages.contains_key(ext_crate)
51                {
52                    c.config
53                        .external_packages
54                        .insert(ext_crate.to_string(), ext_package.clone());
55                }
56            }
57        }
58        Ok(())
59    }
60
61    fn write_bindings(
62        &self,
63        settings: &GenerationSettings,
64        components: &[Component<Self::Config>],
65    ) -> anyhow::Result<()> {
66        let filename_capture = regex::Regex::new(
67            r"(?m)^(?:public\s)?(?:final\s)?(?:sealed\s)?(?:abstract\s)?(?:static\s)?(?:class|interface|enum|record)\s(\w+)",
68        )
69        .unwrap();
70        for Component { ci, config, .. } in components {
71            let bindings_str = gen_java::generate_bindings(config, ci)?;
72            let java_package_out_dir = &settings.out_dir.join(
73                config
74                    .package_name()
75                    .split('.')
76                    .collect::<Vec<_>>()
77                    .join("/"),
78            );
79            fs::create_dir_all(java_package_out_dir)?;
80            let package_line = format!("package {};", config.package_name());
81            let split_classes = bindings_str.split(&package_line);
82            let writable = split_classes
83                .map(|file| (filename_capture.captures(file), file))
84                .filter(|(x, _)| x.is_some())
85                .map(|(captures, file)| (captures.unwrap().get(1).unwrap().as_str(), file))
86                .collect::<Vec<_>>();
87            for (filename, file) in writable {
88                let java_file_location = java_package_out_dir.join(format!("{}.java", filename));
89                fs::write(&java_file_location, format!("{}\n{}", package_line, file))?;
90            }
91            if settings.try_format_code {
92                // TODO: if there's a CLI formatter that makes sense to use here, use it, PRs welcome
93                // seems like palantir-java-format is popular, but it's only exposed through plugins
94                // google-java-format is legacy popular and does have an executable all-deps JAR, but
95                // must be called with the full jar path including version numbers
96                // prettier sorta works but requires npm and packages be around for a java generator
97            }
98        }
99        Ok(())
100    }
101}
102
103#[derive(Parser)]
104#[clap(name = "uniffi-bindgen-java")]
105#[clap(version = clap::crate_version!())]
106#[clap(propagate_version = true, disable_help_subcommand = true)]
107/// Java scaffolding and bindings generator for Rust
108struct Cli {
109    #[clap(subcommand)]
110    command: Commands,
111}
112
113#[derive(Subcommand)]
114enum Commands {
115    /// Generate Java bindings
116    Generate {
117        /// Directory in which to write generated files. Default is same folder as .udl file.
118        #[clap(long, short)]
119        out_dir: Option<Utf8PathBuf>,
120
121        /// Do not try to format the generated bindings.
122        #[clap(long, short)]
123        no_format: bool,
124
125        /// Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence.
126        #[clap(long, short)]
127        config: Option<Utf8PathBuf>,
128
129        /// Extract proc-macro metadata from a native lib (cdylib or staticlib) for this crate.
130        #[clap(long)]
131        lib_file: Option<Utf8PathBuf>,
132
133        /// Pass in a cdylib path rather than a UDL file
134        #[clap(long = "library")]
135        library_mode: bool,
136
137        /// When `--library` is passed, only generate bindings for one crate.
138        /// When `--library` is not passed, use this as the crate name instead of attempting to
139        /// locate and parse Cargo.toml.
140        #[clap(long = "crate")]
141        crate_name: Option<String>,
142
143        /// Path to the UDL file, or cdylib if `library-mode` is specified
144        source: Utf8PathBuf,
145
146        /// Whether we should exclude dependencies when running "cargo metadata".
147        /// This will mean external types may not be resolved if they are implemented in crates
148        /// outside of this workspace.
149        /// This can be used in environments when all types are in the namespace and fetching
150        /// all sub-dependencies causes obscure platform specific problems.
151        #[clap(long)]
152        metadata_no_deps: bool,
153    },
154    /// Generate Rust scaffolding code
155    Scaffolding {
156        /// Directory in which to write generated files. Default is same folder as .udl file.
157        #[clap(long, short)]
158        out_dir: Option<Utf8PathBuf>,
159
160        /// Do not try to format the generated bindings.
161        #[clap(long, short)]
162        no_format: bool,
163
164        /// Path to the UDL file.
165        udl_file: Utf8PathBuf,
166    },
167    /// Print a debug representation of the interface from a dynamic library
168    PrintRepr {
169        /// Path to the library file (.so, .dll, .dylib, or .a)
170        path: Utf8PathBuf,
171    },
172}
173
174pub fn run_main() -> Result<()> {
175    let cli = Cli::parse();
176    match cli.command {
177        Commands::Generate {
178            out_dir,
179            no_format,
180            config,
181            lib_file,
182            library_mode,
183            crate_name,
184            source,
185            metadata_no_deps,
186        } => {
187            if library_mode {
188                use uniffi_bindgen::library_mode::generate_bindings;
189                if lib_file.is_some() {
190                    panic!("--lib-file is not compatible with --library.")
191                }
192                let out_dir = out_dir.expect("--out-dir is required when using --library");
193
194                let config_supplier = {
195                    use uniffi_bindgen::cargo_metadata::CrateConfigSupplier;
196                    let mut cmd = cargo_metadata::MetadataCommand::new();
197                    if metadata_no_deps {
198                        cmd.no_deps();
199                    }
200                    let metadata = cmd.exec()?;
201                    CrateConfigSupplier::from(metadata)
202                };
203
204                generate_bindings(
205                    &source,
206                    crate_name,
207                    &JavaBindingGenerator,
208                    &config_supplier,
209                    config.as_deref(),
210                    &out_dir,
211                    !no_format,
212                )?;
213            } else {
214                use uniffi_bindgen::generate_bindings;
215                generate_bindings(
216                    &source,
217                    config.as_deref(),
218                    JavaBindingGenerator,
219                    out_dir.as_deref(),
220                    lib_file.as_deref(),
221                    crate_name.as_deref(),
222                    !no_format,
223                )?;
224            }
225        }
226        Commands::Scaffolding {
227            out_dir,
228            no_format,
229            udl_file,
230        } => {
231            uniffi_bindgen::generate_component_scaffolding(
232                &udl_file,
233                out_dir.as_deref(),
234                !no_format,
235            )?;
236        }
237        Commands::PrintRepr { path } => {
238            uniffi_bindgen::print_repr(&path)?;
239        }
240    };
241    Ok(())
242}