gobley_uniffi_bindgen/
lib.rs1use std::{collections::HashMap, fs::File, io::Write, process::Command};
8
9use anyhow::Result;
10use camino::{Utf8Path, Utf8PathBuf};
11use fs_err as fs;
12use uniffi_bindgen::{BindingGenerator, Component, ComponentInterface, GenerationSettings};
13
14mod gen_kotlin_multiplatform;
15use gen_kotlin_multiplatform::{generate_bindings, Config};
16
17pub struct KotlinBindingGenerator;
18impl BindingGenerator for KotlinBindingGenerator {
19 type Config = Config;
20
21 fn new_config(&self, root_toml: &toml::value::Value) -> Result<Self::Config> {
22 Ok(root_toml.clone().try_into()?)
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 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 ) -> Result<()> {
66 for Component { ci, config, .. } in components {
67 let bindings = generate_bindings(config, ci)?;
68
69 write_bindings_target(ci, settings, config, "common", bindings.common);
70
71 if let Some(jvm) = bindings.jvm {
72 write_bindings_target(ci, settings, config, "jvm", jvm);
73 }
74 if let Some(android) = bindings.android {
75 write_bindings_target(ci, settings, config, "android", android);
76 }
77 if let Some(native) = bindings.native {
78 write_bindings_target(ci, settings, config, "native", native);
79 }
80 if let Some(stub) = bindings.stub {
81 write_bindings_target(ci, settings, config, "stub", stub);
82 }
83
84 if let Some(header) = bindings.header {
85 write_cinterop(ci, &settings.out_dir, header);
86 }
87 }
88 Ok(())
89 }
90}
91
92fn write_bindings_target(
93 ci: &ComponentInterface,
94 settings: &GenerationSettings,
95 config: &Config,
96 target: &str,
97 content: String,
98) {
99 let source_set_name = if config.kotlin_multiplatform {
100 format!("{}Main", target)
101 } else {
102 String::from("main")
103 };
104 let package_path: Utf8PathBuf = config.package_name().split('.').collect();
105 let file_name = format!("{}.{}.kt", ci.namespace(), target);
106
107 let dest_dir = Utf8PathBuf::from(&settings.out_dir)
108 .join(source_set_name)
109 .join("kotlin")
110 .join(package_path);
111 let file_path = Utf8PathBuf::from(&dest_dir).join(file_name);
112
113 fs::create_dir_all(dest_dir).unwrap();
114 fs::write(&file_path, content).unwrap();
115
116 if settings.try_format_code {
117 println!("Code generation complete, formatting with ktlint (use --no-format to disable)");
118 if let Err(e) = Command::new("ktlint").arg("-F").arg(&file_path).output() {
119 println!(
120 "Warning: Unable to auto-format {} using ktlint: {e:?}",
121 file_path.file_name().unwrap(),
122 );
123 }
124 }
125}
126
127fn write_cinterop(ci: &ComponentInterface, out_dir: &Utf8Path, content: String) {
128 let dst_dir = Utf8PathBuf::from(out_dir)
129 .join("nativeInterop")
130 .join("cinterop")
131 .join("headers")
132 .join(ci.namespace());
133 fs::create_dir_all(&dst_dir).unwrap();
134 let file_path = dst_dir.join(format!("{}.h", ci.namespace()));
135 let mut f = File::create(file_path).unwrap();
136 write!(f, "{}", content).unwrap();
137}