Can a neovim configuration with these runtime dependency requirements be used with nix?

Hello nix community,
I am a total nix beginner, have read nix pills and many other resources and am still struggling with a small project I am trying to achieve:

  1. I want to use neovim with a custom configuration, using the lazy package manager.
  2. Not every neovim plugin is available through the nix store so I would like to leave plugin managament to lazy (using its lockfile).
  3. I also want to make sure, the runtime dependencies are available with installation, e.g. python3-pip, npm, java17, …
  4. I want to pin the system dependencies / runtime dependencies for neovim to specific commits.
  5. Additionally I want to share the configuration also with users, not using nix at all (another reason to not define plugins and other configuration the nix way).

Thus my questions is about how to do orchestration or packaging to meet the requirements above.

I was trying the following things so far, but my impression is more and more that an experienced nix user can give a hint that ends the struggle:

Approach 1: Dev shell

I tried defining a dev shell as follows but had the problems, that

  1. opening the dev shell takes very long (some seconds)
  2. I don’t know how to pass arguments to neovim using nix develop --command nvim, e.g. nix develop --command nvim my_file.txt. My goal would be to use neovim instantly (e.g. using an alias for this command) and not to first have to enter the dev shell seperately.
  3. After exiting neovim, the dev shell should be closed as well

My flake.nix:

{
# Versioning of tools: https://mplanchard.com/posts/installing-a-specific-version-of-a-package-with-nix.html
    description = "very epic stuff";
    inputs = {
        # --- Nixpkgs
        nixpkgs.url = "github:NixOS/nixpkgs/release-23.11";
        # Unstable nix. You can name this whatever you want. We'll call it nixpkgs-unstable.
        nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
        # Some particular revision for installing fd
        nixpkgs-fd.url = "github:NixOS/nixpkgs/bf972dc380f36a3bf83db052380e55f0eaa7dcb6";

        # --- Overlays
        # Provides rust and friends
        rust-overlay.url = "github:oxalica/rust-overlay";
        # neovim
        neovim-overlay.url = "github:nix-community/neovim-nightly-overlay";

        # --- Utilities
        # Proivdes legacy compatibility for nix-shell
        flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
        # Provides some nice helpers for multiple system compatibility
        flake-utils.url = "github:numtide/flake-utils";
    };
    outputs = {
            self,
            # --- Nixpkgs
            nixpkgs, nixpkgs-unstable, nixpkgs-fd,
            # --- Overlays
            rust-overlay, neovim-overlay,
            # --- Utilities
            flake-utils, flake-compat
        }:
        flake-utils.lib.eachDefaultSystem 
        (system: 
            let
                pkgs = import nixpkgs {
                    inherit system;
                    overlays = [
                        rust-overlay.overlays.default
                        neovim-overlay.overlays.default
                    ];
                };

                pkgs-unstable = import nixpkgs-unstable { inherit system; };
                pkgs-fd = import nixpkgs-fd { inherit system; };

            in
            with pkgs;
            {
                devShell = mkShell {
                    buildInputs = [
                        # Add your system dependencies here
                        pkgs-unstable.coreutils
                        pkgs-fd.fd
                        rust-bin.stable.latest.default  # this package is provided by the overlay
                        rust-analyzer
                        pkgs-unstable.bashInteractive
                        pkgs-unstable.zulu17  # java17 and openjdk
                        pkgs-unstable.neovim
                        pkgs.python3
                        pkgs.python311Packages.pip
                        pkgs.luajitPackages.luarocks
                        pkgs.go
                        pkgs.php
                        pkgs.php83Packages.composer
                        pkgs.julia_18
                        pkgs.nodejs_21
                    ];
                };

                derivations = {
                    neovim = pkgs.neovim-nightly.overrideAttrs (oldAttrs: {
                        src = ./.;
                    });
                };
            }
        );
}

Approach 2: Use derivation

I tried to make aderivation myneovim, where neovim is copied into $out/bin and a script points to it, using my configuration.

