/ flake.nix
flake.nix
  1  {
  2    inputs = {
  3      nixpkgs = {
  4        url = "github:nixos/nixpkgs/nixos-23.11";
  5      };
  6      flake-utils.url = "github:numtide/flake-utils";
  7      fenix = {
  8        url = "github:nix-community/fenix";
  9        inputs.nixpkgs.follows = "nixpkgs";
 10      };
 11      flakebox = {
 12        url = "github:dpc/flakebox?rev=34ce1b8f8c60661e06dc54ce07deb1ff0ed2b7f5";
 13        inputs.nixpkgs.follows = "nixpkgs";
 14        inputs.fenix.follows = "fenix";
 15      };
 16      bundlers = {
 17        # TODO: switch back to upstream after https://github.com/matthewbauer/nix-bundle/pull/103 is available
 18        url = "github:dpc/bundlers?branch=24-02-21-tar-deterministic&rev=e8aafe89a11ae0a5f3ce97d1d7d0fcfb354c79eb";
 19      };
 20      advisory-db = {
 21        url = "github:rustsec/advisory-db";
 22        flake = false;
 23      };
 24    };
 25  
 26    outputs = { self, nixpkgs, flake-utils, flakebox, advisory-db, bundlers, ... }:
 27      let
 28        # overlay combining all overlays we use
 29        overlayAll =
 30          nixpkgs.lib.composeManyExtensions
 31            [
 32              (import ./nix/overlays/rocksdb.nix)
 33              (import ./nix/overlays/wasm-bindgen.nix)
 34              (import ./nix/overlays/cargo-nextest.nix)
 35              (import ./nix/overlays/esplora-electrs.nix)
 36              (import ./nix/overlays/clightning.nix)
 37              (import ./nix/overlays/darwin-compile-fixes.nix)
 38              (import ./nix/overlays/cargo-honggfuzz.nix)
 39            ];
 40      in
 41      {
 42        overlays = {
 43          # technically overlay outputs are supposed to be just a function,
 44          # instead of a list, but keeping this one just to phase it out smoothly
 45          fedimint = [ overlayAll ];
 46          all = overlayAll;
 47          wasm-bindgen = import ./nix/overlays/wasm-bindgen.nix;
 48          darwin-compile-fixes = import ./nix/overlays/darwin-compile-fixes.nix;
 49          cargo-honggfuzz = import ./nix/overlays/cargo-honggfuzz.nix;
 50        };
 51  
 52        bundlers = bundlers.bundlers;
 53        defaultBundler = bundlers.defaultBundler;
 54  
 55        nixosModules = {
 56          fedimintd = import ./nix/modules/fedimintd.nix;
 57        };
 58      } //
 59      flake-utils.lib.eachDefaultSystem
 60        (system:
 61          let
 62            pkgs = import nixpkgs {
 63              inherit system;
 64              overlays = [ overlayAll ];
 65            };
 66  
 67            lib = pkgs.lib;
 68  
 69            stdenv = pkgs.stdenv;
 70  
 71            flakeboxLib = flakebox.lib.${system} {
 72              # customizations will go here in the future
 73              config = {
 74                toolchain.components = [
 75                  "rustc"
 76                  "cargo"
 77                  "clippy"
 78                  "rust-analyzer"
 79                  "rust-src"
 80                  "llvm-tools"
 81                ];
 82  
 83                motd = {
 84                  enable = true;
 85                  command = ''
 86                    >&2 echo "🚧 In an enfort to improve documentation, we now require all structs and"
 87                    >&2 echo "🚧 and public methods to be documented with a docstring."
 88                    >&2 echo "🚧 See https://github.com/fedimint/fedimint/issues/3807"
 89                  '';
 90                };
 91                # we have our own weird CI workflows
 92                github.ci.enable = false;
 93                just.importPaths = [
 94                  "justfile.fedimint.just"
 95                ];
 96                # we have a custom final check
 97                just.rules.final-check.enable = false;
 98                git.pre-commit.trailing_newline = false;
 99                git.pre-commit.hooks = {
100                  check_forbidden_dependencies = builtins.readFile ./nix/check-forbidden-deps.sh;
101                };
102              };
103            };
104  
105            toolchainArgs = { } // lib.optionalAttrs pkgs.stdenv.isDarwin {
106              # on Darwin newest stdenv doesn't seem to work
107              # linking rocksdb
108              stdenv = pkgs.clang11Stdenv;
109            };
110  
111            stdTargets = flakeboxLib.mkStdTargets { };
112            stdToolchains = flakeboxLib.mkStdToolchains toolchainArgs;
113  
114  
115            # toolchains for the native build (default shell)
116            toolchainNative = flakeboxLib.mkFenixToolchain (toolchainArgs
117            // {
118              targets = (pkgs.lib.getAttrs
119                [
120                  "default"
121                  "wasm32-unknown"
122                ]
123                stdTargets
124              );
125            });
126  
127            # toolchains for the native + wasm build
128            toolchainWasm = flakeboxLib.mkFenixToolchain (toolchainArgs
129            // {
130              defaultTarget = "wasm32-unknown-unknown";
131              targets = (pkgs.lib.getAttrs
132                [
133                  "default"
134                  "wasm32-unknown"
135                ]
136                stdTargets
137              );
138  
139              args = {
140                nativeBuildInputs = [
141                  pkgs.wasm-bindgen-cli
142                  pkgs.geckodriver
143                  pkgs.wasm-pack
144                ] ++ lib.optionals (stdenv.isLinux) [
145                  pkgs.firefox
146                ];
147              };
148            });
149  
150            # toolchains for the native + wasm build
151            toolchainAll = flakeboxLib.mkFenixToolchain (toolchainArgs
152            // {
153              targets = (pkgs.lib.getAttrs
154                ([
155                  "default"
156                  "aarch64-android"
157                  "x86_64-android"
158                  "arm-android"
159                  "armv7-android"
160                  "wasm32-unknown"
161                ] ++ lib.optionals pkgs.stdenv.isDarwin [
162                  "aarch64-ios"
163                  "aarch64-ios-sim"
164                  "x86_64-ios"
165                ])
166                stdTargets);
167            });
168            # Replace placeholder git hash in a binary
169            #
170            # To avoid impurity, we use a git hash placeholder when building binaries
171            # and then replace them with the real git hash in the binaries themselves.
172            replaceGitHash =
173              let
174                # the hash we will set if the tree is dirty;
175                dirtyHashPrefix = builtins.substring 0 16 self.dirtyRev;
176                dirtyHashSuffix = builtins.substring (40 - 16) 16 self.dirtyRev;
177                # the string needs to be 40 characters, like the original,
178                # so to denote `-dirty` we replace the middle with zeros
179                dirtyHash = "${dirtyHashPrefix}00000000${dirtyHashSuffix}";
180              in
181              { package, name, placeholder, gitHash ? if (self ? rev) then self.rev else dirtyHash }:
182              stdenv.mkDerivation {
183                inherit system;
184                inherit name;
185  
186                dontUnpack = true;
187                dontStrip = !pkgs.stdenv.isDarwin;
188  
189                installPhase = ''
190                  cp -a ${package} $out
191                  for path in `find $out -type f -executable`; do
192                    # need to use a temporary file not to overwrite source as we are reading it
193                    bbe -e 's/${placeholder}/${gitHash}/' $path -o ./tmp || exit 1
194                    chmod +w $path
195                    # use cat to keep all the original permissions etc as they were
196                    cat ./tmp > "$path"
197                    chmod -w $path
198                  done
199                '';
200  
201                buildInputs = [ pkgs.bbe ];
202              };
203  
204  
205            craneMultiBuild = import nix/flakebox.nix {
206              inherit pkgs flakeboxLib advisory-db replaceGitHash;
207  
208              # Yes, you're seeing right. We're passing result of this call as an argument
209              # to it.
210              inherit craneMultiBuild;
211  
212              toolchains = stdToolchains // { "wasm32-unknown" = toolchainWasm; };
213              profiles = [ "dev" "ci" "test" "release" ];
214            };
215  
216            devShells =
217  
218              let
219                commonShellArgs = craneMultiBuild.commonEnvsShell // craneMultiBuild.commonArgs // {
220                  toolchain = toolchainNative;
221                  buildInputs = craneMultiBuild.commonArgs.buildInputs;
222                  nativeBuildInputs = craneMultiBuild.commonArgs.nativeBuildInputs ++ [
223                    pkgs.cargo-udeps
224                    pkgs.cargo-audit
225                    pkgs.cargo-deny
226                    pkgs.parallel
227                    pkgs.just
228                    pkgs.time
229                    pkgs.gawk
230  
231                    (pkgs.writeShellScriptBin "git-recommit" "exec git commit --edit -F <(cat \"$(git rev-parse --git-path COMMIT_EDITMSG)\" | grep -v -E '^#.*') \"$@\"")
232  
233                    # This is required to prevent a mangled bash shell in nix develop
234                    # see: https://discourse.nixos.org/t/interactive-bash-with-nix-develop-flake/15486
235                    (pkgs.hiPrio pkgs.bashInteractive)
236                    pkgs.tmux
237                    pkgs.tmuxinator
238                    (pkgs.mprocs.overrideAttrs (final: prev: {
239                      patches = prev.patches ++ [
240                        (pkgs.fetchurl {
241                          url = "https://github.com/pvolok/mprocs/pull/88.patch";
242                          name = "clipboard-fix.patch";
243                          sha256 = "sha256-9dx1vaEQ6kD66M+vsJLIq1FK+nEObuXSi3cmpSZuQWk=";
244                        })
245                      ];
246                    }))
247                    pkgs.docker-compose
248                    pkgs.tokio-console
249                    pkgs.git
250  
251                    # Nix
252                    pkgs.nixpkgs-fmt
253                    pkgs.shellcheck
254                    pkgs.nil
255                    pkgs.convco
256                    pkgs.nodePackages.bash-language-server
257                    pkgs.sccache
258                  ] ++ lib.optionals (!stdenv.isAarch64 && !stdenv.isDarwin) [
259                    pkgs.semgrep
260                  ] ++ lib.optionals (!stdenv.isDarwin) [
261                    # broken on MacOS?
262                    pkgs.cargo-workspaces
263  
264                    # marked as broken on MacOS
265                    pkgs.cargo-llvm-cov
266                  ];
267  
268                  shellHook = ''
269                    export REPO_ROOT="$(git rev-parse --show-toplevel)"
270                    export PATH="$REPO_ROOT/bin:$PATH"
271  
272                    # workaround https://github.com/rust-lang/cargo/issues/11020
273                    cargo_cmd_bins=( $(ls $HOME/.cargo/bin/cargo-{clippy,udeps,llvm-cov} 2>/dev/null) )
274                    if (( ''${#cargo_cmd_bins[@]} != 0 )); then
275                      >&2 echo "⚠️  Detected binaries that might conflict with reproducible environment: ''${cargo_cmd_bins[@]}" 1>&2
276                      >&2 echo "   Considering deleting them. See https://github.com/rust-lang/cargo/issues/11020 for details" 1>&2
277                    fi
278  
279                    # Note: the string escaping necessary here (Nix's multi-line string and shell's) is mind-twisting.
280                    if [ -n "$TMUX" ]; then
281                      # if [ "$(tmux show-options -A default-command)" == 'default-command* \'\''' ]; then
282                      if [ "$(tmux show-options -A default-command)" == 'bla' ]; then
283                        echo
284                        >&2 echo "⚠️  tmux's 'default-command' not set"
285                        >&2 echo " ️  Please add 'set -g default-command \"\''${SHELL}\"' to your '$HOME/.tmux.conf' for tmuxinator test setup to work correctly"
286                      fi
287                    fi
288  
289                    export RUSTC_WRAPPER=${pkgs.sccache}/bin/sccache
290                    export CARGO_BUILD_TARGET_DIR="''${CARGO_BUILD_TARGET_DIR:-''${REPO_ROOT}/target-nix}"
291                    export FM_DISCOVER_API_VERSION_TIMEOUT=10
292                    [ -f "$REPO_ROOT/.shrc.local" ] && source "$REPO_ROOT/.shrc.local"
293  
294                    if [ ''${#TMPDIR} -ge 40 ]; then
295                        >&2 echo "⚠️  TMPDIR too long. This might lead to problems running tests and regtest fed. Will try to use /tmp/ instead"
296                        # Note: this seems to work fine in `nix develop`, but doesn't work on some `direnv` implementations (doesn't work for dpc at least)
297                        export TMPDIR="/tmp"
298                    fi
299  
300                    if [ "$(ulimit -Sn)" -lt "1024" ]; then
301                        >&2 echo "⚠️  ulimit too small. Run 'ulimit -Sn 1024' to avoid problems running tests"
302                    fi
303  
304                    if [ -z "$(git config --global merge.ours.driver)" ]; then
305                        >&2 echo "⚠️  Recommended to run 'git config --global merge.ours.driver true' to enable better lock file handling. See https://blog.aspect.dev/easier-merges-on-lockfiles for more info"
306                    fi
307                  '';
308                };
309              in
310              {
311                # The default shell - meant to developers working on the project,
312                # so notably not building any project binaries, but including all
313                # the settings and tools necessary to build and work with the codebase.
314                default = flakeboxLib.mkDevShell (commonShellArgs // { });
315  
316                fuzz = flakeboxLib.mkDevShell (commonShellArgs // {
317                  nativeBuildInputs = with pkgs; [
318                    cargo-hongfuzz
319                    libbfd_2_38
320                    libunwind.dev
321                    libopcodes_2_38
322                    libblocksruntime
323                    lldb
324                    clang
325                  ];
326                });
327  
328                lint = flakeboxLib.mkLintShell { };
329  
330                # Shell with extra stuff to support cross-compilation with `cargo build --target <target>`
331                #
332                # This will pull extra stuff so to save time and download time to most common developers,
333                # was moved into another shell.
334                cross = flakeboxLib.mkDevShell (commonShellArgs // craneMultiBuild.commonEnvsShellRocksdbLinkCross // {
335                  toolchain = toolchainAll;
336                  shellHook = ''
337                    # hijack cargo for our evil purposes
338                    export CARGO_ORIG_BIN="$(${pkgs.which}/bin/which cargo)"
339                    export REPO_ROOT="$(git rev-parse --show-toplevel)"
340                    export PATH="$REPO_ROOT/bin:$PATH"
341                    export PATH="''${REPO_ROOT}/nix/cargo-wrapper/:$PATH"
342                  '';
343                });
344  
345                # Like `cross` but only with wasm
346                crossWasm = flakeboxLib.mkDevShell (commonShellArgs // {
347                  toolchain = toolchainWasm;
348  
349                  nativeBuildInputs = commonShellArgs.nativeBuildInputs or [ ] ++ [
350                    pkgs.wasm-pack
351                    pkgs.wasm-bindgen-cli
352                    pkgs.geckodriver
353                  ] ++ lib.optionals (stdenv.isLinux) [
354                    pkgs.firefox
355                  ];
356                });
357  
358                replit = pkgs.mkShell {
359                  nativeBuildInputs = with pkgs; [
360                    pkg-config
361                    openssl
362                  ];
363                };
364  
365                bootstrap = pkgs.mkShell {
366                  nativeBuildInputs = with pkgs; [
367                    cachix
368                  ];
369                };
370              };
371          in
372          {
373            inherit devShells;
374  
375            # Technically nested sets are not allowed in `packages`, so we can
376            # dump the nested things here. They'll work the same way for most
377            # purposes (like `nix build`).
378            legacyPackages = craneMultiBuild;
379  
380            packages = {
381              inherit (craneMultiBuild) gatewayd fedimint-dbtool gateway-cli fedimint-cli fedimintd fedimint-load-test-tool;
382              inherit (craneMultiBuild) client-pkgs gateway-pkgs fedimint-pkgs devimint;
383            };
384  
385            lib = {
386              inherit replaceGitHash devShells;
387            };
388          });
389  
390    nixConfig = {
391      extra-substituters = [ "https://fedimint.cachix.org" ];
392      extra-trusted-public-keys = [ "fedimint.cachix.org-1:FpJJjy1iPVlvyv4OMiN5y9+/arFLPcnZhZVVCHCDYTs=" ];
393    };
394  }