Reference a local file from flake.nix

Hey :wave:

I am trying to wrap my brains around flakes and how they can be useful for building a project

I have a repo with a flake.nix that is supposed to build a rust wasm example. In order to build, it refers to cargoLock.lockFile = ./Cargo.lock;. However, when running nix build it errors out due to a mistake in the path

error: path '/nix/store/szb5p2s1j4kqv4qrk72rhmgnd0r0mz8s-source/Cargo.lock' does not exist
  1. How do I refer to a local file? According to the official nix reference manual, paths starting with ./ should be relative to the flake (in this case the project directory, I assume). The syntax I used is taken from an example on how to build a rust project with makeRustPlatform where they also have cargoLock.lockFile = ./Cargo.lock;

  2. I believe the flake is supposed to build the Cargo.lock if it doesn’t already exist. If it requires Cargo.lock to build normally, should I be building the project through the dev shell to generate the Cargo.lock ? Is flake.nix only useful to make reproducible builds and not actually building incremental changes?

├── Cargo.lock
├── Cargo.toml
├── flake.nix
├── rust-toolchain.toml
├── src
│   ├── lib.rs
│   └── utils.rs
└── tests
    └── web.rs
# flake.nix
{
  description = "Rust with WebAssembly";

  inputs = {
    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nixpkgs.url = "nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, fenix, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        f = fenix.packages.${system};
        target = "wasm32-unknown-unknown";
        toolchain = with fenix.packages.${system}; combine [
          minimal.cargo
          minimal.rustc
          targets.${target}.latest.rust-std
        ];
      in {  
        packages.default = (pkgs.makeRustPlatform {
          cargo = toolchain;
          rustc = toolchain;
        }).buildRustPackage {
          pname = "rust-wasm-game-of-life";
          version = "0.1.0";
          src = ./.;
          cargoLock.lockFile = ./Cargo.lock;
        };

        devShells.default = pkgs.mkShell {
          name = "rust-wasm-first-attempt";

          packages = with pkgs; [
            f.stable.toolchain
            nodejs
            wasm-pack
          ];
        };
      }
  );
}

git add the file. Flakes will only “see” tracked files because nix insists on copying your entire repo to /nix/store… with all the useability, disk use, write, and performance implications. It uses git tracking as a heuristic for which files are needed.

It’s probably the most major flaw in the whole flakes concept, and a large part of why I don’t really think it should be considered ready. The new lazy trees feature helps a bit since it means only files needed for the evaluation are copied (and not, e.g., random build caches or, say, text files with secrets), but it doesn’t address the underlying issue that we’re constantly copying around entire repositories’ worth of files.

Kinda. flake.nix doesn’t do anything, it’s just sugar for invoking nix commands, which ultimately do exactly what the traditional nix-build does with a specified file (or default.nix). Flakes are not at all relevant here.

But the nix builder you invoke is only designed to build full repos, not do incremental builds of anything. It gets a source tree and produces all the binaries in the workspace, and doesn’t keep any other files it produces incidentally.

There are third party builders (e.g. crate2nix) which can do more granular builds, primarily for dependency crates, but in general nix will not keep build caches (since that would be an inherently impure build, and goes against everything nix stands for).

Nix is ultimately a metabuild/integration tool, it’s not a cargo replacement.

For development, you should build a nix shell with specific tool versions to use (and perhaps even a pre-built crate cache with some of the fancy builders) and run cargo manually.

2 Likes

Thank you very much!

I was under the impression that the flakes were evaluated locally in my work dir. Thank you for clearing that up and for the great explanations

For development, you should build a nix shell with specific tool versions to use

For curiosity, would you say that devShells (as seen in my flake) is equivalent to having a shell.nix ?

devShells.default = pkgs.mkShell {
  name = "rust-wasm-first-attempt";

  packages = with pkgs; [
    f.stable.toolchain
    nodejs
    wasm-pack
  ];
};

Cheers.

Pretty much. Both are just a way to tell nix about a build environment created with e.g. pkgs.mkShell. I refer to this concept as a “nix shell” regardless of whether you index it with a flake or use an explicit shell.nix file.

The ultimate work nix does does not meaningfully differ whether you use a flake.nix file or if you spread your nix expressions into the traditional “standard” file tree.

Flakes’ raison d’être is precisely that there was no good standard for how to structure projects using nix - the flake.nix file is a rough standard for that.

There are some subtle differences, the nix develop command invokes the shell slightly differently (leading to some limitations), and since flakes enforce input locking your shells never update with your system - which can be both good and bad, depending on your use case. But it’s pretty much the same idea save for little details.

Flakes also add a better dependency resolution system than channels (though this can be solved without language additions, see e.g. npins) and the silly project copy thing to assert build purity in the least efficient way imaginable.

1 Like