diff --git a/flake.nix b/flake.nix index 993e48ae5..aec12241c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,7 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + hooks = { url = "github:cachix/git-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -32,217 +33,25 @@ outputs = { uv, self, - hooks, nixpkgs, - pyproject, - build-systems, ... }: let inherit (nixpkgs) lib; - - getPythonVersion = let - val = builtins.getEnv "PYTHON_VERSION"; - result = - if val == "" - then "3.13" - else val; - in - builtins.replaceStrings ["."] [""] result; + inherit (import ./nix/helpers.nix {inherit self nixpkgs overlay;}) forAllSystems; workspace = uv.lib.workspace.loadWorkspace {workspaceRoot = ./.;}; overlay = workspace.mkPyprojectOverlay {sourcePreference = "wheel";}; - forAllSystems = f: - lib.genAttrs [ - "x86_64-linux" - "aarch64-linux" - "x86_64-darwin" - "aarch64-darwin" - ] (system: - f rec { - inherit system; - - pkgs = nixpkgs.legacyPackages.${system}; - python = pkgs."python${getPythonVersion}"; - - pythonSet = - (pkgs.callPackage pyproject.build.packages {inherit python;}).overrideScope ( - lib.composeManyExtensions [ - build-systems.overlays.default - overlay - ] - ); + importWithAttrs = path: + forAllSystems (attrs: + import path { + inherit workspace self lib; + inherit (attrs) pkgs system python pythonSet; }); in { - devShells = - forAllSystems ({ - pkgs, - system, - python, - pythonSet, - ... - }: let - check = self.checks.${system}.pre-commit; - in { - default = let - editableOverlay = - workspace.mkEditablePyprojectOverlay { - root = "$REPO_ROOT"; - }; - - editablePythonSet = - pythonSet.overrideScope ( - lib.composeManyExtensions [ - editableOverlay - - (final: prev: { - proselint = - prev.proselint.overrideAttrs (old: { - nativeBuildInputs = - old.nativeBuildInputs - ++ final.resolveBuildSystem { - editables = []; - }; - }); - }) - - (final: prev: { - google-re2 = - prev.google-re2.overrideAttrs (old: { - nativeBuildInputs = - (old.nativeBuildInputs or []) - ++ (with final; [setuptools pybind11]) - ++ (with pkgs; [re2 abseil-cpp]); - }); - }) - ] - ); - - virtualenv = editablePythonSet.mkVirtualEnv "proselint-env" {proselint = ["test" "dev" "web"];}; - in - pkgs.mkShell { - buildInputs = check.enabledPackages; - - packages = [ - virtualenv - pkgs.git-cliff - pkgs.typos - pkgs.uv - ]; - - env = { - UV_NO_SYNC = "1"; - UV_PYTHON = python.interpreter; - UV_PYTHON_DOWNLOADS = "never"; - LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib"; - }; - - shellHook = - '' - export REPO_ROOT=$(git rev-parse --show-toplevel) - unset PYTHONPATH - '' - + check.shellHook; - }; - }); - - packages = - forAllSystems ({ - pythonSet, - pkgs, - ... - }: { - default = pythonSet.mkVirtualEnv "proselint-env" workspace.deps.default; - - wheel = - pythonSet.proselint.override { - pyprojectHook = pythonSet.pyprojectDistHook; - }; - - sdist = - (pythonSet.proselint.override { - pyprojectHook = pythonSet.pyprojectDistHook; - }).overrideAttrs (old: { - env.uvBuildType = "sdist"; - }); - - api = let - env = - pythonSet.mkVirtualEnv "proselint-api-env" { - proselint = ["web"]; - }; - in - pkgs.stdenv.mkDerivation { - name = "proselint-api"; - src = ./.; - - dontBuild = true; - dontConfigure = true; - - installPhase = '' - mkdir -p $out/bin $out/share/proselint-api - - cp $src/app.py $out/share/proselint-api - - cat > $out/bin/proselint-api-run <<-EOF - #!${pkgs.bash}/bin/bash - cd $out/share/proselint-api - exec ${env}/bin/uvicorn app:app "\$@" - EOF - - chmod +x $out/bin/proselint-api-run - ''; - }; - }); - - apps = - forAllSystems ({system, ...}: { - default = { - type = "app"; - program = "${self.packages.${system}.default}/bin/proselint"; - }; - - api = { - type = "app"; - program = "${self.packages.${system}.api}/bin/proselint-api-run"; - }; - }); - - checks = - forAllSystems ({ - system, - pkgs, - ... - }: { - pre-commit = - hooks.lib.${system}.run { - src = ./.; - package = pkgs.prek; - - hooks = { - trim-trailing-whitespace.enable = true; - end-of-file-fixer.enable = true; - mixed-line-endings.enable = true; - markdownlint.enable = true; - - ruff.enable = true; - pyright = let - pyright = pkgs.basedpyright; - in { - enable = true; - package = pyright; - entry = "${pyright}/bin/basedpyright"; - }; - - convco.enable = true; - - alejandra.enable = true; - statix = { - enable = true; - settings.ignore = ["/.direnv"]; - }; - }; - }; - }); + devShells = importWithAttrs ./nix/dev-shells.nix; + packages = importWithAttrs ./nix/packages.nix; + checks = importWithAttrs ./nix/checks.nix; + apps = importWithAttrs ./nix/apps.nix; }; } diff --git a/nix/apps.nix b/nix/apps.nix new file mode 100644 index 000000000..33903ede9 --- /dev/null +++ b/nix/apps.nix @@ -0,0 +1,16 @@ +{ + lib, + self, + system, + ... +}: { + default = { + type = "app"; + program = lib.getExe' self.packages.${system}.default "proselint"; + }; + + api = { + type = "app"; + program = lib.getExe' self.packages.${system}.api "proselint-api-run"; + }; +} diff --git a/nix/checks.nix b/nix/checks.nix new file mode 100644 index 000000000..471b64d68 --- /dev/null +++ b/nix/checks.nix @@ -0,0 +1,34 @@ +{ + lib, + self, + pkgs, + system, + ... +}: { + pre-commit = + self.inputs.hooks.lib.${system}.run { + src = ./.; + package = pkgs.prek; + + hooks = { + trim-trailing-whitespace.enable = true; + end-of-file-fixer.enable = true; + mixed-line-endings.enable = true; + markdownlint.enable = true; + alejandra.enable = true; + convco.enable = true; + ruff.enable = true; + + statix = { + enable = true; + settings.ignore = ["/.direnv"]; + }; + + pyright = { + enable = true; + package = pkgs.basedpyright; + entry = lib.getExe pkgs.basedpyright; + }; + }; + }; +} diff --git a/nix/dev-shells.nix b/nix/dev-shells.nix new file mode 100644 index 000000000..cfc37db23 --- /dev/null +++ b/nix/dev-shells.nix @@ -0,0 +1,66 @@ +{ + workspace, + self, + lib, + system, + pkgs, + python, + pythonSet, +}: let + check = self.checks.${system}.pre-commit; + + editablePythonSet = + pythonSet.overrideScope ( + lib.composeManyExtensions [ + (workspace.mkEditablePyprojectOverlay {root = "$REPO_ROOT";}) + + (final: prev: { + proselint = + prev.proselint.overrideAttrs (old: { + nativeBuildInputs = + old.nativeBuildInputs + ++ final.resolveBuildSystem { + editables = []; + }; + }); + }) + + (final: prev: { + google-re2 = + prev.google-re2.overrideAttrs (old: { + nativeBuildInputs = + (old.nativeBuildInputs or []) + ++ (with final; [setuptools pybind11]) + ++ (with pkgs; [re2 abseil-cpp]); + }); + }) + ] + ); +in { + default = + pkgs.mkShell { + buildInputs = check.enabledPackages; + + packages = [ + (editablePythonSet.mkVirtualEnv "proselint-env" {proselint = ["test" "dev" "web"];}) + + pkgs.git-cliff + pkgs.typos + pkgs.uv + ]; + + env = { + UV_NO_SYNC = "1"; + UV_PYTHON = python.interpreter; + UV_PYTHON_DOWNLOADS = "never"; + LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib"; + }; + + shellHook = + '' + export REPO_ROOT=$(git rev-parse --show-toplevel) + unset PYTHONPATH + '' + + check.shellHook; + }; +} diff --git a/nix/helpers.nix b/nix/helpers.nix new file mode 100644 index 000000000..2b813345e --- /dev/null +++ b/nix/helpers.nix @@ -0,0 +1,36 @@ +{ + self, + nixpkgs, + overlay, +}: let + getPythonVersion = let + val = builtins.getEnv "PYTHON_VERSION"; + + result = + if val == "" + then "3.13" + else val; + in + builtins.replaceStrings ["."] [""] result; +in { + forAllSystems = f: + nixpkgs.lib.genAttrs [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ] (system: + f rec { + inherit system; + + pkgs = nixpkgs.legacyPackages.${system}; + python = pkgs."python${getPythonVersion}"; + pythonSet = + (pkgs.callPackage self.inputs.pyproject.build.packages {inherit python;}).overrideScope ( + nixpkgs.lib.composeManyExtensions [ + self.inputs.build-systems.overlays.default + overlay + ] + ); + }); +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 000000000..a86ff5215 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,13 @@ +{ + workspace, + pythonSet, + pkgs, + ... +}: { + default = pythonSet.mkVirtualEnv "proselint-env" workspace.deps.default; + + wheel = pythonSet.proselint.override {pyprojectHook = pythonSet.pyprojectDistHook;}; + sdist = (pythonSet.proselint.override {pyprojectHook = pythonSet.pyprojectDistHook;}).overrideAttrs (old: {env.uvBuildType = "sdist";}); + + api = pkgs.callPackage ./proselint-api.nix {inherit pythonSet;}; +} diff --git a/nix/proselint-api.nix b/nix/proselint-api.nix new file mode 100644 index 000000000..59419608d --- /dev/null +++ b/nix/proselint-api.nix @@ -0,0 +1,33 @@ +{ + pythonSet, + stdenv, + bash, + lib, + ... +}: let + name = "proselint-api"; + env = + pythonSet.mkVirtualEnv "${name}-env" { + proselint = ["web"]; + }; +in + stdenv.mkDerivation { + inherit name; + src = ../.; + + dontBuild = true; + dontConfigure = true; + + installPhase = '' + mkdir -p $out/bin $out/share/${name} + cp $src/app.py $out/share/${name} + + cat > $out/bin/${name}-run <<-EOF + #!${lib.getExe bash} + cd $out/share/${name} + exec ${lib.getExe' env "uvicorn"} app:app "\$@" + EOF + + chmod +x $out/bin/${name}-run + ''; + }