Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Thumbs.db

# Native libs built locally
libbdkffi.*
android/libs
ios/Release/
bdk_demo/ios/ios/Release/
test_output.txt
test output.txt
lib/bdk.dart
61 changes: 61 additions & 0 deletions lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,64 @@ source control. Run `scripts/generate_bindings.sh` to regenerate

This placeholder file keeps the `lib/` directory present in the
repository so that `dart` tooling can resolve the package structure.

## Mobile build artifacts

Mobile consumers (Flutter/Dart) can build platform-specific binaries using the
scripts in `scripts/`.

### iOS (XCFramework)

1. Generate the required static libraries:
```bash
./scripts/generate_bindings.sh --target ios
```
2. Package them into an XCFramework:
```bash
./scripts/build-ios-xcframework.sh
```
The framework is written to `ios/Release/bdkffi.xcframework/`.

### Android (.so libraries)

1. Ensure `ANDROID_NDK_ROOT` points to an Android NDK r26c (or compatible) installation. For example:
- macOS/Linux (adjust the NDK directory as needed):
```bash
export ANDROID_NDK_ROOT="$HOME/Library/Android/sdk/ndk/29.0.14206865"
```
- Windows PowerShell:
```powershell
$Env:ANDROID_NDK_ROOT = "C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk\\ndk\\29.0.14206865"
```
(Use `setx ANDROID_NDK_ROOT <path>` if you want the variable persisted for future shells.)
2. Build the shared objects:
```bash
./scripts/generate_bindings.sh --target android
```
3. Stage the artifacts for inclusion in a Flutter project:
```bash
./scripts/build-android.sh
```
Output libraries are copied to `android/libs/<abi>/libbdkffi.so`.

### Desktop tests (macOS/Linux)

- Run the base script without a target flag before executing `dart test`:
```bash
./scripts/generate_bindings.sh
```
This regenerates the Dart bindings and drops the host dynamic library
(`libbdkffi.dylib` on macOS, `libbdkffi.so` on Linux) in the project root.
The generated loader expects those files when running in the VM, so tests fail if you only invoke the platform-specific targets.

### Verification

- On iOS, confirm the framework slices:
```bash
lipo -info ios/Release/bdkffi.xcframework/ios-arm64/libbdkffi.a
lipo -info ios/Release/bdkffi.xcframework/ios-arm64_x86_64-simulator/libbdkffi.a
```
- On Android, verify the shared objects:
```bash
find android/libs -name "libbdkffi.so"
```
35 changes: 35 additions & 0 deletions scripts/build-android.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROJECT_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
ANDROID_BUILD_ROOT="$PROJECT_ROOT/build/android"
OUTPUT_ROOT="$PROJECT_ROOT/android/libs"

if [[ -z "${ANDROID_NDK_ROOT:-}" ]]; then
echo "ANDROID_NDK_ROOT must be set" >&2
exit 1
fi

if [[ ! -d "$ANDROID_BUILD_ROOT" ]]; then
echo "Android build artifacts not found in $ANDROID_BUILD_ROOT" >&2
echo "Run scripts/generate_bindings.sh --target android first." >&2
exit 1
fi

mkdir -p "$OUTPUT_ROOT"

for ABI in arm64-v8a armeabi-v7a x86_64; do
ARTIFACT="$ANDROID_BUILD_ROOT/${ABI}/libbdkffi.so"
if [[ ! -f "$ARTIFACT" ]]; then
echo "Missing artifact for $ABI at $ARTIFACT" >&2
exit 1
fi

DEST_DIR="$OUTPUT_ROOT/$ABI"
mkdir -p "$DEST_DIR"
cp "$ARTIFACT" "$DEST_DIR/"
echo "Copied $ABI artifact to $DEST_DIR"
done

echo "Android libraries staged under $OUTPUT_ROOT"
48 changes: 48 additions & 0 deletions scripts/build-ios-xcframework.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROJECT_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
IOS_BUILD_ROOT="$PROJECT_ROOT/build/ios"
OUTPUT_ROOT="$PROJECT_ROOT/ios/Release"
XCFRAMEWORK_NAME="bdkffi.xcframework"

DEVICE_LIB="$IOS_BUILD_ROOT/aarch64-apple-ios/libbdkffi.a"
SIM_ARM64_LIB="$IOS_BUILD_ROOT/aarch64-apple-ios-sim/libbdkffi.a"
SIM_X86_LIB="$IOS_BUILD_ROOT/x86_64-apple-ios/libbdkffi.a"
SIM_UNIVERSAL_DIR="$IOS_BUILD_ROOT/simulator-universal"
SIM_UNIVERSAL_LIB="$SIM_UNIVERSAL_DIR/libbdkffi.a"

if [[ ! -f "$DEVICE_LIB" ]]; then
echo "Missing device library: $DEVICE_LIB" >&2
echo "Run scripts/generate_bindings.sh --target ios first." >&2
exit 1
fi

if [[ ! -f "$SIM_ARM64_LIB" || ! -f "$SIM_X86_LIB" ]]; then
echo "Missing simulator libraries: $SIM_ARM64_LIB or $SIM_X86_LIB" >&2
echo "Run scripts/generate_bindings.sh --target ios first." >&2
exit 1
fi

if ! command -v xcodebuild >/dev/null 2>&1; then
echo "xcodebuild is required to create an XCFramework" >&2
exit 1
fi

mkdir -p "$SIM_UNIVERSAL_DIR"

echo "Combining simulator slices with lipo..."
lipo -create "$SIM_ARM64_LIB" "$SIM_X86_LIB" -output "$SIM_UNIVERSAL_LIB"

mkdir -p "$OUTPUT_ROOT"
OUTPUT_PATH="$OUTPUT_ROOT/$XCFRAMEWORK_NAME"
rm -rf "$OUTPUT_PATH"

echo "Creating XCFramework at $OUTPUT_PATH..."
xcodebuild -create-xcframework \
-library "$DEVICE_LIB" \
-library "$SIM_UNIVERSAL_LIB" \
-output "$OUTPUT_PATH"

echo "XCFramework created: $OUTPUT_PATH"
183 changes: 156 additions & 27 deletions scripts/generate_bindings.sh
Original file line number Diff line number Diff line change
@@ -1,53 +1,182 @@
#!/usr/bin/env bash
set -euo pipefail

TARGET="desktop"
while [[ $# -gt 0 ]]; do
case "$1" in
--target)
TARGET="${2:-desktop}"
shift 2
;;
--help|-h)
echo "Usage: $0 [--target desktop|ios|android]"
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done

OS=$(uname -s)
echo "Running on $OS"
ARCH=$(uname -m)
echo "Running on $OS ($ARCH)"

dart --version
dart pub get

mkdir -p lib
rm -f lib/bdk.dart

# Install Rust targets if on macOS
if [[ "$OS" == "Darwin" ]]; then
LIBNAME=libbdkffi.dylib
elif [[ "$OS" == "Linux" ]]; then
LIBNAME=libbdkffi.so
else
echo "Unsupported os: $OS"
echo "Unsupported os: $OS" >&2
exit 1
fi

# Run from the specific crate inside the embedded submodule
cd ./bdk-ffi/bdk-ffi/
echo "Building bdk-ffi crate and generating Dart bindings..."
cargo build --profile dev -p bdk-ffi

# Generate Dart bindings using local uniffi-bindgen wrapper
(cd ../../ && cargo run --profile dev --bin uniffi-bindgen -- --language dart --library bdk-ffi/bdk-ffi/target/debug/$LIBNAME --out-dir lib/)
generate_bindings() {
echo "Building bdk-ffi crate and generating Dart bindings..."
cargo build --profile dev -p bdk-ffi
(cd ../../ && cargo run --profile dev --bin uniffi-bindgen -- --language dart --library bdk-ffi/bdk-ffi/target/debug/$LIBNAME --out-dir lib/)
}

if [[ "$OS" == "Darwin" ]]; then
echo "Generating native binaries..."
rustup target add aarch64-apple-darwin x86_64-apple-darwin
# This is a test script the actual release should not include the test utils feature
cargo build --profile dev -p bdk-ffi --target aarch64-apple-darwin &
cargo build --profile dev -p bdk-ffi --target x86_64-apple-darwin &
wait

echo "Building macOS fat library"
lipo -create -output ../../$LIBNAME \
target/aarch64-apple-darwin/debug/$LIBNAME \
target/x86_64-apple-darwin/debug/$LIBNAME
else
echo "Generating native binaries..."
rustup target add x86_64-unknown-linux-gnu
# This is a test script the actual release should not include the test utils feature
cargo build --profile dev -p bdk-ffi --target x86_64-unknown-linux-gnu
build_ios() {
echo "Building iOS static libraries..."
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim >/dev/null

echo "Copying bdk-ffi binary"
cp target/x86_64-unknown-linux-gnu/debug/$LIBNAME ../../$LIBNAME
fi
PROFILE="release-smaller"
OUT_ROOT="../../build/ios"
mkdir -p "$OUT_ROOT"

for TARGET_TRIPLE in aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim; do
echo " -> $TARGET_TRIPLE"
cargo build --profile "$PROFILE" -p bdk-ffi --target "$TARGET_TRIPLE"
ARTIFACT="target/${TARGET_TRIPLE}/${PROFILE}/libbdkffi.a"
DEST_DIR="$OUT_ROOT/${TARGET_TRIPLE}"
mkdir -p "$DEST_DIR"
cp "$ARTIFACT" "$DEST_DIR/"
done
}

build_android() {
if [[ -z "${ANDROID_NDK_ROOT:-}" ]]; then
echo "ANDROID_NDK_ROOT must be set to build Android artifacts" >&2
exit 1
fi

echo "Building Android shared libraries..."
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android >/dev/null

API_LEVEL=24
case "$OS" in
Darwin)
HOST_OS=darwin
;;
Linux)
HOST_OS=linux
;;
*)
echo "Unsupported host for Android builds: $OS" >&2
exit 1
;;
esac

case "$ARCH" in
x86_64)
HOST_ARCH=x86_64
;;
arm64|aarch64)
HOST_ARCH=arm64
;;
*)
echo "Unsupported architecture for Android builds: $ARCH" >&2
exit 1
;;
esac

TOOLCHAIN="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/${HOST_OS}-${HOST_ARCH}/bin"
if [[ ! -d "$TOOLCHAIN" ]]; then
echo "Unable to locate NDK toolchain at $TOOLCHAIN" >&2
exit 1
fi

OUT_ROOT="../../build/android"
mkdir -p "$OUT_ROOT"

for TARGET_TRIPLE in aarch64-linux-android armv7-linux-androideabi x86_64-linux-android; do
case "$TARGET_TRIPLE" in
aarch64-linux-android)
ABI="arm64-v8a"
CLANG="${TOOLCHAIN}/aarch64-linux-android${API_LEVEL}-clang"
TARGET_ENV_LOWER="aarch64_linux_android"
;;
armv7-linux-androideabi)
ABI="armeabi-v7a"
CLANG="${TOOLCHAIN}/armv7a-linux-androideabi${API_LEVEL}-clang"
TARGET_ENV_LOWER="armv7_linux_androideabi"
;;
x86_64-linux-android)
ABI="x86_64"
CLANG="${TOOLCHAIN}/x86_64-linux-android${API_LEVEL}-clang"
TARGET_ENV_LOWER="x86_64_linux_android"
;;
esac

TARGET_ENV=$(echo "$TARGET_TRIPLE" | tr '[:lower:]' '[:upper:]' | tr '-' '_')

export CARGO_TARGET_${TARGET_ENV}_LINKER="$CLANG"
export CARGO_TARGET_${TARGET_ENV}_AR="${TOOLCHAIN}/llvm-ar"

export CC_${TARGET_ENV_LOWER}="$CLANG"
export AR_${TARGET_ENV_LOWER}="${TOOLCHAIN}/llvm-ar"

echo " -> $TARGET_TRIPLE ($ABI)"
cargo build --profile release-smaller -p bdk-ffi --target "$TARGET_TRIPLE"
ARTIFACT="target/${TARGET_TRIPLE}/release-smaller/libbdkffi.so"
DEST_DIR="$OUT_ROOT/$ABI"
mkdir -p "$DEST_DIR"
cp "$ARTIFACT" "$DEST_DIR/"
done
}

case "$TARGET" in
ios)
generate_bindings
build_ios
;;
android)
generate_bindings
build_android
;;
desktop|*)
generate_bindings
if [[ "$OS" == "Darwin" ]]; then
echo "Generating native macOS binaries..."
rustup target add aarch64-apple-darwin x86_64-apple-darwin >/dev/null
cargo build --profile dev -p bdk-ffi --target aarch64-apple-darwin &
cargo build --profile dev -p bdk-ffi --target x86_64-apple-darwin &
wait

echo "Building macOS fat library"
lipo -create -output ../../$LIBNAME \
target/aarch64-apple-darwin/debug/$LIBNAME \
target/x86_64-apple-darwin/debug/$LIBNAME
else
echo "Generating native Linux binaries..."
rustup target add x86_64-unknown-linux-gnu >/dev/null
cargo build --profile dev -p bdk-ffi --target x86_64-unknown-linux-gnu

echo "Copying bdk-ffi binary"
cp target/x86_64-unknown-linux-gnu/debug/$LIBNAME ../../$LIBNAME
fi
;;
esac

echo "All done!"
Loading