Start page > A small trick to make using IntelliJ with NixOS nicer

A small trick to make using IntelliJ with NixOS nicer

Niko Strijbol

Published on , last updated on

technical, nixos

In this article, I document how I currently do Ruby-related development using NixOS and IntelliJ. This has some challenges, as IntelliJ was not designed to work with NixOS. While I talk about Ruby here, this trick should generalize pretty well to other programming languages.

Of course, I could make the switch to another editor, but IntelliJ is my editor of choice, and I am not ready to make the switch. It is pretty smart, and even integrates with LanguageTool to provide better spellchecking.

Using bundix doesn’t work

Bundix seems to be the recommended solution when using a project with an existing Gemfile. However, the Ruby environment created by bundix is not usable with Intellij. In a normal Ruby installation, various commands (aside from ruby) itself are actually ruby scripts. However, with bundix, these other commands become binary stubs for some reason. This means you can no longer do ruby -S irb, which is required by IntelliJ.

The solution is to not use bundix; instead install gems using bundler as normal. While this is not an ideal solution, it is also useful for projects where you cannot commit a gemset.nix file.

With the above in mind, a project for me typically looks like this:

  • My system configuration contains intellij.
  • My project-specific flake.nix contains ruby.
  • My gems are listed in the Gemfile and installed normally.

IntelliJ’s SDK model conflicts with Nix

The big issue is that IntelliJ is conceptually not designed to work with NixOS. It has its own SDK manager and expects the dependencies to be installed at the system level. This problem is made worse by the Nix store, since paths change between versions, or even when some dependency down the line changes. A further complication is that the paths to dependencies in NixOS change if the contents of those dependencies change.

This would mean that every time you run nix flake update, you would need to re-configure the SDK in IntelliJ, which is a tedious task.

The workaround

First, you need to start IntelliJ from within the nix development shell, to make sure IntelliJ has access to the SDK and other dependencies that are listed in the flake.nix file.

Second, the flake.nix file needs to symlink the dependencies to a known path. This known path is then used to configure the SDK in IntelliJ.

For example, consider the following (simplified) flake I use for this website, which is built using Nanoc. I have the following (simplified) flake.nix:

Partial source code of the flake.nix file used for this site.
{
  outputs = { self, nixpkgs, devshell, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; overlays = [ devshell.overlay ]; };
        ruby_version = pkgs.ruby_3_1;
      in
      {
        devShells = rec {
          default = site;
          site = pkgs.devshell.mkShell {
            name = "site";
            packages = with pkgs; [
              ruby_version
            ];
            devshell.startup.link.text = ''
              mkdir -p "$PRJ_DATA_DIR/current"
              ln -sfn ${ruby_version} "$PRJ_DATA_DIR/current/ruby"
              ln -sfn $GEM_HOME "$PRJ_DATA_DIR/current/bundle"
            '';
            env = [
              {
                name = "GEM_HOME";
                eval = "$PRJ_DATA_DIR/bundle/$(ruby -e 'puts RUBY_VERSION')";
              }
             {
                name = "PATH";
                prefix = "$GEM_HOME/bin";
              }
            ];
          };
        };
      }
    );
}

Only part of the flake.nix file is shown here; the relevant part is the link command. This will symlink the current ruby and bundle install to known paths: $PRJ_DATA_DIR/current/ruby and $PRJ_DATA_DIR/current/bundle. Note that you also need to set the relevant environment variables (e.g. GEM_HOME) to make this work.

My Ruby SDK configuration in IntelliJ then looks like this (with $PRJ_DATA_DIR = /home/niko/Ontwikkeling/site/.data).

intellij ruby

Finally, you probably want to put .data in your global .gitignore.

The biggest benefit of the symbolic links is that the SDK configuration in IntelliJ is largely immune to path changes in the Nix store. Other benefits of this approach are:

  • The system is not polluted with specific ruby installations.
  • I can use the normal bundler commands to install gems.

There are also downsides to this approach:

  • It is not possible to start IntelliJ outside the nix shell.
  • Working on multiple projects at the same time is impossible, as you cannot start IntelliJ twice, even from a different shell.

In the future, I might investigate a fix for that last issue. By setting the IDE’s properties, you can have multiple IntelliJ instances open at the same time. A potential solution is then to have code in the flake.nix that creates a new location for the IntelliJ stuff (e.g. plugins and settings), copies those from the system install and then finally sets IntelliJ up with that now location.