nix: switch to use nix flakes

Nix Flakes[[0]] are a more modular way of expressing nix derivations.

The `flake.nix` file describes:
- How to build the project, or components of a projet, via `nix build`
- What "checks" can run for a project, which can be executed via
  `nix flake check`
- What binaries can be run, e.g. `nix run .#rad` to execute the CLI

This approach is preferable since a lot of the Nix ecosystem is using
Flakes -- despite it still being marked as experimental.

One interesting benefit, that needs confirming, is that a check can be
run from a remote Git source. So if someone creates a patch on
Radicle, someone else with the `nix` binary can execute:

         nix flake check git+https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5?refs=patches/<patch id>

This means that anyone with `nix` can check patches without having to
fetch the patch itself.

Another obvious benefit will be that it allows the Radicle binaries to
enter the Nix ecosystem.

The big caveat to both of the above is that, currently, the tests are
failing while running `nix flake check`. They are currently disabled
with `doCheck = false`. They are potentially failing due to some
strange sandboxing issues which are being looked into.

[0]: https://nixos.wiki/wiki/Flakes

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-12-06 20:04:45 +00:00 committed by cloudhead
parent 44206cb6ca
commit 9f544ef8c1
No known key found for this signature in database
11 changed files with 356 additions and 280 deletions

4
.config/nextest.toml Normal file
View File

@ -0,0 +1,4 @@
# We want the tests to stop running if they are running
# longer than 1min
[profile.default]
slow-timeout = { period = "30s", terminate-after = 2, grace-period = "10s" }

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake;

View File

@ -1,21 +0,0 @@
{ sources ? import ./sources.nix
, pkgs ? import sources.nixpkgs {
overlays = [ (import sources.rust-overlay) ];
}
, rust-overlay ? pkgs.rust-bin.stable.latest.default
}:
with pkgs;
mkShell {
name = "build";
buildInputs = [
# cargo tooling
cargo-deny
cargo-watch
# hard dependencies
cmake
openssl
pkgconfig
rust-overlay
];
}

View File

@ -1,21 +0,0 @@
{ sources ? import ./sources.nix
, pkgs ? import sources.nixpkgs {
overlays = [ (import sources.rust-overlay) ];
}
}:
let
stable = pkgs.rust-bin.stable.latest.default;
rust-overlay = stable.override {
extensions = [ "rust-src" "rust-analysis" ];
};
devault = (pkgs.callPackage ./default.nix {});
in
with pkgs;
mkShell {
name = "development";
buildInputs = devault.buildInputs ++ [
gcc
ripgrep
rust-analyzer
];
}

View File

@ -1,38 +0,0 @@
{
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "723f0eeb969a730db3c30f977c2b66b9dce9fe4a",
"sha256": "0016l7230gd2kdh0g2w573r9a2krqb7x4ifcjhhsn4h1bwap7qr0",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/723f0eeb969a730db3c30f977c2b66b9dce9fe4a.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {
"branch": "release-21.11",
"description": "Nix Packages collection",
"homepage": "",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2766f77c32e171a04d59b636a91083bae862274e",
"sha256": "1xk1f62n00z7q5i3pf4c8c4rlv5k4jwpgh0pqgzw1l40vhdkixk9",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/2766f77c32e171a04d59b636a91083bae862274e.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"rust-overlay": {
"branch": "master",
"description": "Pure and reproducible nix overlay of binary distributed rust toolchains",
"homepage": "",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "e17bfe3baa0487f0671c9ed0e9057d10987ba7f7",
"sha256": "1kg6kfkc115gcip69vrxz74ryfmh44fdx2480l6qahkgl0d3pzxb",
"type": "tarball",
"url": "https://github.com/oxalica/rust-overlay/archive/e17bfe3baa0487f0671c9ed0e9057d10987ba7f7.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}

View File

@ -1,198 +0,0 @@
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
else
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
fetch_tarball = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
else
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = name: spec:
let
ref =
spec.ref or (
if spec ? branch then "refs/heads/${spec.branch}" else
if spec ? tag then "refs/tags/${spec.tag}" else
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"
);
submodules = spec.submodules or false;
submoduleArg =
let
nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0;
emptyArgWithWarning =
if submodules
then
builtins.trace
(
"The niv input \"${name}\" uses submodules "
+ "but your nix's (${builtins.nixVersion}) builtins.fetchGit "
+ "does not support them"
)
{ }
else { };
in
if nixSupportsSubmodules
then { inherit submodules; }
else emptyArgWithWarning;
in
builtins.fetchGit
({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg);
fetch_local = spec: spec.path;
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
sanitizeName = name:
(
concatMapStrings (s: if builtins.isList s then "-" else s)
(
builtins.split "[^[:alnum:]+._?=-]+"
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
)
);
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources: system:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> { }
else
abort
''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs name spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git name spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
let
saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
in
if ersatz == "" then drv else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
optionalAttrs = cond: as: if cond then as else { };
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs
(
name: spec:
if builtins.hasAttr "outPath" spec
then
abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = replace name (fetch config.pkgs name spec); }
)
config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile)
, system ? builtins.currentSystem
, pkgs ? mkPkgs sources system
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); }

