#
IDE setup with direnv and the kernel devshell

##
Introduction

Language servers do not interpret `build.toml`, so IDE completion for CUDA, ROCm, framework headers, and the kernel’s Python wrapper does not work out of the box. This guide shows how to configure VS Code so that completion resolves against the same toolchain `kernel-builder` uses.

The setup has three pieces:

- `kernel-builder create-pyproject` to emit CMake and setuptools files the IDE can read (see Local Development).
- The kernel-builder devshell, which provides the toolchain (CUDA, ROCm, Torch headers, etc.) from the Nix store.
- `direnv` to activate the devshell on `cd`, so VS Code inherits the environment through the shell.

Pinning the toolchain through Nix keeps IDE completion aligned with the build. It also makes switching between CUDA, ROCm, or XPU a one-line change in `.envrc`.

##
Installing direnv and nix-direnv

On non-NixOS systems, install both via `nix profile`:

    $ nix profile install nixpkgs#nix-direnv

Add the direnv hook to your shell rc (`~/.bashrc` or `~/.zshrc`, for example):

    eval "$(direnv hook bash)"    # or: direnv hook zsh

Source the rc file (or open a new shell) so the hook is active in the current session:

    $ source ~/.bashrc            # or: source ~/.zshrc

Wire `nix-direnv` into direnv:

    $ mkdir -p ~/.config/direnv
    $ echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' \
        >> ~/.config/direnv/direnvrc

On NixOS or with home-manager, enable `programs.direnv` with `nix-direnv` instead. See `terraform/nixos-configuration.nix` for a working example.

##
Activating the devshell with direnv

From the kernel root directory (the one containing `flake.nix` and `build.toml`), tell direnv to use the flake’s default devshell:

    $ echo 'use flake' > .envrc
    $ direnv allow

`direnv` now activates the default devshell whenever you `cd` into the project. The devshell’s `shellHook` creates and activates a `.venv` on first entry. Confirm it picked up the toolchain and venv:

    $ which nvcc
    /nix/store/.../bin/nvcc
    $ ls -ld .venv
    drwxr-xr-x ... .venv
    $ which python
    /path/to/kernel/.venv/bin/python

If `.venv` is missing, re-run `direnv reload` and check the output for the `Creating new venv environment in path: './.venv'` line from the `shellHook`.

To pin a non-default build variant, name it explicitly:

    $ echo 'use flake .#devShells.torch211-cxx11-rocm71-x86_64-linux' > .envrc
    $ direnv allow

See Build Variants for the variant list.

##
Generating IDE-facing project files

direnv puts the toolchain on `PATH`, but the C++ language server still needs a CMake-derived `compile_commands.json` to resolve per-file include paths. Generate the CMake/setuptools project and the file:

    $ kernel-builder create-pyproject -f
    $ cmake -B build-ext -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
    $ ln -sf build-ext/compile_commands.json compile_commands.json

`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` is required: the generated CMake does not set it. The symlink lets the language server find the file at the project root.

As noted in Local Development, do not commit the generated files.

##
Configuring VS Code

Install the `mkhl.direnv` extension. It activates the project’s `.envrc` when VS Code opens the workspace, so language servers and the integrated terminal see the devshell environment without launching `code` from a shell.

Alternatively, skip the extension and open the project from a direnv-activated shell — VS Code inherits the environment that way too:

    $ cd path/to/kernel
    $ code .

Install one of the following first-party extensions for C++/CUDA completion:

- `llvm-vs-code-extensions.vscode-clangd` (recommended for CUDA).
- `ms-vscode.cpptools` (Microsoft C/C++).

Add `.vscode/settings.json` (do not commit):

    {
      "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",

      // clangd
      "clangd.arguments": ["--compile-commands-dir=${workspaceFolder}"],

      // Microsoft C/C++ extension
      "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json"
    }

Depending on the extension being used, the configuration above behaves differently:

- With `clangd`, the `clangd.arguments` line is optional. clangd already looks in the parent directories of each source file for `compile_commands.json` and will find the workspace-root symlink on its own (clangd docs). Setting it explicitly does no harm.
- With the Microsoft C/C++ extension, the `C_Cpp.default.compileCommands` line is required. The extension does not pick up `compile_commands.json` from the workspace root on its own, unless another extension (such as CMake Tools) tells it where to look.

To verify, open `torch-ext/torch_binding.cpp` and hover an `#include <torch/torch.h>` directive. The resolved path should point into `/nix/store/...`, not a system path.

##
Remote development

Use the VS Code Remote-SSH extension and put the direnv hook in the remote shell’s rc. The remote integrated terminal activates the devshell on `cd`, and VS Code’s language servers — which run on the remote — inherit that environment. The `terraform/` setup is already configured this way.

##
Switching toolchains

Change the `use flake` line in `.envrc` to point at a different variant. For example:

    # CUDA 13.0
    use flake .#devShells.torch211-cxx11-cu130-x86_64-linux

    # ROCm 7.1
    use flake .#devShells.torch211-cxx11-rocm71-x86_64-linux

    # XPU
    use flake .#devShells.torch211-cxx11-xpu20253-x86_64-linux

Remove `.venv/` first if it was created against a different variant, then reload direnv to recreate it via the new devshell’s `shellHook`:

    $ rm -rf .venv
    $ direnv reload

##
noarch kernels

For Python-only (noarch) kernels, skip the CMake step in “Generating IDE-facing project files” and the C++ portions of the VS Code configuration. The `direnv` setup and `python.defaultInterpreterPath` are all that is needed.

Update on GitHub