NixOS / nix

Nix, the purely functional package manager
https://nixos.org/
GNU Lesser General Public License v2.1
12.78k stars 1.52k forks source link

`nix repl` configuration file #9940

Open 9999years opened 9 months ago

9999years commented 9 months ago

Is your feature request related to a problem? Please describe. When I'm entering the nix repl, I often like to set up a couple definitions for convenience before I start evaluating expressions, like this:

$ nix repl nixpkgs
Loading installable 'flake:nixpkgs#'...
Added 5 variables.
nix-repl> pkgs = legacyPackages.aarch64-darwin
nix-repl> hsPkgs = pkgs.haskell.packages.ghc96

Unfortunately, there's no way to configure this sort of behavior for nix repl, and even if there was a setting like repl-default-bindings it wouldn't account for the dynamism of the values that might be loaded.

Describe the solution you'd like

I would like a repl-startup-files setting, which could default to a path like ~/.config/nix/repl.nix. The file would contain a function of type AttrSet -> AttrSet. nix repl would call the file with the initial top-level variables as the first argument and replace the top-level variables with the resulting expression.

Then, the bindings I've added manually above could be added automatically like this:

vars: let
  pkgs =
    if vars ? legacyPackages.${builtins.currentSystem}
    then vars.legacyPackages.${builtins.currentSystem}
    else if vars ? packages.${builtins.currentSystem}
    then vars.packages.${builtins.currentSystem}
    else null;

  hsPkgs =
    if pkgs ? haskellPackages
    then pkgs.haskellPackages
    else null;

  optionalAttrs = condition: attrs:
    if condition
    then attrs
    else {};
in
  vars
  // (optionalAttrs (pkgs != null) {inherit pkgs;})
  // (optionalAttrs (hsPkgs != null) {inherit hsPkgs;})

Perhaps we could also provide a global variable like __replSource to indicate which flake or file is being loaded. Then, users could dynamically provide bindings for specific flakes. Alternatively, flakes could set nixConfig.extraReplStartupFiles to check in project-specific bindings.

Describe alternatives you've considered One alternative would be a repl-bindings setting, which might look like this:

repl-bindings = pkgs=legacyPackages.${builtins.currentSystem} hsPkgs=legacyPackages.${builtins.currentSystem}.haskellPackages

I think this would be much clumsier to use and would introduce another unfortunate whitespace-sensitive DSL. The lack of newlines in configuration files would also make it much harder to keep the definitions organized neatly.

Priorities

Add :+1: to issues you find important.

arunoruto commented 1 week ago

One way you could do it now, is using a custom script/derivation and make you own repl launcher :) You could use the Linux expect command to interact with the shell and fill the params you need to be loaded that way! See here some examples for using expect: https://phoenixnap.com/kb/linux-expect

I am currently searching for a solution, but if I don't find one, I will just create the custom script and live that way for now 😂

9999years commented 1 week ago

@arunoruto Lix supports this through the repl-overlays setting and it's fully compatible with CppNix! From the manual:

For example, the following file would alias pkgs to legacyPackages.${info.currentSystem} (if that attribute is defined):

info: final: prev:
 if prev ? legacyPackages
    && prev.legacyPackages ? ${info.currentSystem}
 then
 {
   pkgs = prev.legacyPackages.${info.currentSystem};
 }
 else
 { }

https://docs.lix.systems/manual/lix/stable/command-ref/conf-file.html?highlight=repl-overlay#conf-repl-overlays

arunoruto commented 1 week ago

Damn, that looks slick! Maybe I will give lix a look, but currently I will try to hack something together 😂 When I am done I would share it here, maybe someone has the same "problem" for nix.

arunoruto commented 1 week ago

Okay, now I got something to work with 😁 Here is what I came up with:

{
  config,
  lib,
  pkgs,
  osConfig,
  ...
}@args:
let
  nix-repl = pkgs.writeScriptBin "nix-repl" (
    ''
      #!${lib.getExe pkgs.expect}

      spawn nix repl
      expect {
        "Welcome to Nix" {}
        -r "Nix (\\d+\\.\\d+\\.\\d+)" {}
      }
      send -- ":lf ${config.home.sessionVariables.FLAKE}\r"
      expect -r "Added (\\d+) variables."
      send -- ":lf nixpkgs\r"
      expect -r "Added (\\d+) variables."
      send -- "pkgs = legacyPackages.${pkgs.system}\r"
      send -- "hm = homeConfigurations.${config.home.username}\r"
    ''
    + lib.optionalString (args ? nixosConfig) ''
      send -- "os = nixosConfigurations.${osConfig.networking.hostName}\r"
    ''
    + ''
      interact
    ''
  );
in
{

  home.packages = [
    nix-repl
  ];
}

I do make some presumptions: