Skip to content
This repository has been archived by the owner on Nov 15, 2022. It is now read-only.

Add Gradle plugin for building libraries #37

Merged
merged 13 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 54 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,66 +46,63 @@ val newAddress = wallet.getNewAddress()
### Example Projects

#### `bdk-android`

* [Devkit Wallet](https://github.com/thunderbiscuit/devkit-wallet)
* [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet)

#### `bdk-jvm`

* [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine)

### How to build
_Note that Kotlin version `1.6.10` or later is required to build the library._

1. Clone this repository and init and update it's [`bdk-ffi`] submodule.
```shell
git clone https://github.com/bitcoindevkit/bdk-kotlin
git submodule update --init
```
1. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
1. If building on MacOS install required intel and m1 jvm targets
```sh
rustup target add x86_64-apple-darwin aarch64-apple-darwin
```
1. Install required targets
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android
```
1. Install `uniffi-bindgen`
```sh
cargo install uniffi_bindgen --version 0.16.0
```
See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info
1. Install Android SDK and Build-Tools for API level 30+
1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
build scripts), for example (NDK major version 21 is required):
```shell
export ANDROID_SDK_ROOT=~/Android/Sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.<NDK_VERSION>
```
1. Build kotlin bindings
```sh
./build.sh
```
1. Start android emulator and run tests
```sh
./gradlew connectedAndroidTest
```
1. Clone this repository and initialize and update its [`bdk-ffi`] submodule.
```shell
git clone https://github.com/bitcoindevkit/bdk-kotlin
git submodule update --init
```
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. If building on MacOS install required intel and m1 jvm targets
```sh
rustup target add x86_64-apple-darwin aarch64-apple-darwin
```
4. Install required targets
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
```
5. Install Android SDK and Build-Tools for API level 30+
6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
build tool), for example (NDK major version 21 is required):
```shell
export ANDROID_SDK_ROOT=~/Android/Sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.<NDK_VERSION>
```
7. Build kotlin bindings
```sh
# build JVM library
./gradlew :jvm:buildJvmLib

# build Android library
./gradlew :jvm:buildAndroidLib
thunderbiscuit marked this conversation as resolved.
Show resolved Hide resolved
```
8. Start android emulator and run tests
thunderbiscuit marked this conversation as resolved.
Show resolved Hide resolved
```sh
./gradlew connectedAndroidTest
```

## How to publish

### Publish to your local maven repo

1. Set your `~/.gradle/gradle.properties` signing key values
```properties
signing.gnupg.keyName=<YOUR_GNUPG_ID>
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
```
1. Publish
```shell
./gradlew :jvm:publishToMavenLocal
./gradlew :android:publishToMavenLocal
```
```properties
signing.gnupg.keyName=<YOUR_GNUPG_ID>
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
```
2. Publish
```shell
./gradlew :jvm:publishToMavenLocal
./gradlew :android:publishToMavenLocal
```

Note that if you do not have gpg keys set up to sign the publication, the task will fail. If you wish to publish to your local Maven repository for local testing without signing the release, you can do so by excluding the `signMavenPublication` subtask like so:
```shell
Expand All @@ -116,18 +113,18 @@ Note that if you do not have gpg keys set up to sign the publication, the task w
### Publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only)

1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login
```properties
signing.gnupg.keyName=<YOUR_GNUPG_ID>
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
ossrhUserName=<YOUR_SONATYPE_USERNAME>
ossrhPassword=<YOUR_SONATYPE_PASSWORD>
```
1. Publish
```shell
./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository
./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository
```
```properties
signing.gnupg.keyName=<YOUR_GNUPG_ID>
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>

ossrhUserName=<YOUR_SONATYPE_USERNAME>
ossrhPassword=<YOUR_SONATYPE_PASSWORD>
```
2. Publish
```shell
./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository
./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository
```

[Kotlin]: https://kotlinlang.org/
[Android Studio]: https://developer.android.com/studio/
Expand Down
5 changes: 5 additions & 0 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ plugins {
id("kotlin-android")
id("maven-publish")
id("signing")

// API docs
id("org.jetbrains.dokka") version "1.6.10"

// Custom plugin to generate the native libs and bindings file
id("org.bitcoindevkit.plugins.generate-android-bindings")
}

android {
Expand Down
22 changes: 22 additions & 0 deletions buildSrc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Readme
The purpose of this directory is to host the Gradle plugins that add tasks for building the native binaries required by bdk-jvm and bdk-android, and building the language bindings files.

The plugins are applied to the specific `build.gradle.kts` files in `bdk-jvm` and `bdk-android` through the `plugins` block:
```kotlin
// bdk-jvm
plugins {
id("org.bitcoindevkit.plugin.generate-jvm-bindings")
}

