Developing Python and Rust projects on NixOS using IntelliJ IDEA and PyCharm
Context🔗
I recently gave up on using Ubuntu on my desktop because I needed to repartition and reinstall, but the installer was giving me lots of trouble. It was easier to change distro, so I bit the bullet and gave NixOS a try on my desktop (having started using it again on my server for a few months).
Problem Statement🔗
I need to:
- be able to develop Python and Rust projects
- which have special dependencies
- use the IDEs which I'm used to — IntelliJ IDEA and PyCharm from JetBrains
- not have any Nix rebuilds inbetween me updating the code and running it
- (this notably means that my solutions so far for packaging projects using poetry2nix and naersk don't apply here.)
Note: this is not about packaging, purely about development. I've had comfortable success using poetry2nix (Python + Poetry) and naersk (Rust) for packaging my projects as flakes, but that's out of scope in this document.
Tools🔗
nix-shell
+ shell.nix
? nix develop
+ Flakes?🔗
Some preliminary research pointed to nix-shell
and nix develop
as being plausible tools for doing this.
nix-shell
lets you write ashell.nix
file in order to provide a bash shell environment.nix develop
does the same, except it's experimental and appears to be intended for use with flakes.
I went with nix-shell
because it appeared more widely used at the time and because nix develop
uses flakes. The problem with using flakes is that they are supposed to be pure — they are not meant to have access to the source code's location on disk. This then means that using them with Python editable installs (which I find useful for development) is not entirely straightforward; you have to opt-in to using impure flakes. See poetry2nix issue 425 where this issue is discussed and notably where we are reminded that flakes are an experimental feature.
direnv
(optional)🔗
There's nothing wrong with activating the shell.nix
environments using nix-shell
by hand, but it can soon become tedious, much like activating Python virtualenvs by hand does.
direnv, a tool which was useful for automatically enabling Python virtualenvs or Poetry shells, can also be used to activate Nix shells. Direnv includes built-in support for Nix, but there are actually at least 6(!) approaches of using Nix with direnv. From some hunch of simplicity, performance and flexibility, I chose to install Nix-direnv (I used the instructions for installing it into my NixOS configuration.nix
file).
From now on, I just need to write
use nix
into .envrc
in a directory and then simply cd
ing into that directory will activate the shell.nix
within.
For Poetry-defined Python projects, I installed the 'Poetry layout' into my direnv configuration, by following the wiki. I can now write:
use nix
layout poetry
into .envrc
to both activate the Nix shell and activate the Poetry environment.
Solution (Python)🔗
(Quite minimal for now, so I'll revisit this when I have a more complicated project to develop.)
Minimal example🔗
This is enough to get you able to develop a Poetry-based project and to install pure Python packages.
In the future, it may be looking at whether poetry2nix offers anything more for shell.nix
.
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.python3
pkgs.poetry
];
}
If you need any C libraries or other programs for running your software, adding those as buildInputs
seems to work.
I would note that it's not uncommon for packages that depend on dynamic libraries (.so
shared object files) to not work, because find_library
is not useful on Nix platforms. Nix issue 7307 has more information about this. I believe using poetry2nix would help, because they maintain overrides/patches for some of these cases (and it is likely possible to contribute more) — see this override for the file-magic
library for an example.
Solution (Rust)🔗
I will show some shell.nix
files that I think are useful. I'll start with a minimal example and then expand by giving something a bit more monstruous that got a more complicated project working.
Minimal example🔗
Just adding pkgs.cargo
as a build input ought to be enough to get you able to run cargo build
and therefore able to build projects.
You might want to add other tools like clippy
or rustfmt
(which includes cargo fmt
); just add these to your buildInputs
as shown.
shell.nix
:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.cargo
pkgs.clippy
pkgs.rustfmt
];
}
Monstruous example🔗
I use this monstruous example for developing a Tauri-based app. Notable points I had to bear in mind:
- I need to add a package (
cargo-tauri
) from the unstable channel of Nixpkgs. This is because it's not in the current stable channel (22.05). This won't be a problem for you if you're not developing with Tauri, but the general idea may help you out of another pickle, so I include it here anyway.- I first had to add the unstable channel using
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
. I then addedexport NIX_PATH=$NIX_PATH:$HOME/.nix-defexpr/channels
at the end of my~/.bashrc
, according to nix issue 2033, to make<nixpkgs-unstable>
usable in myshell.nix
file.
- I first had to add the unstable channel using
- I use some libraries that make use of
bindgen
to bind to C libraries (avahi
in my case here). This can be challenging because it uses libclang and it needs to be able to access the source headers for the library you're binding to, as well as the standard C library.- I cargo culted and adapted some flags from the NixOS wiki's page on Rust — notably
LIBCLANG_PATH
andBINDGEN_EXTRA_CLANG_ARGS
. - Having
pkgs.pkg-config
as abuildInputs
input is useful if you need to bind to any libraries, as well as including the libraries themselves asnativeBuildInputs
. Bear in mind that NixOS/Nixpkgs doesn't always make it obvious which package includes a given library...
- I cargo culted and adapted some flags from the NixOS wiki's page on Rust — notably
- I needed
yarn
for the JavaScript portion of my Tauri-based app. It's unlikely to be useful for you if you don't have any JavaScript in your project :-).
shell.nix
:
{ pkgs ? import <nixpkgs> {} }:
let
# We need some packages from nixpkgs-unstable
unstable = import <nixpkgs-unstable> {};
in
pkgs.mkShell {
buildInputs = [
pkgs.cargo
# not in 22.05 pkgs.cargo-tauri
unstable.cargo-tauri
pkgs.pkg-config
pkgs.yarn
];
nativeBuildInputs = [
pkgs.dbus
pkgs.openssl
pkgs.avahi
pkgs.gobject-introspection
pkgs.gtk3 # for gdk3 (!) gdk-pixbuf does not do
pkgs.libsoup
pkgs.webkitgtk # javascriptcore-gtk4 ..
];
# Needed for bindgen when binding to avahi
LIBCLANG_PATH="${pkgs.llvmPackages_latest.libclang.lib}/lib";
# Cargo culted:
# Add to rustc search path (I didn't need!)
#RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
#]);
# Add to bindgen search path
BINDGEN_EXTRA_CLANG_ARGS =
# Includes with normal include path
(builtins.map (a: ''-I"${a}/include"'') [
pkgs.avahi
pkgs.glibc.dev # very important otherwise the #include_nexts fail in clang's headers
])
# Includes with special directory paths
++ [
''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
];
}
For searchability, I will note that adding pkgs.glibc.dev
to the bindgen includes list is what solved the following confusing error:
/nix/store/yq5jvp7dvgxfcna6zmsbas24ns099ld2-clang-13.0.1-lib/lib/clang/13.0.1/include/inttypes.h:21:15: fatal error: 'inttypes.h' file not found, err: true
(This error occurs because clang's inttypes.h
uses #include_next
to fall back to/extend the normal inttypes.h
file, so you still need the one from the standard C library.)
Using JetBrains IDEs (IntelliJ IDEA, PyCharm)🔗
nix-shell
is all well and good, but I use an IDE! I need to make the tools (including compilers and libraries required to build my project) available to my IDE!
There are a few options here:
- launch the IDE from the directory, once the shell has been activated (either manually or automatically using
direnv
).- After installing the IDE into my NixOS
configuration.nix
, this just means typingpycharm-community
oridea-community
in the right shell! The IDE then is capable of using the provided development tools. The IDE also seems to directly open the current project, so it's not too inconvenient.
- After installing the IDE into my NixOS
- (untested) use an out-of-date, unmaintained IDE extension that can import the environment from
shell.nix
: Enter Nix Shell- the extension repository says this is incompatible with the latest versions of the JetBrains IDEs, so I haven't tried it.
- It may or may not be fine if you can force install it; though experience suggests that IDE extensions rot fairly quickly so I wouldn't get my hopes up.
- (untested) use an IDE extension that can import the environment from
direnv
: intellij-direnv- Of course, this means you need to have your direnv setup to use
nix-shell
(as described above) too. - I haven't tested this yet, but it looks actively maintained — latest commit within this month (at time of writing).
- Of course, this means you need to have your direnv setup to use
I don't yet know how this all works if you want to open multiple projects in an IDE; I'm not sure if they will open separate processes or not — presumably you'd need to if you wanted each project to have its own environment.
Remarks🔗
These steps worked on the publication date, 2022-08-31. If they're broken, clarification is needed, or there are other worthwhile resources including on this page, please feel free to send me a message — see the 'Contact' section of my website.