Merge bitcoindevkit/bdk-ffi#126: Add workspace and bdk-ffi-bindgen binary cli tool

b207464fe6f68b615c34eb519d07ddf1f021daa4 Update README.md with bdk-ffi-bindgen info (Steve Myers)
fca5d1602b6c7aa1811a0355e517d7fcbf8440d2 Add workspace and move bin to bdk-ffi-bindgen package (Steve Myers)
f4e097c4ac1fb3fbf7cf88b3c56950cf484efe9a Only print python fix up lib path if used (Steve Myers)
c66dfdd52a843f0b312b4b80a1edb82d336916bd Use structopt to capture generate options (Steve Myers)
ce848725b420f61d74a97b2bb739cf1cada64347 Add binary to remove the need for uniffi-bindgen cli tool (thunderbiscuit)

Pull request description:

  This PR is based on the mozilla/application-services [embedded-uniffi-bindgen](https://github.com/mozilla/application-services/tree/main/tools/embedded-uniffi-bindgen) tool. The purpose is to keep the bdk-ffi and bdk-ffi-bindgen tool in sync with the same version of uniffi-rs.

  Fixes #124, this PR replaces #122.

  The `bdkffi` library code remains unchanged but the `bin/generate` and `bin/generate-bindings` bins are combined and put in a new workspace binary package called `bdk-ffi-bindgen`.  The `bdk-ffi-bindgen` binary uses the following options, defaults, and environment variables:

  ```shell
  % cargo run -p bdk-ffi-bindgen -- --help

  bdk-ffi-bindgen 0.1.0
  A tool to generate bdk-ffi language bindings

  USAGE:
      bdk-ffi-bindgen [OPTIONS] --language <language> --out-dir <out-dir>

  FLAGS:
      -h, --help       Prints help information
      -V, --version    Prints version information

  OPTIONS:
      -l, --language <language>
              Language to generate bindings for [env: BDKFFI_BINDGEN_LANGUAGE=]  [possible values: kotlin, swift, python]

      -o, --out-dir <out-dir>
              Output directory to put generated language bindings [env: BDKFFI_BINDGEN_OUTPUT_DIR=]

      -p, --python-fixup-path <python-fixup-path>    Python fix up lib path [env: BDKFFI_BINDGEN_PYTHON_FIXUP_PATH=]
      -u, --udl-file <udl-file>                      UDL file [env: BDKFFI_BINDGEN_UDL=]  [default: src/bdk.udl]

  ```

Top commit has no ACKs.

Tree-SHA512: fa1a1c097fe5d0e704d76078c10f82c466dad5d045c8c93d579c2d13c448c52fb6a4f99dfd3dbc46be30471477ae2d1f9264201e14bae7948b408c8e0b3c9b81
This commit is contained in:
Steve Myers 2022-04-01 18:58:08 -07:00
commit e797efea57
No known key found for this signature in database
GPG Key ID: 8105A46B22C2D051
5 changed files with 162 additions and 87 deletions

View File

@ -4,6 +4,10 @@ version = "0.4.1"
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
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"

View File

@ -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/)

View File

@ -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"

136
bdk-ffi-bindgen/src/main.rs Normal file
View File

@ -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<Self, Self::Err> {
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<dyn std::error::Error>> {
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<PathBuf>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

View File

@ -1,67 +0,0 @@
pub const BDK_UDL: &str = "src/bdk.udl";
#[cfg(feature = "generate-python")]
fn fixup_python_lib_path<O: AsRef<std::path::Path>>(
out_dir: O,
lib_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
#[cfg(feature = "generate-python")]
generate_python()?;
Ok(())
}