View File

@ -1,4 +1,5 @@
[workspace]
package.version = "0.9.0"
members = [
"radicle",
"radicle-cob",

121
flake.lock Normal file
View File

@ -0,0 +1,121 @@
{
"nodes": {
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1701193254,
"narHash": "sha256-Hr7efA3GjwqBkGYKmd3XmGckdPQikbcCmOrq7fmTp3A=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "43af5fef0591531a72ebb86c5f1c623ee95c62fe",
"type": "github"
},
"original": {
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1701622587,
"narHash": "sha256-o3XhxCCyrUHZ0tlta2W7/MuXzy+n0+BUt3rKFK3DIK4=",
"owner": "ipetkov",
"repo": "crane",
"rev": "c09d2cbe84cc2adfe1943cb2a0b55a71c835ca9a",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": []
},
"locked": {
"lastModified": 1701930186,
"narHash": "sha256-t9uIiU1fE7VXi3t460iOuAUW8Ece9mowRKva1aw48zI=",
"owner": "nix-community",
"repo": "fenix",
"rev": "aeb764a8e4c16da3f36b200a302552d8e457b9f4",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1701961404,
"narHash": "sha256-ieWHyh6kJtabQYUam/dXXi22MgcgLQvl+f2x/95n+us=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8fef9eee026f0d95c06b5880ef9c1af0f643aadf",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"advisory-db": "advisory-db",
"crane": "crane",
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

227
flake.nix Normal file
View File

@ -0,0 +1,227 @@
{
description = "Radicle";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/release-23.11";
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.rust-analyzer-src.follows = "";
};
flake-utils.url = "github:numtide/flake-utils";
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
};
nixConfig = {
keepOutputs = true;
};
outputs = {
self,
nixpkgs,
crane,
fenix,
flake-utils,
advisory-db,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pname = "Heartwood";
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) lib;
craneLib = crane.lib.${system};
srcFilters = path: type:
# Allow sql schemas
(lib.hasSuffix "\.sql" path)
||
# Allow diff files for testing purposes
(lib.hasSuffix "\.diff" path)
||
# Allow md files for testing purposes
(lib.hasSuffix "\.md" path)
||
# Default filter from crane (allow .rs files)
(craneLib.filterCargoSources path type);
src = lib.cleanSourceWith {
src = ./.;
filter = srcFilters;
};
# Common arguments can be set here to avoid repeating them later
commonArgs = {
inherit pname;
inherit src;
strictDeps = true;
buildInputs =
[
pkgs.git
# Add additional build inputs here
]
++ lib.optionals pkgs.stdenv.isDarwin [
# Additional darwin specific inputs can be set here
pkgs.libiconv
];
};
# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts =
craneLib.buildDepsOnly commonArgs;
# Build the actual crate itself, reusing the dependency
# artifacts from above.
radicle = craneLib.buildPackage (commonArgs
// {
inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle/Cargo.toml;});
doCheck = false;
inherit cargoArtifacts;
});
in {
# Formatter
formatter = pkgs.alejandra;
# Set of checks that are run: `nix flake check`
checks = {
# Build the crate as part of `nix flake check` for convenience
inherit radicle;
# Run clippy (and deny all warnings) on the crate source,
# again, reusing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
clippy = craneLib.cargoClippy (commonArgs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
doc = craneLib.cargoDoc (commonArgs
// {
inherit cargoArtifacts;
});
# Check formatting
fmt = craneLib.cargoFmt {
inherit pname;
inherit src;
};
# TODO: audits are failing so skip this check for now
# Audit dependencies
# audit = craneLib.cargoAudit {
# inherit src advisory-db;
# };
# Audit licenses
deny = craneLib.cargoDeny {
inherit pname;
inherit src;
};
# Run tests with cargo-nextest
nextest = craneLib.cargoNextest (commonArgs
// {
inherit cargoArtifacts;
partitions = 1;
partitionType = "count";
nativeBuildInputs = [
# git is required so the sandbox can access it.
pkgs.git
# Ensure that `git-remote-rad` is present for testing.
self.packages.${system}.radicle-remote-helper
];
# Ensure dev is used since we rely on env variables being
# set in tests.
buildPhase = ''
export CARGO_PROFILE=dev;
'';
});
};
packages = {
default = radicle;
radicle-remote-helper = craneLib.buildPackage (commonArgs
// {
inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-remote-helper/Cargo.toml;});
inherit cargoArtifacts;
cargoBuildCommand = "cargo build --release -p radicle-remote-helper";
doCheck = false;
});
radicle-cli = craneLib.buildPackage (commonArgs
// {
inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-cli/Cargo.toml;});
inherit cargoArtifacts;
cargoBuildCommand = "cargo build --release -p radicle-cli";
doCheck = false;
});
radicle-node = craneLib.buildPackage (commonArgs
// {
inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-node/Cargo.toml;});
inherit cargoArtifacts;
cargoBuildCommand = "cargo build --release -p radicle-node";
doCheck = false;
});
radicle-httpd = craneLib.buildPackage (commonArgs
// {
inherit (craneLib.crateNameFromCargoToml {cargoToml = ./radicle-httpd/Cargo.toml;});
inherit cargoArtifacts;
cargoBuildCommand = "cargo build --release -p radicle-httpd";
doCheck = false;
});
};
apps.default = flake-utils.lib.mkApp {
drv = radicle;
};
apps.rad = flake-utils.lib.mkApp {
name = "rad";
drv = self.packages.${system}.radicle-cli;
};
apps.git-remote-rad = flake-utils.lib.mkApp {
name = "git-remote-rad";
drv = self.packages.${system}.radicle-remote-helper;
};
apps.radicle-node = flake-utils.lib.mkApp {
name = "radicle-node";
drv = self.packages.${system}.radicle-node;
};
apps.radicle-httpd = flake-utils.lib.mkApp {
name = "radicle-httpd";
drv = self.packages.${system}.radicle-httpd;
};
devShells.default = craneLib.devShell {
# Inherit inputs from checks.
checks = self.checks.${system};
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = [
pkgs.cargo-watch
pkgs.ripgrep
pkgs.rust-analyzer
];
};
});
}

View File

@ -220,7 +220,7 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
&'a self,
remotes: impl IntoIterator<Item = &'a NodeHandle<G>>,
) -> BTreeSet<(Id, NodeId)> {
converge(iter::once(self).chain(remotes.into_iter()))
converge(iter::once(self).chain(remotes))
}
/// Wait until this node's routing table contains the given routes.

View File

@ -201,7 +201,7 @@ impl Line {
/// Add multiple items to this line.
pub fn extend(mut self, items: impl IntoIterator<Item = Label>) -> Self {
self.items.extend(items.into_iter());
self.items.extend(items);
self
}