gobley_uniffi_bindgen/
lib.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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        // 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    ) -> 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}