1use anyhow::{Context, Result};
2use camino::{Utf8Path, Utf8PathBuf};
3use clap::{Parser, Subcommand};
4use std::collections::HashMap;
5use std::fs;
6use uniffi_bindgen::{
7 BindgenLoader, BindgenPaths, Component, ComponentInterface, interface::rename,
8};
9
10mod gen_java;
11use gen_java::Config;
12
13pub struct GenerateOptions {
15 pub source: Utf8PathBuf,
17 pub out_dir: Utf8PathBuf,
19 pub format: bool,
21 pub crate_filter: Option<String>,
23}
24
25pub fn generate(loader: &BindgenLoader, options: &GenerateOptions) -> Result<()> {
26 let metadata = loader.load_metadata(&options.source)?;
27 let cis = loader.load_cis(metadata)?;
28 let cdylib = loader.library_name(&options.source).map(|l| l.to_string());
29 let mut components =
30 loader.load_components(cis, |ci, toml| parse_config(ci, toml, cdylib.clone()))?;
31
32 apply_renames_and_external_packages(&mut components);
34
35 for c in components.iter_mut() {
37 c.ci.derive_ffi_funcs()?;
38 }
39
40 let filename_capture = regex::Regex::new(
42 r"(?m)^(?:public\s)?(?:final\s)?(?:sealed\s)?(?:abstract\s)?(?:static\s)?(?:class|interface|enum|record)\s(\w+)",
43 )
44 .unwrap();
45
46 for Component { ci, config, .. } in components {
47 if let Some(crate_filter) = &options.crate_filter
48 && ci.crate_name() != crate_filter
49 {
50 continue;
51 }
52
53 let bindings_str = gen_java::generate_bindings(&config, &ci)?;
54 let java_package_out_dir = options.out_dir.join(
55 config
56 .package_name()
57 .split('.')
58 .collect::<Vec<_>>()
59 .join("/"),
60 );
61 fs::create_dir_all(&java_package_out_dir)?;
62
63 let package_line = format!("package {};", config.package_name());
64 let split_classes = bindings_str.split(&package_line);
65 let writable = split_classes
66 .map(|file| (filename_capture.captures(file), file))
67 .filter(|(x, _)| x.is_some())
68 .map(|(captures, file)| (captures.unwrap().get(1).unwrap().as_str(), file))
69 .collect::<Vec<_>>();
70
71 for (filename, file) in writable {
72 let java_file_location = java_package_out_dir.join(format!("{}.java", filename));
73 fs::write(&java_file_location, format!("{}\n{}", package_line, file))?;
74 }
75
76 if options.format {
77 }
83 }
84 Ok(())
85}
86
87fn parse_config(
89 ci: &ComponentInterface,
90 root_toml: toml::Value,
91 cdylib: Option<String>,
92) -> Result<Config> {
93 let mut config: Config = match root_toml.get("bindings").and_then(|b| b.get("java")) {
94 Some(v) => v.clone().try_into()?,
95 None => Default::default(),
96 };
97 config
98 .package_name
99 .get_or_insert_with(|| format!("uniffi.{}", ci.namespace()));
100 config.cdylib_name.get_or_insert_with(|| {
101 cdylib
102 .clone()
103 .unwrap_or_else(|| format!("uniffi_{}", ci.namespace()))
104 });
105 Ok(config)
106}
107
108fn apply_renames_and_external_packages(components: &mut Vec<Component<Config>>) {
111 let mut module_renames = HashMap::new();
113 for c in components.iter() {
114 if !c.config.rename.is_empty() {
115 let module_path = c.ci.crate_name().to_string();
116 module_renames.insert(module_path, c.config.rename.clone());
117 }
118 }
119
120 if !module_renames.is_empty() {
122 for c in &mut *components {
123 rename(&mut c.ci, &module_renames);
124 }
125 }
126
127 let packages = HashMap::<String, String>::from_iter(
129 components
130 .iter()
131 .map(|c| (c.ci.crate_name().to_string(), c.config.package_name())),
132 );
133 for c in components {
134 for (ext_crate, ext_package) in &packages {
135 if ext_crate != c.ci.crate_name() && !c.config.external_packages.contains_key(ext_crate)
136 {
137 c.config
138 .external_packages
139 .insert(ext_crate.to_string(), ext_package.clone());
140 }
141 }
142 }
143}
144
145fn create_bindgen_paths(
147 config_override: Option<&Utf8Path>,
148 metadata_no_deps: bool,
149) -> Result<BindgenPaths> {
150 let mut paths = BindgenPaths::default();
151
152 if let Some(config_path) = config_override {
154 paths.add_config_override_layer(config_path.to_path_buf());
155 }
156
157 paths
159 .add_cargo_metadata_layer(metadata_no_deps)
160 .context("Failed to load cargo metadata")?;
161
162 Ok(paths)
163}
164
165#[derive(Parser)]
166#[clap(name = "uniffi-bindgen-java")]
167#[clap(version = clap::crate_version!())]
168#[clap(propagate_version = true, disable_help_subcommand = true)]
169struct Cli {
171 #[clap(subcommand)]
172 command: Commands,
173}
174
175#[derive(Subcommand)]
176enum Commands {
177 Generate {
179 #[clap(long, short)]
181 out_dir: Option<Utf8PathBuf>,
182
183 #[clap(long, short)]
185 no_format: bool,
186
187 #[clap(long, short)]
189 config: Option<Utf8PathBuf>,
190
191 #[clap(long = "crate")]
195 crate_name: Option<String>,
196
197 source: Utf8PathBuf,
199
200 #[clap(long)]
206 metadata_no_deps: bool,
207 },
208 Scaffolding {
210 #[clap(long, short)]
212 out_dir: Option<Utf8PathBuf>,
213
214 #[clap(long, short)]
216 no_format: bool,
217
218 udl_file: Utf8PathBuf,
220 },
221 PrintRepr {
223 path: Utf8PathBuf,
225 },
226}
227
228pub fn run_main() -> Result<()> {
229 let cli = Cli::parse();
230 match cli.command {
231 Commands::Generate {
232 out_dir,
233 no_format,
234 config,
235 crate_name,
236 source,
237 metadata_no_deps,
238 } => {
239 let out_dir = out_dir.unwrap_or_else(|| {
240 source
241 .parent()
242 .map(|p| p.to_path_buf())
243 .unwrap_or_else(|| Utf8PathBuf::from("."))
244 });
245
246 let paths = create_bindgen_paths(config.as_deref(), metadata_no_deps)?;
248 let loader = BindgenLoader::new(paths);
249
250 fs::create_dir_all(&out_dir)?;
251
252 generate(
253 &loader,
254 &GenerateOptions {
255 source,
256 out_dir,
257 format: !no_format,
258 crate_filter: crate_name,
259 },
260 )?;
261 }
262 Commands::Scaffolding {
263 out_dir,
264 no_format,
265 udl_file,
266 } => {
267 uniffi_bindgen::generate_component_scaffolding(
268 &udl_file,
269 out_dir.as_deref(),
270 !no_format,
271 )?;
272 }
273 Commands::PrintRepr { path } => {
274 uniffi_bindgen::print_repr(&path)?;
275 }
276 };
277 Ok(())
278}