The Problem

Nix is a linux package manager that allows users to create programming environments that are completely independent of each other. The manifests of these environments look like this:

let pkgs = import <nixpkgs> {}; in pkgs.mkShell { buildInputs = [ (pkgs.libpqxx.overrideAttrs (oa: { patches = [./bugfix.patch]; })) ]; }

This manifest creates a patched version of libpqxx that will only exists inside the environment it will generate. That way users can maintain a separate manifest for each project. When they want to work on a project, they can launch a shell in its environment.
Previously my workflow with the nix package manager looked like this:

  1. Enter the project environment in a shell
  2. Launch intellij/clion from that shell
  3. Work on the project

But if I wanted to work on a project whose environment differs from the one in which the current intellij instance is running, I had to kill the current instance and launch another instance in the new environment before I could start working on that project.

Semi-perfect solution

Enter Nix Shell is an intellij plugin that adds a "run" button to nix files that you open in the ide. When you press this button, the plugin will capture the environment of that nix environment and inject it into the intellij process, effectively moving the whole IDE into that nix shell:

Obviously any child processes that were already running (daemons, terminals) will not be moved into the nix-shell. You'll have to restart them.

I said this solution is not perfect because some intellij tasks and run configuration will not see the new environment under certain circumstances:

This is happening because those tasks do not use the process environment directly. They use a layer that caches the state of process environment if the environment variable DESKTOP_STARTUP_ID is set.

Perfect solution

There are two ways you can fix this issue. First is pretty straightforward. Just put unset DESKTOP_STARTUP_ID somewhere in the idea.sh startup script. That will ensure that the edge case is never triggered.

The second is to patch your ide code. This guide describes how to do that without recompiling the ide. You'll have to apply this patch:

diff --git a/EnvironmentUtil.java b/EnvironmentUtil.java index e2519f0..e4207cd 100644 --- a/EnvironmentUtil.java +++ b/EnvironmentUtil.java @@ -152,9 +152,13 @@ public final class EnvironmentUtil { // returned by System.getenv(), is captured before the removal. Map<String, String> env = System.getenv(); if (env.containsKey(DESKTOP_STARTUP_ID)) { - env = new HashMap<>(env); - env.remove(DESKTOP_STARTUP_ID); - env = Collections.unmodifiableMap(env); + try { + Class cl = env.getClass(); + java.lang.reflect.Field m = cl.getDeclaredField("m"); + m.setAccessible(true); + Map wm = (Map) m.get(env) ; + wm.remove(DESKTOP_STARTUP_ID); + } catch (Exception e) {} } return env; }

Class to patch: platform/util/src/com/intellij/util/EnvironmentUtil.java

Either way, you'll get a near perfect integration with nix in your ide: