Angle brackets in a Nix flake world

11 Likes

Nice write up, the use of the angle bracket syntax is interesting. Though i think i disagree that adding buitins.currentFlake would be pure. It’s also very very hard to define, when it should switch a flake input of the toplevel flake. So I’m not sure such a chabge would be a net positive

This is quite interesting indeed, but why don’t you use self instead of getFlake?

flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    nixpkgslib.url = "github:nix-community/nixpkgs.lib";
  };
  description = "Trivial flake that returns a string for eval";

  outputs =
    {
      nixpkgslib,
      nixpkgs,
      self,
    }:
    {
      __findFile =
        nixPath: name:
        let
          lib = nixpkgslib.lib;
        in
        lib.getAttrFromPath (lib.splitString "." name) self;

      hello = "Hello from a flake!";
      example = builtins.scopedImport self ./default.nix;
    };
}
default.nix
<outputs.hello> + " and welcome to Nix!"
$ nix eval .#example
"Hello from a flake! and welcome to Nix!"
2 Likes

Thank you that’s great.
Originally I had the lookup in a different file before I discovered scopedImport

I’ll update the example with this :pinched_fingers::ok_hand:

1 Like

While I appreciate the concept of new angle bracket syntax, I don’t think the Nix language needs flake-specific syntax. Flakes have been adopted by the majority of the community - but NIX_PATH was previously adopted by the majority of the community. The shortcomings of flakes are well-documented, and they are still experimental for established reasons. Even the biggest proponents of Flake stabilization acknowledge that future iterations on Flakes will happen, and plan on supporting them.

I don’t think entrenching ourselves further in flakeland is the right move. I’d prefer to see angle brackets used for something more meaningful, like providing a module system with faster evaluation.

7 Likes

as always another cool article from @fzakaria :sunglasses:

personally i prefer a simplified __findFile like this:

__findFile = nixPath: name: inputs.${name};

that is… map each flake input, but not output
my reasoning is that this is a more natural progression from channels to flakes and keeps the same semantics - flake inputs are an extension/improvement/natural progression of channels

but then again… i don’t like most flake outputs, so i guess i have a bias there… :sweat_smile:

3 Likes

Thanks for the kind words.

The problem with ${name} is that if you do “a.b” it puts it as a single attribute quoted. I guess for inputs it may not matter.

Why support output?
Well I want to access my own packages or modules sometimes instead of through an overlay

I just pushed an update to the blog with your improvement – thank you.

2 Likes

The ability to use angle brackets is very interesting, though scopedImport has huge performance penalty (iirc it disables memoisation). Some testing with nix-instantiate (I don’t use flake but npins) shows that the evaluation time goes from 17 seconds to almost 30 seconds, doubling the evaluation time…

1 Like

might be slighlty off topic:

eventual deprecation of NIX_PATH?

:question: :melting_face:

that document was presented forever ago at a nixcon for ideas on where to head… it is vaporware now, not going to happen


as far as deprecating NIX_PATH though… i bet some people actually want to do that - i hope that won’t happen

recently i started playing around with lon (:heart: @nikstur) and started utilizing angle brackets again after having switched to flakes many years ago now

one of my shell.nix files looks like as an experiment:

let
  sources = import ./lon.nix;

  pkgs = import sources.nixpkgs { };
in
  pkgs.mkShell {
    shellHook = ''
      export NIX_PATH=${pkgs.lib.concatMapAttrsStringSep ":" (k: v: "${k}=${v}") sources}
    '';
  }

i hooked that up to direnv and now whenever i enter that project directory i can access <nixpkgs> in a well defined way - whether in repl or something like nixos-rebuild, everything has access to NIX_PATH

i haven’t been using this long enough to decide how much i like it yet… but it has been a blast from the past at least

4 Likes

maybe explains why i only encountered it a couple hours ago :wink:

I really think NIX_PATH is great. It supremely useful when mucking about with the repl to just say “gimme the nixpkgs in context” whether that’s system wide, or in some project, it’s useful shorthand. I use it all the time for answering “what about??” repl questions in the global system context. If I want to turn into something stable/shareable, well I can just copy the system nixpkgs pin and move from ephemeral to permanent.

NIX_PATH <3 for ever!

1 Like

I think builtins.scopedImport self is rather reckless. You could accidentally override a number of things. It would be safer to do something like this:

{
  outputs =
    { self, nixpkgs, ... }:
    let
      inherit (nixpkgs) lib;
      scope = {
        __findFile = _: name: lib.getAttrFromPath (lib.splitString "." name) self;
      };
    in
    {
      scoped = builtins.scopedImport scope ./default.nix;
    };
  inputs = {
    nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
  };
}

If you were to define something like import in the outputs of the previous example (not intending to override the import global), you might just break all subsequent calls to import. Obviously, I don’t think anyone would actually do that (at least, I hope not), but I’m sure you can see how similar things could happen:

# scoped.nix or selfed.nix
{
  pkgs = import <inputs.nixpkgs> { };
}

# flake.nix
{
  outputs =
    { self, nixpkgs, ... }:
    let
      inherit (nixpkgs) lib;
      scope = {
        __findFile = _: name: lib.getAttrFromPath (lib.splitString "." name) self;
      };
    in
    {
      inherit (scope) __findFile;

      # Oh no! Unanticipated name conflict!
      # If we use `self` as the scope, this overrides the `import` global.
      import = import nixpkgs { };

      # In `selfed.nix`, `import` is a set.
      # That causes an error.
      selfed = builtins.scopedImport self ./selfed.nix;

      # In `scoped.nix`, `import` is the builtin function.
      # Everything goes as expected.
      scoped = builtins.scopedImport scope ./scoped.nix;
    };
  inputs = {
    nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
  };
}

It’s littering, at best, because everything in outputs becomes global.