-
-
-
- nix-bindings-rust
-
-
-
-
-
-
- nix-bindings-rust
- Rust bindings for the Nix C API
- Crates
-
-
- Low-level bindings
-
- These -sys crates provide raw FFI bindings generated by
- bindgen .
- They expose the C API directly without safety wrappers.
- Most users should prefer the high-level crates above.
-
-
-
-
-
- EOF
- '';
- };
- }
- ];
- }).config.public;
-
- devShells.default = pkgs.mkShell {
- name = "nix-bindings-devshell";
- strictDeps = true;
- inputsFrom = [ config.nci.outputs.nix-bindings.devShell ];
- inherit (config.nci.outputs.nix-bindings.devShell.env)
- LIBCLANG_PATH
- NIX_CC_UNWRAPPED
- ;
- NIX_DEBUG_INFO_DIRS =
- let
- # TODO: add to Nixpkgs lib
- getDebug =
- pkg:
- if pkg ? debug then
- pkg.debug
- else if pkg ? lib then
- pkg.lib
- else
- pkg;
- in
- "${getDebug config.packages.nix}/lib/debug";
- buildInputs = [
- config.packages.nix
- ];
- nativeBuildInputs = [
- config.treefmt.build.wrapper
-
- pkgs.rust-analyzer
- pkgs.nixfmt
- pkgs.rustfmt
- pkgs.pkg-config
- pkgs.clang-tools # clangd
- pkgs.valgrind
- pkgs.gdb
- pkgs.hci
- # TODO: set up cargo-valgrind in shell and build
- # currently both this and `cargo install cargo-valgrind`
- # produce a binary that says ENOENT.
- # pkgs.cargo-valgrind
- ];
- shellHook = ''
- ${config.pre-commit.shellHook}
- echo 1>&2 "Welcome to the development shell!"
- '';
- # rust-analyzer needs a NIX_PATH for some reason
- NIX_PATH = "nixpkgs=${inputs.nixpkgs}";
- };
- };
- herculesCI =
- hci@{ lib, ... }:
- {
- ciSystems = [ "x86_64-linux" ];
- onPush.default.outputs = {
- effects.pushDocs = lib.optionalAttrs (hci.config.repo.branch == "main") (
- withSystem "x86_64-linux" (
- { config, hci-effects, ... }:
- hci-effects.gitWriteBranch {
- git.checkout.remote.url = hci.config.repo.remoteHttpUrl;
- git.checkout.forgeType = "github";
- git.checkout.user = "x-access-token";
- git.update.branch = "gh-pages";
- contents = config.packages.docs;
- destination = "development"; # directory
- }
- )
- );
- };
- };
- hercules-ci.flake-update = {
- enable = true;
- baseMerge.enable = true;
- autoMergeMethod = "merge";
- when = {
- dayOfMonth = 1;
- };
- flakes = {
- "." = { };
- "dev" = { };
- };
- };
- hercules-ci.cargo-publish = {
- enable = true;
- secretName = "crates-io";
- assertVersions = true;
- };
- flake = { };
-}
diff --git a/dev/flake.lock b/dev/flake.lock
deleted file mode 100644
index 1604bb7..0000000
--- a/dev/flake.lock
+++ /dev/null
@@ -1,143 +0,0 @@
-{
- "nodes": {
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1767039857,
- "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
- "owner": "NixOS",
- "repo": "flake-compat",
- "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-parts": {
- "inputs": {
- "nixpkgs-lib": [
- "hercules-ci-effects",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1769996383,
- "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
- "type": "github"
- },
- "original": {
- "id": "flake-parts",
- "type": "indirect"
- }
- },
- "gitignore": {
- "inputs": {
- "nixpkgs": [
- "pre-commit-hooks-nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1709087332,
- "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "type": "github"
- }
- },
- "hercules-ci-effects": {
- "inputs": {
- "flake-parts": "flake-parts",
- "nixpkgs": "nixpkgs"
- },
- "locked": {
- "lastModified": 1771131391,
- "narHash": "sha256-HPBNYf7HiKtBVy7/69vKpLYHX6wTcUxndxmybzDlXP8=",
- "owner": "hercules-ci",
- "repo": "hercules-ci-effects",
- "rev": "0b152e0f7c5cc265a529cd63374b80e2771b207b",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "hercules-ci-effects",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1771008912,
- "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "a82ccc39b39b621151d6732718e3e250109076fa",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "pre-commit-hooks-nix": {
- "inputs": {
- "flake-compat": "flake-compat",
- "gitignore": "gitignore",
- "nixpkgs": []
- },
- "locked": {
- "lastModified": 1772024342,
- "narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "hercules-ci-effects": "hercules-ci-effects",
- "pre-commit-hooks-nix": "pre-commit-hooks-nix",
- "treefmt-nix": "treefmt-nix"
- }
- },
- "treefmt-nix": {
- "inputs": {
- "nixpkgs": []
- },
- "locked": {
- "lastModified": 1770228511,
- "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
- "owner": "numtide",
- "repo": "treefmt-nix",
- "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "treefmt-nix",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/dev/flake.nix b/dev/flake.nix
deleted file mode 100644
index 09a06a1..0000000
--- a/dev/flake.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- description = "dependencies only";
- inputs = {
- pre-commit-hooks-nix.url = "github:cachix/pre-commit-hooks.nix";
- pre-commit-hooks-nix.inputs.nixpkgs.follows = "";
- hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
- treefmt-nix.url = "github:numtide/treefmt-nix";
- treefmt-nix.inputs.nixpkgs.follows = "";
- };
- outputs = { ... }: { };
-}
diff --git a/doc/hacking/test-ffi.md b/doc/hacking/test-ffi.md
deleted file mode 100644
index c19c485..0000000
--- a/doc/hacking/test-ffi.md
+++ /dev/null
@@ -1,25 +0,0 @@
-
-# Testing FFI code
-
-If `cargo-valgrind` is broken, you may run `valgrind` manually.
-
-1. `cargo test -v`
-2. find the relevant test suite executable in the log
- - example: `/home/user/src/nix-bindings-rust/target/debug/deps/nix_util-036ec381a9e3fd6d`
-3. `valgrind --leak-check=full `
-4. check that
- - `definitely lost: 0 bytes in 0 blocks`
-
-## Paranoid check
-
-Although normal valgrind tends to catch things, you may choose to enable `--show-leak-kinds=all`.
-This will print a few false positive.
-
-Acceptable leaks are those involving (and this may be Linux-specific)
-- `call_init`: static initializers
- - `nix::GlobalConfig::Register::Register`
- - `_GLOBAL__sub_I_logging.cc`
- - ...
-- `new`: a leak in the rust test framework
-
-When in doubt, compare the log to a run with your new test case commented out.
diff --git a/doc/maintainers/release.md b/doc/maintainers/release.md
deleted file mode 100644
index 2d04d96..0000000
--- a/doc/maintainers/release.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Release process
-
-This project uses simple tags, that trigger a release of all crates using Hercules CI.
-Based on the [HCI Effects cargo publish workflow].
-
-## Steps
-
-1. Create a `release` branch
-2. Decide the version bump (patch for fixes, minor for features, major for breaking changes)
-3. Update `CHANGELOG.md`: make sure the Unreleased section is up to date, then change it to the new version and release date
-4. Open a draft release PR and wait for CI to pass
-5. Create and push a tag matching the version
-6. Add a new Unreleased section to `CHANGELOG.md`
-7. Bump version in all `Cargo.toml` files to the next patch version (e.g., `0.2.0` → `0.2.1`)
- and run `cargo update --workspace` to update `Cargo.lock`,
- so that `cargo publish --dry-run` passes on subsequent commits
-8. Merge the release PR
-
----
-
-Dissatisfied with the coarse grained release process? Complain to @roberth and he'll get it done for you.
-
-[HCI Effects cargo publish workflow]: https://docs.hercules-ci.com/hercules-ci-effects/reference/flake-parts/cargo-publish/#_releasing_a_version
diff --git a/docs/ref.md b/docs/ref.md
new file mode 100644
index 0000000..db53d00
--- /dev/null
+++ b/docs/ref.md
@@ -0,0 +1,3 @@
+# Nix Bindgen-rs References
+- https://github.com/NotAShelf/nix-bindings
+- https://github.com/nixops4/nix-bindings-rust
diff --git a/flake.lock b/flake.lock
index da94010..b306085 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,414 +1,39 @@
{
"nodes": {
- "crane": {
- "flake": false,
- "locked": {
- "lastModified": 1758758545,
- "narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=",
- "owner": "ipetkov",
- "repo": "crane",
- "rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364",
- "type": "github"
- },
- "original": {
- "owner": "ipetkov",
- "ref": "v0.21.1",
- "repo": "crane",
- "type": "github"
- }
- },
- "dream2nix": {
- "inputs": {
- "nixpkgs": [
- "nix-cargo-integration",
- "nixpkgs"
- ],
- "purescript-overlay": "purescript-overlay",
- "pyproject-nix": "pyproject-nix"
- },
- "locked": {
- "lastModified": 1765953015,
- "narHash": "sha256-5FBZbbWR1Csp3Y2icfRkxMJw/a/5FGg8hCXej2//bbI=",
- "owner": "nix-community",
- "repo": "dream2nix",
- "rev": "69eb01fa0995e1e90add49d8ca5bcba213b0416f",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "dream2nix",
- "type": "github"
- }
- },
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1767039857,
- "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
- "owner": "NixOS",
- "repo": "flake-compat",
- "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-compat_2": {
- "flake": false,
- "locked": {
- "lastModified": 1696426674,
- "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-parts": {
- "inputs": {
- "nixpkgs-lib": "nixpkgs-lib"
- },
- "locked": {
- "lastModified": 1769996383,
- "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "flake-parts_2": {
- "inputs": {
- "nixpkgs-lib": [
- "nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1733312601,
- "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "git-hooks-nix": {
- "inputs": {
- "flake-compat": [
- "nix"
- ],
- "gitignore": [
- "nix"
- ],
- "nixpkgs": [
- "nix",
- "nixpkgs"
- ],
- "nixpkgs-stable": [
- "nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1734279981,
- "narHash": "sha256-NdaCraHPp8iYMWzdXAt5Nv6sA3MUzlCiGiR586TCwo0=",
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "rev": "aa9f40c906904ebd83da78e7f328cd8aeaeae785",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "type": "github"
- }
- },
- "mk-naked-shell": {
- "flake": false,
- "locked": {
- "lastModified": 1681286841,
- "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
- "owner": "90-008",
- "repo": "mk-naked-shell",
- "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
- "type": "github"
- },
- "original": {
- "owner": "90-008",
- "repo": "mk-naked-shell",
- "type": "github"
- }
- },
- "nix": {
- "inputs": {
- "flake-compat": "flake-compat",
- "flake-parts": "flake-parts_2",
- "git-hooks-nix": "git-hooks-nix",
- "nixpkgs": [
- "nixpkgs"
- ],
- "nixpkgs-23-11": "nixpkgs-23-11",
- "nixpkgs-regression": "nixpkgs-regression"
- },
- "locked": {
- "lastModified": 1772224943,
- "narHash": "sha256-jJIlRLPPVYu860MVFx4gsRx3sskmLDSRWXXue5tYncw=",
- "owner": "NixOS",
- "repo": "nix",
- "rev": "0acd0566e85e4597269482824711bcde7b518600",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nix",
- "type": "github"
- }
- },
- "nix-cargo-integration": {
- "inputs": {
- "crane": "crane",
- "dream2nix": "dream2nix",
- "mk-naked-shell": "mk-naked-shell",
- "nixpkgs": [
- "nixpkgs"
- ],
- "parts": "parts",
- "rust-overlay": "rust-overlay",
- "treefmt": "treefmt"
- },
- "locked": {
- "lastModified": 1772260057,
- "narHash": "sha256-NaUqM0i6XIGdgRNxxQ9sfgCAVeE2Ko9rz7e19RsNUKw=",
- "owner": "90-008",
- "repo": "nix-cargo-integration",
- "rev": "c783c5dff02c06f2af6226d4dd4d494542d0a4d2",
- "type": "github"
- },
- "original": {
- "owner": "90-008",
- "repo": "nix-cargo-integration",
- "type": "github"
- }
- },
"nixpkgs": {
"locked": {
- "lastModified": 1772198003,
- "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=",
+ "lastModified": 1773222311,
+ "narHash": "sha256-BHoB/XpbqoZkVYZCfXJXfkR+GXFqwb/4zbWnOr2cRcU=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61",
+ "rev": "0590cd39f728e129122770c029970378a79d076a",
"type": "github"
},
"original": {
"owner": "NixOS",
- "ref": "nixos-unstable",
+ "ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
- "nixpkgs-23-11": {
- "locked": {
- "lastModified": 1717159533,
- "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
- "type": "github"
- }
- },
- "nixpkgs-lib": {
- "locked": {
- "lastModified": 1769909678,
- "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
- "owner": "nix-community",
- "repo": "nixpkgs.lib",
- "rev": "72716169fe93074c333e8d0173151350670b824c",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nixpkgs.lib",
- "type": "github"
- }
- },
- "nixpkgs-regression": {
- "locked": {
- "lastModified": 1643052045,
- "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- }
- },
- "parts": {
- "inputs": {
- "nixpkgs-lib": [
- "nix-cargo-integration",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1769996383,
- "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "purescript-overlay": {
- "inputs": {
- "flake-compat": "flake-compat_2",
- "nixpkgs": [
- "nix-cargo-integration",
- "dream2nix",
- "nixpkgs"
- ],
- "slimlock": "slimlock"
- },
- "locked": {
- "lastModified": 1728546539,
- "narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=",
- "owner": "thomashoneyman",
- "repo": "purescript-overlay",
- "rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4",
- "type": "github"
- },
- "original": {
- "owner": "thomashoneyman",
- "repo": "purescript-overlay",
- "type": "github"
- }
- },
- "pyproject-nix": {
- "inputs": {
- "nixpkgs": [
- "nix-cargo-integration",
- "dream2nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1763017646,
- "narHash": "sha256-Z+R2lveIp6Skn1VPH3taQIuMhABg1IizJd8oVdmdHsQ=",
- "owner": "pyproject-nix",
- "repo": "pyproject.nix",
- "rev": "47bd6f296502842643078d66128f7b5e5370790c",
- "type": "github"
- },
- "original": {
- "owner": "pyproject-nix",
- "repo": "pyproject.nix",
- "type": "github"
- }
- },
"root": {
"inputs": {
- "flake-parts": "flake-parts",
- "nix": "nix",
- "nix-cargo-integration": "nix-cargo-integration",
- "nixpkgs": "nixpkgs"
+ "nixpkgs": "nixpkgs",
+ "systems": "systems"
}
},
- "rust-overlay": {
- "inputs": {
- "nixpkgs": [
- "nix-cargo-integration",
- "nixpkgs"
- ]
- },
+ "systems": {
"locked": {
- "lastModified": 1772247314,
- "narHash": "sha256-x6IFQ9bL7YYfW2m2z8D3Em2YtAA3HE8kiCFwai2fwrw=",
- "owner": "oxalica",
- "repo": "rust-overlay",
- "rev": "a1ab5e89ab12e1a37c0b264af6386a7472d68a15",
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
- "owner": "oxalica",
- "repo": "rust-overlay",
- "type": "github"
- }
- },
- "slimlock": {
- "inputs": {
- "nixpkgs": [
- "nix-cargo-integration",
- "dream2nix",
- "purescript-overlay",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1688756706,
- "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=",
- "owner": "thomashoneyman",
- "repo": "slimlock",
- "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c",
- "type": "github"
- },
- "original": {
- "owner": "thomashoneyman",
- "repo": "slimlock",
- "type": "github"
- }
- },
- "treefmt": {
- "inputs": {
- "nixpkgs": [
- "nix-cargo-integration",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1770228511,
- "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
- "owner": "numtide",
- "repo": "treefmt-nix",
- "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "treefmt-nix",
+ "owner": "nix-systems",
+ "repo": "default",
"type": "github"
}
}
diff --git a/flake.nix b/flake.nix
index a8295dd..f52bd4c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,227 +1,128 @@
{
- description = "Rust bindings for the Nix C API";
+ description = "rust wrapper for libnix";
inputs = {
- flake-parts.url = "github:hercules-ci/flake-parts";
- nix.url = "github:NixOS/nix";
- nix.inputs.nixpkgs.follows = "nixpkgs";
- nix-cargo-integration.url = "github:90-008/nix-cargo-integration";
- nix-cargo-integration.inputs.nixpkgs.follows = "nixpkgs";
- nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
+ systems.url = "github:nix-systems/default";
};
- outputs =
- inputs@{ flake-parts, ... }:
- flake-parts.lib.mkFlake { inherit inputs; } (
- toplevel@{
+ outputs = {
+ self,
+ nixpkgs,
+ ...
+ } @ inputs: let
+ systems = import inputs.systems;
+
+ mkPkgs = system: repo:
+ import repo {
+ inherit system;
+ allowUnfree = false;
+ allowBroken = false;
+ overlays = builtins.attrValues self.overlays or {};
+ };
+
+ forAllSystems = f:
+ nixpkgs.lib.genAttrs systems (system:
+ f rec {
+ inherit system;
+ inherit (pkgs) lib;
+ pkgs = mkPkgs system nixpkgs;
+ });
+ in {
+ overlays.default = self: super: {
+ libclang = super.llvmPackages_21.libclang;
+ };
+
+ devShells = forAllSystems (
+ {
+ pkgs,
lib,
...
- }:
- let
- /**
- Makes perSystem.nix-bindings-rust available.
- */
- flake-parts-modules.basic =
- {
- flake-parts-lib,
- ...
- }:
- {
- _file = ./flake.nix;
- imports = [ ./input-propagation-workaround.nix ];
- options.perSystem = flake-parts-lib.mkPerSystemOption (
- { pkgs, ... }:
- {
- options.nix-bindings-rust = {
- nixPackage = lib.mkOption {
- type = lib.types.package;
- default = pkgs.nix;
- defaultText = lib.literalMD "pkgs.nix";
- description = ''
- The Nix package to use when building the `nix-bindings-...` crates.
- '';
- };
- nciBuildConfig = lib.mkOption {
- type = lib.types.deferredModule;
- description = ''
- A module to load into your nix-cargo-integration
- [`perSystem.nci.projects..depsDrvConfig`](https://flake.parts/options/nix-cargo-integration.html#opt-perSystem.nci.projects._name_.depsDrvConfig) or similar such options.
+ }: {
+ default = let
+ nixForBindings = pkgs.nixVersions.nix_2_32;
+ inherit (pkgs.rustc) llvmPackages;
+ in
+ pkgs.mkShell rec {
+ name = "nixide";
+ shell = "${pkgs.bash}/bin/bash";
+ strictDeps = true;
- This provides common build configuration (pkg-config, libclang, etc.) and
- automatically adds Nix C library build inputs based on which nix-bindings
- crates are *direct* dependencies of your crate.
+ # packages we need at runtime
+ packages = with pkgs; [
+ rustc
+ llvmPackages.lld
+ lldb
- To disable automatic build input detection:
- ```nix
- nix-bindings-rust.inputPropagationWorkaround.enable = false;
- ```
+ cargo
+ cargo-c
+ cargo-llvm-cov
+ cargo-nextest
- Example:
- ```nix
- perSystem = perSystem@{ config, ... }: {
- nci.projects."my_project".drvConfig = {
- imports = [ perSystem.config.nix-bindings-rust.nciBuildConfig ];
- };
- }
- ```
- '';
- };
- };
- config.nix-bindings-rust = {
- nciBuildConfig = {
- mkDerivation = rec {
- buildInputs = [
- # stdbool.h
- pkgs.stdenv.cc
- ];
- nativeBuildInputs = [
- pkgs.pkg-config
- ];
- # bindgen uses clang to generate bindings, but it doesn't know where to
- # find our stdenv cc's headers, so when it's gcc, we need to tell it.
- postConfigure = lib.optionalString pkgs.stdenv.cc.isGNU ''
- source ${./bindgen-gcc.sh}
- '';
- shellHook = postConfigure;
- };
- # NOTE: duplicated in flake.nix devShell
- env = {
- LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.buildPackages.llvmPackages.clang-unwrapped ];
- BINDGEN_EXTRA_CLANG_ARGS =
- # Work around missing [[deprecated]] in clang
- "-x c++ -std=c++2a";
- }
- // lib.optionalAttrs pkgs.stdenv.cc.isGNU {
- # Avoid cc wrapper, because we only need to add the compiler/"system" dirs
- NIX_CC_UNWRAPPED = "${pkgs.stdenv.cc.cc}/bin/gcc";
- };
- };
- };
- }
- );
- };
+ rust-analyzer-unwrapped
+ (rustfmt.override {asNightly = true;})
+ clippy
+ taplo
+ ];
- /**
- A flake-parts module for dependents to import. Also dogfooded locally
- (extra, not required for normal CI).
+ # packages we need at build time
+ nativeBuildInputs = with pkgs; [
+ pkg-config
+ glibc.dev
+ nixForBindings.dev
- Adds flake checks that test the nix-bindings crates with the
- dependent's nix package.
+ rustPlatform.bindgenHook
+ ];
- See https://github.com/nixops4/nix-bindings-rust?tab=readme-ov-file#integration-with-nix-projects
- */
- flake-parts-modules.tested =
- # Consumer toplevel
- { ... }:
- {
- _file = ./flake.nix;
- imports = [ flake-parts-modules.basic ];
- config.perSystem =
- # Consumer perSystem
- {
- lib,
- config,
- system,
- pkgs,
- ...
- }:
- let
- # nix-bindings-rust's perSystem, but with the consumer's `pkgs`
- nix-bindings-rust-perSystemConfig =
- # Extending our own perSystem, not the consumer's perSystem!
- toplevel.config.partitions.testing-support.module.nix-bindings-rust.internalWithSystem system
- ({ extendModules, ... }: extendModules)
- {
- modules = [
- {
- config = {
- # Overriding our `perSystem` to use the consumer's `pkgs`
- _module.args.pkgs = lib.mkForce pkgs;
- # ... and `nixPackage`
- nix-bindings-rust.nixPackage = lib.mkForce config.nix-bindings-rust.nixPackage;
- };
- }
- ];
- };
- in
- {
- key = "nix-bindings-rust-add-checks";
- # Exclude clippy checks; those are part of this repo's local CI.
- # This module is for dependents (and local dogfooding), which
- # don't need to run clippy on nix-bindings-rust.
- config.checks = lib.concatMapAttrs (
- k: v:
- lib.optionalAttrs (lib.strings.hasPrefix "nix-bindings-" k && !lib.strings.hasSuffix "-clippy" k) {
- "dependency-${k}" = v;
- }
- ) nix-bindings-rust-perSystemConfig.config.checks;
- };
- };
+ # packages we link against
+ buildInputs = with pkgs; [
+ stdenv.cc
- flake-parts-modules.default = flake-parts-modules.tested;
+ nixForBindings
+ ];
- in
- {
- imports = [
- inputs.nix-cargo-integration.flakeModule
- inputs.flake-parts.flakeModules.partitions
- inputs.flake-parts.flakeModules.modules
- # dogfood
- flake-parts-modules.tested
- ./nci.nix
- ];
- systems = [
- "x86_64-linux"
- "aarch64-linux"
- "x86_64-darwin"
- "aarch64-darwin"
- ];
- perSystem =
- {
- inputs',
- ...
- }:
- {
- packages.nix = inputs'.nix.packages.nix;
- };
+ # bindgen uses clang to generate bindings, but it doesn't know where to
+ # find our stdenv cc's headers, so when it's gcc, we need to tell it.
+ postConfigure = lib.optionalString pkgs.stdenv.cc.isGNU ''
+ #!/usr/bin/env bash
+ # REF: https://github.com/nixops4/nix-bindings-rust/blob/main/bindgen-gcc.sh
+ # Rust bindgen uses Clang to generate bindings, but that means that it can't
+ # find the "system" or compiler headers when the stdenv compiler is GCC.
+ # This script tells it where to find them.
- partitionedAttrs.devShells = "dev";
- partitionedAttrs.checks = "dev";
- partitionedAttrs.herculesCI = "dev";
- # Packages are basically just checks in this project; a library by
- # itself is not useful. That's just not how the Rust integration works.
- # By taking `packages` from `dev` we benefit from this dev-only definition:
- # nix-bindings-rust.nixPackage = inputs'.nix.packages.default;
- partitionedAttrs.packages = "dev";
+ echo "Extending BINDGEN_EXTRA_CLANG_ARGS with system include paths..." 2>&1
+ BINDGEN_EXTRA_CLANG_ARGS="$${BINDGEN_EXTRA_CLANG_ARGS:-}"
+ export BINDGEN_EXTRA_CLANG_ARGS
+ include_paths=$(
+ echo | $NIX_CC_UNWRAPPED -v -E -x c - 2>&1 \
+ | awk '/#include <...> search starts here:/{flag=1;next} \
+ /End of search list./{flag=0} \
+ flag==1 {print $1}'
+ )
+ for path in $include_paths; do
+ echo " - $path" 2>&1
+ BINDGEN_EXTRA_CLANG_ARGS="$BINDGEN_EXTRA_CLANG_ARGS -I$path"
+ done
+ '';
- partitions.dev.extraInputsFlake = ./dev;
- partitions.dev.module = {
- imports = [ ./dev/flake-module.nix ];
- };
+ shellHook = postConfigure;
- # A partition that doesn't dogfood the flake-parts-modules.tested module
- # so that we can actually retrieve `checks` without infinite recursions
- # from trying to include the dogfooded attrs.
- partitions.testing-support.module =
- { withSystem, ... }:
- {
- # Make a clean withSystem available for consumers
- options.nix-bindings-rust.internalWithSystem = lib.mkOption { internal = true; };
- config = {
- nix-bindings-rust.internalWithSystem = withSystem;
- perSystem = {
- # Remove dogfooded checks. This configuration's checks are
- # *consumed* by nix-bindings-rust-add-checks, so they should
- # *NOT* also be *produced* by it.
- disabledModules = [ { key = "nix-bindings-rust-add-checks"; } ];
- };
+ env = let
+ inherit (llvmPackages) llvm libclang;
+ in {
+ LD_LIBRARY_PATH = builtins.toString (lib.makeLibraryPath buildInputs);
+ LIBCLANG_PATH = "${libclang.lib}/lib";
+
+ RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}";
+ BINDGEN_EXTRA_CLANG_ARGS = "--sysroot=${pkgs.glibc.dev}";
+
+ # `cargo-llvm-cov` reads these environment variables to find these binaries,
+ # which are needed to run the tests
+ LLVM_COV = "${llvm}/bin/llvm-cov";
+ LLVM_PROFDATA = "${llvm}/bin/llvm-profdata";
};
};
-
- # flake output attributes
- flake = {
- modules.flake = flake-parts-modules;
- };
}
);
+ };
}
diff --git a/input-propagation-workaround.nix b/input-propagation-workaround.nix
deleted file mode 100644
index c3ac92f..0000000
--- a/input-propagation-workaround.nix
+++ /dev/null
@@ -1,143 +0,0 @@
-# Workaround for missing native input propagation in nix-cargo-integration
-#
-# Automatically adds Nix C library build inputs based on which nix-bindings
-# crates are direct dependencies of the crate being built. The mapping is
-# recursive, so depending on nix-bindings-flake will also bring in the
-# transitive C library dependencies (nix-fetchers-c, nix-expr-c, etc.).
-#
-# Note: For multi-crate workspaces, if your crate A depends on your crate B
-# which depends on nix-bindings, you'll need to add an A -> B mapping to
-# `crateInputMapping` so that A also gets B's nix-bindings inputs.
-{
- perSystem =
- {
- lib,
- config,
- pkgs,
- ...
- }:
- let
- cfg = config.nix-bindings-rust.inputPropagationWorkaround;
- nixPackage = config.nix-bindings-rust.nixPackage;
-
- nixLibs =
- if nixPackage ? libs then
- nixPackage.libs
- else
- # Fallback for older Nix versions without split libs
- {
- nix-util-c = nixPackage;
- nix-store-c = nixPackage;
- nix-expr-c = nixPackage;
- nix-fetchers-c = nixPackage;
- nix-flake-c = nixPackage;
- };
-
- # A module for nciBuildConfig that sets buildInputs based on nix-bindings dependencies.
- # Uses options inspection to detect drvConfig vs depsDrvConfig context.
- workaroundModule =
- {
- lib,
- config,
- options,
- ...
- }:
- let
- # rust-cargo-lock exists in drvConfig but not depsDrvConfig
- isDrvConfig = options ? rust-cargo-lock;
-
- dreamLock = config.rust-cargo-lock.dreamLock;
- depsList = dreamLock.dependencies.${config.name}.${config.version} or [ ];
-
- # Convert list of deps to attrset keyed by name for efficient lookup
- deps = builtins.listToAttrs (
- map (dep: {
- name = dep.name;
- value = dep;
- }) depsList
- );
-
- # Inputs for the crate itself if it's in the mapping
- selfInputs = cfg.crateInputMapping.${config.name} or [ ];
-
- # Inputs for direct dependencies that have mappings
- depInputs = lib.concatLists (lib.attrValues (lib.intersectAttrs deps cfg.crateInputMapping));
-
- allInputs = selfInputs ++ depInputs;
- in
- {
- config = lib.optionalAttrs isDrvConfig {
- mkDerivation.buildInputs = allInputs;
- rust-crane.depsDrv.mkDerivation.buildInputs = allInputs;
- };
- };
- in
- {
- options.nix-bindings-rust.inputPropagationWorkaround = {
- enable = lib.mkOption {
- type = lib.types.bool;
- default = true;
- description = ''
- Whether to automatically add Nix C library build inputs based on
- which nix-bindings crates are direct dependencies.
-
- Set to `false` to disable automatic detection and specify buildInputs manually.
- '';
- };
-
- crateInputMapping = lib.mkOption {
- type = lib.types.lazyAttrsOf (lib.types.listOf lib.types.package);
- description = ''
- Mapping from crate names to build inputs. Entries can reference
- other entries for transitive dependencies.
-
- The input propagation workaround can see direct dependencies, so
- if you have `my-crate -> nix-bindings`, that works out of the box.
- If you have `my-other-crate -> my-crate -> nix-bindings`, then you
- need to specify `my-other-crate -> my-crate` as follows:
-
- ```nix
- nix-bindings-rust.inputPropagationWorkaround.crateInputMapping."my-other-crate" =
- config.nix-bindings-rust.inputPropagationWorkaround.crateInputMapping."my-crate";
- ```
- '';
- default = { };
- };
- };
-
- config = lib.mkIf cfg.enable {
- nix-bindings-rust.inputPropagationWorkaround.crateInputMapping = {
- # -sys crates with their transitive dependencies
- "nix-bindings-bdwgc-sys" = [ pkgs.boehmgc ];
- "nix-bindings-util-sys" = [ nixLibs.nix-util-c.dev ];
- "nix-bindings-store-sys" = [
- nixLibs.nix-store-c.dev
- ]
- ++ cfg.crateInputMapping."nix-bindings-util-sys";
- "nix-bindings-expr-sys" = [
- nixLibs.nix-expr-c.dev
- ]
- ++ cfg.crateInputMapping."nix-bindings-store-sys"
- ++ cfg.crateInputMapping."nix-bindings-bdwgc-sys";
- "nix-bindings-fetchers-sys" = [
- nixLibs.nix-fetchers-c.dev
- ]
- ++ cfg.crateInputMapping."nix-bindings-expr-sys";
- "nix-bindings-flake-sys" = [
- nixLibs.nix-flake-c.dev
- ]
- ++ cfg.crateInputMapping."nix-bindings-fetchers-sys"
- ++ cfg.crateInputMapping."nix-bindings-bdwgc-sys";
- # High-level crates reference their -sys counterparts
- "nix-bindings-bdwgc" = cfg.crateInputMapping."nix-bindings-bdwgc-sys";
- "nix-bindings-util" = cfg.crateInputMapping."nix-bindings-util-sys";
- "nix-bindings-store" = cfg.crateInputMapping."nix-bindings-store-sys";
- "nix-bindings-expr" = cfg.crateInputMapping."nix-bindings-expr-sys";
- "nix-bindings-fetchers" = cfg.crateInputMapping."nix-bindings-fetchers-sys";
- "nix-bindings-flake" = cfg.crateInputMapping."nix-bindings-flake-sys";
- };
-
- nix-bindings-rust.nciBuildConfig.imports = [ workaroundModule ];
- };
- };
-}
diff --git a/nci.nix b/nci.nix
deleted file mode 100644
index 351ac77..0000000
--- a/nci.nix
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- perSystem =
- { config, ... }:
- let
- cfg = config.nix-bindings-rust;
- in
- {
- # https://flake.parts/options/nix-cargo-integration
- nci.projects.nix-bindings = {
- path = ./.;
- profiles = {
- dev.drvConfig.env.RUSTFLAGS = "-D warnings";
- release.runTests = true;
- };
- drvConfig = {
- imports = [
- # Downstream projects import this into depsDrvConfig instead
- cfg.nciBuildConfig
- ];
- # Extra settings for running the tests
- mkDerivation = {
- # Prepare the environment for Nix to work.
- # Nix does not provide a suitable environment for running itself in
- # the sandbox - not by default. We configure it to use a relocated store.
- preCheck = ''
- # nix needs a home directory
- export HOME="$(mktemp -d $TMPDIR/home.XXXXXX)"
-
- # configure a relocated store
- store_data=$(mktemp -d $TMPDIR/store-data.XXXXXX)
- export NIX_REMOTE="$store_data"
- export NIX_BUILD_HOOK=
- export NIX_CONF_DIR=$store_data/etc
- export NIX_LOCALSTATE_DIR=$store_data/nix/var
- export NIX_LOG_DIR=$store_data/nix/var/log/nix
- export NIX_STATE_DIR=$store_data/nix/var/nix
-
- echo "Configuring relocated store at $NIX_REMOTE..."
-
- # Create nix.conf with experimental features enabled
- mkdir -p "$NIX_CONF_DIR"
- echo "experimental-features = ca-derivations flakes" > "$NIX_CONF_DIR/nix.conf"
-
- # Init ahead of time, because concurrent initialization is flaky
- ${cfg.nixPackage}/bin/nix-store --init
-
- echo "Store initialized."
- '';
- };
- };
- };
- nci.crates.nix-bindings-store =
- let
- addHarmoniaProfile = ''
- cat >> Cargo.toml <<'EOF'
-
- [profile.harmonia]
- inherits = "release"
- EOF
- '';
- in
- {
- profiles.harmonia = {
- features = [ "harmonia" ];
- runTests = true;
- # Add harmonia profile to Cargo.toml for both deps and main builds
- depsDrvConfig.mkDerivation.postPatch = addHarmoniaProfile;
- drvConfig.mkDerivation.postPatch = addHarmoniaProfile;
- };
- };
- };
-}
diff --git a/nix-bindings-bdwgc-sys/Cargo.toml b/nix-bindings-bdwgc-sys/Cargo.toml
deleted file mode 100644
index 488f41e..0000000
--- a/nix-bindings-bdwgc-sys/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[package]
-name = "nix-bindings-bdwgc-sys"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Low-level FFI bindings to the Boehm-Demers-Weiser garbage collector"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_bdwgc_sys/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-
-[build-dependencies]
-bindgen = "0.69"
-pkg-config = "0.3"
diff --git a/nix-bindings-bdwgc-sys/README.md b/nix-bindings-bdwgc-sys/README.md
deleted file mode 100644
index b49b5b4..0000000
--- a/nix-bindings-bdwgc-sys/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# nix-bindings-bdwgc-sys
-
-This crate contains generated bindings for the Boehm-Demers-Weiser garbage collector (`bdw-gc`).
-**You should not have to use this crate directly,** and so you should probably not add it to your dependencies.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_bdwgc_sys/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-bdwgc-sys/build.rs b/nix-bindings-bdwgc-sys/build.rs
deleted file mode 100644
index aed2a8c..0000000
--- a/nix-bindings-bdwgc-sys/build.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use std::env;
-use std::path::PathBuf;
-
-fn main() {
- println!("cargo:rerun-if-changed=include/bdwgc.h");
-
- let mut args = Vec::new();
- for path in pkg_config::probe_library("bdw-gc")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
-
- let bindings = bindgen::Builder::default()
- .header("include/bdwgc.h")
- .clang_args(args)
- .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
- .generate()
- .expect("Unable to generate bindings");
-
- let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
- bindings
- .write_to_file(out_path.join("bindings.rs"))
- .expect("Couldn't write bindings!");
-}
diff --git a/nix-bindings-bdwgc-sys/include/bdwgc.h b/nix-bindings-bdwgc-sys/include/bdwgc.h
deleted file mode 100644
index 2a70434..0000000
--- a/nix-bindings-bdwgc-sys/include/bdwgc.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#define GC_THREADS
-#include
diff --git a/nix-bindings-bdwgc-sys/src/lib.rs b/nix-bindings-bdwgc-sys/src/lib.rs
deleted file mode 100644
index e722c20..0000000
--- a/nix-bindings-bdwgc-sys/src/lib.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-//! Raw bindings to Nix C API
-//!
-//! This crate contains automatically generated bindings from the Nix C headers.
-//! The bindings are generated by bindgen and include C-style naming conventions
-//! and documentation comments that don't always conform to Rust standards.
-//!
-//! Normally you don't have to use this crate directly.
-
-// This file must only contain generated code, so that the module-level
-// #![allow(...)] attributes don't suppress warnings in hand-written code.
-// If you need to add hand-written code, use a submodule to isolate the
-// generated code. See:
-// https://github.com/nixops4/nixops4/pull/138/commits/330c3881be3d3cf3e59adebbe0ab1c0f15f6d2c9
-
-// Standard bindgen suppressions for C naming conventions
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// Clippy suppressions for generated C bindings
-// bindgen doesn't generate safety docs
-#![allow(clippy::missing_safety_doc)]
-// Rustdoc suppressions for generated C documentation
-// The C headers contain Doxygen-style documentation that doesn't translate
-// well to Rust's rustdoc format, causing various warnings:
-#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
-#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
-#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like
-#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
-
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/nix-bindings-expr-sys/Cargo.toml b/nix-bindings-expr-sys/Cargo.toml
deleted file mode 100644
index 0cf3e12..0000000
--- a/nix-bindings-expr-sys/Cargo.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[package]
-name = "nix-bindings-expr-sys"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Low-level FFI bindings to the Nix expression evaluator"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_expr_sys/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-nix-bindings-store-sys = { path = "../nix-bindings-store-sys", version = "0.2.1" }
-
-[build-dependencies]
-bindgen = "0.69"
-pkg-config = "0.3"
diff --git a/nix-bindings-expr-sys/README.md b/nix-bindings-expr-sys/README.md
deleted file mode 100644
index 9fdb3a0..0000000
--- a/nix-bindings-expr-sys/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# nix-bindings-expr-sys
-
-This crate contains generated bindings for the Nix C API (`nix-expr-c`).
-**You should not have to use this crate directly,** and so you should probably not add it to your dependencies.
-Instead, use the `nix-bindings-expr` crate, which _should_ be sufficient.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_expr_sys/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-expr-sys/build.rs b/nix-bindings-expr-sys/build.rs
deleted file mode 100644
index 6e0eed2..0000000
--- a/nix-bindings-expr-sys/build.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use std::path::PathBuf;
-
-#[derive(Debug)]
-struct StripNixPrefix;
-
-impl bindgen::callbacks::ParseCallbacks for StripNixPrefix {
- fn item_name(&self, name: &str) -> Option {
- name.strip_prefix("nix_").map(String::from)
- }
-}
-
-fn main() {
- println!("cargo:rerun-if-changed=include/nix-c-expr.h");
- println!("cargo:rustc-link-lib=nixexprc");
-
- let mut args = Vec::new();
- for path in pkg_config::probe_library("nix-expr-c")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
-
- let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
-
- let bindings = bindgen::Builder::default()
- .header("include/nix-c-expr.h")
- .clang_args(args)
- .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
- .parse_callbacks(Box::new(StripNixPrefix))
- // Blocklist symbols from nix-bindings-util-sys
- .blocklist_file(".*nix_api_util\\.h")
- // Blocklist symbols from nix-bindings-store-sys
- .blocklist_file(".*nix_api_store\\.h")
- .generate()
- .expect("Unable to generate bindings");
-
- bindings
- .write_to_file(out_path.join("bindings.rs"))
- .expect("Couldn't write bindings!");
-}
diff --git a/nix-bindings-expr-sys/include/nix-c-expr.h b/nix-bindings-expr-sys/include/nix-c-expr.h
deleted file mode 100644
index c2649a4..0000000
--- a/nix-bindings-expr-sys/include/nix-c-expr.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include
-#include
diff --git a/nix-bindings-expr-sys/src/lib.rs b/nix-bindings-expr-sys/src/lib.rs
deleted file mode 100644
index 739f706..0000000
--- a/nix-bindings-expr-sys/src/lib.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-//! Raw bindings to Nix C API
-//!
-//! This crate contains automatically generated bindings from the Nix C headers.
-//! The bindings are generated by bindgen and include C-style naming conventions
-//! and documentation comments that don't always conform to Rust standards.
-//!
-//! Normally you don't have to use this crate directly.
-//! Instead use `nix-expr`.
-
-// This file must only contain generated code, so that the module-level
-// #![allow(...)] attributes don't suppress warnings in hand-written code.
-// If you need to add hand-written code, use a submodule to isolate the
-// generated code. See:
-// https://github.com/nixops4/nixops4/pull/138/commits/330c3881be3d3cf3e59adebbe0ab1c0f15f6d2c9
-
-// Standard bindgen suppressions for C naming conventions
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// Clippy suppressions for generated C bindings
-// bindgen doesn't generate safety docs
-#![allow(clippy::missing_safety_doc)]
-// Rustdoc suppressions for generated C documentation
-// The C headers contain Doxygen-style documentation that doesn't translate
-// well to Rust's rustdoc format, causing various warnings:
-#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
-#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
-#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like
-#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
-
-use nix_bindings_store_sys::*;
-use nix_bindings_util_sys::*;
-
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/nix-bindings-expr/Cargo.toml b/nix-bindings-expr/Cargo.toml
deleted file mode 100644
index ef9a373..0000000
--- a/nix-bindings-expr/Cargo.toml
+++ /dev/null
@@ -1,38 +0,0 @@
-[package]
-name = "nix-bindings-expr"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Rust bindings to Nix expression evaluator"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_expr/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-anyhow = "1.0"
-nix-bindings-store = { path = "../nix-bindings-store", version = "0.2.1" }
-nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" }
-nix-bindings-bdwgc-sys = { path = "../nix-bindings-bdwgc-sys", version = "0.2.1" }
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-nix-bindings-store-sys = { path = "../nix-bindings-store-sys", version = "0.2.1" }
-nix-bindings-expr-sys = { path = "../nix-bindings-expr-sys", version = "0.2.1" }
-ctor = "0.2"
-tempfile = "3.10"
-cstr = "0.2"
-
-[build-dependencies]
-pkg-config = "0.3"
-nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" }
-
-[lints.rust]
-warnings = "deny"
-dead-code = "allow"
-
-[lints.clippy]
-type-complexity = "allow"
-# We're still trying to make Nix more thread-safe, want forward-compat
-arc-with-non-send-sync = "allow"
diff --git a/nix-bindings-expr/README.md b/nix-bindings-expr/README.md
deleted file mode 100644
index cb92996..0000000
--- a/nix-bindings-expr/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# nix-bindings-expr
-
-Rust bindings to the Nix expression evaluator.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_expr/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-expr/build.rs b/nix-bindings-expr/build.rs
deleted file mode 100644
index f56bc3e..0000000
--- a/nix-bindings-expr/build.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-use nix_bindings_util::nix_version::emit_version_cfg;
-
-fn main() {
- let nix_version = pkg_config::probe_library("nix-expr-c").unwrap().version;
- emit_version_cfg(&nix_version, &["2.26", "2.34.0pre"]);
-}
diff --git a/nix-bindings-expr/src/eval_state.rs b/nix-bindings-expr/src/eval_state.rs
deleted file mode 100644
index 5d8599f..0000000
--- a/nix-bindings-expr/src/eval_state.rs
+++ /dev/null
@@ -1,2925 +0,0 @@
-//! # Nix Expression Evaluation
-//!
-//! This module provides the core [`EvalState`] type for evaluating Nix expressions
-//! and extracting typed values from the results.
-//!
-//! ## Overview
-//!
-//! The [`EvalState`] manages the evaluation context for Nix expressions, including:
-//! - Expression parsing and evaluation with [`eval_from_string`](EvalState::eval_from_string)
-//! - Type-safe value extraction with [`require_*`](EvalState#implementations) methods
-//! - Memory management and garbage collection integration
-//! - Store integration for derivations and store paths
-//! - Custom function creation with [`new_value_primop`](EvalState::new_value_primop) and [`new_value_thunk`](EvalState::new_value_thunk)
-//!
-//! ### Construction
-//!
-//! Create an [`EvalState`] using [`EvalState::new`] or [`EvalStateBuilder`] for advanced configuration:
-//!
-//! ```rust
-//! # use nix_bindings_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
-//! # use nix_bindings_store::store::Store;
-//! # use std::collections::HashMap;
-//! # fn example() -> anyhow::Result<()> {
-//! # test_init(); let guard = gc_register_my_thread()?;
-//! let store = Store::open(None, HashMap::new())?;
-//!
-//! // Simple creation
-//! let mut es = EvalState::new(store.clone(), [])?;
-//!
-//! // With custom lookup paths
-//! let mut es = EvalStateBuilder::new(store)?
-//! .lookup_path(["nixpkgs=/path/to/nixpkgs"])?
-//! .build()?;
-//! # drop(guard);
-//! # Ok(())
-//! # }
-//! ```
-//!
-//! ## Value Extraction
-//!
-//! All `require_*` methods perform these steps:
-//! 1. **Evaluation**: Force evaluation of thunks as needed
-//! 2. **Type checking**: Verify the value matches the expected type
-//! 3. **Extraction**: Return the typed Rust value or an error
-//!
-//! Methods with `_strict` in their name also evaluate their return values before returning them.
-//!
-//! ### Evaluation Strictness
-//!
-//! - **Lazy methods** (e.g., [`require_list_size`](EvalState::require_list_size)):
-//! Evaluate only the structure needed
-//! - **Strict methods** (e.g., [`require_list_strict`](EvalState::require_list_strict)):
-//! Force full evaluation of all contained values
-//! - **Selective methods** (e.g., [`require_list_select_idx_strict`](EvalState::require_list_select_idx_strict)):
-//! Evaluate only the accessed elements
-//!
-//! ## Laziness and Strictness
-//!
-//! The terms "lazy" and "strict" in this API refer to Nix's [Weak Head Normal Form (WHNF)](https://nix.dev/manual/nix/latest/language/evaluation.html#values)
-//! evaluation model, not the kind of deep strictness that is exercised by functions such as `builtins.toJSON` or `builtins.deepSeq`.
-//!
-//! - **WHNF evaluation**: Values are evaluated just enough to determine their type and basic structure
-//! - **Deep evaluation**: All nested values are recursively forced (like `builtins.deepSeq`)
-//!
-//! For example, a list in WHNF has its length determined but individual elements may remain unevaluated thunks.
-//! Methods marked as "strict" in this API force WHNF evaluation of their results, but do not perform deep evaluation
-//! of arbitrarily nested structures unless explicitly documented otherwise.
-//!
-//! ### Thread Safety and Memory Management
-//!
-//! Before using [`EvalState`] in a thread, register it with the (process memory) garbage collector:
-//!
-//! ```rust,no_run
-//! # use nix_bindings_expr::eval_state::{init, gc_register_my_thread, test_init};
-//! # fn example() -> anyhow::Result<()> {
-//! # test_init(); // Use test_init() in tests
-//! init()?; // Initialize Nix library
-//! let guard = gc_register_my_thread()?; // Register thread with GC
-//! // Now safe to use EvalState in this thread
-//! drop(guard);
-//! # Ok(())
-//! # }
-//! ```
-//!
-//! ## Error Handling
-//!
-//! Evaluation methods return [`Result`] types. Common error scenarios include:
-//! - **Type mismatches**: Expected type doesn't match actual value type
-//! - **Evaluation errors**: Nix expressions that throw or have undefined behavior
-//! - **Bounds errors**: Out-of-range access for indexed operations
-//!
-//! ## Examples
-//!
-//! ```rust
-//! use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
-//! use nix_bindings_store::store::Store;
-//! use std::collections::HashMap;
-//!
-//! # fn main() -> anyhow::Result<()> {
-//! test_init(); // init() in non-test code
-//! let guard = gc_register_my_thread()?;
-//!
-//! let store = Store::open(None, HashMap::new())?;
-//! let mut es = EvalState::new(store, [])?;
-//!
-//! // Evaluate a list expression
-//! let list_value = es.eval_from_string("[1 2 3]", "")?;
-//!
-//! // Check the size (lazy - doesn't evaluate elements)
-//! let size = es.require_list_size(&list_value)?;
-//! println!("List has {} elements", size);
-//!
-//! // Access specific elements (evaluates only accessed elements)
-//! if let Some(first) = es.require_list_select_idx_strict(&list_value, 0)? {
-//! let value = es.require_int(&first)?;
-//! println!("First element: {}", value);
-//! }
-//!
-//! // Process all elements (evaluates all elements)
-//! let all_elements: Vec<_> = es.require_list_strict(&list_value)?;
-//! for element in all_elements {
-//! let value = es.require_int(&element)?;
-//! println!("Element: {}", value);
-//! }
-//!
-//! drop(guard);
-//! # Ok(())
-//! # }
-//! ```
-
-use crate::primop;
-use crate::value::{Int, Value, ValueType};
-use anyhow::Context as _;
-use anyhow::{bail, Result};
-use cstr::cstr;
-use nix_bindings_bdwgc_sys as gc;
-use nix_bindings_expr_sys as raw;
-use nix_bindings_store::path::StorePath;
-use nix_bindings_store::store::{Store, StoreWeak};
-use nix_bindings_store_sys as raw_store;
-use nix_bindings_util::context::Context;
-use nix_bindings_util::string_return::{
- callback_get_result_string, callback_get_result_string_data,
-};
-use nix_bindings_util::{check_call, check_call_opt_key, result_string_init};
-use std::ffi::{c_char, CString};
-use std::iter::FromIterator;
-use std::os::raw::c_uint;
-use std::ptr::{null, null_mut, NonNull};
-use std::sync::{Arc, LazyLock, Weak};
-
-static INIT: LazyLock> = LazyLock::new(|| unsafe {
- gc::GC_allow_register_threads();
- check_call!(raw::libexpr_init(&mut Context::new()))?;
- Ok(())
-});
-
-pub fn init() -> Result<()> {
- let x = INIT.as_ref();
- match x {
- Ok(_) => Ok(()),
- Err(e) => {
- // Couldn't just clone the error, so we have to print it here.
- Err(anyhow::format_err!("nix_bindings_expr::init error: {}", e))
- }
- }
-}
-
-/// A string value with its associated [store paths](https://nix.dev/manual/nix/stable/store/store-path.html).
-///
-/// Represents a Nix string with references to store paths.
-pub struct RealisedString {
- /// The string content.
- pub s: String,
- /// Store paths referenced by the string.
- pub paths: Vec,
-}
-
-/// A [Weak] reference to an [EvalState].
-pub struct EvalStateWeak {
- inner: Weak,
- store: StoreWeak,
-}
-impl EvalStateWeak {
- /// Upgrade the weak reference to a proper [EvalState].
- ///
- /// If no normal reference to the [EvalState] is around anymore elsewhere, this fails by returning `None`.
- pub fn upgrade(&self) -> Option {
- self.inner.upgrade().and_then(|eval_state| {
- self.store.upgrade().map(|store| EvalState {
- eval_state,
- store,
- context: Context::new(),
- })
- })
- }
-}
-
-struct EvalStateRef {
- eval_state: NonNull,
-}
-impl EvalStateRef {
- /// Returns a raw pointer to the underlying EvalState.
- ///
- /// # Safety
- ///
- /// The caller must ensure that the pointer is not used beyond the lifetime of the underlying [raw::EvalState].
- unsafe fn as_ptr(&self) -> *mut raw::EvalState {
- self.eval_state.as_ptr()
- }
-}
-impl Drop for EvalStateRef {
- fn drop(&mut self) {
- unsafe {
- raw::state_free(self.eval_state.as_ptr());
- }
- }
-}
-/// Builder for configuring and creating an [`EvalState`].
-///
-/// Provides advanced configuration options for evaluation context setup.
-/// Use [`EvalState::new`] for simple cases or this builder for custom configuration.
-///
-/// Requires Nix 2.26.0 or later.
-///
-/// # Examples
-///
-/// ```rust
-/// # use nix_bindings_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
-/// # use nix_bindings_store::store::Store;
-/// # use std::collections::HashMap;
-/// # fn example() -> anyhow::Result<()> {
-/// # test_init();
-/// # let guard = gc_register_my_thread()?;
-/// let store = Store::open(None, HashMap::new())?;
-///
-/// let mut es: EvalState = EvalStateBuilder::new(store)?
-/// .lookup_path(["nixpkgs=/path/to/nixpkgs", "home-manager=/path/to/hm"])?
-/// .build()?;
-///
-/// let value = es.eval_from_string("", /* path display: */ "in-memory")?;
-/// # drop(guard);
-/// # Ok(())
-/// # }
-/// ```
-#[cfg(nix_at_least = "2.26")]
-pub struct EvalStateBuilder {
- eval_state_builder: *mut raw::eval_state_builder,
- lookup_path: Vec,
- load_ambient_settings: bool,
- store: Store,
-}
-#[cfg(nix_at_least = "2.26")]
-impl Drop for EvalStateBuilder {
- fn drop(&mut self) {
- unsafe {
- raw::eval_state_builder_free(self.eval_state_builder);
- }
- }
-}
-#[cfg(nix_at_least = "2.26")]
-impl EvalStateBuilder {
- /// Creates a new [`EvalStateBuilder`].
- pub fn new(store: Store) -> Result {
- let mut context = Context::new();
- let eval_state_builder =
- unsafe { check_call!(raw::eval_state_builder_new(&mut context, store.raw_ptr())) }?;
- Ok(EvalStateBuilder {
- store,
- eval_state_builder,
- lookup_path: Vec::new(),
- load_ambient_settings: true,
- })
- }
- /// Sets the [lookup path](https://nix.dev/manual/nix/latest/language/constructs/lookup-path.html) for Nix expression evaluation.
- pub fn lookup_path<'a>(mut self, path: impl IntoIterator- ) -> Result
{
- let lookup_path: Vec = path
- .into_iter()
- .map(|path| {
- CString::new(path).with_context(|| {
- format!("EvalStateBuilder::lookup_path: path `{path}` contains null byte")
- })
- })
- .collect::>()?;
- self.lookup_path = lookup_path;
- Ok(self)
- }
- /// Sets whether to load settings from the ambient environment.
- ///
- /// When enabled (default), calls `nix_eval_state_builder_load` to load settings
- /// from NIX_CONFIG and other environment variables. When disabled, only the
- /// explicitly configured settings are used.
- pub fn load_ambient_settings(mut self, load: bool) -> Self {
- self.load_ambient_settings = load;
- self
- }
- /// Builds the configured [`EvalState`].
- pub fn build(&self) -> Result {
- // Make sure the library is initialized
- init()?;
-
- let mut context = Context::new();
-
- // Load settings from global configuration (including readOnlyMode = false).
- // This is necessary for path coercion to work (adding files to the store).
- if self.load_ambient_settings {
- unsafe {
- check_call!(raw::eval_state_builder_load(
- &mut context,
- self.eval_state_builder
- ))?;
- }
- }
-
- // Note: these raw C string pointers borrow from self.lookup_path
- let mut lookup_path: Vec<*const c_char> = self
- .lookup_path
- .iter()
- .map(|s| s.as_ptr())
- .chain(std::iter::once(null())) // signal the end of the array
- .collect();
-
- unsafe {
- check_call!(raw::eval_state_builder_set_lookup_path(
- &mut context,
- self.eval_state_builder,
- lookup_path.as_mut_ptr()
- ))?;
- }
-
- let eval_state =
- unsafe { check_call!(raw::eval_state_build(&mut context, self.eval_state_builder)) }?;
- Ok(EvalState {
- eval_state: Arc::new(EvalStateRef {
- eval_state: NonNull::new(eval_state).unwrap_or_else(|| {
- panic!("nix_state_create returned a null pointer without an error")
- }),
- }),
- store: self.store.clone(),
- context,
- })
- }
- /// Returns a raw pointer to the underlying eval state builder.
- ///
- /// # Safety
- ///
- /// The caller must ensure that the pointer is not used beyond the lifetime of this builder.
- // TODO: This function should be marked `unsafe`.
- pub fn raw_ptr(&self) -> *mut raw::eval_state_builder {
- self.eval_state_builder
- }
-}
-
-pub struct EvalState {
- eval_state: Arc,
- store: Store,
- pub(crate) context: Context,
-}
-impl EvalState {
- /// Creates a new EvalState with basic configuration.
- ///
- /// For more options, use [EvalStateBuilder].
- pub fn new<'a>(store: Store, lookup_path: impl IntoIterator- ) -> Result
{
- EvalStateBuilder::new(store)?
- .lookup_path(lookup_path)?
- .build()
- }
-
- /// Returns a raw pointer to the raw Nix C API EvalState.
- ///
- /// # Safety
- ///
- /// The caller must ensure that the pointer is not used beyond the lifetime of this `EvalState`.
- pub unsafe fn raw_ptr(&self) -> *mut raw::EvalState {
- self.eval_state.as_ptr()
- }
-
- /// Returns a reference to the Store that's used for instantiation, import from derivation, etc.
- pub fn store(&self) -> &Store {
- &self.store
- }
-
- /// Creates a weak reference to this EvalState.
- pub fn weak_ref(&self) -> EvalStateWeak {
- EvalStateWeak {
- inner: Arc::downgrade(&self.eval_state),
- store: self.store.weak_ref(),
- }
- }
-
- /// Parses and evaluates a Nix expression `expr`.
- ///
- /// Expressions can contain relative paths such as `./.` that are resolved relative to the given `path`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
- /// use nix_bindings_store::store::Store;
- /// use nix_bindings_expr::value::Value;
- /// use std::collections::HashMap;
- ///
- /// # fn main() -> anyhow::Result<()> {
- /// # test_init();
- /// # let guard = gc_register_my_thread()?;
- /// # let mut es = EvalState::new(Store::open(None, HashMap::new())?, [])?;
- /// let v: Value = es.eval_from_string("42", ".")?;
- /// assert_eq!(es.require_int(&v)?, 42);
- /// # drop(guard);
- /// # Ok(())
- /// # }
- /// ```
- #[doc(alias = "nix_expr_eval_from_string")]
- #[doc(alias = "parse")]
- #[doc(alias = "eval")]
- #[doc(alias = "evaluate")]
- pub fn eval_from_string(&mut self, expr: &str, path: &str) -> Result {
- let expr_ptr =
- CString::new(expr).with_context(|| "eval_from_string: expr contains null byte")?;
- let path_ptr =
- CString::new(path).with_context(|| "eval_from_string: path contains null byte")?;
- unsafe {
- let value = self.new_value_uninitialized()?;
- check_call!(raw::expr_eval_from_string(
- &mut self.context,
- self.eval_state.as_ptr(),
- expr_ptr.as_ptr(),
- path_ptr.as_ptr(),
- value.raw_ptr()
- ))?;
- Ok(value)
- }
- }
-
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of a value to [weak head normal form](https://nix.dev/manual/nix/latest/language/evaluation.html?highlight=WHNF#values).
- ///
- /// Converts [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) to their evaluated form. Does not modify already-evaluated values.
- ///
- /// Does not perform deep evaluation of nested structures.
- ///
- /// See also: [Shared Evaluation State](Value#shared-evaluation-state)
- #[doc(alias = "evaluate")]
- #[doc(alias = "strict")]
- pub fn force(&mut self, v: &Value) -> Result<()> {
- unsafe {
- check_call!(raw::value_force(
- &mut self.context,
- self.eval_state.as_ptr(),
- v.raw_ptr()
- ))
- }?;
- Ok(())
- }
-
- /// Returns the type of a value without forcing [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html).
- ///
- /// Returns [`None`] if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
- ///
- /// Returns [`Some`] if the value is already evaluated.
- ///
- /// See also: [Shared Evaluation State](Value#shared-evaluation-state)
- #[doc(alias = "type_of")]
- #[doc(alias = "value_type_lazy")]
- #[doc(alias = "nix_get_type")]
- #[doc(alias = "get_type")]
- #[doc(alias = "nix_value_type")]
- pub fn value_type_unforced(&mut self, value: &Value) -> Option {
- let r = unsafe { check_call!(raw::get_type(&mut self.context, value.raw_ptr())) };
- // .unwrap(): no reason for this to fail, as it does not evaluate
- ValueType::from_raw(r.unwrap())
- }
- /// Returns the [type][`ValueType`] of a value, [forcing][`EvalState::force`] [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) if necessary.
- ///
- /// Forces evaluation if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
- ///
- /// Evaluation may fail, producing an [`Err`].
- ///
- /// Guarantees a definitive result if [`Ok`], thanks to the language being [pure](https://nix.dev/manual/nix/latest/language/index.html?highlight=pure#nix-language) and [lazy](https://nix.dev/manual/nix/latest/language/index.html?highlight=lazy#nix-language).
- #[doc(alias = "type_of")]
- #[doc(alias = "value_type_strict")]
- #[doc(alias = "nix_get_type")]
- #[doc(alias = "get_type")]
- #[doc(alias = "nix_value_type_strict")]
- pub fn value_type(&mut self, value: &Value) -> Result {
- match self.value_type_unforced(value) {
- Some(a) => Ok(a),
- None => {
- self.force(value)?;
- match self.value_type_unforced(value) {
- Some(a) => Ok(a),
- None => {
- panic!("Nix value must not be thunk after being forced.")
- }
- }
- }
- }
- }
- /// Extracts the value from an [integer][`ValueType::Int`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an integer.
- ///
- /// Returns the integer value if successful, or an [`Err`] if evaluation failed or the value is not an integer.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
- /// # use nix_bindings_store::store::Store;
- /// # use std::collections::HashMap;
- /// # fn example() -> anyhow::Result<()> {
- /// # test_init();
- /// # let guard = gc_register_my_thread()?;
- /// let store = Store::open(None, HashMap::new())?;
- /// let mut es = EvalState::new(store, [])?;
- ///
- /// let value = es.eval_from_string("42", "")?;
- /// let int_val = es.require_int(&value)?;
- /// assert_eq!(int_val, 42);
- /// # drop(guard);
- /// # Ok(())
- /// # }
- /// ```
- #[doc(alias = "integer")]
- #[doc(alias = "number")]
- #[doc(alias = "nix_get_int")]
- #[doc(alias = "get_int")]
- pub fn require_int(&mut self, v: &Value) -> Result {
- let t = self.value_type(v)?;
- if t != ValueType::Int {
- bail!("expected an int, but got a {:?}", t);
- }
- unsafe { check_call!(raw::get_int(&mut self.context, v.raw_ptr())) }
- }
-
- /// Extracts the value from a [boolean][`ValueType::Bool`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a boolean.
- ///
- /// Returns the boolean value if successful, or an [`Err`] if evaluation failed or the value is not a boolean.
- #[doc(alias = "boolean")]
- #[doc(alias = "nix_get_bool")]
- #[doc(alias = "get_bool")]
- pub fn require_bool(&mut self, v: &Value) -> Result {
- let t = self.value_type(v)?;
- if t != ValueType::Bool {
- bail!("expected a bool, but got a {:?}", t);
- }
- unsafe { check_call!(raw::get_bool(&mut self.context, v.raw_ptr())) }
- }
-
- /// Extracts all elements from a [list][`ValueType::List`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
- ///
- /// Returns the contained values in the specified container type (e.g., [`Vec`], [`VecDeque`][`std::collections::VecDeque`], etc.).
- ///
- /// This is [strict](https://nix.dev/manual/nix/latest/language/evaluation.html#strictness) - all list elements will be evaluated.
- ///
- /// # Examples
- ///
- /// ```rust,no_run
- /// # use nix_bindings_expr::value::Value;
- /// # use std::collections::{VecDeque, LinkedList};
- /// # fn example(es: &mut nix_bindings_expr::eval_state::EvalState, list_value: &Value) -> anyhow::Result<()> {
- /// let vec: Vec = es.require_list_strict(&list_value)?;
- /// let deque: VecDeque = es.require_list_strict(&list_value)?;
- /// let linked_list = es.require_list_strict::>(&list_value)?;
- /// # Ok(())
- /// # }
- /// ```
- #[doc(alias = "collect")]
- #[doc(alias = "to_vec")]
- #[doc(alias = "all")]
- #[doc(alias = "nix_get_list_size")]
- #[doc(alias = "nix_get_list_byidx")]
- pub fn require_list_strict(&mut self, value: &Value) -> Result
- where
- C: FromIterator,
- {
- let t = self.value_type(value)?;
- if t != ValueType::List {
- bail!("expected a list, but got a {:?}", t);
- }
- let size = unsafe { check_call!(raw::get_list_size(&mut self.context, value.raw_ptr())) }?;
-
- (0..size)
- .map(|i| {
- let element_ptr = unsafe {
- check_call!(raw::get_list_byidx(
- &mut self.context,
- value.raw_ptr(),
- self.eval_state.as_ptr(),
- i
- ))
- }?;
- Ok(unsafe { Value::new(element_ptr) })
- })
- .collect()
- }
-
- /// Evaluate, and require that the [`Value`] is a Nix [`ValueType::AttrSet`].
- ///
- /// Returns a list of the keys in the attrset.
- ///
- /// NOTE: this currently implements its own sorting, which probably matches Nix's implementation, but is not guaranteed.
- #[doc(alias = "keys")]
- #[doc(alias = "attributes")]
- #[doc(alias = "fields")]
- pub fn require_attrs_names(&mut self, v: &Value) -> Result> {
- self.require_attrs_names_unsorted(v).map(|mut v| {
- v.sort();
- v
- })
- }
-
- /// For when [`EvalState::require_attrs_names`] isn't fast enough.
- ///
- /// Only use when it's ok that the keys are returned in an arbitrary order.
- #[doc(alias = "keys_unsorted")]
- #[doc(alias = "attributes_unsorted")]
- pub fn require_attrs_names_unsorted(&mut self, v: &Value) -> Result> {
- let t = self.value_type(v)?;
- if t != ValueType::AttrSet {
- bail!("expected an attrset, but got a {:?}", t);
- }
- let n = unsafe { check_call!(raw::get_attrs_size(&mut self.context, v.raw_ptr())) }?;
- let mut attrs = Vec::with_capacity(n as usize);
- for i in 0..n {
- let cstr_ptr: *const c_char = unsafe {
- check_call!(raw::get_attr_name_byidx(
- &mut self.context,
- v.raw_ptr(),
- self.eval_state.as_ptr(),
- i as c_uint
- ))
- }?;
- let cstr = unsafe { std::ffi::CStr::from_ptr(cstr_ptr) };
- let s = cstr
- .to_str()
- .map_err(|e| anyhow::format_err!("Nix attrset key is not valid UTF-8: {}", e))?;
- attrs.insert(i as usize, s.to_owned());
- }
- Ok(attrs)
- }
-
- /// Extracts an attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
- ///
- /// Returns the attribute value if found, or an [`Err`] if evaluation failed, the attribute doesn't exist, or the value is not an attribute set.
- #[doc(alias = "get_attr")]
- #[doc(alias = "attribute")]
- #[doc(alias = "field")]
- pub fn require_attrs_select(&mut self, v: &Value, attr_name: &str) -> Result {
- let t = self.value_type(v)?;
- if t != ValueType::AttrSet {
- bail!("expected an attrset, but got a {:?}", t);
- }
- let attr_name = CString::new(attr_name)
- .with_context(|| "require_attrs_select: attrName contains null byte")?;
- unsafe {
- let v2 = check_call!(raw::get_attr_byname(
- &mut self.context,
- v.raw_ptr(),
- self.eval_state.as_ptr(),
- attr_name.as_ptr()
- ));
- match v2 {
- Ok(v2) => Ok(Value::new(v2)),
- Err(e) => {
- // As of Nix 2.26, the error message is not helpful when it
- // is simply missing, so we provide a better one. (Note that
- // missing attributes requested by Nix expressions OTOH is a
- // different error message which works fine.)
- if e.to_string() == "missing attribute" {
- bail!("attribute `{}` not found", attr_name.to_string_lossy());
- } else {
- Err(e)
- }
- }
- }
- }
- }
-
- /// Extracts an optional attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
- ///
- /// Returns [`Err`] if evaluation failed or the value is not an attribute set.
- ///
- /// Returns `Ok(None)` if the attribute is not present.
- ///
- /// Returns `Ok(Some(value))` if the attribute is present.
- #[doc(alias = "nix_get_attr_byname")]
- #[doc(alias = "get_attr_byname")]
- #[doc(alias = "get_attr_opt")]
- #[doc(alias = "try_get")]
- #[doc(alias = "maybe_get")]
- pub fn require_attrs_select_opt(
- &mut self,
- v: &Value,
- attr_name: &str,
- ) -> Result> {
- let t = self.value_type(v)?;
- if t != ValueType::AttrSet {
- bail!("expected an attrset, but got a {:?}", t);
- }
- let attr_name = CString::new(attr_name)
- .with_context(|| "require_attrs_select_opt: attrName contains null byte")?;
- let v2 = unsafe {
- check_call_opt_key!(raw::get_attr_byname(
- &mut self.context,
- v.raw_ptr(),
- self.eval_state.as_ptr(),
- attr_name.as_ptr()
- ))
- }?;
- Ok(v2.map(|x| unsafe { Value::new(x) }))
- }
-
- /// Returns the number of elements in a [list][`ValueType::List`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the list structure and verifies the value is a list.
- ///
- /// Individual elements remain as lazy [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) and are not evaluated.
- #[doc(alias = "length")]
- #[doc(alias = "count")]
- #[doc(alias = "len")]
- #[doc(alias = "nix_get_list_size")]
- #[doc(alias = "get_list_size")]
- pub fn require_list_size(&mut self, v: &Value) -> Result {
- let t = self.value_type(v)?;
- if t != ValueType::List {
- bail!("expected a list, but got a {:?}", t);
- }
- let ret = unsafe { check_call!(raw::get_list_size(&mut self.context, v.raw_ptr())) }?;
- Ok(ret)
- }
-
- /// Extracts an element from a [list][`ValueType::List`] Nix value by index.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
- /// Forces evaluation of the selected element, similar to [`Self::require_attrs_select`].
- ///
- /// Returns `Ok(Some(value))` if the element is found.
- ///
- /// Returns `Ok(None)` if the index is out of bounds.
- ///
- /// Returns [`Err`] if evaluation failed, the element contains an error (e.g., `throw`), or the value is not a list.
- #[doc(alias = "get")]
- #[doc(alias = "index")]
- #[doc(alias = "at")]
- #[doc(alias = "nix_get_list_byidx")]
- #[doc(alias = "get_list_byidx")]
- pub fn require_list_select_idx_strict(&mut self, v: &Value, idx: u32) -> Result> {
- let t = self.value_type(v)?;
- if t != ValueType::List {
- bail!("expected a list, but got a {:?}", t);
- }
-
- // TODO: Remove this bounds checking once https://github.com/NixOS/nix/pull/14030
- // is merged, which will add proper bounds checking to the underlying C API.
- // Currently we perform bounds checking in Rust to avoid undefined behavior.
- let size = unsafe { check_call!(raw::get_list_size(&mut self.context, v.raw_ptr())) }?;
-
- if idx >= size {
- return Ok(None);
- }
-
- let v2 = unsafe {
- check_call_opt_key!(raw::get_list_byidx(
- &mut self.context,
- v.raw_ptr(),
- self.eval_state.as_ptr(),
- idx
- ))
- }?;
- Ok(v2.map(|x| unsafe { Value::new(x) }))
- }
-
- /// Creates a new [string][`ValueType::String`] Nix value.
- ///
- /// Returns a string value without any [string context](https://nix.dev/manual/nix/latest/language/string-context.html).
- #[doc(alias = "make_string")]
- #[doc(alias = "create_string")]
- #[doc(alias = "string_value")]
- pub fn new_value_str(&mut self, s: &str) -> Result {
- let s = CString::new(s).with_context(|| "new_value_str: contains null byte")?;
- let v = unsafe {
- let value = self.new_value_uninitialized()?;
- check_call!(raw::init_string(
- &mut self.context,
- value.raw_ptr(),
- s.as_ptr()
- ))?;
- value
- };
- Ok(v)
- }
-
- /// Creates a new [integer][`ValueType::Int`] Nix value.
- #[doc(alias = "make_int")]
- #[doc(alias = "create_int")]
- #[doc(alias = "int_value")]
- #[doc(alias = "integer_value")]
- pub fn new_value_int(&mut self, i: Int) -> Result {
- let v = unsafe {
- let value = self.new_value_uninitialized()?;
- check_call!(raw::init_int(&mut self.context, value.raw_ptr(), i))?;
- value
- };
- Ok(v)
- }
-
- /// Creates a new [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) Nix value.
- ///
- /// The [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) will lazily evaluate to the result of the given Rust function when forced.
- /// The Rust function will be called with the current [`EvalState`] and must not return a thunk.
- ///
- /// The name is shown in stack traces.
- #[doc(alias = "make_thunk")]
- #[doc(alias = "create_thunk")]
- #[doc(alias = "lazy_value")]
- pub fn new_value_thunk(
- &mut self,
- name: &str,
- f: Box Result>,
- ) -> Result {
- // Nix doesn't have a function for creating a thunk, so we have to
- // create a function and pass it a dummy argument.
- let name = CString::new(name).with_context(|| "new_thunk: name contains null byte")?;
- let primop = primop::PrimOp::new(
- self,
- primop::PrimOpMeta {
- // name is observable in stack traces, ie if the thunk returns Err
- name: name.as_c_str(),
- // doc is unlikely to be observable, so we provide a constant one for simplicity.
- doc: cstr!("Performs an on demand computation, implemented outside the Nix language in native code."),
- // like doc, unlikely to be observed
- args: [CString::new("internal_unused").unwrap().as_c_str()],
- },
- Box::new(move |eval_state, _dummy: &[Value; 1]| f(eval_state)),
- )?;
-
- let p = self.new_value_primop(primop)?;
- self.new_value_apply(&p, &p)
- }
-
- /// Not exposed, because the caller must always explicitly handle the context or not accept one at all.
- fn get_string(&mut self, value: &Value) -> Result {
- let mut r = result_string_init!();
- unsafe {
- check_call!(raw::get_string(
- &mut self.context,
- value.raw_ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r)
- ))?;
- };
- r
- }
- /// Extracts a string value from a [string][`ValueType::String`] Nix value.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a string.
- /// Returns the string value if successful, or an [`Err`] if evaluation failed or the value is not a string.
- ///
- /// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty.
- #[doc(alias = "str")]
- #[doc(alias = "text")]
- #[doc(alias = "nix_get_string")]
- #[doc(alias = "get_string")]
- pub fn require_string(&mut self, value: &Value) -> Result {
- let t = self.value_type(value)?;
- if t != ValueType::String {
- bail!("expected a string, but got a {:?}", t);
- }
- self.get_string(value)
- }
- /// Realises a [string][`ValueType::String`] Nix value with context information.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html), verifies the value is a string, and builds any derivations
- /// referenced in the [string context](https://nix.dev/manual/nix/latest/language/string-context.html) if required.
- #[doc(alias = "realize_string")]
- #[doc(alias = "string_with_context")]
- #[doc(alias = "build_string")]
- pub fn realise_string(
- &mut self,
- value: &Value,
- is_import_from_derivation: bool,
- ) -> Result {
- let t = self.value_type(value)?;
- if t != ValueType::String {
- bail!("expected a string, but got a {:?}", t);
- }
-
- let rs = unsafe {
- check_call!(raw::string_realise(
- &mut self.context,
- self.eval_state.as_ptr(),
- value.raw_ptr(),
- is_import_from_derivation
- ))
- }?;
-
- let s = unsafe {
- let start = raw::realised_string_get_buffer_start(rs) as *const u8;
- let size = raw::realised_string_get_buffer_size(rs);
- let slice = std::slice::from_raw_parts(start, size);
- String::from_utf8(slice.to_vec())
- .map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e))?
- };
-
- let paths = unsafe {
- let n = raw::realised_string_get_store_path_count(rs);
- let mut paths = Vec::with_capacity(n as usize);
- for i in 0..n {
- let path = raw::realised_string_get_store_path(rs, i);
- let path = NonNull::new(path as *mut raw_store::StorePath).ok_or_else(|| {
- anyhow::format_err!(
- "nix_realised_string_get_store_path returned a null pointer"
- )
- })?;
- paths.push(StorePath::new_raw_clone(path));
- }
- paths
- };
-
- // We've converted the nix_realised_string to a native struct containing copies, so we can free it now.
- unsafe {
- raw::realised_string_free(rs);
- }
-
- Ok(RealisedString { s, paths })
- }
-
- /// Applies a function to an argument and returns the result.
- ///
- /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
- /// For a lazy version, see [`Self::new_value_apply`].
- #[doc(alias = "nix_value_call")]
- #[doc(alias = "value_call")]
- #[doc(alias = "apply")]
- #[doc(alias = "invoke")]
- #[doc(alias = "execute")]
- pub fn call(&mut self, f: Value, a: Value) -> Result {
- let value = self.new_value_uninitialized()?;
- unsafe {
- check_call!(raw::value_call(
- &mut self.context,
- self.eval_state.as_ptr(),
- f.raw_ptr(),
- a.raw_ptr(),
- value.raw_ptr()
- ))
- }?;
- Ok(value)
- }
-
- /// Apply a sequence of [function applications](https://nix.dev/manual/nix/latest/language/operators.html#function-application).
- ///
- /// When argument `f` is a curried function, this applies each argument in sequence.
- /// Equivalent to the Nix expression `f arg1 arg2 arg3`.
- ///
- /// Returns a [`Value`] in at least weak head normal form if successful.
- ///
- /// Returns an [`Err`]
- /// - if `f` did not evaluate to a function
- /// - if `f arg1` had any problems
- /// - if `f arg1` did not evaluate to a function (for `(f arg1) arg2`)
- /// - etc
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
- /// # use nix_bindings_store::store::Store;
- /// # use std::collections::HashMap;
- /// # fn example() -> anyhow::Result<()> {
- /// # test_init();
- /// # let guard = gc_register_my_thread()?;
- /// let store = Store::open(None, HashMap::new())?;
- /// let mut es = EvalState::new(store, [])?;
- ///
- /// // Create a curried function: x: y: x + y
- /// let f = es.eval_from_string("x: y: x + y", "")?;
- /// let arg1 = es.eval_from_string("5", "")?;
- /// let arg2 = es.eval_from_string("3", "")?;
- ///
- /// // Equivalent to: (x: y: x + y) 5 3
- /// let result = es.call_multi(&f, &[arg1, arg2])?;
- /// let value = es.require_int(&result)?;
- /// assert_eq!(value, 8);
- /// # drop(guard);
- /// # Ok(())
- /// # }
- /// ```
- #[doc(alias = "nix_value_call_multi")]
- #[doc(alias = "value_call_multi")]
- #[doc(alias = "apply_multi")]
- #[doc(alias = "curry")]
- #[doc(alias = "call_with_args")]
- pub fn call_multi(&mut self, f: &Value, args: &[Value]) -> Result {
- let value = self.new_value_uninitialized()?;
- unsafe {
- let mut args_ptrs = args.iter().map(|a| a.raw_ptr()).collect::>();
- check_call!(raw::value_call_multi(
- &mut self.context,
- self.eval_state.as_ptr(),
- f.raw_ptr(),
- args_ptrs.len(),
- args_ptrs.as_mut_ptr(),
- value.raw_ptr()
- ))
- }?;
- Ok(value)
- }
-
- /// Applies a function to an argument lazily, creating a [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
- ///
- /// Does not force [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
- /// For an eager version, see [`Self::call`].
- #[doc(alias = "lazy_apply")]
- #[doc(alias = "thunk_apply")]
- #[doc(alias = "defer_call")]
- pub fn new_value_apply(&mut self, f: &Value, a: &Value) -> Result {
- let value = self.new_value_uninitialized()?;
- unsafe {
- check_call!(raw::init_apply(
- &mut self.context,
- value.raw_ptr(),
- f.raw_ptr(),
- a.raw_ptr()
- ))
- }?;
- Ok(value)
- }
-
- fn new_value_uninitialized(&mut self) -> Result {
- unsafe {
- let value = check_call!(raw::alloc_value(
- &mut self.context,
- self.eval_state.as_ptr()
- ))?;
- Ok(Value::new(value))
- }
- }
-
- /// Creates a new [function][`ValueType::Function`] Nix value implemented by a Rust function.
- ///
- /// This is also known as a "primop" in Nix, short for primitive operation.
- /// Most of the `builtins.*` values are examples of primops, but this function
- /// does not affect `builtins`.
- #[doc(alias = "make_primop")]
- #[doc(alias = "create_function")]
- #[doc(alias = "builtin")]
- pub fn new_value_primop(&mut self, primop: primop::PrimOp) -> Result {
- let value = self.new_value_uninitialized()?;
- unsafe {
- check_call!(raw::init_primop(
- &mut self.context,
- value.raw_ptr(),
- primop.ptr
- ))?;
- };
- Ok(value)
- }
-
- /// Creates a new [attribute set][`ValueType::AttrSet`] Nix value from an iterator of name-value pairs.
- ///
- /// Accepts any iterator that yields `(String, Value)` pairs and has an exact size.
- /// Common usage includes [`Vec`], [`std::collections::HashMap`], and array literals.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
- /// # use nix_bindings_store::store::Store;
- /// # use std::collections::HashMap;
- /// # fn example() -> anyhow::Result<()> {
- /// # test_init();
- /// # let guard = gc_register_my_thread()?;
- /// let store = Store::open(None, HashMap::new())?;
- /// let mut es = EvalState::new(store, [])?;
- /// let a = es.new_value_int(1)?;
- /// let b = es.new_value_int(2)?;
- /// let c = es.new_value_int(3)?;
- /// let d = es.new_value_int(4)?;
- ///
- /// // From array
- /// let attrs1 = es.new_value_attrs([
- /// ("x".to_string(), a),
- /// ("y".to_string(), b)
- /// ])?;
- ///
- /// // From HashMap
- /// let mut map = HashMap::new();
- /// map.insert("foo".to_string(), c);
- /// map.insert("bar".to_string(), d);
- /// let attrs2 = es.new_value_attrs(map)?;
- /// # drop(guard);
- /// # Ok(())
- /// # }
- /// ```
- #[doc(alias = "make_attrs")]
- #[doc(alias = "create_attrset")]
- #[doc(alias = "object")]
- #[doc(alias = "record")]
- pub fn new_value_attrs(&mut self, attrs: I) -> Result
- where
- I: IntoIterator- ,
- I::IntoIter: ExactSizeIterator,
- {
- let iter = attrs.into_iter();
- let size = iter.len();
- let bindings_builder = BindingsBuilder::new(self, size)?;
- for (name, value) in iter {
- let name =
- CString::new(name).with_context(|| "new_value_attrs: name contains null byte")?;
- unsafe {
- check_call!(raw::bindings_builder_insert(
- &mut self.context,
- bindings_builder.ptr,
- name.as_ptr(),
- value.raw_ptr()
- ))?;
- }
- }
- let value = self.new_value_uninitialized()?;
- unsafe {
- check_call!(raw::make_attrs(
- &mut self.context,
- value.raw_ptr(),
- bindings_builder.ptr
- ))?;
- }
- Ok(value)
- }
-}
-
-// Internal RAII helper; could be refactored and made pub
-struct BindingsBuilder {
- ptr: *mut raw::BindingsBuilder,
-}
-impl Drop for BindingsBuilder {
- fn drop(&mut self) {
- unsafe {
- raw::bindings_builder_free(self.ptr);
- }
- }
-}
-impl BindingsBuilder {
- fn new(eval_state: &mut EvalState, capacity: usize) -> Result
{
- let ptr = unsafe {
- check_call!(raw::make_bindings_builder(
- &mut eval_state.context,
- eval_state.eval_state.as_ptr(),
- capacity
- ))
- }?;
- Ok(BindingsBuilder { ptr })
- }
-}
-
-/// Triggers garbage collection immediately.
-#[doc(alias = "garbage_collect")]
-#[doc(alias = "collect")]
-#[doc(alias = "gc")]
-pub fn gc_now() {
- unsafe {
- raw::gc_now();
- }
-}
-
-/// RAII guard for thread registration with the garbage collector.
-///
-/// Automatically unregisters the thread when dropped.
-pub struct ThreadRegistrationGuard {
- must_unregister: bool,
-}
-impl Drop for ThreadRegistrationGuard {
- fn drop(&mut self) {
- if self.must_unregister {
- unsafe {
- gc::GC_unregister_my_thread();
- }
- }
- }
-}
-
-fn gc_register_my_thread_do_it() -> Result<()> {
- unsafe {
- let mut sb: gc::GC_stack_base = gc::GC_stack_base {
- mem_base: null_mut(),
- };
- let r = gc::GC_get_stack_base(&mut sb);
- if r as u32 != gc::GC_SUCCESS {
- Err(anyhow::format_err!("GC_get_stack_base failed: {}", r))?;
- }
- gc::GC_register_my_thread(&sb);
- Ok(())
- }
-}
-
-#[doc(alias = "register_thread")]
-#[doc(alias = "thread_setup")]
-#[doc(alias = "gc_register")]
-pub fn gc_register_my_thread() -> Result {
- init()?;
- unsafe {
- let already_done = gc::GC_thread_is_registered();
- if already_done != 0 {
- return Ok(ThreadRegistrationGuard {
- must_unregister: false,
- });
- }
- gc_register_my_thread_do_it()?;
- Ok(ThreadRegistrationGuard {
- must_unregister: true,
- })
- }
-}
-
-impl Clone for EvalState {
- fn clone(&self) -> Self {
- EvalState {
- eval_state: self.eval_state.clone(),
- store: self.store.clone(),
- context: Context::new(),
- }
- }
-}
-
-/// Initialize the Nix library for testing. This includes some modifications to the Nix settings, that must not be used in production.
-/// Use at your own peril, in rust test suites.
-#[doc(alias = "test_initialize")]
-#[doc(alias = "test_setup")]
-pub fn test_init() {
- init().unwrap();
-
- // During development, we encountered a problem where the build hook
- // would cause the test suite to reinvokes itself, causing an infinite loop.
- // While _NIX_TEST_NO_SANDBOX=1 should prevent this, we may also set the
- // build hook to "" to prevent this.
- nix_bindings_util::settings::set("build-hook", "").unwrap();
-
- // When testing in the sandbox, the default build dir would be a parent of the storeDir,
- // which causes an error. So we set a custom build dir here.
- // Only available on linux
- if cfg!(target_os = "linux") {
- nix_bindings_util::settings::set("sandbox-build-dir", "/custom-build-dir-for-test")
- .unwrap();
- }
- std::env::set_var("_NIX_TEST_NO_SANDBOX", "1");
-
- // The tests run offline
- nix_bindings_util::settings::set("substituters", "").unwrap();
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use cstr::cstr;
- use ctor::ctor;
- use std::collections::HashMap;
- use std::fs::read_dir;
- use std::io::Write as _;
- use std::sync::{Arc, Mutex};
-
- #[ctor]
- fn setup() {
- test_init();
-
- // Configure Nix settings for the test suite
- // Set max-call-depth to 1000 (lower than default 10000) for the
- // eval_state_builder_loads_max_call_depth test case, while
- // giving other tests sufficient room for normal evaluation.
- std::env::set_var("NIX_CONFIG", "max-call-depth = 1000");
- }
-
- /// Run a function while making sure that the current thread is registered with the GC.
- pub fn gc_registering_current_thread(f: F) -> Result
- where
- F: FnOnce() -> R,
- {
- let guard = gc_register_my_thread()?;
- let r = f();
- drop(guard);
- Ok(r)
- }
-
- #[test]
- fn eval_state_new_and_drop() {
- gc_registering_current_thread(|| {
- // very basic test: make sure initialization doesn't crash
- let store = Store::open(None, HashMap::new()).unwrap();
- let _e = EvalState::new(store, []).unwrap();
- })
- .unwrap();
- }
-
- #[test]
- fn weak_ref() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let es = EvalState::new(store, []).unwrap();
- let weak = es.weak_ref();
- let _es = weak.upgrade().unwrap();
- })
- .unwrap();
- }
-
- #[test]
- fn weak_ref_gone() {
- gc_registering_current_thread(|| {
- let weak = {
- // Use a slightly different URL which is unique in the test suite, to bypass the global store cache
- let store = Store::open(Some("auto?foo=bar"), HashMap::new()).unwrap();
- let es = EvalState::new(store, []).unwrap();
- es.weak_ref()
- };
- assert!(weak.upgrade().is_none());
- assert!(weak.store.upgrade().is_none());
- assert!(weak.inner.upgrade().is_none());
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_lookup_path() {
- let import_expression = "import + import ";
- let integer0 = 83;
- let integer1 = 103;
- let mut test_file0 = tempfile::NamedTempFile::new().unwrap();
- let mut test_file1 = tempfile::NamedTempFile::new().unwrap();
- writeln!(test_file0, "{integer0}").unwrap();
- writeln!(test_file1, "{integer1}").unwrap();
- gc_registering_current_thread(|| {
- let mut es = EvalState::new(Store::open(None, HashMap::new()).unwrap(), []).unwrap();
- assert!(es.eval_from_string(import_expression, "").is_err());
-
- let mut es = EvalState::new(
- Store::open(None, HashMap::new()).unwrap(),
- [
- format!("test_file0={}", test_file0.path().to_str().unwrap()).as_str(),
- format!("test_file1={}", test_file1.path().to_str().unwrap()).as_str(),
- ],
- )
- .unwrap();
- let ie = &es.eval_from_string(import_expression, "").unwrap();
- let v = es.require_int(ie).unwrap();
- assert_eq!(v, integer0 + integer1);
- })
- .unwrap();
- test_file0.close().unwrap();
- test_file1.close().unwrap();
- }
-
- #[test]
- fn eval_state_eval_from_string() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("1", "").unwrap();
- let v2 = v.clone();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::Int));
- let t2 = es.value_type_unforced(&v2);
- assert!(t2 == Some(ValueType::Int));
- gc_now();
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_bool() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("true", "").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::Bool));
- let b = es.require_bool(&v).unwrap();
- assert!(b);
-
- let v = es.eval_from_string("false", "").unwrap();
- es.require_bool(&v).unwrap();
- let b = es.require_bool(&v).unwrap();
- assert!(!b);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_int() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("1", "").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type(&v).unwrap();
- assert!(t == ValueType::Int);
- let i = es.require_int(&v).unwrap();
- assert!(i == 1);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_int_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("x: x + 1", "").unwrap();
- let a = es.eval_from_string("2", "").unwrap();
- let v = es.new_value_apply(&f, &a).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t.is_none());
- let i = es.require_int(&v).unwrap();
- assert!(i == 3);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_bool_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("x: !x", "").unwrap();
- let a = es.eval_from_string("true", "").unwrap();
- let v = es.new_value_apply(&f, &a).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t.is_none());
- let i = es.require_bool(&v).unwrap();
- assert!(!i);
- })
- .unwrap();
- }
-
- /// A helper that turns an expression into a thunk.
- fn make_thunk(es: &mut EvalState, expr: &str) -> Value {
- // This would be silly in real code, but it works for the current Nix implementation.
- // A Nix implementation that applies the identity function eagerly would be a valid
- // Nix implementation, but annoying because we'll have to change this helper to do
- // something more complicated that isn't optimized away.
- let f = es.eval_from_string("x: x", "").unwrap();
- let v = es.eval_from_string(expr, "").unwrap();
- es.new_value_apply(&f, &v).unwrap()
- }
-
- #[test]
- fn make_thunk_helper_works() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = make_thunk(&mut es, "1");
- let t = es.value_type_unforced(&v);
- assert!(t.is_none());
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_attrs_names_empty() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("{ }", "").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::AttrSet));
- let attrs = es.require_attrs_names_unsorted(&v).unwrap();
- assert_eq!(attrs.len(), 0);
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_names_unsorted_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = make_thunk(&mut es, "{ a = 1; b = 2; }");
- let t = es.value_type_unforced(&v);
- assert!(t.is_none());
- let attrs = es.require_attrs_names_unsorted(&v).unwrap();
- assert_eq!(attrs.len(), 2);
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_names_unsorted_bad_type() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("1", "").unwrap();
- es.force(&v).unwrap();
- let r = es.require_attrs_names_unsorted(&v);
- assert!(r.is_err());
- assert_eq!(
- r.unwrap_err().to_string(),
- "expected an attrset, but got a Int"
- );
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_value_attrs_names_example() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = throw "nope a"; b = throw "nope b"; }"#;
- let v = es.eval_from_string(expr, "").unwrap();
- let attrs = es.require_attrs_names(&v).unwrap();
- assert_eq!(attrs.len(), 2);
- assert_eq!(attrs[0], "a");
- assert_eq!(attrs[1], "b");
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_attrs_select() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = "aye"; b = "bee"; }"#;
- let v = es.eval_from_string(expr, "").unwrap();
- let a = es.require_attrs_select(&v, "a").unwrap();
- let b = es.require_attrs_select(&v, "b").unwrap();
- assert_eq!(es.require_string(&a).unwrap(), "aye");
- assert_eq!(es.require_string(&b).unwrap(), "bee");
- let missing = es.require_attrs_select(&v, "c");
- match missing {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- let s = format!("{e:#}");
- if !s.contains("attribute `c` not found") {
- eprintln!("unexpected error message: {}", s);
- panic!();
- }
- }
- }
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_select_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = "aye"; b = "bee"; }"#;
- let v = make_thunk(&mut es, expr);
- assert!(es.value_type_unforced(&v).is_none());
- let r = es.require_attrs_select(&v, "a");
- assert!(r.is_ok());
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_select_error() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = throw "oh no the error"; }"#;
- let v = es.eval_from_string(expr, "").unwrap();
- let r = es.require_attrs_select(&v, "a");
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("oh no the error") {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- }
- }
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_select_opt() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = "aye"; b = "bee"; }"#;
- let v = es.eval_from_string(expr, "").unwrap();
- let a = es.require_attrs_select_opt(&v, "a").unwrap().unwrap();
- let b = es.require_attrs_select_opt(&v, "b").unwrap().unwrap();
- assert_eq!(es.require_string(&a).unwrap(), "aye");
- assert_eq!(es.require_string(&b).unwrap(), "bee");
- let c = es.require_attrs_select_opt(&v, "c").unwrap();
- assert!(c.is_none());
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_select_opt_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = "aye"; b = "bee"; }"#;
- let v = make_thunk(&mut es, expr);
- assert!(es.value_type_unforced(&v).is_none());
- let r = es.require_attrs_select_opt(&v, "a");
- assert!(r.is_ok());
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_require_attrs_select_opt_error() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"{ a = throw "oh no the error"; }"#;
- let v = es.eval_from_string(expr, "").unwrap();
- let r = es.require_attrs_select_opt(&v, "a");
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("oh no the error") {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- }
- }
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_value_string() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("\"hello\"", "").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::String));
- let s = es.require_string(&v).unwrap();
- assert!(s == "hello");
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_string_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = make_thunk(&mut es, "\"hello\"");
- assert!(es.value_type_unforced(&v).is_none());
- let s = es.require_string(&v).unwrap();
- assert!(s == "hello");
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_string_unexpected_bool() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("true", "").unwrap();
- es.force(&v).unwrap();
- let r = es.require_string(&v);
- assert!(r.is_err());
- // TODO: safe print value (like Nix would)
- assert_eq!(
- r.unwrap_err().to_string(),
- "expected a string, but got a Bool"
- );
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_value_string_unexpected_path_value() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("/foo", "").unwrap();
- es.force(&v).unwrap();
- let r = es.require_string(&v);
- assert!(r.is_err());
- assert_eq!(
- r.unwrap_err().to_string(),
- "expected a string, but got a Path"
- );
- })
- .unwrap()
- }
-
- #[test]
- fn eval_state_value_string_bad_utf() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es
- .eval_from_string("builtins.substring 0 1 \"ü\"", "")
- .unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::String));
- let r = es.require_string(&v);
- assert!(r.is_err());
- assert!(r
- .unwrap_err()
- .to_string()
- .contains("Nix string is not valid UTF-8"));
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_string_unexpected_context() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es
- .eval_from_string("(derivation { name = \"hello\"; system = \"dummy\"; builder = \"cmd.exe\"; }).outPath", "")
- .unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::String));
- // TODO
- // let r = es.require_string_without_context(&v);
- // assert!(r.is_err());
- // assert!(r.unwrap_err().to_string().contains("unexpected context"));
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_new_string() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.new_value_str("hello").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::String));
- let s = es.require_string(&v).unwrap();
- assert!(s == "hello");
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_new_string_empty() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.new_value_str("").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::String));
- let s = es.require_string(&v).unwrap();
- assert!(s.is_empty());
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_new_string_invalid() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let r = es.new_value_str("hell\0no");
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("contains null byte") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_new_int() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.new_value_int(42).unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::Int));
- let i = es.require_int(&v).unwrap();
- assert!(i == 42);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_attrset() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("{ }", "").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::AttrSet));
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_list() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[ ]", "").unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::List));
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_list_strict_empty() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[]", "").unwrap();
- es.force(&v).unwrap();
- let list: Vec = es.require_list_strict(&v).unwrap();
- assert_eq!(list.len(), 0);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_list_strict_int() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[42]", "").unwrap();
- es.force(&v).unwrap();
- let list: Vec = es.require_list_strict(&v).unwrap();
- assert_eq!(list.len(), 1);
- assert_eq!(es.require_int(&list[0]).unwrap(), 42);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_list_strict_int_bool() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[42 true]", "").unwrap();
- es.force(&v).unwrap();
- let list: Vec = es.require_list_strict(&v).unwrap();
- assert_eq!(list.len(), 2);
- assert_eq!(es.require_int(&list[0]).unwrap(), 42);
- assert!(es.require_bool(&list[1]).unwrap());
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_list_strict_error() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string(r#"[(throw "_evaluated_item_")]"#, "").unwrap();
- es.force(&v).unwrap();
- // This should fail because require_list_strict evaluates all elements
- let result: Result, _> = es.require_list_strict(&v);
- assert!(result.is_err());
- match result {
- Err(error_msg) => {
- let error_str = error_msg.to_string();
- assert!(error_str.contains("_evaluated_item_"));
- }
- Ok(_) => panic!("unexpected success. The item should have been evaluated and its error propagated.")
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_value_list_strict_generic_container() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[1 2 3]", "").unwrap();
-
- // Test with Vec
- let vec: Vec = es.require_list_strict(&v).unwrap();
- assert_eq!(vec.len(), 3);
-
- // Test with VecDeque
- let deque: std::collections::VecDeque = es.require_list_strict(&v).unwrap();
- assert_eq!(deque.len(), 3);
-
- // Verify contents are the same
- assert_eq!(es.require_int(&vec[0]).unwrap(), 1);
- assert_eq!(es.require_int(&deque[0]).unwrap(), 1);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_realise_string() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let expr = r#"
- ''
- a derivation output: ${
- derivation { name = "letsbuild";
- system = builtins.currentSystem;
- builder = "/bin/sh";
- args = [ "-c" "echo foo > $out" ];
- }}
- a path: ${builtins.toFile "just-a-file" "ooh file good"}
- a derivation path by itself: ${
- builtins.unsafeDiscardOutputDependency
- (derivation {
- name = "not-actually-built-yet";
- system = builtins.currentSystem;
- builder = "/bin/sh";
- args = [ "-c" "echo foo > $out" ];
- }).drvPath}
- ''
- "#;
- let v = es.eval_from_string(expr, "").unwrap();
- es.force(&v).unwrap();
- let rs = es.realise_string(&v, false).unwrap();
-
- assert!(rs.s.starts_with("a derivation output:"));
- assert!(rs.s.contains("-letsbuild\n"));
- assert!(!rs.s.contains("-letsbuild.drv"));
- assert!(rs.s.contains("a path:"));
- assert!(rs.s.contains("-just-a-file"));
- assert!(!rs.s.contains("-just-a-file.drv"));
- assert!(!rs.s.contains("ooh file good"));
- assert!(rs.s.ends_with("-not-actually-built-yet.drv\n"));
-
- assert_eq!(rs.paths.len(), 3);
- let mut names: Vec = rs.paths.iter().map(|p| p.name().unwrap()).collect();
- names.sort();
- assert_eq!(names[0], "just-a-file");
- assert_eq!(names[1], "letsbuild");
- assert_eq!(names[2], "not-actually-built-yet.drv");
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_call() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("x: x + 1", "").unwrap();
- let a = es.eval_from_string("2", "").unwrap();
- let v = es.call(f, a).unwrap();
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::Int));
- let i = es.require_int(&v).unwrap();
- assert!(i == 3);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_call_multi() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- // This is a function that takes two arguments.
- let f = es.eval_from_string("x: y: x - y", "").unwrap();
- let a = es.eval_from_string("2", "").unwrap();
- let b = es.eval_from_string("3", "").unwrap();
- let v = es.call_multi(&f, &[a, b]).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::Int));
- let i = es.require_int(&v).unwrap();
- assert!(i == -1);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_apply() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- // This is a function that takes two arguments.
- let f = es.eval_from_string("x: x + 1", "").unwrap();
- let a = es.eval_from_string("2", "").unwrap();
- let v = es.new_value_apply(&f, &a).unwrap();
- assert!(es.value_type_unforced(&v).is_none());
- es.force(&v).unwrap();
- let t = es.value_type_unforced(&v);
- assert!(t == Some(ValueType::Int));
- let i = es.require_int(&v).unwrap();
- assert!(i == 3);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_call_fail_body() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("x: x + 1", "").unwrap();
- let a = es.eval_from_string("true", "").unwrap();
- let r = es.call(f, a);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("cannot coerce") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_call_multi_fail_body() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- // This is a function that takes two arguments.
- let f = es.eval_from_string("x: y: x - y", "").unwrap();
- let a = es.eval_from_string("2", "").unwrap();
- let b = es.eval_from_string("true", "").unwrap();
- let r = es.call_multi(&f, &[a, b]);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("expected an integer but found") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_apply_fail_body() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("x: x + 1", "").unwrap();
- let a = es.eval_from_string("true", "").unwrap();
- // Lazy => no error
- let r = es.new_value_apply(&f, &a).unwrap();
- // Force it => error
- let res = es.force(&r);
- match res {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("cannot coerce") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- /// This tests the behavior of `call`, which is strict, unlike `new_value_apply`.
- #[test]
- fn eval_state_call_fail_args() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("{x}: x + 1", "").unwrap();
- let a = es.eval_from_string("{}", "").unwrap();
- let r = es.call(f, a);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("called without required argument") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_call_multi_fail_args() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- // This is a function that takes two arguments.
- let f = es.eval_from_string("{x}: {y}: x - y", "").unwrap();
- let a = es.eval_from_string("{x = 2;}", "").unwrap();
- let b = es.eval_from_string("{}", "").unwrap();
- let r = es.call_multi(&f, &[a, b]);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("called without required argument") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- /// This tests the behavior of `new_value_apply`, which is lazy, unlike `call`.
- #[test]
- fn eval_state_apply_fail_args_lazy() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = es.eval_from_string("{x}: x + 1", "").unwrap();
- let a = es.eval_from_string("{}", "").unwrap();
- // Lazy => no error
- let r = es.new_value_apply(&f, &a).unwrap();
- // Force it => error
- let res = es.force(&r);
- match res {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("called without required argument") {
- eprintln!("{}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn store_open_params() {
- gc_registering_current_thread(|| {
- let store = tempfile::tempdir().unwrap();
- let store_path = store.path().to_str().unwrap();
- let state = tempfile::tempdir().unwrap();
- let state_path = state.path().to_str().unwrap();
- let log = tempfile::tempdir().unwrap();
- let log_path = log.path().to_str().unwrap();
-
- let mut es = EvalState::new(
- Store::open(
- Some("local"),
- HashMap::from([
- ("store", store_path),
- ("state", state_path),
- ("log", log_path),
- ])
- .iter()
- .map(|(a, b)| (*a, *b)),
- )
- .unwrap(),
- [],
- )
- .unwrap();
-
- let expr = r#"
- ''
- a derivation output: ${
- derivation { name = "letsbuild";
- system = builtins.currentSystem;
- builder = "/bin/sh";
- args = [ "-c" "echo foo > $out" ];
- }}
- a path: ${builtins.toFile "just-a-file" "ooh file good"}
- a derivation path by itself: ${
- builtins.unsafeDiscardOutputDependency
- (derivation {
- name = "not-actually-built-yet";
- system = builtins.currentSystem;
- builder = "/bin/sh";
- args = [ "-c" "echo foo > $out" ];
- }).drvPath}
- ''
- "#;
- let derivations: [&[u8]; 3] = [
- b"letsbuild.drv",
- b"just-a-file",
- b"not-actually-built-yet.drv",
- ];
- let _ = es.eval_from_string(expr, "").unwrap();
-
- // assert that all three `derivations` are inside the store and the `state` directory is not empty either.
- let store_contents: Vec<_> = read_dir(store.path())
- .unwrap()
- .map(|dir_entry| dir_entry.unwrap().file_name())
- .collect();
- for derivation in derivations {
- assert!(store_contents
- .iter()
- .any(|f| f.as_encoded_bytes().ends_with(derivation)));
- }
- assert!(!empty(read_dir(state.path()).unwrap()));
-
- store.close().unwrap();
- state.close().unwrap();
- log.close().unwrap();
- })
- .unwrap();
- }
-
- fn empty(foldable: impl IntoIterator) -> bool {
- foldable.into_iter().all(|_| false)
- }
-
- #[test]
- fn eval_state_primop_anon_call() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let bias: Arc> = Arc::new(Mutex::new(0));
- let bias_control = bias.clone();
-
- let primop = primop::PrimOp::new(
- &mut es,
- primop::PrimOpMeta {
- name: cstr!("testFunction"),
- args: [cstr!("a"), cstr!("b")],
- doc: cstr!("anonymous test function"),
- },
- Box::new(move |es, [a, b]| {
- let a = es.require_int(a)?;
- let b = es.require_int(b)?;
- let c = *bias.lock().unwrap();
- es.new_value_int(a + b + c)
- }),
- )
- .unwrap();
-
- let f = es.new_value_primop(primop).unwrap();
-
- {
- *bias_control.lock().unwrap() = 10;
- }
- let a = es.new_value_int(2).unwrap();
- let b = es.new_value_int(3).unwrap();
- let fa = es.call(f, a).unwrap();
- let v = es.call(fa, b).unwrap();
- es.force(&v).unwrap();
- let t = es.value_type(&v).unwrap();
- assert!(t == ValueType::Int);
- let i = es.require_int(&v).unwrap();
- assert!(i == 15);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_primop_anon_call_throw() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let f = {
- let es: &mut EvalState = &mut es;
- let prim = primop::PrimOp::new(
- es,
- primop::PrimOpMeta {
- name: cstr!("throwingTestFunction"),
- args: [cstr!("arg")],
- doc: cstr!("anonymous test function"),
- },
- Box::new(move |es, [a]| {
- let a = es.require_int(a)?;
- bail!("error with arg [{}]", a);
- }),
- )
- .unwrap();
-
- es.new_value_primop(prim)
- }
- .unwrap();
- let a = es.new_value_int(2).unwrap();
- let r = es.call(f, a);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("error with arg [2]") {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_primop_anon_call_no_args() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es
- .new_value_thunk(
- "test_thunk",
- Box::new(move |es: &mut EvalState| es.new_value_int(42)),
- )
- .unwrap();
- es.force(&v).unwrap();
- let t = es.value_type(&v).unwrap();
- eprintln!("{:?}", t);
- assert!(t == ValueType::Int);
- let i = es.require_int(&v).unwrap();
- assert!(i == 42);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_primop_anon_call_no_args_lazy() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es
- .new_value_thunk(
- "test_thunk",
- Box::new(move |_| {
- bail!("error message in test case eval_state_primop_anon_call_no_args_lazy")
- }),
- )
- .unwrap();
- let r = es.force(&v);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains(
- "error message in test case eval_state_primop_anon_call_no_args_lazy",
- ) {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- if !e.to_string().contains("test_thunk") {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- pub fn eval_state_primop_custom() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let primop = primop::PrimOp::new(
- &mut es,
- primop::PrimOpMeta {
- name: cstr!("frobnicate"),
- doc: cstr!("Frobnicates widgets"),
- args: [cstr!("x"), cstr!("y")],
- },
- Box::new(|es, args| {
- let a = es.require_int(&args[0])?;
- let b = es.require_int(&args[1])?;
- es.new_value_int(a + b)
- }),
- )
- .unwrap();
- let f = es.new_value_primop(primop).unwrap();
- let a = es.new_value_int(2).unwrap();
- let b = es.new_value_int(3).unwrap();
- let fa = es.call(f, a).unwrap();
- let fb = es.call(fa, b).unwrap();
- es.force(&fb).unwrap();
- let t = es.value_type(&fb).unwrap();
- assert!(t == ValueType::Int);
- let i = es.require_int(&fb).unwrap();
- assert!(i == 5);
- })
- .unwrap();
- }
-
- #[test]
- pub fn eval_state_primop_custom_throw() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let primop = primop::PrimOp::new(
- &mut es,
- primop::PrimOpMeta {
- name: cstr!("frobnicate"),
- doc: cstr!("Frobnicates widgets"),
- args: [cstr!("x")],
- },
- Box::new(|_es, _args| bail!("The frob unexpectedly fizzled")),
- )
- .unwrap();
- let f = es.new_value_primop(primop).unwrap();
- let a = es.new_value_int(0).unwrap();
- match es.call(f, a) {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- if !e.to_string().contains("The frob unexpectedly fizzled") {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- if !e.to_string().contains("frobnicate") {
- eprintln!("unexpected error message: {}", e);
- panic!();
- }
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- pub fn eval_state_new_value_attrs_from_slice_empty() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let attrs = es.new_value_attrs([]).unwrap();
- let t = es.value_type(&attrs).unwrap();
- assert!(t == ValueType::AttrSet);
- let names = es.require_attrs_names(&attrs).unwrap();
- assert!(names.is_empty());
- })
- .unwrap();
- }
-
- #[test]
- pub fn eval_state_new_value_attrs_from_vec() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let attrs = {
- let a = es.new_value_int(1).unwrap();
- let b = es.new_value_int(2).unwrap();
- es.new_value_attrs(vec![("a".to_string(), a), ("b".to_string(), b)])
- .unwrap()
- };
- let t = es.value_type(&attrs).unwrap();
- assert!(t == ValueType::AttrSet);
- let names = es.require_attrs_names(&attrs).unwrap();
- assert_eq!(names.len(), 2);
- assert_eq!(names[0], "a");
- assert_eq!(names[1], "b");
- let a = es.require_attrs_select(&attrs, "a").unwrap();
- let b = es.require_attrs_select(&attrs, "b").unwrap();
- let i = es.require_int(&a).unwrap();
- assert_eq!(i, 1);
- let i = es.require_int(&b).unwrap();
- assert_eq!(i, 2);
- })
- .unwrap();
- }
-
- #[test]
- pub fn eval_state_new_value_attrs_from_hashmap() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let attrs = {
- let a = es.new_value_int(1).unwrap();
- let b = es.new_value_int(2).unwrap();
- es.new_value_attrs(HashMap::from([("a".to_string(), a), ("b".to_string(), b)]))
- .unwrap()
- };
- let t = es.value_type(&attrs).unwrap();
- assert!(t == ValueType::AttrSet);
- let names = es.require_attrs_names(&attrs).unwrap();
- assert_eq!(names.len(), 2);
- assert_eq!(names[0], "a");
- assert_eq!(names[1], "b");
- let a = es.require_attrs_select(&attrs, "a").unwrap();
- let b = es.require_attrs_select(&attrs, "b").unwrap();
- let i = es.require_int(&a).unwrap();
- assert_eq!(i, 1);
- let i = es.require_int(&b).unwrap();
- assert_eq!(i, 2);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_select_idx_strict_basic() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[ 10 20 30 ]", "").unwrap();
-
- let elem0 = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap();
- let elem1 = es.require_list_select_idx_strict(&v, 1).unwrap().unwrap();
- let elem2 = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap();
-
- assert_eq!(es.require_int(&elem0).unwrap(), 10);
- assert_eq!(es.require_int(&elem1).unwrap(), 20);
- assert_eq!(es.require_int(&elem2).unwrap(), 30);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_select_idx_strict_out_of_bounds() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[ 1 2 3 ]", "").unwrap();
-
- let out_of_bounds = es.require_list_select_idx_strict(&v, 3).unwrap();
- assert!(out_of_bounds.is_none());
-
- // Test boundary case - the last valid index
- let last_elem = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap();
- assert_eq!(es.require_int(&last_elem).unwrap(), 3);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_select_idx_strict_empty_list() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("[ ]", "").unwrap();
-
- // Test that the safe version properly handles empty list access
- let elem = es.require_list_select_idx_strict(&v, 0).unwrap();
- assert!(elem.is_none());
-
- // Verify we can get the size of an empty list
- let size = es.require_list_size(&v).unwrap();
- assert_eq!(size, 0);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_select_idx_strict_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = make_thunk(&mut es, "[ 42 ]");
- assert!(es.value_type_unforced(&v).is_none());
-
- let elem = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap();
- assert_eq!(es.require_int(&elem).unwrap(), 42);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_select_idx_strict_error_element() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
-
- let v = es
- .eval_from_string("[ (1 + 1) (throw \"error\") (3 + 3) ]", "")
- .unwrap();
-
- let elem0 = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap();
- assert_eq!(es.require_int(&elem0).unwrap(), 2);
-
- let elem2 = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap();
- assert_eq!(es.require_int(&elem2).unwrap(), 6);
-
- let elem1_result = es.require_list_select_idx_strict(&v, 1);
- match elem1_result {
- Ok(_) => panic!("expected an error from throw during selection"),
- Err(e) => {
- assert!(e.to_string().contains("error"));
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_select_idx_strict_wrong_type() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("42", "").unwrap();
-
- let r = es.require_list_select_idx_strict(&v, 0);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- let err_msg = e.to_string();
- assert!(err_msg.contains("expected a list, but got a"));
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_size_basic() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
-
- let empty = es.eval_from_string("[ ]", "").unwrap();
- assert_eq!(es.require_list_size(&empty).unwrap(), 0);
-
- let three_elem = es.eval_from_string("[ 1 2 3 ]", "").unwrap();
- assert_eq!(es.require_list_size(&three_elem).unwrap(), 3);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_size_forces_thunk() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = make_thunk(&mut es, "[ 1 2 3 4 5 ]");
- assert!(es.value_type_unforced(&v).is_none());
-
- let size = es.require_list_size(&v).unwrap();
- assert_eq!(size, 5);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_size_lazy_elements() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
-
- let v = es
- .eval_from_string(
- "[ (throw \"error1\") (throw \"error2\") (throw \"error3\") ]",
- "",
- )
- .unwrap();
-
- let size = es.require_list_size(&v).unwrap();
- assert_eq!(size, 3);
- })
- .unwrap();
- }
-
- #[test]
- fn eval_state_require_list_size_wrong_type() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
- let v = es.eval_from_string("\"not a list\"", "").unwrap();
-
- let r = es.require_list_size(&v);
- match r {
- Ok(_) => panic!("expected an error"),
- Err(e) => {
- let err_msg = e.to_string();
- assert!(err_msg.contains("expected a list, but got a"));
- }
- }
- })
- .unwrap();
- }
-
- /// Test for path coercion fix (commit 8f6ec2e, ).
- ///
- /// This test verifies that path coercion works correctly with EvalStateBuilder.
- /// Path coercion requires readOnlyMode = false, which is loaded from global
- /// settings by calling eval_state_builder_load().
- ///
- /// # Background
- ///
- /// Without the eval_state_builder_load() call, settings from global Nix
- /// configuration are never loaded, leaving readOnlyMode = true (the default).
- /// This prevents Nix from adding paths to the store during evaluation,
- /// which could cause errors like: "error: path '/some/local/path' does not exist"
- ///
- /// # Test Coverage
- ///
- /// This test exercises store file creation:
- /// 1. builtins.toFile successfully creates files in the store
- /// 2. Files are actually written to /nix/store
- /// 3. Content is written correctly
- ///
- /// Note: This test may not reliably fail without the fix in all environments.
- /// Use eval_state_builder_loads_max_call_depth for a deterministic test.
- #[test]
- #[cfg(nix_at_least = "2.26" /* real_path, eval_state_builder_load */)]
- fn eval_state_builder_path_coercion() {
- gc_registering_current_thread(|| {
- let mut store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalStateBuilder::new(store.clone())
- .unwrap()
- .build()
- .unwrap();
-
- // Use builtins.toFile to create a file in the store.
- // This operation requires readOnlyMode = false to succeed.
- let expr = r#"builtins.toFile "test-file.txt" "test content""#;
-
- // Evaluate the expression
- let value = es.eval_from_string(expr, "").unwrap();
-
- // Realise the string to get the path and associated store paths
- let realised = es.realise_string(&value, false).unwrap();
-
- // Verify we got exactly one store path
- assert_eq!(
- realised.paths.len(),
- 1,
- "Expected 1 store path, got {}",
- realised.paths.len()
- );
-
- // Get the physical filesystem path for the store path
- // In a relocated store, this differs from realised.s
- let physical_path = store.real_path(&realised.paths[0]).unwrap();
-
- // Verify the store path actually exists on disk
- assert!(
- std::path::Path::new(&physical_path).exists(),
- "Store path should exist: {}",
- physical_path
- );
-
- // Verify the content was written correctly
- let store_content = std::fs::read_to_string(&physical_path).unwrap();
- assert_eq!(store_content, "test content");
- })
- .unwrap();
- }
-
- /// Test that eval_state_builder_load() loads settings.
- ///
- /// Uses max-call-depth as the test setting. The test suite sets
- /// max-call-depth = 1000 via NIX_CONFIG in setup() for the purpose of this test case.
- /// This test creates a recursive function that calls itself 1100 times.
- ///
- /// - WITH the fix: Settings are loaded, max-call-depth=1000 is enforced,
- /// recursion fails at depth 1000
- /// - WITHOUT the fix: Settings aren't loaded, default max-call-depth=10000
- /// is used, recursion of 1100 succeeds when it should fail
- ///
- /// Complementary to eval_state_builder_ignores_ambient_when_disabled which verifies
- /// that ambient settings are NOT loaded when disabled.
- #[test]
- #[cfg(nix_at_least = "2.26")]
- fn eval_state_builder_loads_max_call_depth() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalStateBuilder::new(store).unwrap().build().unwrap();
-
- // Create a recursive function that calls itself 1100 times
- // This should fail because max-call-depth is 1000 (set in setup())
- let expr = r#"
- let
- recurse = n: if n == 0 then "done" else recurse (n - 1);
- in
- recurse 1100
- "#;
-
- let result = es.eval_from_string(expr, "");
-
- match result {
- Err(e) => {
- let err_str = e.to_string();
- assert!(
- err_str.contains("max-call-depth"),
- "Expected max-call-depth error, got: {}",
- err_str
- );
- }
- Ok(_) => {
- panic!(
- "Expected recursion to fail with max-call-depth=1000, but it succeeded. \
- This indicates eval_state_builder_load() was not called."
- );
- }
- }
- })
- .unwrap();
- }
-
- /// Test that load_ambient_settings(false) ignores the ambient environment.
- ///
- /// The test suite sets max-call-depth = 1000 via NIX_CONFIG in setup().
- /// When we disable loading ambient settings, this should be ignored and
- /// the default max-call-depth = 10000 should be used instead.
- ///
- /// Complementary to eval_state_builder_loads_max_call_depth which verifies
- /// that ambient settings ARE loaded when enabled.
- #[test]
- #[cfg(nix_at_least = "2.26")]
- fn eval_state_builder_ignores_ambient_when_disabled() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, HashMap::new()).unwrap();
- let mut es = EvalStateBuilder::new(store)
- .unwrap()
- .load_ambient_settings(false)
- .build()
- .unwrap();
-
- // Create a recursive function that calls itself 1100 times
- // With ambient settings disabled, default max-call-depth=10000 is used,
- // so this should succeed (unlike eval_state_builder_loads_max_call_depth)
- let expr = r#"
- let
- recurse = n: if n == 0 then "done" else recurse (n - 1);
- in
- recurse 1100
- "#;
-
- let result = es.eval_from_string(expr, "");
-
- match result {
- Ok(value) => {
- // Success expected - ambient NIX_CONFIG was ignored
- let result_str = es.require_string(&value).unwrap();
- assert_eq!(result_str, "done");
- }
- Err(e) => {
- panic!(
- "Expected recursion to succeed with default max-call-depth=10000, \
- but it failed: {}. This indicates ambient settings were not ignored.",
- e
- );
- }
- }
- })
- .unwrap();
- }
-
- #[test]
- #[cfg(nix_at_least = "2.34.0pre")]
- fn eval_state_primop_recoverable_error() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
-
- let call_count = std::cell::Cell::new(0u32);
- let v = es
- .new_value_thunk(
- "recoverable_test",
- Box::new(move |es: &mut EvalState| {
- let count = call_count.get();
- call_count.set(count + 1);
- if count == 0 {
- Err(primop::RecoverableError::new("transient failure").into())
- } else {
- es.new_value_int(42)
- }
- }),
- )
- .unwrap();
-
- // First force should fail with the recoverable error
- let r = es.force(&v);
- assert!(r.is_err());
- assert!(
- r.unwrap_err().to_string().contains("transient failure"),
- "Error message should contain 'transient failure'"
- );
-
- // Second force should succeed because the error was recoverable
- es.force(&v).unwrap();
- let i = es.require_int(&v).unwrap();
- assert_eq!(i, 42);
- })
- .unwrap();
- }
-
- #[test]
- #[cfg(nix_at_least = "2.34.0pre")]
- fn eval_state_primop_recoverable_error_in_chain() {
- gc_registering_current_thread(|| {
- let store = Store::open(None, []).unwrap();
- let mut es = EvalState::new(store, []).unwrap();
-
- let call_count = std::cell::Cell::new(0u32);
- let v = es
- .new_value_thunk(
- "recoverable_chain_test",
- Box::new(move |es: &mut EvalState| {
- let count = call_count.get();
- call_count.set(count + 1);
- if count == 0 {
- // Wrap RecoverableError in .context(), pushing it down the chain
- use anyhow::Context;
- Err(primop::RecoverableError::new("transient failure"))
- .context("wrapper context")?
- } else {
- es.new_value_int(42)
- }
- }),
- )
- .unwrap();
-
- // First force should fail
- let r = es.force(&v);
- assert!(r.is_err());
-
- // Second force should succeed if RecoverableError is found in the chain
- es.force(&v).unwrap();
- let i = es.require_int(&v).unwrap();
- assert_eq!(i, 42);
- })
- .unwrap();
- }
-}
diff --git a/nix-bindings-expr/src/lib.rs b/nix-bindings-expr/src/lib.rs
deleted file mode 100644
index 41c4b39..0000000
--- a/nix-bindings-expr/src/lib.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod eval_state;
-pub mod primop;
-pub mod value;
diff --git a/nix-bindings-expr/src/primop.rs b/nix-bindings-expr/src/primop.rs
deleted file mode 100644
index 1504405..0000000
--- a/nix-bindings-expr/src/primop.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-use crate::eval_state::{EvalState, EvalStateWeak};
-use crate::value::Value;
-use anyhow::Result;
-use nix_bindings_expr_sys as raw;
-use nix_bindings_util::check_call;
-use nix_bindings_util_sys as raw_util;
-use std::ffi::{c_int, c_void, CStr, CString};
-use std::mem::ManuallyDrop;
-use std::ptr::{null, null_mut};
-
-/// A primop error that is not memoized in the thunk that triggered it,
-/// allowing the thunk to be forced again.
-///
-/// Since [Nix 2.34](https://nix.dev/manual/nix/2.34/release-notes/rl-2.34.html#c-api-changes),
-/// primop errors are memoized by default: once a thunk fails, forcing it
-/// again returns the same error. Use `RecoverableError` for errors that
-/// are transient, so the caller can retry.
-///
-/// On Nix < 2.34, all errors are already recoverable, so this type has
-/// no additional effect.
-///
-/// Available since nix-bindings-expr 0.2.1.
-#[derive(Debug)]
-pub struct RecoverableError(String);
-
-impl RecoverableError {
- pub fn new(msg: impl Into) -> Self {
- RecoverableError(msg.into())
- }
-}
-
-impl std::fmt::Display for RecoverableError {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.0.fmt(f)
- }
-}
-
-impl std::error::Error for RecoverableError {}
-
-/// Metadata for a primop, used with `PrimOp::new`.
-pub struct PrimOpMeta<'a, const N: usize> {
- /// Name of the primop. Note that primops do not have to be registered as
- /// builtins. Nonetheless, a name is required for documentation purposes, e.g.
- /// :doc in the repl.
- pub name: &'a CStr,
-
- /// Documentation for the primop. This is displayed in the repl when using
- /// :doc. The format is markdown.
- pub doc: &'a CStr,
-
- /// The number of arguments the function takes, as well as names for the
- /// arguments, to be presented in the documentation (if applicable, e.g.
- /// :doc in the repl).
- pub args: [&'a CStr; N],
-}
-
-pub struct PrimOp {
- pub(crate) ptr: *mut raw::PrimOp,
-}
-impl Drop for PrimOp {
- fn drop(&mut self) {
- unsafe {
- raw::gc_decref(null_mut(), self.ptr as *mut c_void);
- }
- }
-}
-impl PrimOp {
- /// Create a new primop with the given metadata and implementation.
- ///
- /// When `f` returns an `Err`, the error is propagated to the Nix evaluator.
- /// To return a [recoverable error](RecoverableError), include it in the
- /// error chain (e.g. `Err(RecoverableError::new("...").into())`).
- pub fn new(
- eval_state: &mut EvalState,
- meta: PrimOpMeta,
- f: Box Result>,
- ) -> Result {
- assert!(N != 0);
-
- let mut args = Vec::new();
- for arg in meta.args {
- args.push(arg.as_ptr());
- }
- args.push(null());
-
- // Primops weren't meant to be dynamically created, as of writing.
- // This leaks, and so do the primop fields in Nix internally.
- let user_data = {
- // We'll be leaking this Box.
- // TODO: Use the GC with finalizer, if possible.
- let user_data = ManuallyDrop::new(Box::new(PrimOpContext {
- arity: N,
- function: Box::new(move |eval_state, args| f(eval_state, args.try_into().unwrap())),
- eval_state: eval_state.weak_ref(),
- }));
- user_data.as_ref() as *const PrimOpContext as *mut c_void
- };
- let op = unsafe {
- check_call!(raw::alloc_primop(
- &mut eval_state.context,
- FUNCTION_ADAPTER,
- N as c_int,
- meta.name.as_ptr(),
- args.as_mut_ptr(), /* TODO add an extra const to bindings to avoid mut here. */
- meta.doc.as_ptr(),
- user_data
- ))?
- };
- Ok(PrimOp { ptr: op })
- }
-}
-
-/// The user_data for our Nix primops
-struct PrimOpContext {
- arity: usize,
- function: Box Result>,
- eval_state: EvalStateWeak,
-}
-
-unsafe extern "C" fn function_adapter(
- user_data: *mut ::std::os::raw::c_void,
- context_out: *mut raw_util::c_context,
- _state: *mut raw::EvalState,
- args: *mut *mut raw::Value,
- ret: *mut raw::Value,
-) {
- let primop_info = (user_data as *const PrimOpContext).as_ref().unwrap();
- let mut eval_state = primop_info.eval_state.upgrade().unwrap_or_else(|| {
- panic!("Nix primop called after EvalState was dropped");
- });
- let args_raw_slice = unsafe { std::slice::from_raw_parts(args, primop_info.arity) };
- let args_vec: Vec = args_raw_slice
- .iter()
- .map(|v| Value::new_borrowed(*v))
- .collect();
- let args_slice = args_vec.as_slice();
-
- let r = primop_info.function.as_ref()(&mut eval_state, args_slice);
-
- match r {
- Ok(v) => unsafe {
- raw::copy_value(context_out, ret, v.raw_ptr());
- },
- Err(e) => unsafe {
- let err_code = error_code(&e);
- let cstr = CString::new(e.to_string()).unwrap_or_else(|_e| {
- CString::new("")
- .unwrap()
- });
- raw_util::set_err_msg(context_out, err_code, cstr.as_ptr());
- },
- }
-}
-
-fn error_code(e: &anyhow::Error) -> raw_util::err {
- #[cfg(nix_at_least = "2.34.0pre")]
- if e.downcast_ref::().is_some() {
- return raw_util::err_NIX_ERR_RECOVERABLE;
- }
- raw_util::err_NIX_ERR_UNKNOWN
-}
-
-static FUNCTION_ADAPTER: raw::PrimOpFun = Some(function_adapter);
diff --git a/nix-bindings-expr/src/value.rs b/nix-bindings-expr/src/value.rs
deleted file mode 100644
index 68bb444..0000000
--- a/nix-bindings-expr/src/value.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-pub mod __private;
-
-use nix_bindings_expr_sys as raw;
-use nix_bindings_util::{check_call, context::Context};
-use std::ptr::{null_mut, NonNull};
-
-// TODO: test: cloning a thunk does not duplicate the evaluation.
-
-pub type Int = i64;
-
-/// The type discriminator of a [`Value`] that has successfully evaluated to at least [weak head normal form](https://nix.dev/manual/nix/latest/language/evaluation.html?highlight=WHNF#values).
-///
-/// Typically acquired with [`EvalState::value_type`][`crate::eval_state::EvalState::value_type`]
-#[derive(Eq, PartialEq, Debug)]
-pub enum ValueType {
- /// A Nix [attribute set](https://nix.dev/manual/nix/stable/language/types.html#type-attrs)
- AttrSet,
- /// A Nix [boolean](https://nix.dev/manual/nix/stable/language/types.html#type-bool)
- Bool,
- /// A Nix external value (mostly-opaque value for plugins, linked applications)
- External,
- /// A Nix [float](https://nix.dev/manual/nix/stable/language/types.html#type-float)
- Float,
- /// A Nix [function](https://nix.dev/manual/nix/stable/language/types.html#type-function)
- Function,
- /// A Nix [integer](https://nix.dev/manual/nix/stable/language/types.html#type-int)
- Int,
- /// A Nix [list](https://nix.dev/manual/nix/stable/language/types.html#type-list)
- List,
- /// A Nix [`null`](https://nix.dev/manual/nix/stable/language/types.html#type-null)
- Null,
- /// A Nix [path value](https://nix.dev/manual/nix/stable/language/types.html#type-path)
- Path,
- /// A Nix [string](https://nix.dev/manual/nix/stable/language/types.html#type-string)
- String,
- /// An unknown value, presumably from a new, partially unsupported version of Nix
- Unknown,
-}
-
-impl ValueType {
- /// Convert a raw value type to a [`ValueType`].
- ///
- /// Return [`None`] if the Value is still a thunk (i.e. not yet evaluated).
- ///
- /// Return `Some(`[`ValueType::Unknown`]`)` if the value type is not recognized.
- pub(crate) fn from_raw(raw: raw::ValueType) -> Option {
- match raw {
- raw::ValueType_NIX_TYPE_ATTRS => Some(ValueType::AttrSet),
- raw::ValueType_NIX_TYPE_BOOL => Some(ValueType::Bool),
- raw::ValueType_NIX_TYPE_EXTERNAL => Some(ValueType::External),
- raw::ValueType_NIX_TYPE_FLOAT => Some(ValueType::Float),
- raw::ValueType_NIX_TYPE_FUNCTION => Some(ValueType::Function),
- raw::ValueType_NIX_TYPE_INT => Some(ValueType::Int),
- raw::ValueType_NIX_TYPE_LIST => Some(ValueType::List),
- raw::ValueType_NIX_TYPE_NULL => Some(ValueType::Null),
- raw::ValueType_NIX_TYPE_PATH => Some(ValueType::Path),
- raw::ValueType_NIX_TYPE_STRING => Some(ValueType::String),
-
- raw::ValueType_NIX_TYPE_THUNK => None,
-
- // This would happen if a new type of value is added in Nix.
- _ => Some(ValueType::Unknown),
- }
- }
-}
-
-/// A pointer to a [value](https://nix.dev/manual/nix/latest/language/types.html) or [thunk](https://nix.dev/manual/nix/2.31/language/evaluation.html?highlight=thunk#laziness), to be used with [`EvalState`][`crate::eval_state::EvalState`] methods.
-///
-/// # Shared Evaluation State
-///
-/// Multiple `Value` instances can reference the same underlying Nix value.
-/// This occurs when a `Value` is [cloned](Clone), or when multiple Nix
-/// expressions reference the same binding.
-///
-/// When any reference to a thunk is evaluated—whether through
-/// [`force`](crate::eval_state::EvalState::force), other `EvalState` methods,
-/// or indirectly as a consequence of evaluating something else—all references
-/// observe the evaluated result. This means
-/// [`value_type_unforced`](crate::eval_state::EvalState::value_type_unforced)
-/// can return `None` (thunk) initially but a specific type later, even without
-/// directly operating on that `Value`. The state will not regress back to a
-/// less determined state.
-pub struct Value {
- inner: NonNull,
-}
-impl Value {
- /// Take ownership of a new [`Value`].
- ///
- /// This does not call [`nix_bindings_util_sys::gc_incref`], but does call [`nix_bindings_util_sys::gc_decref`] when [dropped][`Drop`].
- ///
- /// # Safety
- ///
- /// The caller must ensure that the provided `inner` has a positive reference count, and that `inner` is not used after the returned `Value` is dropped.
- pub(crate) unsafe fn new(inner: *mut raw::Value) -> Self {
- Value {
- inner: NonNull::new(inner).unwrap(),
- }
- }
-
- /// Borrow a reference to a [`Value`].
- ///
- /// This calls [`nix_bindings_util_sys::value_incref`], and the returned Value will call [`nix_bindings_util_sys::value_decref`] when dropped.
- ///
- /// # Safety
- ///
- /// The caller must ensure that the provided `inner` has a positive reference count.
- pub(crate) unsafe fn new_borrowed(inner: *mut raw::Value) -> Self {
- let v = Value::new(inner);
- unsafe { raw::value_incref(null_mut(), inner) };
- v
- }
-
- /// # Safety
- ///
- /// The caller must ensure that the returned pointer is not used after the `Value` is dropped.
- pub(crate) unsafe fn raw_ptr(&self) -> *mut raw::Value {
- self.inner.as_ptr()
- }
-}
-impl Drop for Value {
- fn drop(&mut self) {
- unsafe {
- // ignoring error because the only failure mode is leaking memory
- raw::value_decref(null_mut(), self.inner.as_ptr());
- }
- }
-}
-impl Clone for Value {
- fn clone(&self) -> Self {
- // TODO: Is it worth allocating a new Context here? Ideally cloning is cheap.
- // this is very unlikely to error, and it is not recoverable
- // Maybe try without, and try again with context to report details?
- unsafe {
- check_call!(raw::value_incref(&mut Context::new(), self.inner.as_ptr())).unwrap();
- }
- // can't return an error here, but we don't want to ignore the error either as it means we could use-after-free
- Value { inner: self.inner }
- }
-}
-
-// Tested in eval_state.rs
diff --git a/nix-bindings-expr/src/value/__private.rs b/nix-bindings-expr/src/value/__private.rs
deleted file mode 100644
index 770a217..0000000
--- a/nix-bindings-expr/src/value/__private.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-//! Functions that are relevant for other bindings modules, but normally not end users.
-use super::Value;
-use nix_bindings_expr_sys as raw;
-
-/// Take ownership of a new [`Value`].
-///
-/// This does not call `nix_gc_incref`, but does call `nix_gc_decref` when dropped.
-///
-/// # Safety
-///
-/// The caller must ensure that the provided `ptr` has a positive reference count,
-/// and that `ptr` is not used after the returned `Value` is dropped.
-pub unsafe fn raw_value_new(ptr: *mut raw::Value) -> Value {
- Value::new(ptr)
-}
-
-/// Borrow a reference to a [`Value`].
-///
-/// This calls `value_incref`, and the returned Value will call `value_decref` when dropped.
-///
-/// # Safety
-///
-/// The caller must ensure that the provided `ptr` has a positive reference count.
-pub unsafe fn raw_value_new_borrowed(ptr: *mut raw::Value) -> Value {
- Value::new_borrowed(ptr)
-}
diff --git a/nix-bindings-fetchers-sys/Cargo.toml b/nix-bindings-fetchers-sys/Cargo.toml
deleted file mode 100644
index 2dfaa3b..0000000
--- a/nix-bindings-fetchers-sys/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "nix-bindings-fetchers-sys"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Low-level FFI bindings to the nix-fetchers library"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_fetchers_sys/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-
-[build-dependencies]
-bindgen = "0.69"
-pkg-config = "0.3"
diff --git a/nix-bindings-fetchers-sys/README.md b/nix-bindings-fetchers-sys/README.md
deleted file mode 100644
index 8230b81..0000000
--- a/nix-bindings-fetchers-sys/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# nix-bindings-fetchers-sys
-
-This crate contains generated bindings for the Nix C API (`nix-fetchers-c`).
-**You should not have to use this crate directly,** and so you should probably not add it to your dependencies.
-Instead, use the `nix-bindings-fetchers` crate, which _should_ be sufficient.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_fetchers_sys/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-fetchers-sys/build.rs b/nix-bindings-fetchers-sys/build.rs
deleted file mode 100644
index 34f6640..0000000
--- a/nix-bindings-fetchers-sys/build.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use std::path::PathBuf;
-
-#[derive(Debug)]
-struct StripNixPrefix;
-
-impl bindgen::callbacks::ParseCallbacks for StripNixPrefix {
- fn item_name(&self, name: &str) -> Option {
- name.strip_prefix("nix_").map(String::from)
- }
-}
-
-fn main() {
- println!("cargo:rerun-if-changed=include/nix-c-fetchers.h");
- println!("cargo:rustc-link-lib=nixfetchersc");
-
- let mut args = Vec::new();
- for path in pkg_config::probe_library("nix-fetchers-c")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
-
- let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
-
- let bindings = bindgen::Builder::default()
- .header("include/nix-c-fetchers.h")
- .clang_args(args)
- .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
- .parse_callbacks(Box::new(StripNixPrefix))
- // Blocklist symbols from nix-bindings-util-sys
- .blocklist_file(".*nix_api_util\\.h")
- .generate()
- .expect("Unable to generate bindings");
-
- bindings
- .write_to_file(out_path.join("bindings.rs"))
- .expect("Couldn't write bindings!");
-}
diff --git a/nix-bindings-fetchers-sys/include/nix-c-fetchers.h b/nix-bindings-fetchers-sys/include/nix-c-fetchers.h
deleted file mode 100644
index 2f4c542..0000000
--- a/nix-bindings-fetchers-sys/include/nix-c-fetchers.h
+++ /dev/null
@@ -1 +0,0 @@
-#include
diff --git a/nix-bindings-fetchers-sys/src/lib.rs b/nix-bindings-fetchers-sys/src/lib.rs
deleted file mode 100644
index 97a30ed..0000000
--- a/nix-bindings-fetchers-sys/src/lib.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! Raw bindings to Nix C API
-//!
-//! This crate contains automatically generated bindings from the Nix C headers.
-//! The bindings are generated by bindgen and include C-style naming conventions
-//! and documentation comments that don't always conform to Rust standards.
-//!
-//! Normally you don't have to use this crate directly.
-//! Instead use `nix-fetchers`.
-
-// This file must only contain generated code, so that the module-level
-// #![allow(...)] attributes don't suppress warnings in hand-written code.
-// If you need to add hand-written code, use a submodule to isolate the
-// generated code. See:
-// https://github.com/nixops4/nixops4/pull/138/commits/330c3881be3d3cf3e59adebbe0ab1c0f15f6d2c9
-
-// Standard bindgen suppressions for C naming conventions
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// Clippy suppressions for generated C bindings
-// bindgen doesn't generate safety docs
-#![allow(clippy::missing_safety_doc)]
-// Rustdoc suppressions for generated C documentation
-// The C headers contain Doxygen-style documentation that doesn't translate
-// well to Rust's rustdoc format, causing various warnings:
-#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
-#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
-#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like
-#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
-
-use nix_bindings_util_sys::*;
-
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/nix-bindings-fetchers/Cargo.toml b/nix-bindings-fetchers/Cargo.toml
deleted file mode 100644
index 4ae993d..0000000
--- a/nix-bindings-fetchers/Cargo.toml
+++ /dev/null
@@ -1,30 +0,0 @@
-[package]
-name = "nix-bindings-fetchers"
-version = "0.2.1"
-edition = "2021"
-license = "LGPL-2.1"
-description = "Rust bindings to Nix fetchers"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_fetchers/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-anyhow = "1.0"
-nix-bindings-store = { path = "../nix-bindings-store", version = "0.2.1" }
-nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" }
-nix-bindings-fetchers-sys = { path = "../nix-bindings-fetchers-sys", version = "0.2.1" }
-ctor = "0.2"
-tempfile = "3.10"
-cstr = "0.2"
-
-[lints.rust]
-warnings = "deny"
-dead-code = "allow"
-
-[lints.clippy]
-type-complexity = "allow"
-# We're still trying to make Nix more thread-safe, want forward-compat
-arc-with-non-send-sync = "allow"
diff --git a/nix-bindings-fetchers/README.md b/nix-bindings-fetchers/README.md
deleted file mode 100644
index b8f3941..0000000
--- a/nix-bindings-fetchers/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# nix-bindings-fetchers
-
-Rust bindings to the nix-fetchers library.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_fetchers/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-fetchers/src/lib.rs b/nix-bindings-fetchers/src/lib.rs
deleted file mode 100644
index 0b7b6cc..0000000
--- a/nix-bindings-fetchers/src/lib.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use anyhow::{Context as _, Result};
-use nix_bindings_fetchers_sys as raw;
-use nix_bindings_util::context::{self, Context};
-use std::ptr::NonNull;
-
-pub struct FetchersSettings {
- pub(crate) ptr: NonNull,
-}
-impl Drop for FetchersSettings {
- fn drop(&mut self) {
- unsafe {
- raw::fetchers_settings_free(self.ptr.as_ptr());
- }
- }
-}
-impl FetchersSettings {
- pub fn new() -> Result {
- let mut ctx = Context::new();
- let ptr = unsafe { context::check_call!(raw::fetchers_settings_new(&mut ctx))? };
- Ok(FetchersSettings {
- ptr: NonNull::new(ptr).context("fetchers_settings_new unexpectedly returned null")?,
- })
- }
-
- pub fn raw_ptr(&self) -> *mut raw::fetchers_settings {
- self.ptr.as_ptr()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn fetchers_settings_new() {
- let _ = FetchersSettings::new().unwrap();
- }
-}
diff --git a/nix-bindings-flake-sys/Cargo.toml b/nix-bindings-flake-sys/Cargo.toml
deleted file mode 100644
index 987cd6d..0000000
--- a/nix-bindings-flake-sys/Cargo.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-[package]
-name = "nix-bindings-flake-sys"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Low-level FFI bindings to Nix flakes"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_flake_sys/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-nix-bindings-store-sys = { path = "../nix-bindings-store-sys", version = "0.2.1" }
-nix-bindings-expr-sys = { path = "../nix-bindings-expr-sys", version = "0.2.1" }
-nix-bindings-fetchers-sys = { path = "../nix-bindings-fetchers-sys", version = "0.2.1" }
-nix-bindings-bdwgc-sys = { path = "../nix-bindings-bdwgc-sys", version = "0.2.1" }
-
-[build-dependencies]
-bindgen = "0.69"
-pkg-config = "0.3"
diff --git a/nix-bindings-flake-sys/README.md b/nix-bindings-flake-sys/README.md
deleted file mode 100644
index 1d26c8d..0000000
--- a/nix-bindings-flake-sys/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# nix-bindings-flake-sys
-
-This crate contains generated bindings for the Nix C API (`nix-flake-c`).
-**You should not have to use this crate directly,** and so you should probably not add it to your dependencies.
-Instead, use the `nix-bindings-flake` crate, which _should_ be sufficient.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_flake_sys/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-flake-sys/build.rs b/nix-bindings-flake-sys/build.rs
deleted file mode 100644
index 1fb3233..0000000
--- a/nix-bindings-flake-sys/build.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use std::path::PathBuf;
-
-#[derive(Debug)]
-struct StripNixPrefix;
-
-impl bindgen::callbacks::ParseCallbacks for StripNixPrefix {
- fn item_name(&self, name: &str) -> Option {
- name.strip_prefix("nix_").map(String::from)
- }
-}
-
-fn main() {
- println!("cargo:rerun-if-changed=include/nix-c-flake.h");
- println!("cargo:rustc-link-lib=nixflakec");
-
- let mut args = Vec::new();
- for path in pkg_config::probe_library("nix-flake-c")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
- for path in pkg_config::probe_library("bdw-gc")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
-
- let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
-
- let bindings = bindgen::Builder::default()
- .header("include/nix-c-flake.h")
- .clang_args(args)
- .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
- .parse_callbacks(Box::new(StripNixPrefix))
- // Blocklist symbols from nix-bindings-util-sys
- .blocklist_file(".*nix_api_util\\.h")
- // Blocklist symbols from nix-bindings-store-sys
- .blocklist_file(".*nix_api_store\\.h")
- // Blocklist symbols from nix-bindings-expr-sys
- .blocklist_file(".*nix_api_expr\\.h")
- .blocklist_file(".*nix_api_value\\.h")
- // Blocklist symbols from nix-bindings-fetchers-sys
- .blocklist_file(".*nix_api_fetchers\\.h")
- // Blocklist symbols from nix-bindings-bdwgc-sys
- .blocklist_file(".*/gc\\.h")
- .generate()
- .expect("Unable to generate bindings");
-
- bindings
- .write_to_file(out_path.join("bindings.rs"))
- .expect("Couldn't write bindings!");
-}
diff --git a/nix-bindings-flake-sys/include/nix-c-flake.h b/nix-bindings-flake-sys/include/nix-c-flake.h
deleted file mode 100644
index 89a45f1..0000000
--- a/nix-bindings-flake-sys/include/nix-c-flake.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#define GC_THREADS
-#include
-#include
diff --git a/nix-bindings-flake-sys/src/lib.rs b/nix-bindings-flake-sys/src/lib.rs
deleted file mode 100644
index 2e9209b..0000000
--- a/nix-bindings-flake-sys/src/lib.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-//! Raw bindings to Nix C API
-//!
-//! This crate contains automatically generated bindings from the Nix C headers.
-//! The bindings are generated by bindgen and include C-style naming conventions
-//! and documentation comments that don't always conform to Rust standards.
-//!
-//! Normally you don't have to use this crate directly.
-//! Instead use `nix-flake`.
-
-// This file must only contain generated code, so that the module-level
-// #![allow(...)] attributes don't suppress warnings in hand-written code.
-// If you need to add hand-written code, use a submodule to isolate the
-// generated code. See:
-// https://github.com/nixops4/nixops4/pull/138/commits/330c3881be3d3cf3e59adebbe0ab1c0f15f6d2c9
-
-// Standard bindgen suppressions for C naming conventions
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// Clippy suppressions for generated C bindings
-// bindgen doesn't generate safety docs
-#![allow(clippy::missing_safety_doc)]
-// Rustdoc suppressions for generated C documentation
-// The C headers contain Doxygen-style documentation that doesn't translate
-// well to Rust's rustdoc format, causing various warnings:
-#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
-#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
-#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like
-#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
-
-use nix_bindings_expr_sys::*;
-use nix_bindings_fetchers_sys::*;
-use nix_bindings_util_sys::*;
-
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/nix-bindings-flake/Cargo.toml b/nix-bindings-flake/Cargo.toml
deleted file mode 100644
index 5fbb42a..0000000
--- a/nix-bindings-flake/Cargo.toml
+++ /dev/null
@@ -1,32 +0,0 @@
-[package]
-name = "nix-bindings-flake"
-version = "0.2.1"
-edition = "2021"
-license = "LGPL-2.1"
-description = "Rust bindings to Nix flakes"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_flake/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-anyhow = "1.0"
-nix-bindings-expr = { path = "../nix-bindings-expr", version = "0.2.1" }
-nix-bindings-fetchers = { path = "../nix-bindings-fetchers", version = "0.2.1" }
-nix-bindings-store = { path = "../nix-bindings-store", version = "0.2.1" }
-nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" }
-nix-bindings-flake-sys = { path = "../nix-bindings-flake-sys", version = "0.2.1" }
-ctor = "0.2"
-tempfile = "3.10"
-cstr = "0.2"
-
-[lints.rust]
-warnings = "deny"
-dead-code = "allow"
-
-[lints.clippy]
-type-complexity = "allow"
-# We're still trying to make Nix more thread-safe, want forward-compat
-arc-with-non-send-sync = "allow"
diff --git a/nix-bindings-flake/README.md b/nix-bindings-flake/README.md
deleted file mode 100644
index 43dd11b..0000000
--- a/nix-bindings-flake/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# nix-bindings-flake
-
-Rust bindings to Nix flakes.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_flake/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-store-sys/Cargo.toml b/nix-bindings-store-sys/Cargo.toml
deleted file mode 100644
index 9e8c374..0000000
--- a/nix-bindings-store-sys/Cargo.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[package]
-name = "nix-bindings-store-sys"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Low-level FFI bindings to the Nix store library"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_store_sys/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-zerocopy = { version = "0.8", features = ["derive"] }
-
-[build-dependencies]
-bindgen = "0.69"
-pkg-config = "0.3"
diff --git a/nix-bindings-store-sys/README.md b/nix-bindings-store-sys/README.md
deleted file mode 100644
index 936505d..0000000
--- a/nix-bindings-store-sys/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# nix-bindings-store-sys
-
-This crate contains generated bindings for the Nix C API (`nix-store-c`).
-**You should not have to use this crate directly,** and so you should probably not add it to your dependencies.
-Instead, use the `nix-bindings-store` crate, which _should_ be sufficient.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_store_sys/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-store-sys/build.rs b/nix-bindings-store-sys/build.rs
deleted file mode 100644
index 8702756..0000000
--- a/nix-bindings-store-sys/build.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use std::path::PathBuf;
-
-#[derive(Debug)]
-struct StripNixPrefix;
-
-impl bindgen::callbacks::ParseCallbacks for StripNixPrefix {
- fn item_name(&self, name: &str) -> Option {
- name.strip_prefix("nix_").map(String::from)
- }
-}
-
-#[derive(Debug)]
-struct AddZerocopyDerives {}
-impl bindgen::callbacks::ParseCallbacks for AddZerocopyDerives {
- fn add_derives(&self, info: &bindgen::callbacks::DeriveInfo<'_>) -> Vec {
- if info.name == "store_path_hash_part" {
- vec![
- "zerocopy::FromBytes".to_string(),
- "zerocopy::IntoBytes".to_string(),
- "zerocopy::Immutable".to_string(),
- ]
- } else {
- vec![]
- }
- }
-}
-
-fn main() {
- println!("cargo:rerun-if-changed=include/nix-c-store.h");
- println!("cargo:rustc-link-lib=nixstorec");
-
- let mut args = Vec::new();
- for path in pkg_config::probe_library("nix-store-c")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
-
- let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
-
- let bindings = bindgen::Builder::default()
- .header("include/nix-c-store.h")
- .clang_args(args)
- .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
- .parse_callbacks(Box::new(StripNixPrefix))
- .parse_callbacks(Box::new(AddZerocopyDerives {}))
- // Blocklist symbols from nix-bindings-util-sys
- .blocklist_file(".*nix_api_util\\.h")
- .generate()
- .expect("Unable to generate bindings");
-
- bindings
- .write_to_file(out_path.join("bindings.rs"))
- .expect("Couldn't write bindings!");
-}
diff --git a/nix-bindings-store-sys/include/nix-c-store.h b/nix-bindings-store-sys/include/nix-c-store.h
deleted file mode 100644
index 2c04d1d..0000000
--- a/nix-bindings-store-sys/include/nix-c-store.h
+++ /dev/null
@@ -1 +0,0 @@
-#include
diff --git a/nix-bindings-store-sys/src/lib.rs b/nix-bindings-store-sys/src/lib.rs
deleted file mode 100644
index eb41931..0000000
--- a/nix-bindings-store-sys/src/lib.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! Raw bindings to Nix C API
-//!
-//! This crate contains automatically generated bindings from the Nix C headers.
-//! The bindings are generated by bindgen and include C-style naming conventions
-//! and documentation comments that don't always conform to Rust standards.
-//!
-//! Normally you don't have to use this crate directly.
-//! Instead use `nix-store`.
-
-// This file must only contain generated code, so that the module-level
-// #![allow(...)] attributes don't suppress warnings in hand-written code.
-// If you need to add hand-written code, use a submodule to isolate the
-// generated code. See:
-// https://github.com/nixops4/nixops4/pull/138/commits/330c3881be3d3cf3e59adebbe0ab1c0f15f6d2c9
-
-// Standard bindgen suppressions for C naming conventions
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// Clippy suppressions for generated C bindings
-// bindgen doesn't generate safety docs
-#![allow(clippy::missing_safety_doc)]
-// Rustdoc suppressions for generated C documentation
-// The C headers contain Doxygen-style documentation that doesn't translate
-// well to Rust's rustdoc format, causing various warnings:
-#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
-#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
-#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like
-#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
-
-use nix_bindings_util_sys::*;
-
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/nix-bindings-store/Cargo.toml b/nix-bindings-store/Cargo.toml
deleted file mode 100644
index 24336db..0000000
--- a/nix-bindings-store/Cargo.toml
+++ /dev/null
@@ -1,45 +0,0 @@
-[package]
-name = "nix-bindings-store"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Rust bindings to Nix store library"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_store/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-anyhow = "1.0"
-nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" }
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-nix-bindings-store-sys = { path = "../nix-bindings-store-sys", version = "0.2.1" }
-zerocopy = "0.8"
-harmonia-store-core = { version = "0.0.0-alpha.0", optional = true }
-serde_json = { version = "1.0", optional = true }
-
-[dev-dependencies]
-ctor = "0.2"
-hex-literal = "0.4"
-tempfile = "3.10"
-serde_json = "1.0"
-
-[build-dependencies]
-pkg-config = "0.3"
-# Needed for version parsing in build.rs
-nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" }
-
-[features]
-harmonia = [ "dep:harmonia-store-core", "dep:serde_json" ]
-
-[lints.rust]
-warnings = "deny"
-dead-code = "allow"
-
-[lints.clippy]
-type-complexity = "allow"
-# We're still trying to make Nix more thread-safe, want forward-compat
-arc-with-non-send-sync = "allow"
diff --git a/nix-bindings-store/README.md b/nix-bindings-store/README.md
deleted file mode 100644
index 8002364..0000000
--- a/nix-bindings-store/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# nix-bindings-store
-
-Rust bindings to the Nix store library.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_store/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-store/build.rs b/nix-bindings-store/build.rs
deleted file mode 100644
index 85a20d6..0000000
--- a/nix-bindings-store/build.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-use nix_bindings_util::nix_version::emit_version_cfg;
-
-fn main() {
- let nix_version = pkg_config::probe_library("nix-store-c").unwrap().version;
- emit_version_cfg(&nix_version, &["2.26", "2.33.0pre", "2.33"]);
-}
diff --git a/nix-bindings-store/src/derivation/harmonia.rs b/nix-bindings-store/src/derivation/harmonia.rs
deleted file mode 100644
index 44b34a2..0000000
--- a/nix-bindings-store/src/derivation/harmonia.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use anyhow::Context as _;
-
-use super::Derivation;
-
-impl Derivation {
- /// Convert harmonia Derivation to nix-bindings Derivation.
- ///
- /// This requires a Store instance because the Nix C API needs it for internal validation.
- pub fn from_harmonia(
- store: &mut crate::store::Store,
- harmonia_drv: &harmonia_store_core::derivation::Derivation,
- ) -> anyhow::Result {
- let json = serde_json::to_string(harmonia_drv)
- .context("Failed to serialize harmonia Derivation to JSON")?;
-
- store.derivation_from_json(&json)
- }
-}
-
-impl TryFrom<&Derivation> for harmonia_store_core::derivation::Derivation {
- type Error = anyhow::Error;
-
- fn try_from(nix_drv: &Derivation) -> anyhow::Result {
- let json = nix_drv
- .to_json_string()
- .context("Failed to convert nix Derivation to JSON")?;
-
- serde_json::from_str(&json).context("Failed to parse JSON as harmonia Derivation")
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn create_harmonia_derivation() -> harmonia_store_core::derivation::Derivation {
- use harmonia_store_core::derivation::{Derivation, DerivationOutput};
- use harmonia_store_core::derived_path::OutputName;
- use harmonia_store_core::store_path::StorePath;
- use std::collections::{BTreeMap, BTreeSet};
- use std::str::FromStr;
-
- let system = format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS);
- let out_path = "8bs8sd27bzzy6w94fznjd2j8ldmdg7x6-myname";
-
- let env = BTreeMap::from([
- ("builder".into(), "/bin/sh".into()),
- ("name".into(), "myname".into()),
- ("out".into(), format!("/{out_path}").into()),
- ("system".into(), system.clone().into()),
- ]);
- let mut outputs = BTreeMap::new();
- outputs.insert(
- OutputName::from_str("out").unwrap(),
- DerivationOutput::InputAddressed(StorePath::from_base_path(out_path).unwrap()),
- );
-
- Derivation {
- args: vec!["-c".into(), "echo $name foo > $out".into()],
- builder: "/bin/sh".into(),
- env,
- inputs: BTreeSet::new(),
- name: b"myname".as_slice().try_into().unwrap(),
- outputs,
- platform: system.into(),
- structured_attrs: None,
- }
- }
-
- #[test]
- fn derivation_round_trip_harmonia() {
- let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
- let harmonia_drv = create_harmonia_derivation();
-
- // Convert to nix-bindings Derivation
- let nix_drv = Derivation::from_harmonia(&mut store, &harmonia_drv).unwrap();
-
- // Convert back to harmonia Derivation
- let harmonia_round_trip: harmonia_store_core::derivation::Derivation =
- (&nix_drv).try_into().unwrap();
-
- assert_eq!(harmonia_drv, harmonia_round_trip);
- }
-
- #[test]
- fn derivation_clone() {
- let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
- let harmonia_drv = create_harmonia_derivation();
-
- let derivation = Derivation::from_harmonia(&mut store, &harmonia_drv).unwrap();
- let cloned_derivation = derivation.clone();
-
- let original_harmonia: harmonia_store_core::derivation::Derivation =
- (&derivation).try_into().unwrap();
- let cloned_harmonia: harmonia_store_core::derivation::Derivation =
- (&cloned_derivation).try_into().unwrap();
-
- assert_eq!(original_harmonia, cloned_harmonia);
- }
-}
diff --git a/nix-bindings-store/src/derivation/mod.rs b/nix-bindings-store/src/derivation/mod.rs
deleted file mode 100644
index 557994e..0000000
--- a/nix-bindings-store/src/derivation/mod.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-#![cfg(nix_at_least = "2.33.0pre")]
-
-use nix_bindings_store_sys as raw;
-#[cfg(nix_at_least = "2.33")]
-use nix_bindings_util::{
- check_call,
- context::Context,
- result_string_init,
- string_return::{callback_get_result_string, callback_get_result_string_data},
-};
-use std::ptr::NonNull;
-
-/// A Nix derivation
-///
-/// **Requires Nix 2.33 or later.**
-pub struct Derivation {
- pub(crate) inner: NonNull,
-}
-
-impl Derivation {
- pub(crate) fn new_raw(inner: NonNull) -> Self {
- Derivation { inner }
- }
-
- /// Convert the derivation to JSON (which is encoded to a string).
- ///
- /// **Requires Nix 2.33 or later.**
- ///
- /// The JSON format follows the [Nix derivation JSON schema](https://nix.dev/manual/nix/latest/protocols/json/derivation.html).
- /// Note that this format is experimental as of writing.
- #[cfg(nix_at_least = "2.33")]
- pub fn to_json_string(&self) -> anyhow::Result {
- let mut ctx = Context::new();
-
- unsafe {
- let mut r = result_string_init!();
- check_call!(raw::derivation_to_json(
- &mut ctx,
- self.inner.as_ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r)
- ))?;
- r
- }
- }
-
- /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
- ///
- /// Construct a new `Derivation` by first cloning the C derivation.
- ///
- /// # Safety
- ///
- /// This does not take ownership of the C derivation, so it should be a borrowed pointer, or you should free it.
- pub unsafe fn new_raw_clone(inner: NonNull) -> Self {
- Self::new_raw(
- NonNull::new(raw::derivation_clone(inner.as_ptr()))
- .or_else(|| panic!("nix_derivation_clone returned a null pointer"))
- .unwrap(),
- )
- }
-
- /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
- ///
- /// Get a pointer to the underlying Nix C API derivation.
- ///
- /// # Safety
- ///
- /// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of this `Derivation`.
- pub unsafe fn as_ptr(&self) -> *mut raw::derivation {
- self.inner.as_ptr()
- }
-}
-
-impl Clone for Derivation {
- fn clone(&self) -> Self {
- unsafe { Self::new_raw_clone(self.inner) }
- }
-}
-
-impl Drop for Derivation {
- fn drop(&mut self) {
- unsafe {
- raw::derivation_free(self.inner.as_ptr());
- }
- }
-}
-
-#[cfg(feature = "harmonia")]
-mod harmonia;
-
-#[cfg(test)]
-mod tests {}
diff --git a/nix-bindings-store/src/lib.rs b/nix-bindings-store/src/lib.rs
deleted file mode 100644
index 6010f2e..0000000
--- a/nix-bindings-store/src/lib.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod derivation;
-pub mod path;
-pub mod store;
diff --git a/nix-bindings-store/src/path/harmonia.rs b/nix-bindings-store/src/path/harmonia.rs
deleted file mode 100644
index 660fa1f..0000000
--- a/nix-bindings-store/src/path/harmonia.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use anyhow::{Context as _, Result};
-
-use super::{StorePath, STORE_PATH_HASH_SIZE};
-
-impl TryFrom<&harmonia_store_core::store_path::StorePath> for StorePath {
- type Error = anyhow::Error;
-
- fn try_from(harmonia_path: &harmonia_store_core::store_path::StorePath) -> Result {
- let hash: &[u8; STORE_PATH_HASH_SIZE] = harmonia_path.hash().as_ref();
- StorePath::from_parts(hash, harmonia_path.name().as_ref())
- }
-}
-
-impl TryFrom<&StorePath> for harmonia_store_core::store_path::StorePath {
- type Error = anyhow::Error;
-
- fn try_from(nix_path: &StorePath) -> Result {
- let hash = nix_path
- .hash()
- .context("Failed to get hash from nix StorePath")?;
- let harmonia_hash = harmonia_store_core::store_path::StorePathHash::new(hash);
-
- let name = nix_path
- .name()
- .context("Failed to get name from nix StorePath")?;
-
- let harmonia_name: harmonia_store_core::store_path::StorePathName = name
- .parse()
- .context("Failed to parse name as StorePathName")?;
-
- Ok(harmonia_store_core::store_path::StorePath::from((
- harmonia_hash,
- harmonia_name,
- )))
- }
-}
-
-#[cfg(test)]
-mod tests {
-
- #[test]
- fn store_path_round_trip_harmonia() {
- let harmonia_path: harmonia_store_core::store_path::StorePath =
- "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv".parse().unwrap();
-
- let nix_path: crate::path::StorePath = (&harmonia_path).try_into().unwrap();
-
- let harmonia_round_trip: harmonia_store_core::store_path::StorePath =
- (&nix_path).try_into().unwrap();
-
- assert_eq!(harmonia_path, harmonia_round_trip);
- }
-
- #[test]
- fn store_path_harmonia_clone() {
- let harmonia_path: harmonia_store_core::store_path::StorePath =
- "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv".parse().unwrap();
-
- let nix_path: crate::path::StorePath = (&harmonia_path).try_into().unwrap();
- let cloned_path = nix_path.clone();
-
- assert_eq!(nix_path.name().unwrap(), cloned_path.name().unwrap());
- assert_eq!(nix_path.hash().unwrap(), cloned_path.hash().unwrap());
- }
-}
diff --git a/nix-bindings-store/src/path/mod.rs b/nix-bindings-store/src/path/mod.rs
deleted file mode 100644
index c3478fa..0000000
--- a/nix-bindings-store/src/path/mod.rs
+++ /dev/null
@@ -1,160 +0,0 @@
-use std::ptr::NonNull;
-
-use anyhow::{Context as _, Result};
-use nix_bindings_store_sys as raw;
-#[cfg(nix_at_least = "2.33")]
-use nix_bindings_util::{check_call, context::Context};
-use nix_bindings_util::{
- result_string_init,
- string_return::{callback_get_result_string, callback_get_result_string_data},
-};
-
-/// The size of a store path hash in bytes (20 bytes, decoded from nix32).
-pub const STORE_PATH_HASH_SIZE: usize = 20;
-
-#[cfg(nix_at_least = "2.33")]
-const _: () = assert!(std::mem::size_of::() == STORE_PATH_HASH_SIZE);
-
-pub struct StorePath {
- raw: NonNull,
-}
-
-impl StorePath {
- /// Get the name of the store path.
- ///
- /// For a store path like `/nix/store/abc1234...-foo-1.2`, this function will return `foo-1.2`.
- pub fn name(&self) -> Result {
- unsafe {
- let mut r = result_string_init!();
- raw::store_path_name(
- self.as_ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r),
- );
- r
- }
- }
-
- /// Get the hash part of the store path.
- ///
- /// This returns the decoded hash (not the nix32-encoded string).
- #[cfg(nix_at_least = "2.33")]
- pub fn hash(&self) -> Result<[u8; STORE_PATH_HASH_SIZE]> {
- let mut result = [0u8; STORE_PATH_HASH_SIZE];
- let hash_part: &mut raw::store_path_hash_part = zerocopy::transmute_mut!(&mut result);
-
- let mut ctx = Context::new();
-
- unsafe {
- check_call!(raw::store_path_hash(&mut ctx, self.as_ptr(), hash_part))?;
- }
- Ok(result)
- }
-
- /// Create a StorePath from hash and name components.
- #[cfg(nix_at_least = "2.33")]
- pub fn from_parts(hash: &[u8; STORE_PATH_HASH_SIZE], name: &str) -> Result {
- let hash_part: &raw::store_path_hash_part = zerocopy::transmute_ref!(hash);
-
- let mut ctx = Context::new();
-
- let out_path = unsafe {
- check_call!(raw::store_create_from_parts(
- &mut ctx,
- hash_part,
- name.as_ptr() as *const i8,
- name.len()
- ))?
- };
-
- NonNull::new(out_path)
- .map(|ptr| unsafe { Self::new_raw(ptr) })
- .context("store_create_from_parts returned null")
- }
-
- /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
- ///
- /// Construct a new `StorePath` by first cloning the C store path.
- ///
- /// # Safety
- ///
- /// This does not take ownership of the C store path, so it should be a borrowed pointer, or you should free it.
- pub unsafe fn new_raw_clone(raw: NonNull) -> Self {
- Self::new_raw(
- NonNull::new(raw::store_path_clone(raw.as_ptr()))
- .or_else(|| panic!("nix_store_path_clone returned a null pointer"))
- .unwrap(),
- )
- }
-
- /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
- ///
- /// Takes ownership of a C `nix_store_path`. It will be freed when the `StorePath` is dropped.
- ///
- /// # Safety
- ///
- /// The caller must ensure that the provided `NonNull` is valid and that the ownership
- /// semantics are correctly followed. The `raw` pointer must not be used after being passed to this function.
- pub unsafe fn new_raw(raw: NonNull) -> Self {
- StorePath { raw }
- }
-
- /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
- ///
- /// Get a pointer to the underlying Nix C API store path.
- ///
- /// # Safety
- ///
- /// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of this `StorePath`.
- pub unsafe fn as_ptr(&self) -> *mut raw::StorePath {
- self.raw.as_ptr()
- }
-}
-
-impl Clone for StorePath {
- fn clone(&self) -> Self {
- unsafe { Self::new_raw_clone(self.raw) }
- }
-}
-
-impl Drop for StorePath {
- fn drop(&mut self) {
- unsafe {
- raw::store_path_free(self.as_ptr());
- }
- }
-}
-
-#[cfg(all(feature = "harmonia", nix_at_least = "2.33"))]
-mod harmonia;
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use hex_literal::hex;
-
- #[test]
- #[cfg(nix_at_least = "2.26" /* get_storedir */)]
- fn store_path_name() {
- let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
- let store_dir = store.get_storedir().unwrap();
- let store_path_string =
- format!("{store_dir}/rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26");
- let store_path = store.parse_store_path(store_path_string.as_str()).unwrap();
- assert_eq!(store_path.name().unwrap(), "bash-interactive-5.2p26");
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn store_path_round_trip() {
- let original_hash: [u8; STORE_PATH_HASH_SIZE] =
- hex!("0123456789abcdef0011223344556677deadbeef");
- let original_name = "foo.drv";
-
- let store_path = StorePath::from_parts(&original_hash, original_name).unwrap();
-
- // Round trip gets back what we started with
- assert_eq!(store_path.hash().unwrap(), original_hash);
- assert_eq!(store_path.name().unwrap(), original_name);
- }
-}
diff --git a/nix-bindings-store/src/store.rs b/nix-bindings-store/src/store.rs
deleted file mode 100644
index cdd0977..0000000
--- a/nix-bindings-store/src/store.rs
+++ /dev/null
@@ -1,1020 +0,0 @@
-use anyhow::{bail, Error, Result};
-use nix_bindings_store_sys as raw;
-use nix_bindings_util::context::Context;
-use nix_bindings_util::string_return::{
- callback_get_result_string, callback_get_result_string_data,
-};
-use nix_bindings_util::{check_call, result_string_init};
-use nix_bindings_util_sys as raw_util;
-#[cfg(nix_at_least = "2.33.0pre")]
-use std::collections::BTreeMap;
-use std::collections::HashMap;
-use std::ffi::{c_char, CString};
-use std::ptr::null_mut;
-use std::ptr::NonNull;
-use std::sync::{Arc, LazyLock, Mutex, Weak};
-
-#[cfg(nix_at_least = "2.33.0pre")]
-use crate::derivation::Derivation;
-use crate::path::StorePath;
-
-/* TODO make Nix itself thread safe */
-static INIT: LazyLock> = LazyLock::new(|| unsafe {
- check_call!(raw::libstore_init(&mut Context::new()))?;
- Ok(())
-});
-
-struct StoreRef {
- inner: NonNull,
-}
-impl StoreRef {
- /// # Safety
- ///
- /// The returned pointer is only valid as long as the `StoreRef` is alive.
- pub unsafe fn ptr(&self) -> *mut raw::Store {
- self.inner.as_ptr()
- }
-}
-impl Drop for StoreRef {
- fn drop(&mut self) {
- unsafe {
- raw::store_free(self.inner.as_ptr());
- }
- }
-}
-unsafe impl Send for StoreRef {}
-/// Unlike pointers in general, operations on raw::Store are thread safe and it is therefore safe to share them between threads.
-unsafe impl Sync for StoreRef {}
-
-/// A [Weak] reference to a store.
-pub struct StoreWeak {
- inner: Weak,
-}
-impl StoreWeak {
- /// Upgrade the weak reference to a proper [Store].
- ///
- /// If no normal reference to the [Store] is around anymore elsewhere, this fails by returning `None`.
- pub fn upgrade(&self) -> Option {
- self.inner.upgrade().map(|inner| Store {
- inner,
- context: Context::new(),
- })
- }
-}
-
-/// Protects against https://github.com/NixOS/nix/issues/11979 (unless different parameters are passed, in which case it's up to luck, but you do get your own parameters as you asked for).
-type StoreCacheMap = HashMap<(Option, Vec<(String, String)>), StoreWeak>;
-
-static STORE_CACHE: LazyLock>> =
- LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
-
-#[cfg(nix_at_least = "2.33.0pre")]
-unsafe extern "C" fn callback_get_result_store_path_set(
- _context: *mut raw_util::c_context,
- user_data: *mut std::os::raw::c_void,
- store_path: *const raw::StorePath,
-) {
- let ret = user_data as *mut Vec;
- let ret: &mut Vec = &mut *ret;
-
- let store_path = raw::store_path_clone(store_path);
-
- let store_path =
- NonNull::new(store_path).expect("nix_store_parse_path returned a null pointer");
- let store_path = StorePath::new_raw(store_path);
- ret.push(store_path);
-}
-
-#[cfg(nix_at_least = "2.33.0pre")]
-fn callback_get_result_store_path_set_data(vec: &mut Vec) -> *mut std::os::raw::c_void {
- vec as *mut Vec as *mut std::os::raw::c_void
-}
-
-pub struct Store {
- inner: Arc,
- /* An error context to reuse. This way we don't have to allocate them for each store operation. */
- context: Context,
-}
-impl Store {
- /// Open a store.
- ///
- /// See [`nix_bindings_store_sys::store_open`] for more information.
- #[doc(alias = "nix_store_open")]
- pub fn open<'a, 'b>(
- url: Option<&str>,
- params: impl IntoIterator- ,
- ) -> Result
{
- let params = params
- .into_iter()
- .map(|(k, v)| (k.to_owned(), v.to_owned()))
- .collect::>();
- let params2 = params.clone();
- let mut store_cache = STORE_CACHE
- .lock()
- .map_err(|_| Error::msg("Failed to lock store cache. This should never happen."))?;
- match store_cache.entry((url.map(Into::into), params)) {
- std::collections::hash_map::Entry::Occupied(mut e) => {
- if let Some(store) = e.get().upgrade() {
- Ok(store)
- } else {
- let store = Self::open_uncached(
- url,
- params2.iter().map(|(k, v)| (k.as_str(), v.as_str())),
- )?;
- e.insert(store.weak_ref());
- Ok(store)
- }
- }
- std::collections::hash_map::Entry::Vacant(e) => {
- let store = Self::open_uncached(
- url,
- params2.iter().map(|(k, v)| (k.as_str(), v.as_str())),
- )?;
- e.insert(store.weak_ref());
- Ok(store)
- }
- }
- }
- fn open_uncached<'a, 'b>(
- url: Option<&str>,
- params: impl IntoIterator- ,
- ) -> Result
{
- let x = INIT.as_ref();
- match x {
- Ok(_) => {}
- Err(e) => {
- // Couldn't just clone the error, so we have to print it here.
- bail!("nix_libstore_init error: {}", e);
- }
- }
-
- let mut context: Context = Context::new();
-
- let uri_cstring = match url {
- Some(url) => Some(CString::new(url)?),
- None => None,
- };
- let uri_ptr = uri_cstring
- .as_ref()
- .map(|s| s.as_ptr())
- .unwrap_or(null_mut());
-
- // this intermediate value must be here and must not be moved
- // because it owns the data the `*const c_char` pointers point to.
- let params: Vec<(CString, CString)> = params
- .into_iter()
- .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?))) // to do. context
- .collect::>()?;
- // this intermediate value owns the data the `*mut *const c_char` pointer points to.
- let mut params: Vec<_> = params
- .iter()
- .map(|(k, v)| [k.as_ptr(), v.as_ptr()])
- .collect();
- // this intermediate value owns the data the `*mut *mut *const c_char` pointer points to.
- let mut params: Vec<*mut *const c_char> = params
- .iter_mut()
- .map(|t| t.as_mut_ptr())
- .chain(std::iter::once(null_mut())) // signal the end of the array
- .collect();
-
- let store =
- unsafe { check_call!(raw::store_open(&mut context, uri_ptr, params.as_mut_ptr())) }?;
- if store.is_null() {
- panic!("nix_c_store_open returned a null pointer without an error");
- }
- let store = Store {
- inner: Arc::new(StoreRef {
- inner: NonNull::new(store).unwrap(),
- }),
- context,
- };
- Ok(store)
- }
-
- /// # Safety
- ///
- /// The returned pointer is only valid as long as the `Store` is alive.
- pub unsafe fn raw_ptr(&self) -> *mut raw::Store {
- self.inner.ptr()
- }
-
- #[doc(alias = "nix_store_get_uri")]
- pub fn get_uri(&mut self) -> Result {
- let mut r = result_string_init!();
- unsafe {
- check_call!(raw::store_get_uri(
- &mut self.context,
- self.inner.ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r)
- ))
- }?;
- r
- }
-
- #[cfg(nix_at_least = "2.26")]
- #[doc(alias = "nix_store_get_storedir")]
- pub fn get_storedir(&mut self) -> Result {
- let mut r = result_string_init!();
- unsafe {
- check_call!(raw::store_get_storedir(
- &mut self.context,
- self.inner.ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r)
- ))
- }?;
- r
- }
-
- #[doc(alias = "nix_store_parse_path")]
- pub fn parse_store_path(&mut self, path: &str) -> Result {
- let path = CString::new(path)?;
- unsafe {
- let store_path = check_call!(raw::store_parse_path(
- &mut self.context,
- self.inner.ptr(),
- path.as_ptr()
- ))?;
- let store_path =
- NonNull::new(store_path).expect("nix_store_parse_path returned a null pointer");
- Ok(StorePath::new_raw(store_path))
- }
- }
-
- #[doc(alias = "nix_store_real_path")]
- pub fn real_path(&mut self, path: &StorePath) -> Result {
- let mut r = result_string_init!();
- unsafe {
- check_call!(raw::store_real_path(
- &mut self.context,
- self.inner.ptr(),
- path.as_ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r)
- ))
- }?;
- r
- }
-
- /// Parse a derivation from JSON.
- ///
- /// **Requires Nix 2.33 or later.**
- ///
- /// The JSON format follows the [Nix derivation JSON schema](https://nix.dev/manual/nix/latest/protocols/json/derivation.html).
- /// Note that this format is experimental as of writing.
- /// The derivation is not added to the store; use [`Store::add_derivation`] for that.
- ///
- /// # Parameters
- /// - `json`: A JSON string representing the derivation
- ///
- /// # Returns
- /// A [`Derivation`] object if parsing succeeds, or an error if the JSON is invalid
- /// or malformed.
- #[cfg(nix_at_least = "2.33.0pre")]
- #[doc(alias = "nix_derivation_from_json")]
- pub fn derivation_from_json(&mut self, json: &str) -> Result {
- let json_cstr = CString::new(json)?;
- unsafe {
- let drv = check_call!(raw::derivation_from_json(
- &mut self.context,
- self.inner.ptr(),
- json_cstr.as_ptr()
- ))?;
- let inner = NonNull::new(drv)
- .ok_or_else(|| Error::msg("derivation_from_json returned null"))?;
- Ok(Derivation::new_raw(inner))
- }
- }
-
- /// Add a derivation to the store.
- ///
- /// **Requires Nix 2.33 or later.**
- ///
- /// This computes the store path for the derivation and registers it in the store.
- /// The derivation itself is written to the store as a `.drv` file.
- ///
- /// # Parameters
- /// - `drv`: The derivation to add
- ///
- /// # Returns
- /// The store path of the derivation (ending in `.drv`).
- #[cfg(nix_at_least = "2.33.0pre")]
- #[doc(alias = "nix_add_derivation")]
- pub fn add_derivation(&mut self, drv: &Derivation) -> Result {
- unsafe {
- let path = check_call!(raw::add_derivation(
- &mut self.context,
- self.inner.ptr(),
- drv.inner.as_ptr()
- ))?;
- let path =
- NonNull::new(path).ok_or_else(|| Error::msg("add_derivation returned null"))?;
- Ok(StorePath::new_raw(path))
- }
- }
-
- /// Build a derivation and return its outputs.
- ///
- /// **Requires Nix 2.33 or later.**
- ///
- /// This builds the derivation at the given store path and returns a map of output
- /// names to their realized store paths. The derivation must already exist in the store
- /// (see [`Store::add_derivation`]).
- ///
- /// # Parameters
- /// - `path`: The store path of the derivation to build (typically ending in `.drv`)
- ///
- /// # Returns
- /// A [`BTreeMap`] mapping output names (e.g., "out", "dev", "doc") to their store paths.
- /// The map is ordered alphabetically by output name for deterministic iteration.
- #[cfg(nix_at_least = "2.33.0pre")]
- #[doc(alias = "nix_store_realise")]
- pub fn realise(&mut self, path: &StorePath) -> Result> {
- let mut outputs = BTreeMap::new();
- let userdata =
- &mut outputs as *mut BTreeMap as *mut std::os::raw::c_void;
-
- unsafe extern "C" fn callback(
- userdata: *mut std::os::raw::c_void,
- outname: *const c_char,
- out_path: *const raw::StorePath,
- ) {
- let outputs = userdata as *mut BTreeMap;
- let outputs = &mut *outputs;
-
- let name = std::ffi::CStr::from_ptr(outname)
- .to_string_lossy()
- .into_owned();
-
- let path = raw::store_path_clone(out_path);
- let path = NonNull::new(path).expect("store_path_clone returned null");
- let path = StorePath::new_raw(path);
-
- outputs.insert(name, path);
- }
-
- unsafe {
- check_call!(raw::store_realise(
- &mut self.context,
- self.inner.ptr(),
- path.as_ptr(),
- userdata,
- Some(callback)
- ))?;
- }
-
- Ok(outputs)
- }
-
- /// Get the closure of a specific store path.
- ///
- /// **Requires Nix 2.33 or later.**
- ///
- /// Computes the filesystem closure (dependency graph) of a store path, with options
- /// to control the direction and which related paths to include.
- ///
- /// # Parameters
- /// - `store_path`: The path to compute the closure from
- /// - `flip_direction`: If false, compute the forward closure (paths referenced by this path).
- /// If true, compute the backward closure (paths that reference this path).
- /// - `include_outputs`: When `flip_direction` is false: for any derivation in the closure, include its outputs.
- /// When `flip_direction` is true: for any output in the closure, include derivations that produce it.
- /// - `include_derivers`: When `flip_direction` is false: for any output in the closure, include the derivation that produced it.
- /// When `flip_direction` is true: for any derivation in the closure, include its outputs.
- ///
- /// # Returns
- /// A vector of store paths in the closure, in no particular order.
- #[cfg(nix_at_least = "2.33.0pre")]
- #[doc(alias = "nix_store_get_fs_closure")]
- pub fn get_fs_closure(
- &mut self,
- store_path: &StorePath,
- flip_direction: bool,
- include_outputs: bool,
- include_derivers: bool,
- ) -> Result> {
- let mut r = Vec::new();
- unsafe {
- check_call!(raw::store_get_fs_closure(
- &mut self.context,
- self.inner.ptr(),
- store_path.as_ptr(),
- flip_direction,
- include_outputs,
- include_derivers,
- callback_get_result_store_path_set_data(&mut r),
- Some(callback_get_result_store_path_set)
- ))
- }?;
- Ok(r)
- }
-
- pub fn weak_ref(&self) -> StoreWeak {
- StoreWeak {
- inner: Arc::downgrade(&self.inner),
- }
- }
-}
-
-impl Clone for Store {
- fn clone(&self) -> Self {
- Store {
- inner: self.inner.clone(),
- context: Context::new(),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use ctor::ctor;
- use std::collections::HashMap;
-
- use super::*;
-
- #[ctor]
- fn test_setup() {
- // Initialize settings for tests
- let _ = INIT.as_ref();
-
- // Enable ca-derivations for all tests
- nix_bindings_util::settings::set("experimental-features", "ca-derivations").ok();
-
- // Disable build hooks to prevent test recursion
- nix_bindings_util::settings::set("build-hook", "").ok();
-
- // Set custom build dir for sandbox
- if cfg!(target_os = "linux") {
- nix_bindings_util::settings::set("sandbox-build-dir", "/custom-build-dir-for-test")
- .ok();
- }
-
- std::env::set_var("_NIX_TEST_NO_SANDBOX", "1");
-
- // Tests run offline
- nix_bindings_util::settings::set("substituters", "").ok();
- }
-
- #[test]
- fn none_works() {
- let res = Store::open(None, HashMap::new());
- res.unwrap();
- }
-
- #[test]
- fn auto_works() {
- // This is not actually a given.
- // Maybe whatever is in NIX_REMOTE or nix.conf is really important.
- let res = Store::open(Some("auto"), HashMap::new());
- res.unwrap();
- }
-
- #[test]
- fn invalid_uri_fails() {
- let res = Store::open(Some("invalid://uri"), HashMap::new());
- assert!(res.is_err());
- }
-
- #[test]
- fn get_uri() {
- let mut store = Store::open(None, HashMap::new()).unwrap();
- let uri = store.get_uri().unwrap();
- assert!(!uri.is_empty());
- // must be ascii
- assert!(uri.is_ascii());
- // usually something like "daemon", but that's not something we can check here.
- println!("uri: {}", uri);
- }
-
- #[test]
- #[ignore] // Needs network access
- fn get_uri_nixos_cache() {
- let mut store = Store::open(Some("https://cache.nixos.org/"), HashMap::new()).unwrap();
- let uri = store.get_uri().unwrap();
- assert_eq!(uri, "https://cache.nixos.org");
- }
-
- #[test]
- #[cfg(nix_at_least = "2.26" /* get_storedir */)]
- fn parse_store_path_ok() {
- let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
- let store_dir = store.get_storedir().unwrap();
- let store_path_string =
- format!("{store_dir}/rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26");
- let store_path = store.parse_store_path(store_path_string.as_str()).unwrap();
- let real_store_path = store.real_path(&store_path).unwrap();
- assert_eq!(store_path.name().unwrap(), "bash-interactive-5.2p26");
- assert_eq!(real_store_path, store_path_string);
- }
-
- #[test]
- fn parse_store_path_fail() {
- let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
- let store_path_string = "bash-interactive-5.2p26".to_string();
- let r = store.parse_store_path(store_path_string.as_str());
- match r {
- Err(e) => {
- assert!(e.to_string().contains("bash-interactive-5.2p26"));
- }
- _ => panic!("Expected error"),
- }
- }
-
- #[test]
- fn weak_ref() {
- let mut store = Store::open(None, HashMap::new()).unwrap();
- let uri = store.get_uri().unwrap();
- let weak = store.weak_ref();
- let mut store2 = weak.upgrade().unwrap();
- assert_eq!(store2.get_uri().unwrap(), uri);
- }
- #[test]
- fn weak_ref_gone() {
- let weak = {
- // Concurrent tests calling Store::open will keep the weak reference to auto alive,
- // so for this test we need to bypass the global cache.
- let store = Store::open_uncached(None, HashMap::new()).unwrap();
- store.weak_ref()
- };
- assert!(weak.upgrade().is_none());
- assert!(weak.inner.upgrade().is_none());
- }
-
- #[cfg(nix_at_least = "2.33.0pre")]
- fn create_temp_store() -> (Store, tempfile::TempDir) {
- let temp_dir = tempfile::tempdir().unwrap();
-
- let store_dir = temp_dir.path().join("store");
- let state_dir = temp_dir.path().join("state");
- let log_dir = temp_dir.path().join("log");
-
- let store_dir_str = store_dir.to_str().unwrap();
- let state_dir_str = state_dir.to_str().unwrap();
- let log_dir_str = log_dir.to_str().unwrap();
-
- let params = vec![
- ("store", store_dir_str),
- ("state", state_dir_str),
- ("log", log_dir_str),
- ];
-
- let store = Store::open(Some("local"), params).unwrap();
- (store, temp_dir)
- }
-
- fn current_system() -> Result {
- nix_bindings_util::settings::get("system")
- }
-
- #[cfg(nix_at_least = "2.33")]
- fn create_test_derivation_json() -> serde_json::Value {
- let system = current_system().unwrap_or_else(|_| {
- // Fallback to Rust's platform detection
- format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
- });
- serde_json::json!({
- "args": ["-c", "echo $name foo > $out"],
- "builder": "/bin/sh",
- "env": {
- "builder": "/bin/sh",
- "name": "myname",
- "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
- "system": system
- },
- "inputs": {
- "drvs": {},
- "srcs": []
- },
- "name": "myname",
- "outputs": {
- "out": {
- "hashAlgo": "sha256",
- "method": "nar"
- }
- },
- "system": system,
- "version": 4
- })
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn derivation_from_json() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- // If we got here, parsing succeeded
- drop(drv);
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33.0pre")]
- fn derivation_from_invalid_json() {
- let (mut store, temp_dir) = create_temp_store();
- let result = store.derivation_from_json("not valid json");
- assert!(result.is_err());
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn derivation_to_json_round_trip() {
- let (mut store, _temp_dir) = create_temp_store();
- let original_value = create_test_derivation_json();
-
- // Parse JSON to Derivation
- let drv = store
- .derivation_from_json(&original_value.to_string())
- .unwrap();
-
- // Convert back to JSON
- let round_trip_json = drv.to_json_string().unwrap();
- let round_trip_value: serde_json::Value = serde_json::from_str(&round_trip_json).unwrap();
-
- // Verify the round-trip JSON matches the original
- assert_eq!(
- original_value, round_trip_value,
- "Round-trip JSON should match original.\nOriginal: {}\nRound-trip: {}",
- original_value, round_trip_value
- );
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn add_derivation() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Verify we got a .drv path
- let name = drv_path.name().unwrap();
- assert!(name.ends_with(".drv"));
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn realise() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Build the derivation
- let outputs = store.realise(&drv_path).unwrap();
-
- // Verify we got the expected output
- assert!(outputs.contains_key("out"));
- let out_path = &outputs["out"];
- let out_name = out_path.name().unwrap();
- assert_eq!(out_name, "myname");
-
- drop(store);
- drop(temp_dir);
- }
-
- #[cfg(nix_at_least = "2.33")]
- fn create_multi_output_derivation_json() -> serde_json::Value {
- let system = current_system()
- .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
-
- serde_json::json!({
- "version": 4,
- "name": "multi-output-test",
- "system": system,
- "builder": "/bin/sh",
- "args": ["-c", "echo a > $outa; echo b > $outb; echo c > $outc; echo d > $outd; echo e > $oute; echo f > $outf; echo g > $outg; echo h > $outh; echo i > $outi; echo j > $outj"],
- "env": {
- "builder": "/bin/sh",
- "name": "multi-output-test",
- "system": system,
- "outf": "/1vkfzqpwk313b51x0xjyh5s7w1lx141mr8da3dr9wqz5aqjyr2fh",
- "outd": "/1ypxifgmbzp5sd0pzsp2f19aq68x5215260z3lcrmy5fch567lpm",
- "outi": "/1wmasjnqi12j1mkjbxazdd0qd0ky6dh1qry12fk8qyp5kdamhbdx",
- "oute": "/1f9r2k1s168js509qlw8a9di1qd14g5lqdj5fcz8z7wbqg11qp1f",
- "outh": "/1rkx1hmszslk5nq9g04iyvh1h7bg8p92zw0hi4155hkjm8bpdn95",
- "outc": "/1rj4nsf9pjjqq9jsq58a2qkwa7wgvgr09kgmk7mdyli6h1plas4w",
- "outb": "/1p7i1dxifh86xq97m5kgb44d7566gj7rfjbw7fk9iij6ca4akx61",
- "outg": "/14f8qi0r804vd6a6v40ckylkk1i6yl6fm243qp6asywy0km535lc",
- "outj": "/0gkw1366qklqfqb2lw1pikgdqh3cmi3nw6f1z04an44ia863nxaz",
- "outa": "/039akv9zfpihrkrv4pl54f3x231x362bll9afblsgfqgvx96h198"
- },
- "inputs": {
- "drvs": {},
- "srcs": []
- },
- "outputs": {
- "outd": { "hashAlgo": "sha256", "method": "nar" },
- "outf": { "hashAlgo": "sha256", "method": "nar" },
- "outg": { "hashAlgo": "sha256", "method": "nar" },
- "outb": { "hashAlgo": "sha256", "method": "nar" },
- "outc": { "hashAlgo": "sha256", "method": "nar" },
- "outi": { "hashAlgo": "sha256", "method": "nar" },
- "outj": { "hashAlgo": "sha256", "method": "nar" },
- "outh": { "hashAlgo": "sha256", "method": "nar" },
- "outa": { "hashAlgo": "sha256", "method": "nar" },
- "oute": { "hashAlgo": "sha256", "method": "nar" }
- }
- })
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn realise_multi_output_ordering() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_multi_output_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Build the derivation
- let outputs = store.realise(&drv_path).unwrap();
-
- // Verify outputs are complete (BTreeMap guarantees ordering)
- let output_names: Vec<&String> = outputs.keys().collect();
- let expected_order = vec![
- "outa", "outb", "outc", "outd", "oute", "outf", "outg", "outh", "outi", "outj",
- ];
- assert_eq!(output_names, expected_order);
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn realise_invalid_system() {
- let (mut store, temp_dir) = create_temp_store();
-
- // Create a derivation with an invalid system
- let system = "bogus65-bogusos";
- let drv_json = serde_json::json!({
- "args": ["-c", "echo $name foo > $out"],
- "builder": "/bin/sh",
- "env": {
- "builder": "/bin/sh",
- "name": "myname",
- "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
- "system": system
- },
- "inputs": {
- "drvs": {},
- "srcs": []
- },
- "name": "myname",
- "outputs": {
- "out": {
- "hashAlgo": "sha256",
- "method": "nar"
- }
- },
- "system": system,
- "version": 4
- });
-
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Try to build - should fail
- let result = store.realise(&drv_path);
- let err = match result {
- Ok(_) => panic!("Build should fail with invalid system"),
- Err(e) => e.to_string(),
- };
- assert!(
- err.contains("required system or feature not available")
- || err.contains("platform mismatch"),
- "Error should mention system not available, got: {}",
- err
- );
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn realise_builder_fails() {
- let (mut store, temp_dir) = create_temp_store();
-
- let system = current_system()
- .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
-
- // Create a derivation where the builder exits with error
- let drv_json = serde_json::json!({
- "args": ["-c", "exit 1"],
- "builder": "/bin/sh",
- "env": {
- "builder": "/bin/sh",
- "name": "failing",
- "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
- "system": system
- },
- "inputs": {
- "drvs": {},
- "srcs": []
- },
- "name": "failing",
- "outputs": {
- "out": {
- "hashAlgo": "sha256",
- "method": "nar"
- }
- },
- "system": system,
- "version": 4
- });
-
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Try to build - should fail
- let result = store.realise(&drv_path);
- let err = match result {
- Ok(_) => panic!("Build should fail when builder exits with error"),
- Err(e) => e.to_string(),
- };
- assert!(
- err.contains("builder failed with exit code 1"),
- "Error should mention builder failed with exit code, got: {}",
- err
- );
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn realise_builder_no_output() {
- let (mut store, temp_dir) = create_temp_store();
-
- let system = current_system()
- .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
-
- // Create a derivation where the builder succeeds but produces no output
- let drv_json = serde_json::json!({
- "args": ["-c", "true"],
- "builder": "/bin/sh",
- "env": {
- "builder": "/bin/sh",
- "name": "no-output",
- "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
- "system": system
- },
- "inputs": {
- "drvs": {},
- "srcs": []
- },
- "name": "no-output",
- "outputs": {
- "out": {
- "hashAlgo": "sha256",
- "method": "nar"
- }
- },
- "system": system,
- "version": 4
- });
-
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Try to build - should fail
- let result = store.realise(&drv_path);
- let err = match result {
- Ok(_) => panic!("Build should fail when builder produces no output"),
- Err(e) => e.to_string(),
- };
- assert!(
- err.contains("failed to produce output path"),
- "Error should mention failed to produce output, got: {}",
- err
- );
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn get_fs_closure_with_outputs() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Build the derivation to get the output path
- let outputs = store.realise(&drv_path).unwrap();
- let out_path = &outputs["out"];
- let out_path_name = out_path.name().unwrap();
-
- // Get closure with include_outputs=true
- let closure = store.get_fs_closure(&drv_path, false, true, false).unwrap();
-
- // The closure should contain at least the derivation and its output
- assert!(
- closure.len() >= 2,
- "Closure should contain at least drv and output"
- );
-
- // Verify the output path is in the closure
- let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
- assert!(
- out_in_closure,
- "Output path should be in closure when include_outputs=true"
- );
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn get_fs_closure_without_outputs() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Build the derivation to get the output path
- let outputs = store.realise(&drv_path).unwrap();
- let out_path = &outputs["out"];
- let out_path_name = out_path.name().unwrap();
-
- // Get closure with include_outputs=false
- let closure = store
- .get_fs_closure(&drv_path, false, false, false)
- .unwrap();
-
- // Verify the output path is NOT in the closure
- let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
- assert!(
- !out_in_closure,
- "Output path should not be in closure when include_outputs=false"
- );
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn get_fs_closure_flip_direction() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
-
- // Build the derivation to get the output path
- let outputs = store.realise(&drv_path).unwrap();
- let out_path = &outputs["out"];
- let out_path_name = out_path.name().unwrap();
-
- // Get closure with flip_direction=true (reverse dependencies)
- let closure = store.get_fs_closure(&drv_path, true, true, false).unwrap();
-
- // Verify the output path is NOT in the closure when direction is flipped
- let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
- assert!(
- !out_in_closure,
- "Output path should not be in closure when flip_direction=true"
- );
-
- drop(store);
- drop(temp_dir);
- }
-
- #[test]
- #[cfg(nix_at_least = "2.33")]
- fn get_fs_closure_include_derivers() {
- let (mut store, temp_dir) = create_temp_store();
- let drv_json = create_test_derivation_json();
- let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
- let drv_path = store.add_derivation(&drv).unwrap();
- let drv_path_name = drv_path.name().unwrap();
-
- // Build the derivation to get the output path
- let outputs = store.realise(&drv_path).unwrap();
- let out_path = &outputs["out"];
-
- // Get closure of the output path with include_derivers=true
- let closure = store.get_fs_closure(out_path, false, false, true).unwrap();
-
- // Verify the derivation path is in the closure
- let drv_in_closure = closure.iter().any(|p| p.name().unwrap() == drv_path_name);
- assert!(
- drv_in_closure,
- "Derivation should be in closure when include_derivers=true"
- );
-
- drop(store);
- drop(temp_dir);
- }
-}
diff --git a/nix-bindings-util-sys/Cargo.toml b/nix-bindings-util-sys/Cargo.toml
deleted file mode 100644
index 2441074..0000000
--- a/nix-bindings-util-sys/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[package]
-name = "nix-bindings-util-sys"
-version = "0.2.1"
-edition = "2021"
-build = "build.rs"
-license = "LGPL-2.1"
-description = "Low-level FFI bindings to Nix utility library"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_util_sys/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-
-[build-dependencies]
-bindgen = "0.69"
-pkg-config = "0.3"
diff --git a/nix-bindings-util-sys/README.md b/nix-bindings-util-sys/README.md
deleted file mode 100644
index b504cb8..0000000
--- a/nix-bindings-util-sys/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# nix-bindings-util-sys
-
-This crate contains generated bindings for the Nix C API (`nix-util-c`).
-**You should not have to use this crate directly,** and so you should probably not add it to your dependencies.
-Instead, use the `nix-bindings-util` crate, which _should_ be sufficient.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_util_sys/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-util-sys/build.rs b/nix-bindings-util-sys/build.rs
deleted file mode 100644
index 2851ee2..0000000
--- a/nix-bindings-util-sys/build.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use std::env;
-use std::path::PathBuf;
-
-#[derive(Debug)]
-struct StripNixPrefix {}
-impl bindgen::callbacks::ParseCallbacks for StripNixPrefix {
- fn item_name(&self, name: &str) -> Option {
- name.strip_prefix("nix_").map(String::from)
- }
-}
-
-fn main() {
- // Tell cargo to invalidate the built crate whenever the wrapper changes
- println!("cargo:rerun-if-changed=include/nix-c-util.h");
- println!("cargo:rustc-link-lib=nixutil");
-
- // https://rust-lang.github.io/rust-bindgen/library-usage.html
- let bindings = bindgen::Builder::default()
- .header("include/nix-c-util.h")
- // Find the includes
- .clang_args(c_headers())
- // Tell cargo to invalidate the built crate whenever any of the
- // included header files changed.
- .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
- .parse_callbacks(Box::new(StripNixPrefix {}))
- // Finish the builder and generate the bindings.
- .generate()
- // Unwrap the Result and panic on failure.
- .expect("Unable to generate bindings");
-
- // Write the bindings to the $OUT_DIR/bindings.rs file.
- let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
- bindings
- .write_to_file(out_path.join("bindings.rs"))
- .expect("Couldn't write bindings!");
-}
-
-fn c_headers() -> Vec {
- let mut args = Vec::new();
- // args.push("-isystem".to_string());
- for path in pkg_config::probe_library("nix-util-c")
- .unwrap()
- .include_paths
- .iter()
- {
- args.push(format!("-I{}", path.to_str().unwrap()));
- }
-
- // write to stderr for debugging
- eprintln!("c_headers: {:?}", args);
- args
-}
diff --git a/nix-bindings-util-sys/include/nix-c-util.h b/nix-bindings-util-sys/include/nix-c-util.h
deleted file mode 100644
index 7fd0bc6..0000000
--- a/nix-bindings-util-sys/include/nix-c-util.h
+++ /dev/null
@@ -1 +0,0 @@
-#include
diff --git a/nix-bindings-util-sys/src/lib.rs b/nix-bindings-util-sys/src/lib.rs
deleted file mode 100644
index b7c06dc..0000000
--- a/nix-bindings-util-sys/src/lib.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-//! Raw bindings to Nix C API
-//!
-//! This crate contains automatically generated bindings from the Nix C headers.
-//! The bindings are generated by bindgen and include C-style naming conventions
-//! and documentation comments that don't always conform to Rust standards.
-//!
-//! Normally you don't have to use this crate directly.
-//! Instead use `nix-util`.
-
-// This file must only contain generated code, so that the module-level
-// #![allow(...)] attributes don't suppress warnings in hand-written code.
-// If you need to add hand-written code, use a submodule to isolate the
-// generated code. See:
-// https://github.com/nixops4/nixops4/pull/138/commits/330c3881be3d3cf3e59adebbe0ab1c0f15f6d2c9
-
-// Standard bindgen suppressions for C naming conventions
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// Clippy suppressions for generated C bindings
-// bindgen doesn't generate safety docs
-#![allow(clippy::missing_safety_doc)]
-// Rustdoc suppressions for generated C documentation
-// The C headers contain Doxygen-style documentation that doesn't translate
-// well to Rust's rustdoc format, causing various warnings:
-#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
-#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
-#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like
-#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
-
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/nix-bindings-util/Cargo.toml b/nix-bindings-util/Cargo.toml
deleted file mode 100644
index 78e8353..0000000
--- a/nix-bindings-util/Cargo.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-[package]
-name = "nix-bindings-util"
-version = "0.2.1"
-edition = "2021"
-license = "LGPL-2.1"
-description = "Rust bindings to Nix utility library"
-repository = "https://github.com/nixops4/nix-bindings-rust"
-documentation = "https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_util/"
-readme = "README.md"
-
-[lib]
-path = "src/lib.rs"
-
-[dependencies]
-anyhow = "1.0"
-nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
-
-[dev-dependencies]
-ctor = "0.2"
-
-[lints.rust]
-warnings = "deny"
-dead-code = "allow"
-
-[lints.clippy]
-type-complexity = "allow"
-# We're still trying to make Nix more thread-safe, want forward-compat
-arc-with-non-send-sync = "allow"
diff --git a/nix-bindings-util/README.md b/nix-bindings-util/README.md
deleted file mode 100644
index 0a29f29..0000000
--- a/nix-bindings-util/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# nix-bindings-util
-
-Rust bindings to the Nix utility library.
-
-[API Documentation](https://nixops4.github.io/nix-bindings-rust/development/nix_bindings_util/)
-
-## Changelog
-
-See the [nix-bindings-rust changelog](https://github.com/nixops4/nix-bindings-rust/blob/main/CHANGELOG.md).
diff --git a/nix-bindings-util/src/context.rs b/nix-bindings-util/src/context.rs
deleted file mode 100644
index dbb333e..0000000
--- a/nix-bindings-util/src/context.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-use anyhow::{bail, Result};
-use nix_bindings_util_sys as raw;
-use std::ptr::null_mut;
-use std::ptr::NonNull;
-
-/// A context for error handling, when interacting directly with the generated bindings for the C API in [nix_bindings_util_sys].
-///
-/// The `nix-store` and `nix-expr` libraries that consume this type internally store a private context in their `EvalState` and `Store` structs to avoid allocating a new context for each operation. The state of a context is irrelevant when used correctly (e.g. with [check_call!]), so it's safe to reuse, and safe to allocate more contexts in methods such as [Clone::clone].
-pub struct Context {
- inner: NonNull,
-}
-
-impl Default for Context {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl Context {
- pub fn new() -> Self {
- let ctx = unsafe { raw::c_context_create() };
- if ctx.is_null() {
- // We've failed to allocate a (relatively small) Context struct.
- // We're almost certainly going to crash anyways.
- panic!("nix_c_context_create returned a null pointer");
- }
- Context {
- inner: NonNull::new(ctx).unwrap(),
- }
- }
-
- /// Access the C context pointer.
- ///
- /// We recommend to use `check_call!` if possible.
- pub fn ptr(&mut self) -> *mut raw::c_context {
- self.inner.as_ptr()
- }
-
- /// Check the error code and return an error if it's not `NIX_OK`.
- ///
- /// We recommend to use `check_call!` if possible.
- pub fn check_err(&self) -> Result<()> {
- let err = unsafe { raw::err_code(self.inner.as_ptr()) };
- if err != raw::err_NIX_OK {
- // msgp is a borrowed pointer (pointing into the context), so we don't need to free it
- let msgp = unsafe { raw::err_msg(null_mut(), self.inner.as_ptr(), null_mut()) };
- // Turn the i8 pointer into a Rust string by copying
- let msg: &str = unsafe { core::ffi::CStr::from_ptr(msgp).to_str()? };
- bail!("{}", msg);
- }
- Ok(())
- }
-
- pub fn clear(&mut self) {
- unsafe {
- raw::set_err_msg(self.inner.as_ptr(), raw::err_NIX_OK, c"".as_ptr());
- }
- }
-
- pub fn check_err_and_clear(&mut self) -> Result<()> {
- let r = self.check_err();
- if r.is_err() {
- self.clear();
- }
- r
- }
-
- pub fn check_one_call_or_key_none T>(
- &mut self,
- f: F,
- ) -> Result> {
- let t = f(self.ptr());
- if unsafe { raw::err_code(self.inner.as_ptr()) == raw::err_NIX_ERR_KEY } {
- self.clear();
- return Ok(None);
- }
- self.check_err_and_clear()?;
- Ok(Some(t))
- }
-}
-
-impl Drop for Context {
- fn drop(&mut self) {
- unsafe {
- raw::c_context_free(self.inner.as_ptr());
- }
- }
-}
-
-#[macro_export]
-macro_rules! check_call {
- ($($f:ident)::+($ctx:expr $(, $arg:expr)*)) => {
- {
- let ctx : &mut $crate::context::Context = $ctx;
- let ret = $($f)::*(ctx.ptr() $(, $arg)*);
- match ctx.check_err() {
- Ok(_) => Ok(ret),
- Err(e) => {
- ctx.clear();
- Err(e)
- }
- }
- }
- }
-}
-
-pub use check_call;
-
-// TODO: Generalize this macro to work with any error code or any error handling logic
-#[macro_export]
-macro_rules! check_call_opt_key {
- ($($f:ident)::+($ctx:expr, $($arg:expr),*)) => {
- {
- let ctx : &mut $crate::context::Context = $ctx;
- let ret = $($f)::*(ctx.ptr(), $($arg,)*);
- if unsafe { $crate::raw_sys::err_code(ctx.ptr()) == $crate::raw_sys::err_NIX_ERR_KEY } {
- ctx.clear();
- return Ok(None);
- }
- match ctx.check_err() {
- Ok(_) => Ok(Some(ret)),
- Err(e) => {
- ctx.clear();
- Err(e)
- }
- }
- }
- }
-}
-
-pub use check_call_opt_key;
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn context_new_and_drop() {
- // don't crash
- let _c = Context::new();
- }
-
- fn set_dummy_err(ctx_ptr: *mut raw::c_context) {
- unsafe {
- raw::set_err_msg(
- ctx_ptr,
- raw::err_NIX_ERR_UNKNOWN,
- c"dummy error message".as_ptr(),
- );
- }
- }
-
- #[test]
- fn check_call_dynamic_context() {
- let r = check_call!(set_dummy_err(&mut Context::new()));
- assert!(r.is_err());
- assert_eq!(r.unwrap_err().to_string(), "dummy error message");
- }
-}
diff --git a/nix-bindings-util/src/lib.rs b/nix-bindings-util/src/lib.rs
deleted file mode 100644
index f626d96..0000000
--- a/nix-bindings-util/src/lib.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-pub mod context;
-pub mod settings;
-#[macro_use]
-pub mod string_return;
-pub mod nix_version;
-
-// Re-export for use in macros
-pub use nix_bindings_util_sys as raw_sys;
diff --git a/nix-bindings-util/src/nix_version.rs b/nix-bindings-util/src/nix_version.rs
deleted file mode 100644
index b6da8ce..0000000
--- a/nix-bindings-util/src/nix_version.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-//! Nix version parsing and conditional compilation support.
-
-/// Emit [`cargo:rustc-cfg`] directives for Nix version-based conditional compilation.
-///
-/// Call from build.rs with the Nix version and desired version gates.
-///
-/// [`cargo:rustc-cfg`]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-cfg
-///
-/// # Example
-///
-/// ```
-/// use nix_bindings_util::nix_version::emit_version_cfg;
-/// # // Stub pkg_config so that we can render a full usage example
-/// # mod pkg_config { pub fn probe_library(_: &str) -> Result { Ok(Library { version: "2.33.0pre".into() }) }
-/// # pub struct Library { pub version: String } }
-///
-/// let nix_version = pkg_config::probe_library("nix-store-c").unwrap().version;
-/// emit_version_cfg(&nix_version, &["2.26", "2.33.0pre", "2.33"]);
-/// ```
-///
-/// Emits `nix_at_least="2.26"` and `nix_at_least="2.33.0pre"` for version 2.33.0pre,
-/// usable as `#[cfg(nix_at_least = "2.26")]`.
-pub fn emit_version_cfg(nix_version: &str, relevant_versions: &[&str]) {
- // Declare the known versions for cargo check-cfg
- let versions = relevant_versions
- .iter()
- .map(|v| format!("\"{}\"", v))
- .collect::>()
- .join(",");
-
- println!(
- "cargo:rustc-check-cfg=cfg(nix_at_least,values({}))",
- versions
- );
-
- let nix_version = parse_version(nix_version);
-
- for version_str in relevant_versions {
- let version = parse_version(version_str);
- if nix_version >= version {
- println!("cargo:rustc-cfg=nix_at_least=\"{}\"", version_str);
- }
- }
-}
-
-/// Parse a Nix version string into a comparable tuple `(major, minor, patch)`.
-///
-/// Pre-release versions (containing `"pre"`) get patch = -1, sorting before stable releases.
-/// Omitted patch defaults to 0.
-///
-/// # Examples
-///
-/// ```
-/// use nix_bindings_util::nix_version::parse_version;
-///
-/// assert_eq!(parse_version("2.26"), (2, 26, 0));
-/// assert_eq!(parse_version("2.33.0pre"), (2, 33, -1));
-/// assert_eq!(parse_version("2.33"), (2, 33, 0));
-/// assert_eq!(parse_version("2.33.1"), (2, 33, 1));
-///
-/// // Pre-release versions sort before stable
-/// assert!(parse_version("2.33.0pre") < parse_version("2.33"));
-/// ```
-pub fn parse_version(version_str: &str) -> (u32, u32, i32) {
- let parts = version_str.split('.').collect::>();
- let major = parts[0].parse::().unwrap();
- let minor = parts[1].parse::().unwrap();
- let patch = if parts.get(2).is_some_and(|s| s.contains("pre")) {
- -1i32
- } else {
- parts
- .get(2)
- .and_then(|s| s.parse::().ok())
- .unwrap_or(0)
- };
- (major, minor, patch)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_version() {
- assert_eq!(parse_version("2.26"), (2, 26, 0));
- assert_eq!(parse_version("2.33.0pre"), (2, 33, -1));
- assert_eq!(parse_version("2.33"), (2, 33, 0));
- assert_eq!(parse_version("2.33.1"), (2, 33, 1));
- }
-
- #[test]
- fn test_version_ordering() {
- // Pre-release versions should sort before stable
- assert!(parse_version("2.33.0pre") < parse_version("2.33"));
- assert!(parse_version("2.33.0pre") < parse_version("2.33.0"));
-
- // Normal version ordering
- assert!(parse_version("2.26") < parse_version("2.33"));
- assert!(parse_version("2.33") < parse_version("2.33.1"));
- }
-}
diff --git a/nix-bindings-util/src/settings.rs b/nix-bindings-util/src/settings.rs
deleted file mode 100644
index 8c213aa..0000000
--- a/nix-bindings-util/src/settings.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use anyhow::Result;
-use nix_bindings_util_sys as raw;
-use std::sync::Mutex;
-
-use crate::{
- check_call, context, result_string_init,
- string_return::{callback_get_result_string, callback_get_result_string_data},
-};
-
-// Global mutex to protect concurrent access to Nix settings
-// See the documentation on `set()` for important thread safety information.
-static SETTINGS_MUTEX: Mutex<()> = Mutex::new(());
-
-/// Set a Nix setting.
-///
-/// # Thread Safety
-///
-/// This function uses a mutex to serialize access through the Rust API.
-/// However, the underlying Nix settings system uses global mutable state
-/// without internal synchronization.
-///
-/// The mutex provides protection between Rust callers but cannot prevent:
-/// - C++ Nix code from modifying settings concurrently
-/// - Other Nix operations from reading settings during modification
-///
-/// For multi-threaded applications, ensure that no other Nix operations
-/// are running while changing settings. Settings are best modified during
-/// single-threaded initialization.
-pub fn set(key: &str, value: &str) -> Result<()> {
- // Lock the mutex to ensure thread-safe access to global settings
- let guard = SETTINGS_MUTEX.lock().unwrap();
-
- let mut ctx = context::Context::new();
- let key = std::ffi::CString::new(key)?;
- let value = std::ffi::CString::new(value)?;
- unsafe {
- check_call!(raw::setting_set(&mut ctx, key.as_ptr(), value.as_ptr()))?;
- }
- drop(guard);
- Ok(())
-}
-
-/// Get a Nix setting.
-///
-/// # Thread Safety
-///
-/// See the documentation on [`set()`] for important thread safety information.
-pub fn get(key: &str) -> Result {
- // Lock the mutex to ensure thread-safe access to global settings
- let guard = SETTINGS_MUTEX.lock().unwrap();
-
- let mut ctx = context::Context::new();
- let key = std::ffi::CString::new(key)?;
- let mut r: Result = result_string_init!();
- unsafe {
- check_call!(raw::setting_get(
- &mut ctx,
- key.as_ptr(),
- Some(callback_get_result_string),
- callback_get_result_string_data(&mut r)
- ))?;
- }
- drop(guard);
- r
-}
-
-#[cfg(test)]
-mod tests {
- use crate::check_call;
-
- use super::*;
-
- #[ctor::ctor]
- fn setup() {
- let mut ctx = context::Context::new();
- unsafe {
- check_call!(nix_bindings_util_sys::libutil_init(&mut ctx)).unwrap();
- }
- }
-
- #[test]
- fn set_get() {
- // Something that shouldn't matter if it's a different value temporarily
- let key = "json-log-path";
-
- // Save the old value, in case it's important. Probably not.
- // If this doesn't work, pick a different setting to test with
- let old_value = get(key).unwrap();
-
- let new_value = "/just/a/path/that/we/are/storing/into/some/option/for/testing/purposes";
-
- let res_e = (|| {
- set(key, new_value)?;
- get(key)
- })();
-
- // Restore immediately; try not to affect other tests (if relevant).
- set(key, old_value.as_str()).unwrap();
-
- let res = res_e.unwrap();
-
- assert_eq!(res, new_value);
- }
-}
diff --git a/nix-bindings-util/src/string_return.rs b/nix-bindings-util/src/string_return.rs
deleted file mode 100644
index 205cebe..0000000
--- a/nix-bindings-util/src/string_return.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-use anyhow::Result;
-
-/// Callback for nix_store_get_uri and other functions that return a string.
-///
-/// This function is used by the other nix_* crates, and you should never need to call it yourself.
-///
-/// Some functions in the nix library "return" strings without giving you ownership over them, by letting you pass a callback function that gets to look at that string. This callback simply turns that string pointer into an owned rust String.
-///
-/// # Safety
-///
-/// _Manual memory management_
-///
-/// Only for passing to the nix C API. Do not call this function directly.
-pub unsafe extern "C" fn callback_get_result_string(
- start: *const ::std::os::raw::c_char,
- n: std::os::raw::c_uint,
- user_data: *mut std::os::raw::c_void,
-) {
- let ret = user_data as *mut Result;
-
- if start.is_null() {
- if n != 0 {
- panic!("callback_get_result_string: start is null but n is not zero");
- }
- *ret = Ok(String::new());
- return;
- }
-
- let slice = std::slice::from_raw_parts(start as *const u8, n as usize);
-
- if (*ret).is_ok() {
- panic!(
- "callback_get_result_string: Result must be initialized to Err. Did Nix call us twice?"
- );
- }
-
- *ret = String::from_utf8(slice.to_vec())
- .map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e));
-}
-
-pub fn callback_get_result_string_data(vec: &mut Result) -> *mut std::os::raw::c_void {
- vec as *mut Result as *mut std::os::raw::c_void
-}
-
-#[macro_export]
-macro_rules! result_string_init {
- () => {
- Err(anyhow::anyhow!("String was not set by Nix C API"))
- };
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use nix_bindings_util_sys as raw;
-
- /// Typecheck the function signature against the generated bindings in nix_bindings_util_sys.
- static _CALLBACK_GET_RESULT_STRING: raw::get_string_callback = Some(callback_get_result_string);
-
- #[test]
- fn test_callback_get_result_string_empty() {
- let mut ret: Result = result_string_init!();
- let start: *const std::os::raw::c_char = std::ptr::null();
- let n: std::os::raw::c_uint = 0;
- let user_data: *mut std::os::raw::c_void = callback_get_result_string_data(&mut ret);
-
- unsafe {
- callback_get_result_string(start, n, user_data);
- }
-
- let s = ret.unwrap();
- assert_eq!(s, "");
- }
-
- #[test]
- fn test_callback_result_string() {
- let mut ret: Result = result_string_init!();
- let start = b"helloGARBAGE".as_ptr() as *const std::os::raw::c_char;
- let n: std::os::raw::c_uint = 5;
- let user_data: *mut std::os::raw::c_void = callback_get_result_string_data(&mut ret);
- unsafe {
- callback_get_result_string(start, n, user_data);
- }
-
- let s = ret.unwrap();
- assert_eq!(s, "hello");
- }
-}
diff --git a/nixide-sys/Cargo.toml b/nixide-sys/Cargo.toml
new file mode 100644
index 0000000..0be064c
--- /dev/null
+++ b/nixide-sys/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "nixide-sys"
+description = "Unsafe direct FFI bindings to libnix C API"
+version = "0.1.0"
+readme = "../README.md"
+license = "GPL-3.0"
+repository = "https://codeberg.org/luminary/nixide"
+authors = [
+ "_cry64 ",
+ "foxxyora "
+]
+
+edition = "2024"
+build = "build.rs"
+
+[package.metadata.docs.rs]
+targets = [ "x86_64-unknown-linux-gnu" ]
+
+[lib]
+path = "lib.rs"
+
+[features]
+default = ["util"]
+expr = []
+fetchers = []
+flakes = []
+store = []
+util = []
+gc = []
+
+[build-dependencies]
+bindgen = { default-features = false, features = [ "logging", "runtime" ], version = "0.72.1" }
+doxygen-bindgen = "0.1.3"
+pkg-config.workspace = true
+cc.workspace = true
+
+[dev-dependencies]
+serial_test = "3.4.0"
diff --git a/nixide-sys/build.rs b/nixide-sys/build.rs
new file mode 100644
index 0000000..6631264
--- /dev/null
+++ b/nixide-sys/build.rs
@@ -0,0 +1,74 @@
+use std::env;
+use std::path::PathBuf;
+
+use bindgen::callbacks::ParseCallbacks;
+
+#[derive(Debug)]
+struct DoxygenCallbacks;
+
+impl ParseCallbacks for DoxygenCallbacks {
+ fn process_comment(&self, comment: &str) -> Option {
+ match doxygen_bindgen::transform(comment) {
+ Ok(res) => Some(res),
+ Err(err) => {
+ println!("cargo:warning=Problem processing doxygen comment: {comment}\n{err}");
+ None
+ }
+ }
+ }
+}
+
+fn main() {
+ // Invalidate the built crate whenever the wrapper changes
+ println!("cargo:rerun-if-changed=include/wrapper.h");
+
+ // Use pkg-config to find nix-store include and link paths
+ // This NEEDS to be included, or otherwise `nix_api_store.h` cannot
+ // be found.
+ let libs = [
+ "nix-main-c",
+ "nix-expr-c",
+ "nix-store-c",
+ "nix-util-c",
+ "nix-flake-c",
+ ];
+
+ let lib_args: Vec = libs
+ .iter()
+ .map(|&name| {
+ let lib = pkg_config::probe_library(name)
+ .expect(&format!("Unable to find .pc file for {}", name));
+
+ for p in lib.link_files {
+ println!("cargo:rustc-link-lib={}", p.display());
+ }
+
+ lib.include_paths
+ .into_iter()
+ .map(|p| format!("-I{}", p.display()))
+ })
+ .flatten()
+ .collect();
+
+ let bindings = bindgen::Builder::default()
+ .clang_args(lib_args)
+ // The input header we would like to generate bindings for
+ .header("include/wrapper.h")
+ // Invalidate the built crate when an included header file changes
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+ // Add `doxygen_bindgen` callbacks
+ .parse_callbacks(Box::new(DoxygenCallbacks))
+ // Format generated bindings with rustfmt
+ .formatter(bindgen::Formatter::Rustfmt)
+ .rustfmt_configuration_file(std::fs::canonicalize(".rustfmt.toml").ok())
+ // Finish the builder and generate the bindings
+ .generate()
+ // Unwrap the Result and panic on failure
+ .expect("Unable to generate bindings");
+
+ // Write the bindings to the $OUT_DIR/bindings.rs file
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
diff --git a/nixide-sys/include/wrapper.h b/nixide-sys/include/wrapper.h
new file mode 100644
index 0000000..30dd94e
--- /dev/null
+++ b/nixide-sys/include/wrapper.h
@@ -0,0 +1,23 @@
+// Pure C API for store operations
+#include
+
+// Pure C API for error handling
+#include
+
+// Pure C API for the Nix evaluator
+#include
+
+// Pure C API for external values
+#include
+
+// Pure C API for value manipulation
+#include