summaryrefslogtreecommitdiff
path: root/pkgs/development/interpreters/python/editable.nix
blob: 2a492e54c30166fc42a44f56cb4eb77d2c29ac39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
{
  buildPythonPackage,
  lib,
  hatchling,
  tomli-w,
}:
{
  pname,
  version,

  # Editable root as string.
  # Environment variables will be expanded at runtime using os.path.expandvars.
  root,

  # Arguments passed on verbatim to buildPythonPackage
  derivationArgs ? { },

  # Python dependencies
  dependencies ? [ ],
  optional-dependencies ? { },

  # PEP-518 build-system https://peps.python.org/pep-518
  build-system ? [ ],

  # PEP-621 entry points https://peps.python.org/pep-0621/#entry-points
  scripts ? { },
  gui-scripts ? { },
  entry-points ? { },

  passthru ? { },
  meta ? { },
}:

# Create a PEP-660 (https://peps.python.org/pep-0660/) editable package pointing to an impure location outside the Nix store.
# 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.

assert lib.isString root;
let
  # In editable mode build-system's are considered to be runtime dependencies.
  dependencies' = dependencies ++ build-system;

  pyproject = {
    # PEP-621 project table
    project = {
      name = pname;
      inherit
        version
        scripts
        gui-scripts
        entry-points
        ;
      dependencies = map lib.getName dependencies';
      optional-dependencies = lib.mapAttrs (_: map lib.getName) optional-dependencies;
    };

    # Allow empty package
    tool.hatch.build.targets.wheel.bypass-selection = true;

    # Include our editable pointer file in build
    tool.hatch.build.targets.wheel.force-include."_${pname}.pth" = "_${pname}.pth";

    # Build editable package using hatchling
    build-system = {
      requires = [ "hatchling" ];
      build-backend = "hatchling.build";
    };
  };

in
buildPythonPackage (
  {
    inherit
      pname
      version
      optional-dependencies
      passthru
      meta
      ;
    dependencies = dependencies';

    pyproject = true;

    unpackPhase = ''
      python -c "import json, tomli_w; print(tomli_w.dumps(json.load(open('$pyprojectContentsPath'))))" > pyproject.toml
      echo 'import os.path, sys; sys.path.insert(0, os.path.expandvars("${root}"))' > _${pname}.pth
    '';

    build-system = [ hatchling ];
  }
  // derivationArgs
  // {
    # Note: Using formats.toml generates another intermediary derivation that needs to be built.
    # We inline the same functionality for better UX.
    nativeBuildInputs = (derivationArgs.nativeBuildInputs or [ ]) ++ [ tomli-w ];
    pyprojectContents = builtins.toJSON pyproject;
    passAsFile = [ "pyprojectContents" ];
    preferLocalBuild = true;
  }
)