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 config.nullness_annotations() {
77 let package_info = format!("@org.jspecify.annotations.NullMarked\n{}", package_line);
78 fs::write(java_package_out_dir.join("package-info.java"), package_info)?;
79 }
80
81 if options.format {
82 }
88 }
89 Ok(())
90}
91
92fn parse_config(
94 ci: &ComponentInterface,
95 root_toml: toml::Value,
96 cdylib: Option<String>,
97) -> Result<Config> {
98 let mut config: Config = match root_toml.get("bindings").and_then(|b| b.get("java")) {
99 Some(v) => v.clone().try_into()?,
100 None => Default::default(),
101 };
102 config
103 .package_name
104 .get_or_insert_with(|| format!("uniffi.{}", ci.namespace()));
105 config.cdylib_name.get_or_insert_with(|| {
106 cdylib
107 .clone()
108 .unwrap_or_else(|| format!("uniffi_{}", ci.namespace()))
109 });
110 Ok(config)
111}
112
113fn apply_renames_and_external_packages(components: &mut Vec<Component<Config>>) {
116 let mut module_renames = HashMap::new();
118 for c in components.iter() {
119 if !c.config.rename.is_empty() {
120 let module_path = c.ci.crate_name().to_string();
121 module_renames.insert(module_path, c.config.rename.clone());
122 }
123 }
124
125 if !module_renames.is_empty() {
127 for c in &mut *components {
128 rename(&mut c.ci, &module_renames);
129 }
130 }
131
132 let packages = HashMap::<String, String>::from_iter(
134 components
135 .iter()
136 .map(|c| (c.ci.crate_name().to_string(), c.config.package_name())),
137 );
138 for c in components {
139 for (ext_crate, ext_package) in &packages {
140 if ext_crate != c.ci.crate_name() && !c.config.external_packages.contains_key(ext_crate)
141 {
142 c.config
143 .external_packages
144 .insert(ext_crate.to_string(), ext_package.clone());
145 }
146 }
147 }
148}
149
150fn create_bindgen_paths(
152 config_override: Option<&Utf8Path>,
153 metadata_no_deps: bool,
154) -> Result<BindgenPaths> {
155 let mut paths = BindgenPaths::default();
156
157 if let Some(config_path) = config_override {
159 paths.add_config_override_layer(config_path.to_path_buf());
160 }
161
162 paths
164 .add_cargo_metadata_layer(metadata_no_deps)
165 .context("Failed to load cargo metadata")?;
166
167 Ok(paths)
168}
169
170#[derive(Parser)]
171#[clap(name = "uniffi-bindgen-java")]
172#[clap(version = clap::crate_version!())]
173#[clap(propagate_version = true, disable_help_subcommand = true)]
174struct Cli {
176 #[clap(subcommand)]
177 command: Commands,
178}
179
180#[derive(Subcommand)]
181enum Commands {
182 Generate {
184 #[clap(long, short)]
186 out_dir: Option<Utf8PathBuf>,
187
188 #[clap(long, short)]
190 no_format: bool,
191
192 #[clap(long, short)]
194 config: Option<Utf8PathBuf>,
195
196 #[clap(long = "crate")]
200 crate_name: Option<String>,
201
202 source: Utf8PathBuf,
204
205 #[clap(long)]
211 metadata_no_deps: bool,
212 },
213 Scaffolding {
215 #[clap(long, short)]
217 out_dir: Option<Utf8PathBuf>,
218
219 #[clap(long, short)]
221 no_format: bool,
222
223 udl_file: Utf8PathBuf,
225 },
226 PrintRepr {
228 path: Utf8PathBuf,
230 },
231}
232
233pub fn run_main() -> Result<()> {
234 let cli = Cli::parse();
235 match cli.command {
236 Commands::Generate {
237 out_dir,
238 no_format,
239 config,
240 crate_name,
241 source,
242 metadata_no_deps,
243 } => {
244 let out_dir = out_dir.unwrap_or_else(|| {
245 source
246 .parent()
247 .map(|p| p.to_path_buf())
248 .unwrap_or_else(|| Utf8PathBuf::from("."))
249 });
250
251 let paths = create_bindgen_paths(config.as_deref(), metadata_no_deps)?;
253 let loader = BindgenLoader::new(paths);
254
255 fs::create_dir_all(&out_dir)?;
256
257 generate(
258 &loader,
259 &GenerateOptions {
260 source,
261 out_dir,
262 format: !no_format,
263 crate_filter: crate_name,
264 },
265 )?;
266 }
267 Commands::Scaffolding {
268 out_dir,
269 no_format,
270 udl_file,
271 } => {
272 uniffi_bindgen::generate_component_scaffolding(
273 &udl_file,
274 out_dir.as_deref(),
275 !no_format,
276 )?;
277 }
278 Commands::PrintRepr { path } => {
279 uniffi_bindgen::print_repr(&path)?;
280 }
281 };
282 Ok(())
283}