1{
2 buildPythonPackage,
3 lib,
4 hatchling,
5 tomli-w,
6}:
7{
8 pname,
9 version,
10
11 # Editable root as string.
12 # Environment variables will be expanded at runtime using os.path.expandvars.
13 root,
14
15 # Arguments passed on verbatim to buildPythonPackage
16 derivationArgs ? { },
17
18 # Python dependencies
19 dependencies ? [ ],
20 optional-dependencies ? { },
21
22 # PEP-518 build-system https://peps.python.org/pep-518
23 build-system ? [ ],
24
25 # PEP-621 entry points https://peps.python.org/pep-0621/#entry-points
26 scripts ? { },
27 gui-scripts ? { },
28 entry-points ? { },
29
30 passthru ? { },
31 meta ? { },
32}:
33
34# Create a PEP-660 (https://peps.python.org/pep-0660/) editable package pointing to an impure location outside the Nix store.
35# The primary use case of this function is to enable local development workflows where the local package is installed into a virtualenv-like environment using withPackages.
36
37assert lib.isString root;
38let
39 # In editable mode build-system's are considered to be runtime dependencies.
40 dependencies' = dependencies ++ build-system;
41
42 pyproject = {
43 # PEP-621 project table
44 project = {
45 name = pname;
46 inherit
47 version
48 scripts
49 gui-scripts
50 entry-points
51 ;
52 dependencies = map lib.getName dependencies';
53 optional-dependencies = lib.mapAttrs (_: map lib.getName) optional-dependencies;
54 };
55
56 # Allow empty package
57 tool.hatch.build.targets.wheel.bypass-selection = true;
58
59 # Include our editable pointer file in build
60 tool.hatch.build.targets.wheel.force-include."_${pname}.pth" = "_${pname}.pth";
61
62 # Build editable package using hatchling
63 build-system = {
64 requires = [ "hatchling" ];
65 build-backend = "hatchling.build";
66 };
67 };
68
69in
70buildPythonPackage (
71 {
72 inherit
73 pname
74 version
75 optional-dependencies
76 passthru
77 meta
78 ;
79 dependencies = dependencies';
80
81 pyproject = true;
82
83 unpackPhase = ''
84 python -c "import json, tomli_w; print(tomli_w.dumps(json.load(open('$pyprojectContentsPath'))))" > pyproject.toml
85 echo 'import os.path, sys; sys.path.insert(0, os.path.expandvars("${root}"))' > _${pname}.pth
86 '';
87
88 build-system = [ hatchling ];
89 }
90 // derivationArgs
91 // {
92 # Note: Using formats.toml generates another intermediary derivation that needs to be built.
93 # We inline the same functionality for better UX.
94 nativeBuildInputs = (derivationArgs.nativeBuildInputs or [ ]) ++ [ tomli-w ];
95 pyprojectContents = builtins.toJSON pyproject;
96 passAsFile = [ "pyprojectContents" ];
97 preferLocalBuild = true;
98 }
99)