diff --git a/Cargo.toml b/Cargo.toml index 802493c..0573f6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,10 @@ version = "0.4.1" authors = ["Steve Myers ", "Sudarsan Balaji "] edition = "2018" +[workspace] +members = [".","bdk-ffi-bindgen"] +default-members = [".", "bdk-ffi-bindgen"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["staticlib", "cdylib"] @@ -17,16 +21,6 @@ rusqlite = { version = "0.25.3", features = ["bundled"] } uniffi_macros = { version = "0.16.0", features = ["builtin-bindgen"] } uniffi = { version = "0.16.0", features = ["builtin-bindgen"] } -thiserror = "1.0" -anyhow = "=1.0.45" # remove after upgrading to next version of uniffi - -uniffi_bindgen = { version = "0.16.0", optional = true } [build-dependencies] uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] } - -[features] -generate-python = ["uniffi_bindgen"] - -[[bin]] -name = "generate" diff --git a/README.md b/README.md index 78b2eef..0966e46 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Native language bindings for BDK -This repository contains source code for generating native language bindings for the rust based -[bdk] library which is the central artifact of the [Bitcoin Dev Kit] project. +The workspace in this repository creates the `libbdkffi` multi-language library for the rust based +[bdk] library from the [Bitcoin Dev Kit] project. The `bdk-ffi-bindgen` package builds a tool for +generating the actual language binding code used to access the `libbdkffi` library. Each supported language has its own repository that includes this project as a [git submodule]. The rust code in this project is a wrapper around the [bdk] library to expose it's APIs in a @@ -19,6 +20,14 @@ language binding for [bdk] supported by this project. | Swift | iOS, macOS | [bdk-swift] | | Python | linux, macOS | [bdk-python] | +## Language bindings generator tool + +Use the `bdk-ffi-bindgen` tool to generate language binding code for the above supported languages. +To run `bdk-ffi-bindgen` and see the available options use the command: +```shell +cargo run -p bdk-ffi-bindgen -- --help +``` + [bdk]: https://github.com/bitcoindevkit/bdk [Bitcoin Dev Kit]: https://github.com/bitcoindevkit [git submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules @@ -30,14 +39,6 @@ language binding for [bdk] supported by this project. ## Contributing -### Install uniffi-bindgen cli tool - -Install the uniffi-bindgen binary on your system using: - -`cargo install uniffi_bindgen` - -The version must be the same as the `uniffi` dependency in `Cargo.toml`. - ### Adding new structs and functions See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) diff --git a/bdk-ffi-bindgen/Cargo.toml b/bdk-ffi-bindgen/Cargo.toml new file mode 100644 index 0000000..994fc15 --- /dev/null +++ b/bdk-ffi-bindgen/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bdk-ffi-bindgen" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "=1.0.45" # remove after upgrading to next version of uniffi +structopt = "0.3" +uniffi_bindgen = "0.16.0" diff --git a/bdk-ffi-bindgen/src/main.rs b/bdk-ffi-bindgen/src/main.rs new file mode 100644 index 0000000..4e26f37 --- /dev/null +++ b/bdk-ffi-bindgen/src/main.rs @@ -0,0 +1,136 @@ +use std::fmt; +use std::path::PathBuf; +use std::str::FromStr; +use structopt::StructOpt; +use uniffi_bindgen; + +#[derive(Debug, PartialEq)] +pub enum Language { + KOTLIN, + PYTHON, + SWIFT, +} + +impl fmt::Display for Language { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Language::KOTLIN => write!(f, "kotlin"), + Language::SWIFT => write!(f, "swift"), + Language::PYTHON => write!(f, "python"), + } + } +} + +#[derive(Debug)] +pub enum Error { + UnsupportedLanguage, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl FromStr for Language { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "kotlin" => Ok(Language::KOTLIN), + "python" => Ok(Language::PYTHON), + "swift" => Ok(Language::SWIFT), + _ => Err(Error::UnsupportedLanguage), + } + } +} + +fn generate_bindings(opt: &Opt) -> anyhow::Result<(), anyhow::Error> { + uniffi_bindgen::generate_bindings( + &opt.udl_file, + None, + vec![opt.language.to_string().as_str()], + Some(&opt.out_dir), + false, + )?; + + Ok(()) +} + +fn fixup_python_lib_path( + out_dir: &PathBuf, + lib_name: &PathBuf, +) -> Result<(), Box> { + use std::fs; + use std::io::Write; + + const LOAD_INDIRECT_DEF: &str = "def loadIndirect():"; + + let bindings_file = out_dir.join("bdk.py"); + let mut data = fs::read_to_string(&bindings_file)?; + + let pos = data.find(LOAD_INDIRECT_DEF).expect(&format!( + "loadIndirect not found in `{}`", + bindings_file.display() + )); + let range = pos..pos + LOAD_INDIRECT_DEF.len(); + + let replacement = format!( + r#" +def loadIndirect(): + import glob + return getattr(ctypes.cdll, glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.*'))[0]) + +def _loadIndirectOld():"#, + &lib_name.to_str().expect("lib name") + ); + data.replace_range(range, &replacement); + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(&bindings_file)?; + file.write(data.as_bytes())?; + + Ok(()) +} + +#[derive(Debug, StructOpt)] +#[structopt( + name = "bdk-ffi-bindgen", + about = "A tool to generate bdk-ffi language bindings" +)] +struct Opt { + /// UDL file + #[structopt(env = "BDKFFI_BINDGEN_UDL", short, long, default_value("src/bdk.udl"), parse(try_from_str = PathBuf::from_str))] + udl_file: PathBuf, + + /// Language to generate bindings for + #[structopt(env = "BDKFFI_BINDGEN_LANGUAGE", short, long, possible_values(&["kotlin","swift","python"]), parse(try_from_str = Language::from_str))] + language: Language, + + /// Output directory to put generated language bindings + #[structopt(env = "BDKFFI_BINDGEN_OUTPUT_DIR", short, long, parse(try_from_str = PathBuf::from_str))] + out_dir: PathBuf, + + /// Python fix up lib path + #[structopt(env = "BDKFFI_BINDGEN_PYTHON_FIXUP_PATH", short, long, parse(try_from_str = PathBuf::from_str))] + python_fixup_path: Option, +} + +fn main() -> Result<(), Box> { + let opt = Opt::from_args(); + + println!("Input UDL file is {:?}", opt.udl_file); + println!("Chosen language is {}", opt.language); + println!("Output directory is {:?}", opt.out_dir); + + generate_bindings(&opt)?; + + if opt.language == Language::PYTHON { + if let Some(path) = opt.python_fixup_path { + println!("Fixing up python lib path, {:?}", &path); + fixup_python_lib_path(&opt.out_dir, &path)?; + } + } + Ok(()) +} diff --git a/src/bin/generate.rs b/src/bin/generate.rs deleted file mode 100644 index fc6ecf8..0000000 --- a/src/bin/generate.rs +++ /dev/null @@ -1,67 +0,0 @@ -pub const BDK_UDL: &str = "src/bdk.udl"; - -#[cfg(feature = "generate-python")] -fn fixup_python_lib_path>( - out_dir: O, - lib_name: &str, -) -> Result<(), Box> { - use std::fs; - use std::io::Write; - - const LOAD_INDIRECT_DEF: &str = "def loadIndirect():"; - - let bindings_file = out_dir.as_ref().join("bdk.py"); - let mut data = fs::read_to_string(&bindings_file)?; - - let pos = data.find(LOAD_INDIRECT_DEF).expect(&format!( - "loadIndirect not found in `{}`", - bindings_file.display() - )); - let range = pos..pos + LOAD_INDIRECT_DEF.len(); - - let replacement = format!( - r#" -def loadIndirect(): - import glob - return getattr(ctypes.cdll, glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.*'))[0]) - -def _loadIndirectOld():"#, - lib_name - ); - data.replace_range(range, &replacement); - - let mut file = fs::OpenOptions::new() - .write(true) - .truncate(true) - .open(&bindings_file)?; - file.write(data.as_bytes())?; - - Ok(()) -} - -#[cfg(feature = "generate-python")] -fn generate_python() -> Result<(), Box> { - use std::env; - - let out_path = env::var("GENERATE_PYTHON_BINDINGS_OUT") - .map_err(|_| String::from("`GENERATE_PYTHON_BINDINGS_OUT` env variable missing"))?; - uniffi_bindgen::generate_bindings( - &format!("{}/{}", env!("CARGO_MANIFEST_DIR"), BDK_UDL), - None, - vec!["python"], - Some(&out_path), - false, - )?; - - if let Some(name) = env::var("GENERATE_PYTHON_BINDINGS_FIXUP_LIB_PATH").ok() { - fixup_python_lib_path(&out_path, &name)?; - } - - Ok(()) -} - -fn main() -> Result<(), Box> { - #[cfg(feature = "generate-python")] - generate_python()?; - Ok(()) -}