Problems I had:

  1. Runtime dependencies seem not to be installed. E.g. pip is not available when running myneovim and doing healthcheck
  2. As I understood, the dependencies have to be used in the installation process to be persisted. But in the postInstall phase, I cannot use myneovim to make neovim try to check the dependencies through the healthchecker. Running nix-env --install --file default.nix fails with /nix/store/xfhkjnpqjwlf6hlk1ysmq3aaq80f3bjj-stdenv-linux/setup: line 1579: /nix/store/264zxspxsim23p6k2sn4qahz62yxmhwd-myneovim/myneovim: No such file or directory

My default.nix:

{ pkgs ? import <nixpkgs> {} }:
    pkgs.stdenv.mkDerivation {
      name = "myneovim";

      buildInputs = [
          pkgs.neovim
          pkgs.git
          pkgs.python3
          pkgs.python3Packages.pynvim
          pkgs.python3Packages.pip
        ];

      src = ./.;

      phases = [
        "unpackPhase"
        "buildPhase"
        "installPhase"
        "postInstall"
      ];

      buildPhase = ''
        mkdir -p $out/bin
        echo "#!/bin/sh" > $out/bin/myneovim
        echo "NVIM_APPNAME=\"myneovim\" $out/bin/nvim -u $out/config/init.lua \"\$@\"" >> $out/bin/myneovim
        chmod +x $out/bin/myneovim
      '';

      installPhase = ''
        ls ${pkgs.cowsay}/bin/
        mkdir -p $out
        cp -r ${pkgs.neovim}/bin/* $out/bin
        cp -r $src/src/config $out/
      '';

      postInstall = ''
        $out/myneovim --headless "+checkhealth" "+w!health.log" +qa
      '';
    }

Having a dev shell seems to be clean regarding runtime dependencies. But it looks like I need to use a derivation to “seamlessly” use neovim with my configuration.
With the latter approach, I am not sure how to install the runtime dependencies and how to make neovim check them. Or should I instead use nix-env --install instead initially for all runtime dependencies?

Afterall, I have the feeling, there could be a much easier and cleaner way and that I might not think “nixy” enough or have not yet enough/correct understanding of how to achieve this.

Any help is appreciated.

Best wishes,
snaeil

What do you mean by “runtime dependencies”? Are you talking about the plugins’ runtime dependencies? I don’t know any neovim plugins that require npm and java17

I didn’t read through all of your attempts, but I think that your goal is similar to what I wanted to achieve when I started with Nix - rely upon Nix as less as possible, so that the Neovim configuration could be shared with other machines and users who don’t necessarily use Nix. My Nix configuration is here:

There is nothing too interesting there, besides the usage of the Nix defined tree-sitter plugins, which are shared objects that must be compiled with Nix because otherwise they will become broken after Nix garbage collections… My General Neovim/Vim configuration is here:

Thank you for your configuration @doronbehar .

Yes, for example I use Mason, which installs formatters, linters, lsp, …
one nvim-jdtls requires java17 for example. neovim healthcheck for Mason shows me whether it’s available.
I don’t know how that would be possible following your example - if at all.

I thought you want to use lazy.nvim … In any case, since the formatters, linters and lsp are all external programs, you would want to install them via Nix, preferably decoratively (and not with nix profile for example), or with a project local declaration with direnv and use flake or use nix.

I personally don’t use any plugin managers - my plugin manager is git submodule. That’s because there is no value with these plugin managers if eventually I install all the external tools by myself - which is something I’d probably have preferred to do even if I was on a different OS.

I barely develop with Java, so I’m not familiar with this project, but a quick glance in the configuration docs of it indicate that you basically should rely upon java in your $PATH, and knowing the location of the eclipse .jar file. I’d simply get it with the eclipse’s instructions, and put it somewhere and point nvim-jdtls to it.

Yes, I use both lazy and Mason and the problem is the same (expectations on pre-existing binaries on the system).

I will think about all your suggestions and try them out. I’ll update this post asap.
So far, thank you for these hints!

Hey @snaeil, i had a similar problem when trying to package my nvim config via flakes. I still haven’t made the flake fully functional on NixOS (i plan to) but it seems to work on imperative distros (debian in this case) with Nix installed.
link to the part handling it in flake

          # bins = [...];
          
          # add runtime deps
          neovim = pkgs.symlinkJoin {
            name = "bartbie-nvim";
            paths = [ bartbie-neovim ] ++ bins;
            buildInputs = [ pkgs.makeWrapper ];
            postBuild = ''
              wrapProgram $out/bin/nvim \
                --prefix PATH : ${lib.makeBinPath bins}
            '';
          };

Basically, I keep a list of bins that i then put together with my nvim package via symlinkJoin & wrapProgram.
Since Mason installs the LSPs and other tools for you it will chug happily on a imperative distro but fail on NixOS (hence why i will have to fix it).

Idk how idiomatic this solution is (again, i have to make it work properly on NixOS), but hey maybe it will help you.

1 Like

I also stumbled upon wrappers and tried the non-flake way:

    let

    xxxvim-config = pkgs.stdenv.mkDerivation rec {
        pname = "xxxvim-config";
        version = "0.1";
        src = ./.;

        buildInputs = [ ];

        phases = [
          "unpackPhase"
          "installPhase"
        ];

        installPhase = ''
        mkdir -p $out
        cp -r $src/src/config $out/
        '';
    };

    in

    pkgs.writeShellApplication {
            name = "xxxvim";
            runtimeInputs = [ pkgs.php ];
            text = ''
                exec ${pkgs.neovim}/bin/nvim -u ${xxxvim-config}/config/init.lua "$@"
            '';
        }

But your solution is even cleaner I would say. I will try to adapt my current approach to that.
Thanks a lot.

@bartbie Can your flakes.nix file be used to put the derivation to the nix store? If yes, which command are you using? With nix build I am only getting the result folder.

1 Like

@snaeil
I just run nix run github:nickname/repo/branch and Nix will handle it for me, if I want to drop in a shell with those deps i do the same args but with shell. I think nix profile install should probably handle it fine too, since other nix API works.
e.g.
nix profile install github:snaeil/nvim
nix run github:bartbie/nvim/dev
nix shell github:bartbie/nvim/dev nixpkgs#zsh -c zsh to run in zsh (or fish or any other shell from nixpkgs.

All those commands, their APIs and other documentation can be found here,
+ shorter introduction here in the flakes book.

If you want to have it added permanently and declaratively in NixOS, via your system flake, then you need to add it to your inputs and then to your systemPackages. I do it via overlays, where i add a namespace with my packages inside, e.g. systemPackages = with pkgs; [snaeil.nvim];.

Hope this helps somehow!

@bartbie you are right, nix profile install . does it for me locally.
Thanks again, this is a great starting point :slight_smile:

Update:

I am for now using something like this:

{
  description = "bartbie Neovim config";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils } @ inputs:
    flake-utils.lib.eachDefaultSystem
      (system:
        let
          pkgs = import nixpkgs {
            inherit system;
          };

          inherit (pkgs) lib;

          bins = with pkgs; [
            gcc
            cmake
            curl
            unzip
            git
            ripgrep
            php
            julia
            fzf
            fd
            jq
          ];

          snaeil-config = pkgs.stdenv.mkDerivation rec {
              pname = "snaeil-config";
              version = "0.1";
              src = ./.;

              buildInputs = [ ];

              phases = [
                "unpackPhase"
                "installPhase"
              ];

              installPhase = ''
              mkdir -p $out/config
              cp $src/init.lua $out/config/
              '';
          };
        in
        rec {
          snaeil-wrapper = pkgs.writeShellApplication {
                  name = "snaeil";
                  runtimeInputs = bins;
                  text = ''
                      exec ${pkgs.neovim}/bin/nvim -u ${snaeil-config}/config/init.lua "$@"
                  '';
          };
          packages.snaeil = snaeil-wrapper;
          packages.default = packages.snaeil;
          apps.snaeil = flake-utils.lib.mkApp {
            drv = packages.snaeil;
            name = "snaeil";
            exePath = "/bin/snaeil";
          };
          apps.default = apps.snaeil;
          devShell = pkgs.mkShell {
            packages = [ packages.snaeil ] ++ bins;
          };
        }

      );
}
1 Like