Can I use flakes within a git repo without committing flake.nix?

ok, now I know that

  1. nix develop . on a folder with .git is identical to nix develop git+file:. (NOTE: not to nix develop git:. and not to nix develop file:.)

  2. and nix develop . on a folder without .git is identical to nix develop path:.

Example

I have an empty dir with two files (from Super fast nix-shell in Flakes - NixOS Wiki), git add was not yet called

shell.nix

{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell {
  buildInputs = [
    nixpkgs-fmt
  ];

  shellHook = ''
    # ...
  '';
}

flake.nix

{
  description = "my project description";

  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem
      (system:
        let pkgs = nixpkgs.legacyPackages.${system}; in
        {
          devShells.default = import ./shell.nix { inherit pkgs; };
        }
      );
}
$ git init
Initialized empty Git repository in /home/srghma/projects/hello/.git/

$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        flake.nix
        shell.nix

nothing added to commit but untracked files present (use "git add" to track)

$ nix develop .
warning: Git tree '/home/srghma/projects/hello' is dirty
error: getting status of '/nix/store/0ccnxa25whszw7mgbgyzdm4nqc0zwnm8-source/flake.nix': No such file or directory

$ nix develop git+file:.
warning: Git tree '.' is dirty
error: getting status of '/nix/store/0ccnxa25whszw7mgbgyzdm4nqc0zwnm8-source/flake.nix': No such file or directory

$ nix develop path:.
warning: creating lock file './flake.lock'

[srghma@machine:~/projects/hello]$

$ cat flake.lock
{
  "nodes": {
    "flake-utils": {
      "inputs": {
        "systems": "systems"
      },
      "locked": {
        "lastModified": 1681202837,
        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
        "owner": "numtide",
        "repo": "flake-utils",
        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
        "type": "github"
      },
      "original": {
        "owner": "numtide",
        "repo": "flake-utils",
        "type": "github"
      }
    },
    "nixpkgs": {
      "locked": {
        "lastModified": 1685399834,
        "narHash": "sha256-Lt7//5snriXSdJo5hlVcDkpERL1piiih0UXIz1RUcC4=",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "58c85835512b0db938600b6fe13cc3e3dc4b364e",
        "type": "github"
      },
      "original": {
        "id": "nixpkgs",
        "type": "indirect"
      }
    },
    "root": {
      "inputs": {
        "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
}
$ rm -frd flake.lock
$ nix develop path:.
warning: creating lock file './flake.lock'

[srghma@machine:~/projects/hello]$
exit
$ cat flake.lock
{
  "nodes": {
    "flake-utils": {
      "inputs": {
        "systems": "systems"
      },
      "locked": {
        "lastModified": 1681202837,
        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
        "owner": "numtide",
        "repo": "flake-utils",
        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
        "type": "github"
      },
      "original": {
        "owner": "numtide",
        "repo": "flake-utils",
        "type": "github"
      }
    },
    "nixpkgs": {
      "locked": {
        "lastModified": 1685399834,
        "narHash": "sha256-Lt7//5snriXSdJo5hlVcDkpERL1piiih0UXIz1RUcC4=",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "58c85835512b0db938600b6fe13cc3e3dc4b364e",
        "type": "github"
      },
      "original": {
        "id": "nixpkgs",
        "type": "indirect"
      }
    },
    "root": {
      "inputs": {
        "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
}

$ rm -frd flake.lock
$ git add --all
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   flake.nix
        new file:   shell.nix

$ nix develop git:.
ssh: Could not resolve hostname git: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
warning: could not read HEAD ref from repo at 'git:.', using 'master'
evaluating derivation 'git:.#devShells.x86_64-linux.default'ssh: Could not resolve hostname git: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
error: program 'git' failed with exit code 128
(use '--show-trace' to show detailed location information)
$ nix develop file:.
error: unable to download 'file:.': URL using bad/illegal format or missing URL (3)
(use '--show-trace' to show detailed location information)

$ nix develop git+file:.
warning: Git tree '.' is dirty
warning: creating lock file './flake.lock'
warning: Git tree '.' is dirty

[srghma@machine:~/projects/hello]$
exit

Example 2 (use `nix develop git+file:.` on a folder without `.git`)
$ rm -frd flake.lock
$ rm -frd .git
$ nix develop .
warning: creating lock file '/home/srghma/projects/hello/flake.lock'

[srghma@machine:~/projects/hello]$
exit
$ ls
flake.lock  flake.nix  shell.nix
$ rm -frd flake.lock
$ nix develop git+file:.
ssh: Could not resolve hostname file: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
warning: could not read HEAD ref from repo at 'file:.', using 'master'
evaluating derivation 'git+file:.#devShells.x86_64-linux.default'ssh: Could not resolve hostname file: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
error: program 'git' failed with exit code 128
(use '--show-trace' to show detailed location information)
$ nix develop git:.
ssh: Could not resolve hostname git: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
warning: could not read HEAD ref from repo at 'git:.', using 'master'
evaluating derivation 'git:.#devShells.x86_64-linux.default'ssh: Could not resolve hostname git: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
error: program 'git' failed with exit code 128
(use '--show-trace' to show detailed location information)

$ nix develop file:.
error: unable to download 'file:.': URL using bad/illegal format or missing URL (3)
(use '--show-trace' to show detailed location information)

Question:

  1. how git is used for “type inference”?
  2. Why not to make the nix develop . on a folder with .git identical to nix develop path:. instead?

I still dont understand what throwing No such file or directory error in

$ nix develop .
warning: Git tree '/home/srghma/projects/hello' is dirty
error: getting status of '/nix/store/0ccnxa25whszw7mgbgyzdm4nqc0zwnm8-source/flake.nix': No such file or directory

gives us

3 Likes

It ensures that your flake is self-contained.

What means self-contained?

Same as “self-describing” but with “containing things” rather than “describing things”.

At least the flakes are tied to the evaluation cache. That should have been two separate features. Flakes could already be stable for several NixOS releases, but instead “the evaluation cache feature” is still not ready and so is flakes.

I agree that it’s a problem that Flakes is tied to the evaluation cache. There’s a proposal here to untangle this, and I have another proposal myself in the works too.

However I don’t think Flakes is ready to be stabilized even considering this. There’s a number of significant issues with Flakes yet unresolved:

I think these are the major ones at least

10 Likes

As much as I agree that flakes are not ready yet, and in the danger to fully derail this thread, I might have an unpopular and extreme opinion:

Either do a breaking change that ruptures contintents within the remainer of the year, or just realease them as they are. The “expermintal” status became a farce and everyone uses them as a stable tool. We are at a point where the “experimental” thing became basically just that thing that you enable anyway right after install to be able to use nix proper, or alternatively you use the determinate installer which by default enables flakes and nix-commands…

TL’DR: Break them now or just release flakes…

9 Likes

Also some serious lock file issues:

  • Updating of individual inputs is broken
  • The lock file will grow indefinitely through mutual dependency updates
  • Inputs aren’t lazy when locking, penalizing development-only inputs, test-only inputs and other use case specific inputs. Not scalable.

I think all three should be solved by first un-flattening the lock file (ie getting transitive deps from dependency locks by default) and resolving follows at evaluation time.

(EDIT: my own thoughts, not decided by the Nix team)

1 Like

Changing the locking may well be such a change. You may need the newest Nix to read new lock files, unless we keep copying the transitive stuff for a migration window.

(EDIT: hypothesizing and, again, speaking for myself)

More tests

Test 1 (throws `expected a string but got a thunk`)
$ git status
On branch main

No commits yet

Changes to be committed:
 (use "git rm --cached <file>..." to unstage)
       new file:   asdfasdf.nix
       new file:   flake.nix
       new file:   shell.nix

$ cat flake.nix
{
description = "my project description";

inputs.flake-utils.url = import ./asdfasdf.nix;

outputs =
 { self, nixpkgs, flake-utils }:
 flake-utils.lib.eachDefaultSystem
   (system:
     let pkgs = nixpkgs.legacyPackages.${system}; in
     {
       devShells.default = import ./shell.nix { inherit pkgs; };
     }
   );

}
$ cat asdfasdf.nix
"github:numtide/flake-utils"
$ nix develop .
warning: Git tree '/home/srghma/projects/hello' is dirty
error: expected a string but got a thunk at /nix/store/ny5h4254c0x95xv6nniqq89q83vpw0a7-source/flake.nix:24:1
(use '--show-trace' to show detailed location information)

Test 2 (throws errors, unless all used nix files are in repo and git added)

flake.nix

{
  inputs.flake-utils.url = "github:numtide/flake-utils";

  # WILL THROW WITH ERROR
  #
  # ✘  ~/projects/hello   main ±✚  nix develop .
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # error: anonymous function at /nix/store/pphykv9fdgj8vb6zsd1vn1mam1am0y8y-source/asdfasdf.nix:1:1 called without required argument 'nixpkgs'
  #
  #       at /nix/store/pphykv9fdgj8vb6zsd1vn1mam1am0y8y-source/flake.nix:3:19:
  #
  #            2|   inputs.flake-utils.url = "github:numtide/flake-utils";
  #            3|   outputs = args: import ./asdfasdf.nix args;
  #             |                   ^
  #            4| }
  #
  # outputs = args: import ./asdfasdf.nix args;

  # WORKS (but all files should be `git add`ed)
  #
  # If they are not added, then
  #
  # ✘  ~/projects/hello   main ✚  nix develop .
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # warning: creating lock file '/home/srghma/projects/hello/flake.lock'
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # error: getting status of '/nix/store/20w3fp4x5v37z8waql91vvq77rmbpw5n-source/asdfasdf.nix': No such file or directory
  # (use '--show-trace' to show detailed location information)
  #
  # ✘  ~/projects/hello   main ✚  ga asdfasdf.nix
  #
  # ~/projects/hello   main ±✚  nix develop .
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # error: getting status of '/nix/store/wcdk1h7x83km048qmnxqys2ghaiisa3g-source/shell.nix': No such file or directory
  # (use '--show-trace' to show detailed location information)

  outputs = { self, nixpkgs, flake-utils }@args: import ./asdfasdf.nix args;

  # THROWS ERROR
  #
  # ✘  ~/projects/hello   main ✚  nix develop .
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # warning: creating lock file '/home/srghma/projects/hello/flake.lock'
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # error: access to absolute path '/nix/store/asdfasdf.nix' is forbidden in pure eval mode (use '--impure' to override)
  # (use '--show-trace' to show detailed location information)
  #
  # outputs = { self, nixpkgs, flake-utils }@args: import ../asdfasdf.nix args;

  # THROWS ERROR TOO
  #
  # ~/projects/hello   main ✚  nix develop .
  # warning: Git tree '/home/srghma/projects/hello' is dirty
  # error: access to absolute path '/home/srghma/projects/asdfasdf.nix' is forbidden in pure eval mode (use '--impure' to override)
  # (use '--show-trace' to show detailed location information)
  #
  # outputs = { self, nixpkgs, flake-utils }@args: import /home/srghma/projects/asdfasdf.nix args;
}

Summary

1.

IF output is trying to import ../some-file-from-outside-git-repo.nix
THEN nix build . will throw error access to absolute path is forbidden

This is a meaning of self-contained / hermetic evaluation (taken from frase Note that any file that is not tracked by Git is invisible during Nix evaluation, in order to ensure hermetic evaluation)

2. (based on test 2) it seems like nix is using the git database as a cache to find anwser on question

Do I need to evaluate flake.nix at all? OR I will just use flake.lock?

(seems weird that nix cannot do this without git)


Am I right?

This one specifically bothers me such an enormous amount. We can’t have arbitrary expressions in flakes because it could lead to complex computations? Can you imagine if we’d said that about nixpkgs? The whole reason Nix is better than JSON is because you can write arbitrary expressions.

7 Likes

created question nixos - nix flakes: what is the difference between `nix build git+file:.` and `nix build path:.` - Stack Overflow

1 Like

After having watched the question on SO for nearly 2 weeks now, I am kind of curious how you managed to not get downvoted much, as the question in prose is quite different from what to expect when reading the subject…

Anyway…

  1. what is the difference?

The difference between path and git+file is quite obvious… path just treats the full filesystem location as the base of the flake, git+file tries to read the flakes content from a git repository. This is explained in the manual. nix flake - Nix Reference Manual which has already been linked.

  1. what is self-contained / hermetic evaluation?

In quite simple terms, this means, that everything required is included, a declared input or by definition always available.

So a US military MRE is “self-contained” as you just need to add some tap-water and due to some chemical reaction in the outer bag, it will heat up the contents of the inner bag. Tap-water is considered to be always available.

A can of beans though is not self contained, as you require to have a can opener and a camp fire.

  1. git-flake-evaluation is hermetic, but is path-flake-evaluation hermetic?

Yes, both are hermetic and pure. But a git-type is easier to send and reason about, and to check if you are really looking at the same code.

  1. does nix build git+file:. have some additional caching, that nix build path:. doesnt have?

Yes. Evaluation cache can only work on “snapshottable” flake types.

  1. why not make nix build . just equal to nix build path:., instead of this find type automatically behavior, described above (Because: what problem its trying to solve? The problem, solutions to which require all these weird behaviors, like unsolicited git add behind the scenes? But I can easily get around/silence it with path:.? This design is so weird, reason is so shady)

To avoid surprises when you share your code. In general defaulting to the version control system is sane, as one can assume that you want to use the version control to share your code.

2 Likes

To put that even more succinctly:

  • path:<path> ~= cp -r <path> /nix/store
  • git+file:<path> ~= git clone file:///<path> /nix/store

After that, the flake is evaluated in its new path in /nix/store. This is how it actually works today, in the future there will be some fancy mechanism to avoid doing this copy while pretending it has happened.

cp -r obviously copies everything, while git clone only copies files that have been git add-ed.

The path variant is generally git-unaware, so you can’t ask nix to use a specific commit or branch instead of what the repository is currently at, and you can’t ask it to exclude the .git directory.

You typically want to exclude the .git directory for caching (because otherwise any change will bust the cache), so nix defaults to the git protocol if at all possible. Same with other VC systems. The heuristic is deterministic, and very stable, so there’s not really a reason not to do this.

@NobbZ I didn’t uderstand anything new, the philosophical meaning of self-contained was evident without comparing to MRE, I would better see examples and links to the code

E.g. "we need to copy whole dir, bc IF we would not copy THEN … "

But thank you for trying, no insulting, I understand that you cannot see what is in my brain


Thx @TLATER Now I understand more

But, probably, you should change

git+file:<path> ~= git clone file:///<path> /nix/store

To

git+file:<path> ~= git clone file:///<path> /nix/git (because it’s probably behavior similar to builtins.fetchGit)

Question:

Why the full directory is copied to nix store ? cp -r <path> /nix/store

Why not? … not copy anything at all? I mean, nix build path:. will just read flake.nix and flake.lock and imported nix files (if they exist) to build required derivations.

Wer would not be able to ensure it exists at all, nor that path literals would point at the same location.

nix build (or any flake related thing in general) does not imply that the flake is actually local by any means.

Also, if we wouldn’t copy the flakes content to the store, then ./. would be some /home/srghma/… for you, /home/nmelzer/… for me, and /Users/foo/… for someone running on the mac.

Even worse, how would you resolve a ./. in a “remote” flake without copying first? Fully rely on a temporary folder created by mktemp -d with a random suffix?

All of these do not sound quite reproducible to me, as the state of the system influences what you get out of the build.

TLater was talking about “conceptually” which skips away a lot of the details… What actually happens for git+file is much more involved than a simple git clone (would take far too long for huge repos), instead in reality a git ls-files or so is ran in the repo and all files listed get copied over. But as said, TLater did simplify for getting concepts over, rather than implementations. They would be in the way here.

Because the lazytree hasn’t been merged yet, as it causes much more troubles than you might think it would.

See above.

2 Likes

@NobbZ

I think i now understood (more) the answer on "Why to copy whole repo? What else it needs except flake.nix and flake.lock? " (after reading edolstra post about flakes on tweag once again)

flake is simply a source tree (such as a Git repository) containing a file named flake.nix

Because flake is a repo by desing

Whatever is refered to by the flake. Which potentially is everything in its location. Even with lazytree, you will not be able to know in advance what exactly a derivation needs to be able to build. So when you hit a src = "${self}" or src = ./. you have to copy everything to satisfy the build.

1 Like

To clarify- under the new abstraction, this can be resolved by triggering after such dynamic paths are encountered, and won’t necessitate performing a full copy regardless of such encounters, right? I just want to make sure we aren’t losing the primary aspect of lazy trees I was looking forward to.

Otherwise, it could be worth looking into a “temporary” nix-store entry that read-only-links the original source until it is sure it was actually utilized.

Note that https://devenv.sh lifts the restriction for files needing to be tracked by git.

The downside is that if there are many/big files in the repo, loading the flake takes a lot longer.

One use case is reading .env files from Nix, but that file shouldn’t be committed to git, since it contains secrets.

1 Like