From b4848bb6610fe016f608a303e75c6d3924af0dda Mon Sep 17 00:00:00 2001 From: Konstantin Fickel Date: Sun, 15 Feb 2026 17:31:02 +0100 Subject: [PATCH] build: migrate to devenv - Add devenv.nix with Python/uv setup and git-hooks - Add devenv.yaml and devenv.lock - Refactor flake.nix to use devenv integration - Update .envrc from flake to devenv - Add devenv artifacts to .gitignore --- .envrc | 4 +- .gitignore | 3 + devenv.lock | 123 +++++++++++++++++++++ devenv.nix | 28 +++++ devenv.yaml | 6 + flake.lock | 82 ++++++++++++-- flake.nix | 311 +++++++++++++++++++++++----------------------------- 7 files changed, 373 insertions(+), 184 deletions(-) create mode 100644 devenv.lock create mode 100644 devenv.nix create mode 100644 devenv.yaml diff --git a/.envrc b/.envrc index 99764ea..163bbd9 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,3 @@ -use flake .#impure +#!/usr/bin/env bash +eval "$(devenv direnvrc)" +use devenv diff --git a/.gitignore b/.gitignore index 2b9abaa..ff4088b 100644 --- a/.gitignore +++ b/.gitignore @@ -177,3 +177,6 @@ pyrightconfig.json .direnv test-report.xml +.devenv +.devenv.flake.nix +.pre-commit-config.yaml diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..9996189 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,123 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1771157881, + "owner": "cachix", + "repo": "devenv", + "rev": "b0b3dfa70ec90fa49f672e579f186faf4f61bd4b", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770726378, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762808025, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "inputs": { + "nixpkgs-src": "nixpkgs-src" + }, + "locked": { + "lastModified": 1770434727, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "8430f16a39c27bdeef236f1eeb56f0b51b33d348", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-src": { + "flake": false, + "locked": { + "lastModified": 1769922788, + "narHash": "sha256-H3AfG4ObMDTkTJYkd8cz1/RbY9LatN5Mk4UF48VuSXc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "207d15f1a6603226e1e223dc79ac29c7846da32e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..ce1097e --- /dev/null +++ b/devenv.nix @@ -0,0 +1,28 @@ +{ + pkgs, + ... +}: +{ + languages = { + python = { + enable = true; + uv.enable = true; + }; + }; + + packages = [ + pkgs.basedpyright + ]; + + git-hooks.hooks = { + basedpyright = { + enable = true; + entry = "${pkgs.basedpyright}/bin/basedpyright"; + files = "\\.py$"; + types = [ "file" ]; + }; + ruff.enable = true; + ruff-format.enable = true; + commitizen.enable = true; + }; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..28877ba --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,6 @@ +inputs: + git-hooks: + url: github:cachix/git-hooks.nix + inputs: + nixpkgs: + follows: nixpkgs diff --git a/flake.lock b/flake.lock index 4fa0a20..b04d8e3 100644 --- a/flake.lock +++ b/flake.lock @@ -1,16 +1,75 @@ { "nodes": { - "nixpkgs": { + "flake-compat": { + "flake": false, "locked": { - "lastModified": 1770197578, - "narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "NixOS", - "repo": "nixpkgs", - "rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770726378, + "narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1771008912, + "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a82ccc39b39b621151d6732718e3e250109076fa", + "type": "github" + }, + "original": { + "owner": "nixos", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" @@ -29,11 +88,11 @@ ] }, "locked": { - "lastModified": 1763662255, - "narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=", + "lastModified": 1771039651, + "narHash": "sha256-WZOfX4APbc6vmL14ZWJXgBeRfEER8H+OIX0D0nSmv0M=", "owner": "pyproject-nix", "repo": "build-system-pkgs", - "rev": "042904167604c681a090c07eb6967b4dd4dae88c", + "rev": "69bc2b53b79cbd6ce9f66f506fc962b45b5e68b9", "type": "github" }, "original": { @@ -64,6 +123,7 @@ }, "root": { "inputs": { + "git-hooks": "git-hooks", "nixpkgs": "nixpkgs", "pyproject-build-systems": "pyproject-build-systems", "pyproject-nix": "pyproject-nix", @@ -80,11 +140,11 @@ ] }, "locked": { - "lastModified": 1770331927, - "narHash": "sha256-jlOvO++uvne/lTgWqdI4VhTV5OpVWi70ZDVBlT6vGSs=", + "lastModified": 1770770348, + "narHash": "sha256-A2GzkmzdYvdgmMEu5yxW+xhossP+txrYb7RuzRaqhlg=", "owner": "pyproject-nix", "repo": "uv2nix", - "rev": "5b43a934e15b23bfba6c408cba1c570eccf80080", + "rev": "5d1b2cb4fe3158043fbafbbe2e46238abbc954b0", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d112b66..02359db 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,8 @@ { - description = "Hello world flake using uv2nix"; + description = "Using Markdown Files to organize your life as a @Tag-Stream"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; pyproject-nix = { url = "github:pyproject-nix/pyproject.nix"; @@ -21,70 +21,135 @@ inputs.uv2nix.follows = "uv2nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + git-hooks = { + url = "github:cachix/git-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { self, nixpkgs, - uv2nix, pyproject-nix, + uv2nix, pyproject-build-systems, + git-hooks, ... }: let inherit (nixpkgs) lib; + forAllSystems = lib.genAttrs lib.systems.flakeExposed; - # Load a uv workspace from a workspace root. - # Uv2nix treats all uv projects as workspace projects. workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; - # Create package overlay from workspace. overlay = workspace.mkPyprojectOverlay { - # Prefer prebuilt binary wheels as a package source. - # Sdists are less likely to "just work" because of the metadata missing from uv.lock. - # Binary wheels are more likely to, but may still require overrides for library dependencies. - sourcePreference = "wheel"; # or sourcePreference = "sdist"; - # Optionally customise PEP 508 environment - # environ = { - # platform_release = "5.10.65"; - # }; + sourcePreference = "wheel"; }; - # Extend generated overlay with build fixups - # - # Uv2nix can only work with what it has, and uv.lock is missing essential metadata to perform some builds. - # This is an additional overlay implementing build fixups. - # See: - # - https://pyproject-nix.github.io/uv2nix/FAQ.html - pyprojectOverrides = _final: _prev: { - # Implement build fixups here. - # Note that uv2nix is _not_ using Nixpkgs buildPythonPackage. - # It's using https://pyproject-nix.github.io/pyproject.nix/build.html + editableOverlay = workspace.mkEditablePyprojectOverlay { + root = "$REPO_ROOT"; }; - # This example is only using x86_64-linux - pkgs = nixpkgs.legacyPackages.x86_64-linux; + pythonSets = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + inherit (pkgs) stdenv; - # Use Python 3.14 from nixpkgs - python = pkgs.python314; + baseSet = pkgs.callPackage pyproject-nix.build.packages { + python = pkgs.python313; + }; - # Construct package set - pythonSet = - # Use base package set from pyproject.nix builders - (pkgs.callPackage pyproject-nix.build.packages { - inherit python; - }).overrideScope - ( - lib.composeManyExtensions [ - pyproject-build-systems.overlays.default - overlay - pyprojectOverrides - ] - ); + pyprojectOverrides = _final: prev: { + streamd = prev.streamd.overrideAttrs (old: { + passthru = old.passthru // { + tests = (old.passthru.tests or { }) // { + pytest = stdenv.mkDerivation { + name = "${_final.streamd.name}-pytest"; + inherit (_final.streamd) src; + nativeBuildInputs = [ + (_final.mkVirtualEnv "streamd-pytest-env" { + streamd = [ "dev" ]; + }) + ]; + dontConfigure = true; + buildPhase = '' + runHook preBuild + # Exit code 5 means no tests collected — allow it so the + # check succeeds on an empty test suite. + pytest || [ $? -eq 5 ] + runHook postBuild + ''; + installPhase = '' + runHook preInstall + touch $out + runHook postInstall + ''; + }; + }; + }; + }); + }; + + in + baseSet.overrideScope ( + lib.composeManyExtensions [ + pyproject-build-systems.overlays.default + overlay + pyprojectOverrides + ] + ) + ); + + mkGitHooksCheck = + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + pythonSet = pythonSets.${system}; + venv = pythonSet.mkVirtualEnv "streamd-check-env" workspace.deps.default; + in + git-hooks.lib.${system}.run { + src = ./.; + hooks = { + basedpyright = { + enable = true; + entry = "${pkgs.basedpyright}/bin/basedpyright --pythonpath ${venv}/bin/python --project ${ + pkgs.writeText "pyrightconfig.json" ( + builtins.toJSON { + reportMissingTypeStubs = false; + reportUnnecessaryTypeIgnoreComment = false; + } + ) + }"; + files = "\\.py$"; + types = [ "file" ]; + }; + ruff.enable = true; + ruff-format.enable = true; + commitizen.enable = true; + }; + }; in { + packages = forAllSystems ( + system: + let + pythonSet = pythonSets.${system}; + pkgs = nixpkgs.legacyPackages.${system}; + inherit (pkgs.callPackages pyproject-nix.build.util { }) mkApplication; + in + rec { + streamd = mkApplication { + venv = pythonSet.mkVirtualEnv "streamd-env" workspace.deps.default; + package = pythonSet.streamd; + }; + default = streamd; + } + ); + homeManagerModules.default = { lib, @@ -93,176 +158,78 @@ ... }: let - cfg = config.programs.streamer; + cfg = config.programs.streamd; in { - options.programs.streamer = { - enable = lib.mkEnableOption "streamer"; + options.programs.streamd = { + enable = lib.mkEnableOption "streamd"; base-folder = lib.mkOption { type = lib.types.str; - description = "Base Folder of Streamer"; + description = "Base Folder of streamd"; }; package = lib.mkOption { type = lib.types.package; - default = self.packages.${pkgs.system}.streamer; - defaultText = lib.literalExpression "inputs.streamer.packages.\${pkgs.system}.streamer"; - description = "The package to use for the streamer binary."; + default = self.packages.${pkgs.system}.streamd; + defaultText = lib.literalExpression "inputs.streamd.packages.\${pkgs.system}.streamd"; + description = "The package to use for the streamd binary."; }; }; config = lib.mkIf cfg.enable { assertions = [ - (lib.hm.assertions.assertPlatform "programs.streamer" pkgs lib.platforms.linux) + (lib.hm.assertions.assertPlatform "programs.streamd" pkgs lib.platforms.linux) ]; home.packages = [ cfg.package ]; - xdg.configFile."streamer/config.yaml".source = - (pkgs.formats.yaml { }).generate "streamer-configuration" + xdg.configFile."streamd/config.yaml".source = + (pkgs.formats.yaml { }).generate "streamd-configuration" { base_folder = cfg.base-folder; }; - home.shellAliases.s = "streamer"; + home.shellAliases.s = "streamd"; }; }; - # Package a virtual environment as our main application. - # - # Enable no optional dependencies for production build. - packages.x86_64-linux = + checks = forAllSystems ( + system: let - streamer = pythonSet.mkVirtualEnv "streamer-env" workspace.deps.default; + pythonSet = pythonSets.${system}; in { - inherit streamer; - default = streamer; - }; + inherit (pythonSet.streamd.passthru.tests) pytest; + pre-commit = mkGitHooksCheck system; + } + ); - # Make streamer runnable with `nix run` - apps.x86_64-linux = { - default = { - type = "app"; - program = "${self.packages.x86_64-linux.default}/bin/streamer"; - }; - }; - - # This example provides two different modes of development: - # - Impurely using uv to manage virtual environments - # - Pure development using uv2nix to manage virtual environments - devShells.x86_64-linux = { - # It is of course perfectly OK to keep using an impure virtualenv workflow and only use uv2nix to build packages. - # This devShell simply adds Python and undoes the dependency leakage done by Nixpkgs Python infrastructure. - impure = pkgs.mkShell { - packages = with pkgs; [ - python - uv - pre-commit - bashInteractive - ]; - env = { - # Prevent uv from managing Python downloads - UV_PYTHON_DOWNLOADS = "never"; - # Force uv to use nixpkgs Python interpreter - UV_PYTHON = python.interpreter; - } - // lib.optionalAttrs pkgs.stdenv.isLinux { - # Python libraries often load native shared objects using dlopen(3). - # Setting LD_LIBRARY_PATH makes the dynamic library loader aware of libraries without using RPATH for lookup. - LD_LIBRARY_PATH = lib.makeLibraryPath pkgs.pythonManylinuxPackages.manylinux1; - }; - shellHook = '' - unset PYTHONPATH - ''; - }; - - # This devShell uses uv2nix to construct a virtual environment purely from Nix, using the same dependency specification as the application. - # The notable difference is that we also apply another overlay here enabling editable mode ( https://setuptools.pypa.io/en/latest/userguide/development_mode.html ). - # - # This means that any changes done to your local files do not require a rebuild. - # - # Note: Editable package support is still unstable and subject to change. - uv2nix = - let - # Create an overlay enabling editable mode for all local dependencies. - editableOverlay = workspace.mkEditablePyprojectOverlay { - # Use environment variable - root = "$REPO_ROOT"; - # Optional: Only enable editable for these packages - # members = [ "streamer" ]; - }; - - # Override previous set with our overrideable overlay. - editablePythonSet = pythonSet.overrideScope ( - lib.composeManyExtensions [ - editableOverlay - - # Apply fixups for building an editable package of your workspace packages - (final: prev: { - streamer = prev.streamer.overrideAttrs (old: { - # It's a good idea to filter the sources going into an editable build - # so the editable package doesn't have to be rebuilt on every change. - src = lib.fileset.toSource { - root = old.src; - fileset = lib.fileset.unions [ - (old.src + "/pyproject.toml") - (old.src + "/README.md") - (old.src + "/src/streamer/__init__.py") - ]; - }; - - # Hatchling (our build system) has a dependency on the `editables` package when building editables. - # - # In normal Python flows this dependency is dynamically handled, and doesn't need to be explicitly declared. - # This behaviour is documented in PEP-660. - # - # With Nix the dependency needs to be explicitly declared. - nativeBuildInputs = - old.nativeBuildInputs - ++ final.resolveBuildSystem { - editables = [ ]; - }; - }); - - }) - ] - ); - - # Build virtual environment, with local packages being editable. - # - # Enable all optional dependencies for development. - virtualenv = editablePythonSet.mkVirtualEnv "streamer-dev-env" workspace.deps.all; - - in - pkgs.mkShell { - packages = with pkgs; [ + devShells = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + pythonSet = pythonSets.${system}.overrideScope editableOverlay; + virtualenv = pythonSet.mkVirtualEnv "streamd-dev-env" workspace.deps.all; + in + { + default = pkgs.mkShell { + packages = [ virtualenv - uv - pre-commit - bashInteractive + pkgs.uv ]; - env = { - # Don't create venv using uv UV_NO_SYNC = "1"; - - # Force uv to use Python interpreter from venv - UV_PYTHON = "${virtualenv}/bin/python"; - - # Prevent uv from downloading managed Python's + UV_PYTHON = pythonSet.python.interpreter; UV_PYTHON_DOWNLOADS = "never"; }; - shellHook = '' - # Undo dependency propagation by nixpkgs. unset PYTHONPATH - - # Get repository root using git. This is expanded at runtime by the editable `.pth` machinery. export REPO_ROOT=$(git rev-parse --show-toplevel) + ${(mkGitHooksCheck system).shellHook} ''; }; - }; + } + ); }; }