From ce848725b420f61d74a97b2bb739cf1cada64347 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 24 Mar 2022 12:54:26 -0400 Subject: [PATCH 1/5] Add binary to remove the need for uniffi-bindgen cli tool --- Cargo.toml | 5 +-- src/bin/generate-bindings.rs | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/bin/generate-bindings.rs diff --git a/Cargo.toml b/Cargo.toml index 802493c..2df73f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,14 +19,15 @@ 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 = "0.16.0" -uniffi_bindgen = { version = "0.16.0", optional = true } +#uniffi_bindgen = { version = "0.16.0", optional = true } [build-dependencies] uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] } [features] -generate-python = ["uniffi_bindgen"] +#generate-python = ["uniffi_bindgen"] [[bin]] name = "generate" diff --git a/src/bin/generate-bindings.rs b/src/bin/generate-bindings.rs new file mode 100644 index 0000000..3e77a9a --- /dev/null +++ b/src/bin/generate-bindings.rs @@ -0,0 +1,67 @@ +use uniffi_bindgen; + +pub const BDK_UDL: &str = "src/bdk.udl"; + +#[derive(Debug)] +#[derive(PartialEq)] +pub enum Language { + KOTLIN, + // PYTHON, + // SWIFT, + UNSUPPORTED, +} + +impl Language { + fn to_string(self) -> &'static str { + if self == Language::KOTLIN { "kotlin" } + // else if self == Language::PYTHON { "python" } + // else if self == Language::SWIFT { "swift" } + else { panic!("Not a supported language") } + } +} + +fn parse_language(language_argument: &str) -> Language { + match language_argument { + "kotlin" => Language::KOTLIN, + // "python" => Language::PYTHON, + // "swift" => Language::SWIFT, + _ => panic!("Unsupported language") + } +} + +fn generate_bindings() -> Result<(), Box> { + use std::env; + let args: Vec = env::args().collect(); + let language: Language; + let output_directory: &String; + + if &args[1] != "--language" { + panic!("Please provide the --language option"); + } else { + language = parse_language(&args[2]); + } + + if &args[3] != "--out-dir" { + panic!("Please provide the --out-dir option"); + } else { + output_directory = &args[4] + } + + println!("Chosen language is {:?}", language); + println!("Output directory is {:?}", output_directory); + + uniffi_bindgen::generate_bindings( + &format!("{}/{}", env!("CARGO_MANIFEST_DIR"), BDK_UDL), + None, + vec![language.to_string()], + Some(&output_directory), + false, + )?; + + Ok(()) +} + +fn main() -> Result<(), Box> { + generate_bindings()?; + Ok(()) +} From c66dfdd52a843f0b312b4b80a1edb82d336916bd Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 27 Mar 2022 23:46:25 -0700 Subject: [PATCH 2/5] Use structopt to capture generate options --- Cargo.toml | 8 +- src/bin/generate-bindings.rs | 145 +++++++++++++++++++++++++---------- 2 files changed, 108 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2df73f8..7fedfd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,14 +20,10 @@ 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 = "0.16.0" - -#uniffi_bindgen = { version = "0.16.0", optional = true } +structopt = "0.3" [build-dependencies] uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] } -[features] -#generate-python = ["uniffi_bindgen"] - [[bin]] -name = "generate" +name = "generate-bindings" diff --git a/src/bin/generate-bindings.rs b/src/bin/generate-bindings.rs index 3e77a9a..af6fa08 100644 --- a/src/bin/generate-bindings.rs +++ b/src/bin/generate-bindings.rs @@ -1,67 +1,134 @@ +use std::fmt; +use std::str::FromStr; +use structopt::StructOpt; use uniffi_bindgen; pub const BDK_UDL: &str = "src/bdk.udl"; -#[derive(Debug)] -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] pub enum Language { KOTLIN, - // PYTHON, - // SWIFT, - UNSUPPORTED, + PYTHON, + SWIFT, } -impl Language { - fn to_string(self) -> &'static str { - if self == Language::KOTLIN { "kotlin" } - // else if self == Language::PYTHON { "python" } - // else if self == Language::SWIFT { "swift" } - else { panic!("Not a supported language") } +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"), + } } } -fn parse_language(language_argument: &str) -> Language { - match language_argument { - "kotlin" => Language::KOTLIN, - // "python" => Language::PYTHON, - // "swift" => Language::SWIFT, - _ => panic!("Unsupported language") - } +#[derive(Debug)] +pub enum Error { + UnsupportedLanguage, } -fn generate_bindings() -> Result<(), Box> { - use std::env; - let args: Vec = env::args().collect(); - let language: Language; - let output_directory: &String; - - if &args[1] != "--language" { - panic!("Please provide the --language option"); - } else { - language = parse_language(&args[2]); +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) } +} - if &args[3] != "--out-dir" { - panic!("Please provide the --out-dir option"); - } else { - output_directory = &args[4] +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), + } } +} - println!("Chosen language is {:?}", language); - println!("Output directory is {:?}", output_directory); - +fn generate_bindings(opt: &Opt) -> anyhow::Result<(), anyhow::Error> { uniffi_bindgen::generate_bindings( &format!("{}/{}", env!("CARGO_MANIFEST_DIR"), BDK_UDL), None, - vec![language.to_string()], - Some(&output_directory), + vec![opt.language.to_string().as_str()], + Some(&opt.out_dir), false, )?; Ok(()) } -fn main() -> Result<(), Box> { - generate_bindings()?; +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(()) +} + +#[derive(Debug, StructOpt)] +#[structopt( + name = "generate-bindings", + about = "A tool to generate bdk-ffi language bindings" +)] +struct Opt { + /// Language to generate bindings for + #[structopt(env = "UNIFFI_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 = "UNIFFI_BINDGEN_OUTPUT_DIR", short, long)] + out_dir: String, + + /// Python fix up lib path + #[structopt(env = "UNIFFI_BINDGEN_PYTHON_FIXUP_PATH", short, long)] + python_fixup_path: Option, +} + +fn main() -> Result<(), Box> { + let opt = Opt::from_args(); + println!("{:?}", opt); + + println!("Chosen language is {}", opt.language); + println!("Output directory is {}", opt.out_dir); + println!("Python fix up lib path is {:?}", opt.python_fixup_path); + + generate_bindings(&opt)?; + + if opt.language == Language::PYTHON { + if let Some(name) = opt.python_fixup_path { + println!("Fixing up python lib path"); + fixup_python_lib_path(&opt.out_dir, &name)?; + } + } Ok(()) } From f4e097c4ac1fb3fbf7cf88b3c56950cf484efe9a Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 30 Mar 2022 12:43:55 -0700 Subject: [PATCH 3/5] Only print python fix up lib path if used --- src/bin/generate-bindings.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/bin/generate-bindings.rs b/src/bin/generate-bindings.rs index af6fa08..04f69f2 100644 --- a/src/bin/generate-bindings.rs +++ b/src/bin/generate-bindings.rs @@ -116,18 +116,16 @@ struct Opt { fn main() -> Result<(), Box> { let opt = Opt::from_args(); - println!("{:?}", opt); println!("Chosen language is {}", opt.language); println!("Output directory is {}", opt.out_dir); - println!("Python fix up lib path is {:?}", opt.python_fixup_path); generate_bindings(&opt)?; if opt.language == Language::PYTHON { - if let Some(name) = opt.python_fixup_path { - println!("Fixing up python lib path"); - fixup_python_lib_path(&opt.out_dir, &name)?; + if let Some(path) = &opt.python_fixup_path { + println!("Fixing up python lib path, {}", &path); + fixup_python_lib_path(&opt.out_dir, &path)?; } } Ok(()) From fca5d1602b6c7aa1811a0355e517d7fcbf8440d2 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 30 Mar 2022 18:12:19 -0700 Subject: [PATCH 4/5] Add workspace and move bin to bdk-ffi-bindgen package --- Cargo.toml | 11 ++- bdk-ffi-bindgen/Cargo.toml | 11 +++ .../src/main.rs | 38 ++++++----- src/bin/generate.rs | 67 ------------------- 4 files changed, 36 insertions(+), 91 deletions(-) create mode 100644 bdk-ffi-bindgen/Cargo.toml rename src/bin/generate-bindings.rs => bdk-ffi-bindgen/src/main.rs (73%) delete mode 100644 src/bin/generate.rs diff --git a/Cargo.toml b/Cargo.toml index 7fedfd2..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,13 +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 = "0.16.0" -structopt = "0.3" [build-dependencies] uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] } - -[[bin]] -name = "generate-bindings" 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/src/bin/generate-bindings.rs b/bdk-ffi-bindgen/src/main.rs similarity index 73% rename from src/bin/generate-bindings.rs rename to bdk-ffi-bindgen/src/main.rs index 04f69f2..4e26f37 100644 --- a/src/bin/generate-bindings.rs +++ b/bdk-ffi-bindgen/src/main.rs @@ -1,10 +1,9 @@ use std::fmt; +use std::path::PathBuf; use std::str::FromStr; use structopt::StructOpt; use uniffi_bindgen; -pub const BDK_UDL: &str = "src/bdk.udl"; - #[derive(Debug, PartialEq)] pub enum Language { KOTLIN, @@ -47,7 +46,7 @@ impl FromStr for Language { fn generate_bindings(opt: &Opt) -> anyhow::Result<(), anyhow::Error> { uniffi_bindgen::generate_bindings( - &format!("{}/{}", env!("CARGO_MANIFEST_DIR"), BDK_UDL), + &opt.udl_file, None, vec![opt.language.to_string().as_str()], Some(&opt.out_dir), @@ -57,16 +56,16 @@ fn generate_bindings(opt: &Opt) -> anyhow::Result<(), anyhow::Error> { Ok(()) } -fn fixup_python_lib_path>( - out_dir: O, - lib_name: &str, +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.as_ref().join("bdk.py"); + 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!( @@ -82,7 +81,7 @@ def loadIndirect(): return getattr(ctypes.cdll, glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.*'))[0]) def _loadIndirectOld():"#, - lib_name + &lib_name.to_str().expect("lib name") ); data.replace_range(range, &replacement); @@ -97,34 +96,39 @@ def _loadIndirectOld():"#, #[derive(Debug, StructOpt)] #[structopt( - name = "generate-bindings", + 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 = "UNIFFI_BINDGEN_LANGUAGE", short, long, possible_values(&["kotlin","swift","python"]), parse(try_from_str = Language::from_str))] + #[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 = "UNIFFI_BINDGEN_OUTPUT_DIR", short, long)] - out_dir: String, + #[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 = "UNIFFI_BINDGEN_PYTHON_FIXUP_PATH", short, long)] - python_fixup_path: Option, + #[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); + 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); + if let Some(path) = opt.python_fixup_path { + println!("Fixing up python lib path, {:?}", &path); fixup_python_lib_path(&opt.out_dir, &path)?; } } 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(()) -} From b207464fe6f68b615c34eb519d07ddf1f021daa4 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 31 Mar 2022 19:58:39 -0700 Subject: [PATCH 5/5] Update README.md with bdk-ffi-bindgen info --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d859b92..8b08a1a 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 it's 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/)