diff --git a/README.md b/README.md index aa5eb40..5e85853 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,39 @@ Use the Nix [C API] from Rust. +## Build with `nix-cargo-integration` + +The development environment and building with Nix are taken care of by [nix-cargo-integration](https://github.com/90-008/nix-cargo-integration#readme) ([options](https://flake.parts/options/nix-cargo-integration.html)). + +The dependency on Nix is taken care of with the [`nix-bindings-rust` flake-parts module](). + +Example usage: + +```nix +{ + outputs = + inputs@{ self, flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } + { + imports = [ + inputs.nix-cargo-integration.flakeModule + inputs.nix-bindings-rust.modules.flake.default + ]; + + perSystem = { config, pkgs, ... }: { + # optional: + nix-bindings-rust.nixPackage = pkgs.nix; + + nci.projects."myproject" = { + depsDrvConfig = { + imports = [ config.nix-bindings-rust.nciBuildConfig ]; + }; + }; + }; + }; +} +``` + ## Hacking The following will open a shell with dependencies, and install pre-commit for automatic formatting. diff --git a/dev/flake-module.nix b/dev/flake-module.nix index 811ae0e..d19b37b 100644 --- a/dev/flake-module.nix +++ b/dev/flake-module.nix @@ -1,7 +1,5 @@ { - lib, inputs, - withSystem, ... }: { @@ -10,8 +8,14 @@ inputs.hercules-ci-effects.flakeModule ]; perSystem = - { config, pkgs, ... }: { + config, + pkgs, + inputs', + ... + }: + { + nix-bindings-rust.nixPackage = inputs'.nix.packages.default; pre-commit.settings.hooks.nixfmt-rfc-style.enable = true; # Temporarily disable rustfmt due to configuration issues @@ -86,7 +90,7 @@ }; }; herculesCI = - hci@{ config, ... }: + { config, ... }: { ciSystems = [ "x86_64-linux" ]; }; diff --git a/flake.nix b/flake.nix index b14286b..7414299 100644 --- a/flake.nix +++ b/flake.nix @@ -13,14 +13,160 @@ outputs = inputs@{ self, flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } ( - { + toplevel@{ lib, + withSystem, ... }: + let + /** + Makes perSystem.nix-bindings-rust available. + */ + flake-parts-modules.basic = + { + config, + flake-parts-lib, + withSystem, + ... + }: + { + _file = ./flake.nix; + options.perSystem = flake-parts-lib.mkPerSystemOption ( + { config, pkgs, ... }: + let + cfg = config.nix-bindings-rust; + in + { + 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. + + Example: + ```nix + perSystem = perSystem@{ config, ... }: { + nci.projects."my_project".depsDrvConfig = perSystem.config.nix-bindings-rust.nciBuildConfig; + } + ``` + ''; + }; + }; + config.nix-bindings-rust = { + nciBuildConfig = { + mkDerivation = { + buildInputs = [ + # stdbool.h + pkgs.stdenv.cc + ] + ++ ( + if cfg.nixPackage ? libs then + let + l = cfg.nixPackage.libs; + in + [ + l.nix-expr-c + l.nix-store-c + l.nix-util-c + l.nix-fetchers-c or null # Nix >= 2.29 + l.nix-flake-c + ] + else + [ cfg.nixPackage ] + ); + 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} + ''; + }; + # 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"; + }; + }; + }; + } + ); + }; + + /** + Adds flake checks that test the bindings with the provided nix package. + */ + flake-parts-modules.tested = + # Consumer toplevel + { options, config, ... }: + { + _file = ./flake.nix; + imports = [ flake-parts-modules.basic ]; + config.perSystem = + # Consumer perSystem + consumerPerSystem@{ + 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 consumerPerSystem.pkgs; + # ... and `nixPackage` + nix-bindings-rust.nixPackage = lib.mkForce consumerPerSystem.config.nix-bindings-rust.nixPackage; + }; + } + ]; + }; + in + { + key = "nix-bindings-rust-add-checks"; + 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; + }; + }; + + flake-parts-modules.default = flake-parts-modules.tested; + + 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 = [ @@ -44,10 +190,40 @@ 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"; + partitions.dev.extraInputsFlake = ./dev; partitions.dev.module = { imports = [ ./dev/flake-module.nix ]; }; + + # 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"; } ]; + }; + }; + }; + + # flake output attributes + flake = { + modules.flake = flake-parts-modules; + }; } ); } diff --git a/nci.nix b/nci.nix index 1509336..f0b4b50 100644 --- a/nci.nix +++ b/nci.nix @@ -11,33 +11,12 @@ nci.projects.nix-bindings = { path = ./.; drvConfig = { + imports = [ + # Downstream projects import this into depsDrvConfig instead + config.nix-bindings-rust.nciBuildConfig + ]; + # Extra settings for running the tests mkDerivation = { - buildInputs = [ - # stdbool.h - pkgs.stdenv.cc - ] - ++ ( - if config.packages.nix ? libs then - let - l = config.packages.nix.libs; - in - [ - l.nix-expr-c - l.nix-store-c - l.nix-util-c - l.nix-flake-c - ] - else - [ config.packages.nix ] - ); - 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} - ''; # 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. @@ -61,26 +40,11 @@ echo "experimental-features = ca-derivations flakes" > "$NIX_CONF_DIR/nix.conf" # Init ahead of time, because concurrent initialization is flaky - ${ - # Not using nativeBuildInputs because this should (hopefully) be - # the only place where we need a nix binary. Let's stay in control. - pkgs.buildPackages.nix - }/bin/nix-store --init + ${config.nix-bindings-rust.nixPackage}/bin/nix-store --init echo "Store initialized." ''; }; - # 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"; - }; }; }; };