From 51e95ff6651103660cb9be9cc1bcaffb6c68ca87 Mon Sep 17 00:00:00 2001 From: Panadestein Date: Tue, 18 Aug 2026 10:55:07 +0000 Subject: [PATCH 1/2] feat(nix): :snowflake: add a flake with package, app and dev shell `nix build .#monoprop{,-mpi}` builds the bindings, `nix run` drops into a Python interpreter with monoprop importable, and `nix develop` provides the toolchain the DevContainer installs so `uv sync` and `just` work unchanged. The packaged build turns off the C++ unit tests (they resolve msgpack-cxx through a git fetch the sandbox denies) and `-march=native`, and pins the version because setuptools-scm cannot read git metadata in the sandbox. Assisted-by: GitHubCopilot:claude-opus-5 --- .gitignore | 5 ++ AGENTS.md | 5 ++ README.md | 13 ++++ docs/content/docs/building.mdx | 27 ++++++++ flake.lock | 61 +++++++++++++++++ flake.nix | 65 ++++++++++++++++++ nix/devshell.nix | 68 +++++++++++++++++++ nix/monoprop.nix | 120 +++++++++++++++++++++++++++++++++ 8 files changed, 364 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/devshell.nix create mode 100644 nix/monoprop.nix diff --git a/.gitignore b/.gitignore index e830d9ef..b4e96698 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,8 @@ benches/results/** # devcontainer files .devcontainer/devcontainer-lock.json + +# nix build symlinks and direnv state +/result +/result-* +.direnv/ diff --git a/AGENTS.md b/AGENTS.md index a932c4ab..ffef0c4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,6 +85,11 @@ uv run pytest # Run tests just build-docs # Build documentation ``` +Nix users get the same toolchain with `nix develop`. The flake (`flake.nix` plus +`nix/`) also exposes `packages.monoprop{,-mpi}` and a `nix run` app; the packaged +build disables the C++ unit tests and arch flags, and its `version` is pinned by +hand because setuptools-scm cannot read git metadata in the build sandbox. + ### Template Metaprogramming diff --git a/README.md b/README.md index 9bf340f0..c2b2e5f6 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,19 @@ Without a DevContainer, install the prerequisites from the [building guide](https://docs.monoprop.algorithmiq.tech/building#prerequisites) by hand. +### Nix + +The repository is a [Nix flake](https://wiki.nixos.org/wiki/Flakes), so on Nix or +NixOS none of the prerequisites have to be installed by hand: + +```bash +nix develop # dev shell: C++ toolchain, hwloc, MPI, uv, just, node +nix build .#monoprop # build the package (`.#monoprop-mpi` for the MPI build) +nix run # Python interpreter with monoprop importable +``` + +Inside `nix develop` the usual `uv sync` and `just` workflows apply unchanged. + ## Contributing Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. diff --git a/docs/content/docs/building.mdx b/docs/content/docs/building.mdx index aa0fe949..ff025ed7 100644 --- a/docs/content/docs/building.mdx +++ b/docs/content/docs/building.mdx @@ -31,6 +31,33 @@ The repository ships a [DevContainer](https://containers.dev/) with all of the above pre-configured; opening the folder in VS Code and rebuilding the container is the quickest route to a working environment. +## Building with Nix + +The repository is also a [Nix flake](https://wiki.nixos.org/wiki/Flakes): + +| Command | What it gives you | +| --- | --- | +| `nix develop` | a shell with every prerequisite above, plus `uv`, `just` and Node.js | +| `nix build .#monoprop` | the Python package, built without MPI | +| `nix build .#monoprop-mpi` | the same package with `monoprop_ENABLE_MPI=ON` | +| `nix run` | a Python interpreter with `monoprop` importable | + +Inside `nix develop` the `uv sync` and `just` workflows below apply unchanged. The +shell sets two variables that only matter on NixOS: `UV_PYTHON_PREFERENCE=only-system`, +because uv's managed interpreters expect a loader NixOS does not provide, and +`LD_LIBRARY_PATH`, so that manylinux wheels can resolve `libstdc++`. + +The packaged build deviates from the `uv` build in three places, all in +`nix/monoprop.nix`: + +- the C++ unit tests are disabled, because they resolve msgpack-cxx through a git + fetch that the build sandbox denies — build them from the dev shell instead; +- `monoprop_ENABLE_ARCH_FLAGS` is off, since a store path may be substituted onto a + machine other than the one that built it; pass + `.override { enableArchFlags = true; }` for a native build; +- the version is pinned, because setuptools-scm cannot read git metadata inside the + sandbox; bump it alongside the release tag. + ## Building the Python bindings `uv` creates a virtual environment, installs the Python dependencies, and diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..de06ac71 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1786862985, + "narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..968abb00 --- /dev/null +++ b/flake.nix @@ -0,0 +1,65 @@ +{ + description = "monoprop: Majorana and Pauli propagation"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + # Pinned rather than taken from `pkgs.python3` so that a nixpkgs bump of + # the default interpreter cannot silently change the ABI of the wheel. + python = pkgs.python312; + + # nixpkgs still ships 1.0.2; `[build-system] requires` asks for 1.0.3. + # Drop this once nixpkgs catches up. + scikit-build-core = python.pkgs.scikit-build-core.overridePythonAttrs (_: rec { + version = "1.0.3"; + src = pkgs.fetchPypi { + pname = "scikit_build_core"; + inherit version; + hash = "sha256-pNegWXjuN5dcN3Q1EMiZHi3rzn74OvsKB8DFdv1PFug="; + }; + doCheck = false; + }); + + monoprop = python.pkgs.callPackage ./nix/monoprop.nix { inherit scikit-build-core; }; + monoprop-mpi = monoprop.override { withMPI = true; }; + + replEnv = python.withPackages (ps: [ + monoprop + ps.numpy + ]); + in + { + packages = { + default = monoprop; + inherit monoprop monoprop-mpi; + }; + + apps.default = { + type = "app"; + program = "${replEnv}/bin/python"; + meta.description = "Python interpreter with monoprop importable"; + }; + + devShells.default = pkgs.callPackage ./nix/devshell.nix { inherit python; }; + + checks = { + inherit monoprop; + }; + + formatter = pkgs.nixfmt-tree; + } + ); +} diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 00000000..22ec6347 --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,68 @@ +{ + lib, + mkShell, + stdenv, + python, + + # C++ toolchain + cmake, + ninja, + pkg-config, + clang-tools, + gdb, + lcov, + doxygen, + + # C++ dependencies + boost, + hwloc, + openmpi, + + # workflow tooling + git, + just, + nodejs, + uv, + zlib, +}: + +mkShell { + nativeBuildInputs = [ + cmake + ninja + pkg-config + clang-tools + gdb + lcov + doxygen + git + just + nodejs + python + uv + ]; + + # Host inputs, so that CMake's setup hook puts them on NIXPKGS_CMAKE_PREFIX_PATH + # and PKG_CONFIG_PATH for the CMake run scikit-build-core drives. + buildInputs = [ + boost + hwloc + openmpi + ]; + + shellHook = '' + # `[tool.uv] python-preference = "only-managed"` pulls prebuilt interpreters + # that expect a loader NixOS does not provide; use this shell's CPython. + export UV_PYTHON_PREFERENCE=only-system + export UV_PYTHON="${python}/bin/python" + + # manylinux wheels (numpy, qiskit, ...) resolve libstdc++ through the ambient + # loader path, which is empty on NixOS. + export LD_LIBRARY_PATH="${ + lib.makeLibraryPath [ + stdenv.cc.cc.lib + zlib + ] + }''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + ''; +} diff --git a/nix/monoprop.nix b/nix/monoprop.nix new file mode 100644 index 00000000..dbc9b0db --- /dev/null +++ b/nix/monoprop.nix @@ -0,0 +1,120 @@ +{ + lib, + buildPythonPackage, + python, + + # build tooling + cmake, + ninja, + pkg-config, + nanobind, + scikit-build-core, + setuptools-scm, + + # C++ dependencies + boost, + hwloc, + + # runtime dependencies + msgpack, + numpy, + + # optional MPI support + mpi, + mpi4py, + withMPI ? false, + + # `-march=native` is the upstream default; it is off here because a store path + # may be built on one machine and substituted onto another. + enableArchFlags ? false, + + # setuptools-scm derives the version from git metadata, which the build sandbox + # does not see. Bump alongside the release tag. + version ? "0.8.0", +}: + +buildPythonPackage { + pname = "monoprop"; + inherit version; + pyproject = true; + + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ + ../CMakeLists.txt + ../LICENSE + ../README.md + ../pyproject.toml + # `tools/generate-dispatch.py` stamps this header onto the generated files. + ../.github/license-header.txt + ../cmake + ../cpp + ../src + ../tools + ]; + }; + + # scikit-build-core invokes CMake itself; the nixpkgs hook must not configure + # the tree first. + dontUseCmakeConfigure = true; + + nativeBuildInputs = [ + cmake + ninja + pkg-config + ]; + + build-system = [ + nanobind + scikit-build-core + setuptools-scm + # Upstream lists mpi4py in `[build-system] requires` unconditionally, even + # though the extension only links MPI when `withMPI` is set. + mpi4py + ]; + + buildInputs = [ + boost + hwloc + ] + ++ lib.optionals withMPI [ mpi ]; + + dependencies = [ + msgpack + numpy + ] + ++ lib.optionals withMPI [ mpi4py ]; + + env = { + SETUPTOOLS_SCM_PRETEND_VERSION = version; + SKBUILD_CMAKE_DEFINE = lib.concatStringsSep ";" ( + [ + # The C++ test target resolves msgpack-cxx through CPM, i.e. a git fetch + # the sandbox denies; C++ tests belong to the dev shell anyway. + "monoprop_ENABLE_CXX_UNIT_TESTS=OFF" + "monoprop_ENABLE_ARCH_FLAGS=${if enableArchFlags then "ON" else "OFF"}" + # nanobind's CMake config lives inside its Python package, which is not + # on the interpreter's own site-packages path under nixpkgs. + "nanobind_DIR=${nanobind}/${python.sitePackages}/nanobind/cmake" + ] + ++ lib.optionals withMPI [ "monoprop_ENABLE_MPI=ON" ] + ); + }; + + # Overrides the single-job default that `[tool.scikit-build]` sets for laptops. + preBuild = '' + export SKBUILD_BUILD_TOOL_ARGS="-j$NIX_BUILD_CORES" + ''; + + # The test suite lives outside the wheel and needs fixtures excluded from src. + doCheck = false; + pythonImportsCheck = [ "monoprop" ]; + + meta = { + description = "High-performance Majorana and Pauli propagation"; + homepage = "https://github.com/Algorithmiq/monoprop"; + changelog = "https://github.com/Algorithmiq/monoprop/releases"; + license = lib.licenses.asl20; + platforms = lib.platforms.unix; + }; +} From 5dac45c21f4460741e0473678464e492d910a821 Mon Sep 17 00:00:00 2001 From: Panadestein Date: Tue, 18 Aug 2026 15:09:55 +0000 Subject: [PATCH 2/2] chore: better flake description. --- flake.nix | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/flake.nix b/flake.nix index 968abb00..768e8f48 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "monoprop: Majorana and Pauli propagation"; + description = "monoprop: because your operators deserve to propagate at escape velocity."; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -16,13 +16,7 @@ system: let pkgs = nixpkgs.legacyPackages.${system}; - - # Pinned rather than taken from `pkgs.python3` so that a nixpkgs bump of - # the default interpreter cannot silently change the ABI of the wheel. python = pkgs.python312; - - # nixpkgs still ships 1.0.2; `[build-system] requires` asks for 1.0.3. - # Drop this once nixpkgs catches up. scikit-build-core = python.pkgs.scikit-build-core.overridePythonAttrs (_: rec { version = "1.0.3"; src = pkgs.fetchPypi {