From b781680879d288cc12a9c46a5c96084d1abdc334 Mon Sep 17 00:00:00 2001 From: Jan Baudisch Date: Tue, 9 Jul 2024 18:49:56 +0200 Subject: [PATCH] feat(ffi): initial version --- .github/workflows/Kotlin.yaml | 66 +++ .github/workflows/Pages.yaml | 21 + .github/workflows/Python.yaml | 32 ++ .gitignore | 34 +- Cargo.lock | 398 ++++++++++++++++++ Cargo.toml | 3 +- bindings/README.md | 14 + bindings/kotlin/README.md | 69 +++ bindings/kotlin/build.gradle.kts | 114 +++++ bindings/kotlin/settings.gradle.kts | 1 + bindings/kotlin/src/test/kotlin/Ddnnf.kt | 41 ++ bindings/python/pyproject.toml | 11 + bindings/python/test/test_ddnnf.py | 22 + ddnnife/Cargo.toml | 5 + ddnnife/src/ddnnf.rs | 45 +- ddnnife/src/ddnnf/anomalies/atomic_sets.rs | 2 + ddnnife/src/ddnnf/anomalies/core.rs | 2 +- .../src/ddnnf/anomalies/t_wise_sampling.rs | 2 + .../ddnnf/anomalies/t_wise_sampling/config.rs | 1 + .../ddnnf/anomalies/t_wise_sampling/sample.rs | 1 + .../t_wise_sampling/sampling_result.rs | 1 + ddnnife/src/ddnnf/node.rs | 6 +- ddnnife/src/ffi.rs | 117 +++++ ddnnife/src/lib.rs | 6 + ddnnife/uniffi.toml | 22 + ddnnife_bin/Cargo.toml | 6 +- ddnnife_bindgen/Cargo.toml | 15 + ddnnife_bindgen/src/main.rs | 3 + flake.lock | 26 +- flake.nix | 50 ++- 30 files changed, 1089 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/Kotlin.yaml create mode 100644 .github/workflows/Pages.yaml create mode 100644 .github/workflows/Python.yaml create mode 100644 bindings/README.md create mode 100644 bindings/kotlin/README.md create mode 100644 bindings/kotlin/build.gradle.kts create mode 100644 bindings/kotlin/settings.gradle.kts create mode 100644 bindings/kotlin/src/test/kotlin/Ddnnf.kt create mode 100644 bindings/python/pyproject.toml create mode 100644 bindings/python/test/test_ddnnf.py create mode 100644 ddnnife/src/ffi.rs create mode 100644 ddnnife/uniffi.toml create mode 100644 ddnnife_bindgen/Cargo.toml create mode 100644 ddnnife_bindgen/src/main.rs diff --git a/.github/workflows/Kotlin.yaml b/.github/workflows/Kotlin.yaml new file mode 100644 index 0000000..2a99d96 --- /dev/null +++ b/.github/workflows/Kotlin.yaml @@ -0,0 +1,66 @@ +name: Kotlin + +on: + - push + +jobs: + Build: + strategy: + fail-fast: false + matrix: + target: + - double: x86_64-linux + jna: linux-x86-64 + runner: ubuntu-latest + docs: true + - double: x86_64-darwin + jna: darwin-x86-64 + runner: macos-13 + - double: aarch64-darwin + jna: darwin-aarch64 + runner: macos-latest + variant: + - '' + - '-d4' + runs-on: ${{ matrix.target.runner }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Nix + uses: DeterminateSystems/nix-installer-action@v10 + - name: Cache + uses: DeterminateSystems/magic-nix-cache-action@v4 + - name: Build (bindgen) + run: | + nix build -L .#bindgen + cp -L result/bin/uniffi-bindgen . + - name: Build (library) + run: | + nix build -L .#libddnnife${{ matrix.variant }} + cp -rL result libraries + chmod -R 755 libraries + mv libraries/lib libraries/${{ matrix.target.jna }} + - name: Build + run: | + cd bindings/kotlin + nix run nixpkgs#gradle -- build --no-daemon -Plibraries=../../libraries -Pbindgen=../../uniffi-bindgen + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: ddnnife-kotlin-${{ matrix.target.double }}${{ matrix.variant }} + path: bindings/kotlin/build/libs + - name: Docs + if: ${{ matrix.target.docs && matrix.variant == '' }} + run: | + nix run nixpkgs#gradle -- dokkaHtml + mkdir docs + mv build/dokka/html docs/kotlin + - name: Upload + if: ${{ matrix.target.docs && matrix.variant == '' }} + uses: actions/upload-artifact@v4 + with: + name: pages-kotlin + path: bindings/kotlin/docs + Deploy: + needs: Build + uses: ./.github/workflows/Pages.yaml diff --git a/.github/workflows/Pages.yaml b/.github/workflows/Pages.yaml new file mode 100644 index 0000000..f8ad24f --- /dev/null +++ b/.github/workflows/Pages.yaml @@ -0,0 +1,21 @@ +name: Pages + +on: + workflow_call: + +jobs: + Deploy: + runs-on: ubuntu-latest + steps: + - name: Download + uses: actions/download-artifact@v4 + with: + path: pages + pattern: pages-* + merge-multiple: true + - name: Upload + uses: actions/upload-pages-artifact@v3 + with: + path: pages + - name: Deploy + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/Python.yaml b/.github/workflows/Python.yaml new file mode 100644 index 0000000..a9b12c7 --- /dev/null +++ b/.github/workflows/Python.yaml @@ -0,0 +1,32 @@ +name: Python + +on: + - push + +jobs: + Build: + strategy: + fail-fast: false + matrix: + target: + - double: x86_64-linux + runner: ubuntu-latest + - double: x86_64-darwin + runner: macos-13 + - double: aarch64-darwin + runner: macos-latest + runs-on: ${{ matrix.target.runner }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Nix + uses: DeterminateSystems/nix-installer-action@v10 + - name: Cache + uses: DeterminateSystems/magic-nix-cache-action@v4 + - name: Build + run: nix build -L .#python + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: ddnnife-python-${{ matrix.target.double }} + path: result diff --git a/.gitignore b/.gitignore index 4549d1d..14e6c19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# These are backup files generated by rustfmt +# Rust +/target **/*.rs.bk # Local files for testing, debugging and analysing the code @@ -33,14 +30,25 @@ valcom *-mermaid.md *.svg -# vsc -.idea -.vscode/ +# Nix +result + +# Kotlin +bindings/kotlin/src/main +bindings/kotlin/.gradle +bindings/kotlin/gradle +bindings/kotlin/gradlew +bindings/kotlin/gradlew.bat +bindings/kotlin/build -# intellij -ideas/ +# Python +.venv +__pycache__ + +# IDEs +.idea *.iml +.vscode -# d4 repo and binary -d4v2 -d4v2.bin \ No newline at end of file +# macOS +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 0954629..9a700b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,53 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "assert_cmd" version = "2.0.14" @@ -72,6 +119,24 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -101,6 +166,44 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.0.100" @@ -285,6 +388,7 @@ dependencies = [ "serial_test", "streaming-iterator", "tempfile", + "uniffi", "workctl", ] @@ -297,6 +401,13 @@ dependencies = [ "mimalloc", ] +[[package]] +name = "ddnnife_bindgen" +version = "0.7.0" +dependencies = [ + "uniffi", +] + [[package]] name = "ddnnife_dhone" version = "0.7.0" @@ -358,6 +469,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "funty" version = "2.0.0" @@ -458,6 +578,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -584,6 +715,22 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -709,6 +856,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "petgraph" version = "0.6.5" @@ -737,6 +890,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -889,6 +1048,35 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + [[package]] name = "serde" version = "1.0.203" @@ -909,6 +1097,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serial_test" version = "2.0.0" @@ -934,6 +1133,12 @@ dependencies = [ "syn", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -949,6 +1154,18 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "streaming-iterator" version = "0.1.9" @@ -1005,6 +1222,53 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -1017,12 +1281,137 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "uniffi" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31bff6daf87277a9014bcdefbc2842b0553392919d1096843c5aad899ca4588" +dependencies = [ + "anyhow", + "camino", + "clap", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96061d7e01b185aa405f7c9b134741ab3e50cc6796a47d6fd8ab9a5364b5feed" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "once_cell", + "paste", + "serde", + "textwrap", + "toml", + "uniffi_meta", + "uniffi_testing", + "uniffi_udl", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcfa22f55829d3aaa7acfb1c5150224188fe0f27c59a8a3eddcaa24d1ffbe58" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "uniffi_core" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3210d57d6ab6065ab47a2898dacdb7c606fd6a4156196831fa3bf82e34ac58a6" +dependencies = [ + "anyhow", + "bytes", + "camino", + "log", + "once_cell", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58691741080935437dc862122e68d7414432a11824ac1137868de46181a0bd2" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7663eacdbd9fbf4a88907ddcfe2e6fa85838eb6dc2418a7d91eebb3786f8e20b" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f922465f7566f25f8fe766920205fdfa9a3fcdc209c6bfb7557f0b5bf45b04dd" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef408229a3a407fafa4c36dc4f6ece78a6fb258ab28d2b64bddd49c8cb680f6" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1038,6 +1427,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + [[package]] name = "winapi-util" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 63c655f..db8d2ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ members = [ "ddnnife", "ddnnife_bin", - "ddnnife_dhone" + "ddnnife_bindgen", + "ddnnife_dhone", ] resolver = "2" diff --git a/bindings/README.md b/bindings/README.md new file mode 100644 index 0000000..bad4217 --- /dev/null +++ b/bindings/README.md @@ -0,0 +1,14 @@ +# Bindings + +`ddnnife` bindings are provided via [`uniffi`][uniffi]. +Each language binding is located in its own subdirectory and should stay separated from the Rust codebase. + +For building instructions, see the specific binding's README. + +## `ddnnife_bindgen` + +This is a version of `uniffi-bindgen` specific to this project. +Its task is to actually generate the bindings to the Rust library. +The binary of this crate is called `uniffi-bindgen` to work with build tools. + +[uniffi]: https://mozilla.github.io/uniffi-rs diff --git a/bindings/kotlin/README.md b/bindings/kotlin/README.md new file mode 100644 index 0000000..ec51665 --- /dev/null +++ b/bindings/kotlin/README.md @@ -0,0 +1,69 @@ +# Kotlin bindings for `ddnnife` + +> [!IMPORTANT] +> A note on `d4`: +> As the Rust crate, these bindings can also work with `d4` to provide a CNF to d-DNNF compiler. +> `libddnnife` has to be built with the `d4` feature and for running it, all dependencies have to be present. +> This is **not** the case with the JARs built by the CI, they only contain the library. + +## Requirements + + - [Rust][rust] (or prebuilt `ddnnife` libraries and `ddnnife-bindgen`) + - [Gradle][gradle] + - [ktlint][ktlint] (optional) + +## Build + +There are two ways to build the bindings: +Locally by building the Rust library first, or by passing a directory containing the pre-built libraries. + +### Locally + +``` +gradle build +``` + +This will invoke a Rust build of `ddnnife`, generate the Kotlin bindings by running `ddnnife-bindgen` via cargo. + +### Pre-built + +With a directory containing the pre-built libraries for each platform in the following layout: + +``` +/path/to/prebuilt/libraries +├── darwin-aarch64 +│ └── libddnnife.dylib +├── darwin-x86-64 +│ └── libddnnife.dylib +├── linux-aarch64 +│ └── libddnnife.so +└── linux-x86-64 + └── libddnnife.so +``` + +... running the following will build the bindings without a Rust build of `ddnnife` + +``` +gradle build -Plibraries=/path/to/prebuilt/libraries +``` + +### `uniffi-bindgen` + +By default, the Gradle build will run the corresponding crate in this workspace via cargo. +To prevent this, a command/path to the `uniffi-bindgen` binary can be provided: + +``` +gradle build -Pbindgen=/path/to/uniffi-bindgen +``` + +Using this together with the pre-built libraries a Gradle built without any Rust invocation is possible. + +### Generated sources + +The `de.softvare.ddnnife` package in `src/main/kotlin` is generated by `uniffi` but kept in this location for better compliance with tools like [Dokka][dokka]. +It will be auto-generated by Gradle and should not be committed into Git. + +[rust]: https://www.rust-lang.org +[gradle]: https://gradle.org +[ktlint]: https://github.com/pinterest/ktlint +[dokka]: https://kotlinlang.org/docs/dokka-introduction.html diff --git a/bindings/kotlin/build.gradle.kts b/bindings/kotlin/build.gradle.kts new file mode 100644 index 0000000..e693069 --- /dev/null +++ b/bindings/kotlin/build.gradle.kts @@ -0,0 +1,114 @@ +import com.sun.jna.Platform +import org.gradle.internal.os.OperatingSystem + +group = "de.softvare" +version = "0.7.0" + +plugins { + kotlin("jvm") version "2.0.0" + id("org.jetbrains.dokka") version "1.9.20" +} + +kotlin { + jvmToolchain(17) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("net.java.dev.jna:jna:5.14.0") + testImplementation(kotlin("test")) +} + +buildscript { + dependencies { + classpath("net.java.dev.jna:jna:5.14.0") + } +} + +// Directory structure for generated files. +val generatedResources = "${layout.buildDirectory.get()}/main/resources" + +// When no library folder is passed, we only target the current system and directly use Rust to build the library. +val onlyCurrent = !hasProperty("libraries") + +// Otherwise, extract the path to the pre-built libraries. +var librariesPath = "" +if (!onlyCurrent) { + librariesPath = property("libraries").toString() +} + +// OS specific directories for the native library. +// This only covers the current platform used for generation. +val os: OperatingSystem = OperatingSystem.current() +val resourcePrefix = Platform.RESOURCE_PREFIX; +val libraryDest = "${generatedResources}/${resourcePrefix}" +val libraryName: String = os.getSharedLibraryName("ddnnife") + +// The bindgen tool can be passed via the `bindgen` property, otherwise we invoke it via cargo. +val bindgen = if (hasProperty("bindgen")) { + listOf(property("bindgen").toString()) +} else { + listOf("cargo", "run", "--bin", "uniffi-bindgen") +} + +tasks.register("nativeLibrary") { + group = "Build" + description = "Copies the native library." + + if (onlyCurrent) { + from("../../target/release/${libraryName}") + into(libraryDest) + } else { + from(librariesPath) + into(generatedResources) + } +} + +tasks.processResources { + dependsOn("nativeLibrary") +} + +// Skip building the Rust library in case its path was given. +if (onlyCurrent) { + tasks.register("buildRust") { + group = "Build" + description = "Compiles the Rust crate." + commandLine("cargo", "build", "--release", "--package", "ddnnife", "--features", "uniffi") + } + + tasks.named("nativeLibrary") { + dependsOn("buildRust") + } +} + +tasks.register("generateBindings") { + group = "Build" + description = "Generates the Kotlin uniffi bindings for the Rust crate." + commandLine(bindgen) + args("generate", "--language", "kotlin", "--out-dir", "${layout.projectDirectory}/src/main/kotlin", "--library", "${libraryDest}/${libraryName}") + + dependsOn("nativeLibrary") +} + +tasks.compileKotlin { + dependsOn("generateBindings") +} + +tasks.test { + useJUnitPlatform() + + testLogging { + events("passed", "skipped", "failed") + } +} + +sourceSets { + main { + resources { + srcDir(generatedResources) + } + } +} diff --git a/bindings/kotlin/settings.gradle.kts b/bindings/kotlin/settings.gradle.kts new file mode 100644 index 0000000..8a60b1f --- /dev/null +++ b/bindings/kotlin/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "ddnnife" diff --git a/bindings/kotlin/src/test/kotlin/Ddnnf.kt b/bindings/kotlin/src/test/kotlin/Ddnnf.kt new file mode 100644 index 0000000..e10b85e --- /dev/null +++ b/bindings/kotlin/src/test/kotlin/Ddnnf.kt @@ -0,0 +1,41 @@ +import de.softvare.ddnnife.Ddnnf +import de.softvare.ddnnife.SamplingResult +import java.math.BigInteger +import kotlin.test.assertEquals +import kotlin.test.Test +import kotlin.test.fail + +internal class Ddnnf { + private val ddnnf = Ddnnf.fromFile("../../example_input/busybox-1.18.0_c2d.nnf", null) + + @Test + fun count() { + val count = ddnnf.rc() + val expected = BigInteger("2061138519356781760670618805653750167349287991336595876373542198990734653489713239449032049664199494301454199336000050382457451123894821886472278234849758979132037884598159833615564800000000000000000000") + assertEquals(count, expected) + } + + @Test + fun core() { + val core = ddnnf.getCore() + assertEquals(41, core.size) + } + + @Test + fun atomicSets() { + val atomicSets = ddnnf.asMut().atomicSets(null, listOf(1), true) + assertEquals(854, atomicSets[0].size) + } + + @Test + fun tWise() { + when(val sample = ddnnf.sampleTWise(1u)) { + is SamplingResult.ResultWithSample -> { + assertEquals(854, sample.v1.vars.size) + } + else -> { + fail("T-wise sample is invalid.") + } + } + } +} diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml new file mode 100644 index 0000000..c1c138b --- /dev/null +++ b/bindings/python/pyproject.toml @@ -0,0 +1,11 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[tool.maturin] +profile = "release" +bindings = "uniffi" +features = ["uniffi"] +manifest-path = "../../ddnnife/Cargo.toml" +frozen = true +strip = true diff --git a/bindings/python/test/test_ddnnf.py b/bindings/python/test/test_ddnnf.py new file mode 100644 index 0000000..6d43eab --- /dev/null +++ b/bindings/python/test/test_ddnnf.py @@ -0,0 +1,22 @@ +from ddnnife import Ddnnf + +ddnnf = Ddnnf.from_file("../../example_input/busybox-1.18.0_c2d.nnf", None) + + +def test_count(): + count = ddnnf.rc() + assert count == 2061138519356781760670618805653750167349287991336595876373542198990734653489713239449032049664199494301454199336000050382457451123894821886472278234849758979132037884598159833615564800000000000000000000 + + +def test_core(): + core = ddnnf.get_core() + assert len(core) == 41 + + +def test_atomic_sets(): + atomic_sets = ddnnf.as_mut().atomic_sets(None, [1], True) + assert len(atomic_sets[0]) == 854 + + +def test_t_wise(): + sample = ddnnf.sample_t_wise(1) diff --git a/ddnnife/Cargo.toml b/ddnnife/Cargo.toml index 76d94e3..8357f5a 100644 --- a/ddnnife/Cargo.toml +++ b/ddnnife/Cargo.toml @@ -10,6 +10,10 @@ workspace = ".." [features] d4 = ["dep:d4-oxide"] deterministic = [] +uniffi = ["dep:uniffi"] + +[lib] +crate-type = ["lib", "cdylib"] [dependencies] bitvec = "1.0" @@ -25,6 +29,7 @@ rand_distr = "0.4" rand_pcg = "0.3" streaming-iterator = "0.1" tempfile = "3.10" +uniffi = { version = "0.28", optional = true } workctl = "0.2" [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))'.dependencies] diff --git a/ddnnife/src/ddnnf.rs b/ddnnife/src/ddnnf.rs index 5c93e89..455bc68 100644 --- a/ddnnife/src/ddnnf.rs +++ b/ddnnife/src/ddnnf.rs @@ -6,19 +6,19 @@ pub mod multiple_queries; pub mod node; pub mod stream; -use std::collections::{BTreeSet, HashMap, HashSet}; - +use self::{clause_cache::ClauseCache, node::Node}; +use crate::parser::build_ddnnf; use itertools::Either; use num::BigInt; - -use self::{clause_cache::ClauseCache, node::Node}; +use std::collections::{BTreeSet, HashMap, HashSet}; type Clause = BTreeSet; type ClauseSet = BTreeSet; type EditOperation = (Vec, Vec); -#[derive(Clone, Debug)] /// A Ddnnf holds all the nodes as a vector, also includes meta data and further information that is used for optimations +#[derive(Clone, Debug)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct Ddnnf { /// The actual nodes of the d-DNNF in postorder pub nodes: Vec, @@ -36,7 +36,9 @@ pub struct Ddnnf { pub max_worker: u16, } +#[cfg_attr(feature = "uniffi", uniffi::export)] impl Default for Ddnnf { + #[cfg_attr(feature = "uniffi", uniffi::constructor)] fn default() -> Self { Ddnnf { nodes: Vec::new(), @@ -51,6 +53,31 @@ impl Default for Ddnnf { } } +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl Ddnnf { + /// Loads a d-DNNF from file. + #[cfg_attr(feature = "uniffi", uniffi::constructor)] + fn from_file(path: String, features: Option) -> Self { + build_ddnnf(&path.clone(), features) + } + + /// Returns the current count of the root node in the d-DNNF. + /// + /// This value is the same during all computations. + #[cfg_attr(feature = "uniffi", uniffi::method)] + pub fn rc(&self) -> BigInt { + self.nodes[self.nodes.len() - 1].count.clone() + } + + /// Returns the core features of this d-DNNF. + /// + /// This is only calculated once at creation of the d-DNNF. + #[cfg_attr(feature = "uniffi", uniffi::method)] + pub fn get_core(&self) -> HashSet { + self.core.clone() + } +} + impl Ddnnf { /// Creates a new ddnnf including dead and core features pub fn new( @@ -70,7 +97,7 @@ impl Ddnnf { number_of_variables, max_worker: 4, }; - ddnnf.get_core(); + ddnnf.calculate_core(); if let Some(c) = clauses { ddnnf.update_cached_state(Either::Right(c), Some(number_of_variables)); } @@ -150,12 +177,6 @@ impl Ddnnf { } } - // Returns the current count of the root node in the ddnnf. - // That value is the same during all computations - pub fn rc(&self) -> BigInt { - self.nodes[self.nodes.len() - 1].count.clone() - } - // Returns the current temp count of the root node in the ddnnf. // That value is changed during computations fn rt(&self) -> BigInt { diff --git a/ddnnife/src/ddnnf/anomalies/atomic_sets.rs b/ddnnife/src/ddnnf/anomalies/atomic_sets.rs index be072bc..1a00d63 100644 --- a/ddnnife/src/ddnnf/anomalies/atomic_sets.rs +++ b/ddnnife/src/ddnnf/anomalies/atomic_sets.rs @@ -200,7 +200,9 @@ impl Ddnnf { } subsets } +} +impl Ddnnf { /// Computes the signs of the features in multiple uniform random samples. /// Each of the features is represented by an BitArray holds as many entries as random samples /// with a 0 indicating that the feature occurs negated and a 1 indicating the feature occurs affirmed. diff --git a/ddnnife/src/ddnnf/anomalies/core.rs b/ddnnife/src/ddnnf/anomalies/core.rs index 0a5132c..9da4aab 100644 --- a/ddnnife/src/ddnnf/anomalies/core.rs +++ b/ddnnife/src/ddnnf/anomalies/core.rs @@ -6,7 +6,7 @@ impl Ddnnf { /// Computes all dead and core features. /// A feature is a core feature iff there exists only the positiv occurence of that feature. /// A feature is a dead feature iff there exists only the negativ occurence of that feature. - pub(crate) fn get_core(&mut self) { + pub(crate) fn calculate_core(&mut self) { self.core = (-(self.number_of_variables as i32)..=self.number_of_variables as i32) .filter(|f| self.literals.contains_key(f) && !self.literals.contains_key(&-f)) .collect::>() diff --git a/ddnnife/src/ddnnf/anomalies/t_wise_sampling.rs b/ddnnife/src/ddnnf/anomalies/t_wise_sampling.rs index 1bc0809..ccee64b 100644 --- a/ddnnife/src/ddnnf/anomalies/t_wise_sampling.rs +++ b/ddnnife/src/ddnnf/anomalies/t_wise_sampling.rs @@ -19,8 +19,10 @@ use std::path::Path; use std::{fs, io, iter}; use t_wise_sampler::TWiseSampler; +#[cfg_attr(feature = "uniffi", uniffi::export)] impl Ddnnf { /// Generates samples so that all t-wise interactions between literals are covered. + #[cfg_attr(feature = "uniffi", uniffi::method)] pub fn sample_t_wise(&self, t: usize) -> SamplingResult { // Setup everything needed for the sampling process. let sat_solver = SatWrapper::new(self); diff --git a/ddnnife/src/ddnnf/anomalies/t_wise_sampling/config.rs b/ddnnife/src/ddnnf/anomalies/t_wise_sampling/config.rs index 1c162aa..a2f2d95 100644 --- a/ddnnife/src/ddnnf/anomalies/t_wise_sampling/config.rs +++ b/ddnnife/src/ddnnf/anomalies/t_wise_sampling/config.rs @@ -3,6 +3,7 @@ use crate::util::format_vec; use std::fmt::Display; /// Represents a (partial) configuration +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[derive(Debug, Clone, Eq)] pub struct Config { /// A vector of selected features (positive values) and deselected features (negative values) diff --git a/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sample.rs b/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sample.rs index 8e2170c..a7cc709 100644 --- a/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sample.rs +++ b/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sample.rs @@ -7,6 +7,7 @@ use std::iter; /// The sample differentiates between complete and partial configs. /// A config is complete (in the context of this sample) if it contains all variables this sample /// defines. Otherwise the config is partial. +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Sample { /// Configs that contain all variables of this sample diff --git a/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sampling_result.rs b/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sampling_result.rs index b5220b6..f185eb8 100644 --- a/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sampling_result.rs +++ b/ddnnife/src/ddnnf/anomalies/t_wise_sampling/sampling_result.rs @@ -3,6 +3,7 @@ use crate::util::format_vec_separated_by; use std::fmt; /// An abstraction over the result of sampling as it might be invalid or empty. +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] #[derive(Debug, Clone, PartialEq, Eq)] pub enum SamplingResult { /// An empty result that is *valid* (a regular sample containing 0 configurations). diff --git a/ddnnife/src/ddnnf/node.rs b/ddnnife/src/ddnnf/node.rs index 5c38a05..bb35d3b 100644 --- a/ddnnife/src/ddnnf/node.rs +++ b/ddnnife/src/ddnnf/node.rs @@ -1,8 +1,9 @@ use num::BigInt; use NodeType::{And, False, Literal, Or, True}; -#[derive(Debug, Clone, PartialEq)] /// Represents all types of Nodes with its different parts +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct Node { pub(crate) marker: bool, /// The cardinality of the node for the cardinality of a feature model @@ -17,8 +18,9 @@ pub struct Node { pub ntype: NodeType, } -#[derive(Debug, Clone, PartialEq)] /// The Type of the Node declares how we handle the computation for the different types of cardinalities +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[derive(Debug, Clone, PartialEq)] pub enum NodeType { /// The cardinality of an And node is always the product of its childs And { children: Vec }, diff --git a/ddnnife/src/ffi.rs b/ddnnife/src/ffi.rs new file mode 100644 index 0000000..9832ac1 --- /dev/null +++ b/ddnnife/src/ffi.rs @@ -0,0 +1,117 @@ +use crate::{Ddnnf, UniffiCustomTypeConverter}; +use num::BigInt; +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; + +uniffi::custom_type!(BigInt, Vec); + +impl UniffiCustomTypeConverter for BigInt { + type Builtin = Vec; + + fn into_custom(value: Self::Builtin) -> uniffi::Result { + Ok(BigInt::from_signed_bytes_be(&value)) + } + + fn from_custom(custom: Self) -> Self::Builtin { + custom.to_signed_bytes_be() + } +} + +uniffi::custom_type!(usize, u64); + +impl UniffiCustomTypeConverter for usize { + type Builtin = u64; + + fn into_custom(value: Self::Builtin) -> uniffi::Result { + Ok(value as usize) + } + + fn from_custom(custom: Self) -> Self::Builtin { + custom as u64 + } +} + +type HashSetu32 = HashSet; +type Vecu32 = Vec; + +uniffi::custom_type!(HashSetu32, Vecu32); + +impl UniffiCustomTypeConverter for HashSet { + type Builtin = Vec; + + fn into_custom(value: Self::Builtin) -> uniffi::Result { + Ok(value.into_iter().collect()) + } + + fn from_custom(custom: Self) -> Self::Builtin { + custom.into_iter().collect() + } +} + +type HashSeti32 = HashSet; +type Veci32 = Vec; + +uniffi::custom_type!(HashSeti32, Veci32); + +impl UniffiCustomTypeConverter for HashSet { + type Builtin = Vec; + + fn into_custom(value: Self::Builtin) -> uniffi::Result { + Ok(value.into_iter().collect()) + } + + fn from_custom(custom: Self) -> Self::Builtin { + custom.into_iter().collect() + } +} + +/// A mutable version of a d-DNNF, required for some computations. +/// +/// This version has thread-safe access to computations requiring mutability. +/// A lock will be managed directly by the library. +/// Converting into and out from the mutable version will render the original variant unusable. +#[derive(uniffi::Object)] +pub struct DdnnfMut(pub Mutex); + +#[uniffi::export] +impl Ddnnf { + /// Turn this d-DNNF into a mutable version. + /// + /// This version cannot be used again. + #[uniffi::method] + fn to_mut(self: Arc) -> DdnnfMut { + DdnnfMut(Mutex::new(Arc::into_inner(self).expect("d-DNNF currently has more references and cannot be converted."))) + } +} + +#[uniffi::export] +impl DdnnfMut { + /// Turn this d-DNNF into a non-mutable version. + /// + /// This version cannot be used again. + #[uniffi::method] + fn to_ddnnf(self: Arc) -> Ddnnf { + Arc::into_inner(self) + .expect("Mutable d-DNNF currently has more references and cannot be converted back.") + .0 + .into_inner() + .expect("Failed to lock d-DNNF.") + } + + /// Compute all atomic sets. + /// + /// A group forms an atomic set iff every valid configuration either includes + /// or excludes all members of that atomic set. + #[uniffi::method] + fn atomic_sets( + &self, + candidates: Option>, + assumptions: &[i32], + cross: bool, + ) -> Vec> { + self.0 + .lock() + .unwrap() + .get_atomic_sets(candidates, assumptions, cross) + } +} diff --git a/ddnnife/src/lib.rs b/ddnnife/src/lib.rs index ec649ab..83c5ed1 100644 --- a/ddnnife/src/lib.rs +++ b/ddnnife/src/lib.rs @@ -7,6 +7,9 @@ #[cfg(all(test, feature = "benchmarks"))] extern crate test; +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); + pub mod parser; pub mod util; pub use crate::parser::c2d_lexer; @@ -14,3 +17,6 @@ pub use crate::parser::d4_lexer; pub mod ddnnf; pub use crate::ddnnf::{node::*, Ddnnf}; + +#[cfg(feature = "uniffi")] +mod ffi; diff --git a/ddnnife/uniffi.toml b/ddnnife/uniffi.toml new file mode 100644 index 0000000..01c947f --- /dev/null +++ b/ddnnife/uniffi.toml @@ -0,0 +1,22 @@ +[bindings.kotlin] +package_name = "de.softvare.ddnnife" + +[bindings.python] +cdylib_name = "ddnnife" + +[bindings.kotlin.custom_types.BigInt] +type_name = "BigInteger" +imports = ["java.math.BigInteger"] +into_custom = "BigInteger({})" +# TODO: does it work? +from_custom = "{}.toByteArray()" + +[bindings.python.custom_types.BigInt] +into_custom = "int.from_bytes({}, 'big')" +# TODO: does it work? +from_custom = "{}.to_bytes()" + +[bindings.kotlin.custom_types.HashSetu32] +type_name = "HashSet" +into_custom = "HashSet({})" +from_custom = "{}.toList()" diff --git a/ddnnife_bin/Cargo.toml b/ddnnife_bin/Cargo.toml index 9753577..71fdf51 100644 --- a/ddnnife_bin/Cargo.toml +++ b/ddnnife_bin/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" license = "LGPL-3.0-or-later" workspace = ".." -[[bin]] -name = "ddnnife" -path = "src/main.rs" +#[[bin]] +#name = "ddnnife" +#path = "src/main.rs" [features] d4 = ["ddnnife/d4"] diff --git a/ddnnife_bindgen/Cargo.toml b/ddnnife_bindgen/Cargo.toml new file mode 100644 index 0000000..766d040 --- /dev/null +++ b/ddnnife_bindgen/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ddnnife_bindgen" +description = "uniffi wrapper for ddnnife ffi bindings" +version = "0.7.0" +authors = ["Heiko Raab ", "Chico Sundermann ", "Jan Baudisch "] +edition = "2021" +license = "LGPL-3.0-or-later" +workspace = ".." + +[[bin]] +name = "uniffi-bindgen" +path = "src/main.rs" + +[dependencies] +uniffi = { version = "0.28", features = ["cli"] } diff --git a/ddnnife_bindgen/src/main.rs b/ddnnife_bindgen/src/main.rs new file mode 100644 index 0000000..f6cff6c --- /dev/null +++ b/ddnnife_bindgen/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/flake.lock b/flake.lock index 81a5373..b6432a7 100644 --- a/flake.lock +++ b/flake.lock @@ -7,16 +7,16 @@ ] }, "locked": { - "lastModified": 1717383740, - "narHash": "sha256-559HbY4uhNeoYvK3H6AMZAtVfmR3y8plXZ1x6ON/cWU=", + "lastModified": 1720226507, + "narHash": "sha256-yHVvNsgrpyNTXZBEokL8uyB2J6gB1wEx0KOJzoeZi1A=", "owner": "ipetkov", "repo": "crane", - "rev": "b65673fce97d277934488a451724be94cc62499a", + "rev": "0aed560c5c0a61c9385bddff471a13036203e11c", "type": "github" }, "original": { "owner": "ipetkov", - "ref": "v0.17.3", + "ref": "v0.18.0", "repo": "crane", "type": "github" } @@ -50,11 +50,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1719296889, - "narHash": "sha256-rX9GzfrzvjfqrjfyKnX+zmXTYNRZXqEUWUX2u+LBdi0=", + "lastModified": 1720679322, + "narHash": "sha256-6ffU/E1I9lwDoOcaXmDLf3HEwC2aab1BGh2JQVXgwYE=", "owner": "nix-community", "repo": "fenix", - "rev": "049a6ecec1da711d3d84072732e4b14f98e0edd4", + "rev": "87536ab1ef7378c982cbe3455e3c0d48f1879bc5", "type": "github" }, "original": { @@ -65,11 +65,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1719145550, - "narHash": "sha256-K0i/coxxTEl30tgt4oALaylQfxqbotTSNb1/+g+mKMQ=", + "lastModified": 1720553833, + "narHash": "sha256-IXMiHQMtdShDXcBW95ctA+m5Oq2kLxnBt7WlMxvDQXA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e4509b3a560c87a8d4cb6f9992b8915abf9e36d8", + "rev": "249fbde2a178a2ea2638b65b9ecebd531b338cf9", "type": "github" }, "original": { @@ -90,11 +90,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1719233333, - "narHash": "sha256-+BgWRK3bWVIFwdn43DGRVscnu9P63Mndyhte/hgEwUA=", + "lastModified": 1720632104, + "narHash": "sha256-wAQtRrZ070fIq/O3GedkUxCt25wvg72jaGraa2wcAB0=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "7b11fdeb681c12002861b9804a388efde81c9647", + "rev": "13ac073ea94cced17b598dbbbe288f9e0d14cc75", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d94da03..a354ee6 100644 --- a/flake.nix +++ b/flake.nix @@ -8,7 +8,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; crane = { - url = "github:ipetkov/crane/v0.17.3"; + url = "github:ipetkov/crane/v0.18.0"; inputs.nixpkgs.follows = "nixpkgs"; }; d4 = { @@ -72,7 +72,7 @@ # A ddnnife build for the specified platform. # Build and host packages can be used for a cross-build. # This outputs a set of the corresponding crane library, the crate build definition, - # the package and dependeny artifacts. + # the package, the ffi library, the ffi bindgen tool and dependeny artifacts. ddnnife = buildPkgs: hostPkgs: withD4: let @@ -154,12 +154,52 @@ }; artifacts = craneLib.buildDepsOnly crate; + + library = + crate + // { + pname = "libddnnife"; + + # The output will be a shared library, therefore the static target cannot be used. + CARGO_BUILD_TARGET = rust.${hostSystem}.default; + + cargoExtraArgs = "--package ddnnife --features uniffi"; + + # Testing is done for the actual binary package. + doCheck = false; + } + // lib.optionalAttrs withD4 { cargoExtraArgs = "--package ddnnife --features \"d4 uniffi\""; }; + + bindgen = library // { + pname = "ddnnife-bindgen"; + + cargoExtraArgs = "--package ddnnife_bindgen"; + }; + + python = library // { + nativeBuildInputs = [ buildPkgs.maturin ]; + + buildPhaseCargoCommand = '' + cd bindings/python + maturin build --offline + ''; + + installPhaseCommand = '' + mkdir -p $out + cp ../../target/wheels/* $out/ + ''; + }; + + libraryArtifacts = craneLib.buildDepsOnly library; in { inherit craneLib; inherit crate; inherit artifacts; package = craneLib.buildPackage (crate // { cargoArtifacts = artifacts; }); + library = craneLib.buildPackage (library // { cargoArtifacts = libraryArtifacts; }); + bindgen = craneLib.buildPackage (bindgen // { cargoArtifacts = libraryArtifacts; }); + python = craneLib.buildPackage (python // { cargoArtifacts = libraryArtifacts; }); }; # A simple README explaining how to setup the built directories to run the binaries. @@ -207,6 +247,12 @@ ddnnife-windows = (ddnnife pkgs pkgs-windows false).package; ddnnife-d4-windows = (ddnnife pkgs pkgs-windows true).package; + libddnnife = (ddnnife pkgs pkgs false).library; + libddnnife-d4 = (ddnnife pkgs pkgs true).library; + + bindgen = (ddnnife pkgs pkgs false).bindgen; + python = (ddnnife pkgs pkgs false).python; + container = pkgs.dockerTools.buildLayeredImage { name = "ddnnife"; contents = [ self.packages.${system}.ddnnife-d4 ];