// bdk-android
plugins {
id("org.bitcoindevkit.plugins.generate-android-bindings")
}
```

They add a series of tasks which are brought together into an aggregate task called `buildJvmLib` for `bdk-jvm` and `buildAndroidLib` for `bdk-android`.

This aggregate task:
1. Builds the native library(ies) using `bdk-ffi`
2. Places it in the correct resource directory
3. Builds the bindings file
8 changes: 8 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
`kotlin-dsl`
// id("org.gradle.kotlin.kotlin-dsl") version "2.2.0"
}

repositories {
mavenCentral()
}
Empty file added buildSrc/settings.gradle.kts
Empty file.
26 changes: 26 additions & 0 deletions buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.bitcoindevkit.plugins


val operatingSystem: OS = when {
System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC
System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX
else -> OS.OTHER
}

val architecture: Arch = when (System.getProperty("os.arch")) {
"x86_64" -> Arch.X86_64
"aarch64" -> Arch.AARCH64
else -> Arch.OTHER
}

enum class Arch {
AARCH64,
X86_64,
OTHER,
}

enum class OS {
MAC,
LINUX,
OTHER,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.bitcoindevkit.plugins

import org.gradle.kotlin.dsl.register

val llvmArchPath = when (operatingSystem) {
OS.MAC -> "darwin-x86_64"
OS.LINUX -> "linux-x86_64"
OS.OTHER -> throw Error("Cannot build Android library from current architecture")
}

// arm64-v8a is the most popular hardware architecture for Android
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {

workingDir("${project.projectDir}/../bdk-ffi")
val cargoArgs: MutableList<String> = mutableListOf("build", "--release", "--target", "aarch64-linux-android")

executable("cargo")
args(cargoArgs)

// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}

environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),

Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"),
Pair("CC", "aarch64-linux-android21-clang")
)

doLast {
println("Native library for bdk-android on aarch64 built successfully")
}
}

// the x86_64 version of the library is mostly used by emulators
val buildAndroidX86_64Binary by tasks.register<Exec>("buildAndroidX86_64Binary") {

workingDir("${project.projectDir}/../bdk-ffi")
val cargoArgs: MutableList<String> = mutableListOf("build", "--release", "--target", "x86_64-linux-android")

executable("cargo")
args(cargoArgs)

// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}

environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),

Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"),
Pair("CC", "x86_64-linux-android21-clang")
)

doLast {
println("Native library for bdk-android on x86_64 built successfully")
}
}

// move the native libs build by cargo from bdk-ffi/target/<architecture>/release/
// to their place in the bdk-android library
// the task only copies the available binaries built using the buildAndroid<architecture>Binary tasks
val moveNativeAndroidLibs by tasks.register<Copy>("moveNativeAndroidLibs") {

dependsOn(buildAndroidAarch64Binary)

into("${project.projectDir}/../android/src/main/jniLibs/")

into("arm64-v8a") {
from("${project.projectDir}/../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so")
}

into("x86_64") {
from("${project.projectDir}/../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so")
}

doLast {
println("Native binaries for Android moved to ./android/src/main/jniLibs/")
}
}

// generate the bindings using the bdk-ffi-bindgen tool located in the bdk-ffi submodule
val generateAndroidBindings by tasks.register<Exec>("generateAndroidBindings") {
dependsOn(moveNativeAndroidLibs)

workingDir("${project.projectDir}/../bdk-ffi")
executable("cargo")
args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../android/src/main/kotlin")

doLast {
println("Android bindings file successfully created")
}
}

// create an aggregate task which will run the required tasks to build the Android libs in order
// the task will also appear in the printout of the ./gradlew tasks task with group and description
tasks.register("buildAndroidLib") {
group = "Bitcoindevkit"
description = "Aggregate task to build Android library"

dependsOn(
buildAndroidAarch64Binary,
buildAndroidX86_64Binary,
moveNativeAndroidLibs,
generateAndroidBindings
)
}