mirror of
https://github.com/nix-community/dream2nix.git
synced 2024-12-23 06:21:30 +03:00
Python DevShells (for pip) (#951)
* fetchPipMetadata: set meta.mainProgram * fetchPipMetadata: deduplicate test fixtures * fetchPipMetadata: add is_direct to lock file * devShell: proof of concept * python-local-development: add editables example * devshell: simplify editables interface * buildPythonPackage: remove editable option again * devshell: use findRoot for editables * devshell: add support for .whl sources * devshell: avoid polluting the shells environment * devshell: make editable.nix flatter because there's only 1 attr left now * devshell: fix pyEnv by filtering editables from the environment. * devshell: skip existing editables * devshell: remove patched pyEnv, rewrite sys.path * devshell: add special case for root package * python-local-development: improve editable example * python-local-development: filter source * devshell: reset site_dir on each load * pip: don't ignoreCollisions in pyEnv * pip: default to no sitecustomize.py... and teach the devshell how to load it * pip: rewrite editable in python * editable: use shutil.copytree * editable: refactor into functions * editables: add suport for console_scripts * improve pip editables interface - change editables interface to use bool or path - expose public.shellHook for composition - use shellHook by default in public.devShell - extend example to include a script * improve edtiable support: - remove dependency on root package build - always set root package as editable by default - compute dit-info dir by calling a packages build backend or extracting directly from the wheel - only pass required sources to editables shell hook - add build inputs of all editables to teh dev shell - move editables state to .dream2nix/python to remove likelyhood of collision with other ecosystems state - delete editables state if editables configuration changed * refine python editable support: - never copy dependency sources - force user to specify a local path --------- Co-authored-by: DavHau <hsngrmpf+github@gmail.com>
This commit is contained in:
parent
b6b11585f9
commit
6a169a73bd
@ -12,13 +12,21 @@ in {
|
||||
];
|
||||
|
||||
deps = {nixpkgs, ...}: {
|
||||
python = nixpkgs.python310;
|
||||
python = nixpkgs.python3;
|
||||
};
|
||||
|
||||
inherit (pyproject.project) name version;
|
||||
|
||||
mkDerivation = {
|
||||
src = ./.;
|
||||
src = lib.cleanSourceWith {
|
||||
src = lib.cleanSource ./.;
|
||||
filter = name: type:
|
||||
!(builtins.any (x: x) [
|
||||
(lib.hasSuffix ".nix" name)
|
||||
(lib.hasPrefix "." (builtins.baseNameOf name))
|
||||
(lib.hasSuffix "flake.lock" name)
|
||||
]);
|
||||
};
|
||||
};
|
||||
|
||||
buildPythonPackage = {
|
||||
@ -29,11 +37,19 @@ in {
|
||||
};
|
||||
|
||||
pip = {
|
||||
pypiSnapshotDate = "2023-08-27";
|
||||
# Setting editables.$pkg.null will link the current project root as an editable
|
||||
# for the root package (my-tool here), or otherwise copy the contents of mkDerivation.src
|
||||
# to .dream2nix/editables to make them writeable.
|
||||
# Alternatively you can point it to an existing checkout via an absolute path, i.e.:
|
||||
# editables.charset-normalizer = "/home/my-user/src/charset-normalizer";
|
||||
editables.charset-normalizer = ".editables/charset_normalizer";
|
||||
|
||||
requirementsList =
|
||||
pyproject.build-system.requires
|
||||
or []
|
||||
++ pyproject.project.dependencies;
|
||||
flattenDependencies = true;
|
||||
|
||||
overrides.click.mkDerivation.nativeBuildInputs = [config.deps.python.pkgs.flit-core];
|
||||
};
|
||||
}
|
||||
|
@ -13,11 +13,10 @@
|
||||
...
|
||||
}: let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
# All packages defined in ./packages/<name> are automatically added to the flake outputs
|
||||
# e.g., 'packages/hello/default.nix' becomes '.#packages.hello'
|
||||
packages.${system}.default = dream2nix.lib.evalModules {
|
||||
packageSets.nixpkgs = inputs.dream2nix.inputs.nixpkgs.legacyPackages.${system};
|
||||
packageSets.nixpkgs = pkgs;
|
||||
modules = [
|
||||
./default.nix
|
||||
{
|
||||
@ -28,5 +27,13 @@
|
||||
}
|
||||
];
|
||||
};
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
# inherit from the dream2nix generated dev shell
|
||||
inputsFrom = [self.packages.${system}.default.devShell];
|
||||
# add extra packages
|
||||
packages = [
|
||||
pkgs.hello
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -2,46 +2,61 @@
|
||||
"fetchPipMetadata": {
|
||||
"sources": {
|
||||
"certifi": {
|
||||
"sha256": "92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9",
|
||||
"is_direct": false,
|
||||
"sha256": "dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1",
|
||||
"type": "url",
|
||||
"url": "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl",
|
||||
"version": "2023.7.22"
|
||||
"url": "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl",
|
||||
"version": "2024.2.2"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"sha256": "193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad",
|
||||
"is_direct": false,
|
||||
"sha256": "753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
|
||||
"type": "url",
|
||||
"url": "https://files.pythonhosted.org/packages/a4/65/057bf29660aae6ade0816457f8db4e749e5c0bfa2366eb5f67db9912fa4c/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||
"version": "3.2.0"
|
||||
"url": "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||
"version": "3.3.2"
|
||||
},
|
||||
"click": {
|
||||
"is_direct": true,
|
||||
"rev": "f8857cb03268b5b952b88b2acb3e11d9f0f7b6e4",
|
||||
"sha256": "0pcapxavyn8fqxjkwdidl7cdy24cvysyqi9rbhfsfxivp9715bvf",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pallets/click.git",
|
||||
"version": "8.2.0.dev0"
|
||||
},
|
||||
"idna": {
|
||||
"sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2",
|
||||
"is_direct": false,
|
||||
"sha256": "82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0",
|
||||
"type": "url",
|
||||
"url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl",
|
||||
"version": "3.4"
|
||||
"url": "https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl",
|
||||
"version": "3.7"
|
||||
},
|
||||
"requests": {
|
||||
"is_direct": false,
|
||||
"sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
|
||||
"type": "url",
|
||||
"url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl",
|
||||
"version": "2.31.0"
|
||||
},
|
||||
"setuptools": {
|
||||
"sha256": "3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b",
|
||||
"is_direct": false,
|
||||
"sha256": "c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32",
|
||||
"type": "url",
|
||||
"url": "https://files.pythonhosted.org/packages/4f/ab/0bcfebdfc3bfa8554b2b2c97a555569c4c1ebc74ea288741ea8326c51906/setuptools-68.1.2-py3-none-any.whl",
|
||||
"version": "68.1.2"
|
||||
"url": "https://files.pythonhosted.org/packages/f7/29/13965af254e3373bceae8fb9a0e6ea0d0e571171b80d6646932131d6439b/setuptools-69.5.1-py3-none-any.whl",
|
||||
"version": "69.5.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"sha256": "de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4",
|
||||
"is_direct": false,
|
||||
"sha256": "450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
|
||||
"type": "url",
|
||||
"url": "https://files.pythonhosted.org/packages/9b/81/62fd61001fa4b9d0df6e31d47ff49cfa9de4af03adecf339c7bc30656b37/urllib3-2.0.4-py3-none-any.whl",
|
||||
"version": "2.0.4"
|
||||
"url": "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl",
|
||||
"version": "2.2.1"
|
||||
}
|
||||
},
|
||||
"targets": {
|
||||
"default": {
|
||||
"certifi": [],
|
||||
"charset-normalizer": [],
|
||||
"click": [],
|
||||
"idna": [],
|
||||
"requests": [
|
||||
"certifi",
|
||||
@ -54,5 +69,5 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidationHash": "9d63b829115c1d449d6830377e4a586d684393b9b63a76a1f89f7486b6d0ed33"
|
||||
"invalidationHash": "0b9e5e3dccfe479110aacf335f3d8f939c6fd18f6fbc03b850e196178b6897c3"
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import requests
|
||||
|
||||
|
||||
print("Hello World!")
|
||||
def main():
|
||||
print("Hello World!")
|
||||
|
@ -7,5 +7,9 @@ name = "my-tool"
|
||||
description = "my tool"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"requests"
|
||||
"requests",
|
||||
"click @ git+https://github.com/pallets/click.git@main"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
my-tool = "my_tool:main"
|
||||
|
@ -14,6 +14,9 @@
|
||||
isRootDrv = drv: cfg.rootDependencies.${drv.name} or false;
|
||||
isBuildInput = drv: cfg.buildDependencies.${drv.name} or false;
|
||||
|
||||
# actually wanted editables (minus the ones set to false)
|
||||
editables = lib.filterAttrs (_name: path: path != false) config.pip.editables;
|
||||
|
||||
writers = import ../../../pkgs/writers {
|
||||
inherit lib;
|
||||
inherit
|
||||
@ -142,7 +145,7 @@ in {
|
||||
pythonInterpreter = "${python}/bin/python";
|
||||
};
|
||||
setuptools = config.deps.python.pkgs.setuptools;
|
||||
inherit (nixpkgs) nix fetchgit;
|
||||
inherit (nixpkgs) nix fetchgit writeText;
|
||||
inherit (writers) writePureShellScript;
|
||||
};
|
||||
|
||||
@ -176,6 +179,17 @@ in {
|
||||
drvs = drvs;
|
||||
rootDependencies =
|
||||
l.genAttrs (targets.default.${config.name} or []) (_: true);
|
||||
editables =
|
||||
# make root package always editable
|
||||
{${config.name} = config.paths.package;};
|
||||
editablesShellHook = import ./editable.nix {
|
||||
inherit lib;
|
||||
inherit (config.deps) unzip writeText;
|
||||
inherit (config.paths) findRoot;
|
||||
inherit (config.public) pyEnv;
|
||||
inherit (cfg) editables;
|
||||
rootName = config.name;
|
||||
};
|
||||
};
|
||||
|
||||
mkDerivation = {
|
||||
@ -197,13 +211,46 @@ in {
|
||||
};
|
||||
|
||||
public.pyEnv = let
|
||||
pyEnv' = config.deps.python.withPackages (ps: config.mkDerivation.propagatedBuildInputs);
|
||||
pyEnv' = config.deps.python.withPackages (
|
||||
ps:
|
||||
config.mkDerivation.propagatedBuildInputs
|
||||
# the editableShellHook requires wheel and other build system deps.
|
||||
++ config.mkDerivation.buildInputs
|
||||
++ [config.deps.python.pkgs.wheel]
|
||||
);
|
||||
in
|
||||
pyEnv'.override (old: {
|
||||
# namespaced packages are triggering a collision error, but this can be
|
||||
# safely ignored. They are still set up correctly and can be imported.
|
||||
ignoreCollisions = true;
|
||||
postBuild =
|
||||
old.postBuild
|
||||
or ""
|
||||
+ ''
|
||||
# Nixpkgs ships a sitecustomize.py with all of it's pyEnvs to add support for NIX_PYTHONPATH.
|
||||
# This is unfortunate as sitecustomize is a regular module, so there can only be one.
|
||||
# So we move nixpkgs to _sitecustomize.py, effectively removing it but allowing users
|
||||
# to re-activate it by doing "import _sitecustomize".
|
||||
# https://github.com/NixOS/nixpkgs/pull/297628 would fix this, but it was reverted for now in
|
||||
# https://github.com/NixOS/nixpkgs/pull/302385
|
||||
mv "$out/${pyEnv'.sitePackages}/sitecustomize.py" "$out/${pyEnv'.sitePackages}/_sitecustomize.py"
|
||||
'';
|
||||
});
|
||||
|
||||
public.devShell = config.public.pyEnv.env;
|
||||
# a shell hook for composition purposes
|
||||
public.shellHook = config.pip.editablesShellHook;
|
||||
# a dev shell for development
|
||||
public.devShell = config.deps.mkShell {
|
||||
packages = [config.public.pyEnv];
|
||||
shellHook = config.public.shellHook;
|
||||
buildInputs =
|
||||
[(config.pip.drvs.tomli.public or config.deps.python.pkgs.tomli)]
|
||||
++ lib.flatten (
|
||||
lib.mapAttrsToList
|
||||
(name: _path: config.pip.drvs.${name}.mkDerivation.buildInputs or [])
|
||||
editables
|
||||
);
|
||||
nativeBuildInputs = lib.flatten (
|
||||
lib.mapAttrsToList
|
||||
(name: _path: config.pip.drvs.${name}.mkDerivation.nativeBuildInputs or [])
|
||||
editables
|
||||
);
|
||||
};
|
||||
}
|
||||
|
17
modules/dream2nix/pip/editable.nix
Normal file
17
modules/dream2nix/pip/editable.nix
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
lib,
|
||||
findRoot,
|
||||
writeText,
|
||||
unzip,
|
||||
pyEnv,
|
||||
editables,
|
||||
rootName,
|
||||
}: let
|
||||
args = writeText "args" (builtins.toJSON {
|
||||
inherit findRoot unzip rootName pyEnv editables;
|
||||
inherit (pyEnv) sitePackages;
|
||||
inherit (builtins) storeDir;
|
||||
});
|
||||
in ''
|
||||
source <(${pyEnv}/bin/python ${./editable.py} ${args})
|
||||
''
|
272
modules/dream2nix/pip/editable.py
Normal file
272
modules/dream2nix/pip/editable.py
Normal file
@ -0,0 +1,272 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import configparser
|
||||
import importlib
|
||||
from contextlib import redirect_stdout
|
||||
from textwrap import dedent
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import tomli
|
||||
|
||||
|
||||
class Colors:
|
||||
ENDC = "\033[0m"
|
||||
CYAN = "\033[96m"
|
||||
GREEN = "\033[92m"
|
||||
PURPLE = "\033[95m"
|
||||
BOLD = "\033[1m"
|
||||
WARNING = "\033[93m"
|
||||
|
||||
|
||||
def run(args):
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
args,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
encoding="utf8",
|
||||
)
|
||||
return proc.stdout.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error while calling {' '.join(args)}", file=sys.stderr)
|
||||
print(e.output, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def make_editable(
|
||||
python_environment,
|
||||
bin_dir,
|
||||
site_dir,
|
||||
editables_dir,
|
||||
site_packages,
|
||||
name,
|
||||
path,
|
||||
root_dir,
|
||||
):
|
||||
normalized_name = name.replace("-", "_")
|
||||
editable_dir = editables_dir / name
|
||||
full_path = path if path.is_absolute() else root_dir / path
|
||||
if editable_dir.exists():
|
||||
relative_editable_dir = editable_dir.relative_to(root_dir)
|
||||
return
|
||||
if not full_path.exists():
|
||||
print(
|
||||
f"Error: The python dependency {name} of {root_name} is configured to be installed in editable mode, but the provided source location {full_path} does not exist.\n"
|
||||
f"Please provide a path to a local copy of the source code of {name}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
exit(1)
|
||||
# link_editable_source(editable_dir, site_dir, normalized_name, full_path)
|
||||
make_pth(site_dir, full_path, normalized_name)
|
||||
editable_dist_info = make_dist_info(
|
||||
site_dir, full_path, site_packages, normalized_name
|
||||
)
|
||||
make_entrypoints(python_environment, bin_dir, editable_dist_info)
|
||||
|
||||
|
||||
def make_pth(site_dir, editable_dir, normalized_name):
|
||||
# Check if source uses a "src"-layout or a flat-layout and
|
||||
# write the .pth file
|
||||
if (editable_dir / "src").exists():
|
||||
pth = editable_dir / "src"
|
||||
else:
|
||||
# TODO this approach is risky as it puts everything inside
|
||||
# upstreams repo on $PYTHONPATH. Maybe we should try to
|
||||
# get packages from toplevel.txt first and if found,
|
||||
# create a dir with only them linked?
|
||||
pth = editable_dir
|
||||
with open((site_dir / normalized_name).with_suffix(".pth"), "w") as f:
|
||||
f.write(f"{pth}\n")
|
||||
|
||||
|
||||
# create a packages .dist-info by calling its build backend
|
||||
def make_dist_info(site_dir, editable_dir, site_packages, normalized_name):
|
||||
os.chdir(editable_dir)
|
||||
pyproject_file = editable_dir / "pyproject.toml"
|
||||
if pyproject_file.exists():
|
||||
with open(pyproject_file, "rb") as f:
|
||||
pyproject = tomli.load(f)
|
||||
build_system = (
|
||||
pyproject["build-system"] if "build-system" in pyproject else "setuptools"
|
||||
)
|
||||
build_backend = (
|
||||
build_system["build-backend"]
|
||||
if "build-backend" in build_system
|
||||
else "setuptools.build_meta.__legacy__"
|
||||
)
|
||||
build_system_import = (
|
||||
build_backend
|
||||
if build_backend != "setuptools.build_meta.__legacy__"
|
||||
else "setuptools.build_meta"
|
||||
)
|
||||
else:
|
||||
build_system_import = "setuptools.build_meta"
|
||||
backend = importlib.import_module(build_system_import)
|
||||
with redirect_stdout(open(os.devnull, "w")), TemporaryDirectory() as tmp_dir:
|
||||
if hasattr(backend, "prepare_metadata_for_build_editable"):
|
||||
# redirect stdout to stderr to avoid leaking the metadata to the user
|
||||
dist_info_name = backend.prepare_metadata_for_build_editable(tmp_dir)
|
||||
else:
|
||||
dist_info_name = backend.prepare_metadata_for_build_wheel(tmp_dir)
|
||||
# copy the dist-info to the site-packages
|
||||
shutil.copytree(Path(tmp_dir) / dist_info_name, site_dir / dist_info_name)
|
||||
dist_info_path = site_dir / dist_info_name
|
||||
for egg_info in site_dir.glob("*.egg-info"):
|
||||
shutil.rmtree(egg_info)
|
||||
write_direct_url_json(dist_info_path, editable_dir)
|
||||
return dist_info_path
|
||||
|
||||
|
||||
def write_direct_url_json(dist_info_path, editable_dir):
|
||||
with open(dist_info_path / "direct_url.json", "w") as f:
|
||||
json.dump(
|
||||
{"url": f"file://{editable_dir}", "dir_info": {"editable": True}},
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
def make_entrypoints(python_environment, bin_dir, dist_info):
|
||||
entry_points_file = dist_info / "entry_points.txt"
|
||||
if not entry_points_file.exists():
|
||||
return
|
||||
entry_points = configparser.ConfigParser()
|
||||
entry_points.read(entry_points_file)
|
||||
if "console_scripts" not in entry_points:
|
||||
return
|
||||
for name, spec in entry_points["console_scripts"].items():
|
||||
# https://setuptools.pypa.io/en/latest/userguide/entry_point.html#entry-points-syntax
|
||||
package, obj_attrs = spec.split(":", 1) if ":" in spec else (spec, None)
|
||||
obj, attrs = (
|
||||
obj_attrs.split(".", 1)
|
||||
if obj_attrs and "." in obj_attrs
|
||||
else (obj_attrs, None)
|
||||
)
|
||||
script = (
|
||||
f"#!{python_environment}/bin/python\n"
|
||||
+ dedent(
|
||||
f"""
|
||||
from {package} import {obj}
|
||||
{obj}{f".{attrs}" if attrs else ""}()
|
||||
"""
|
||||
if obj
|
||||
else f"""
|
||||
import {package}
|
||||
{package}()
|
||||
"""
|
||||
).strip()
|
||||
)
|
||||
with open(bin_dir / name, "w") as f:
|
||||
f.write(script)
|
||||
os.chmod(bin_dir / name, 0o755)
|
||||
|
||||
|
||||
def needs_update(args, dream2nix_python_dir):
|
||||
if not (dream2nix_python_dir / "editable-args.json").exists():
|
||||
return True
|
||||
with open(dream2nix_python_dir / "editable-args.json", "r") as f:
|
||||
old_args = json.load(f)
|
||||
return old_args != args
|
||||
|
||||
|
||||
def pretty_print_editables(editables, root_dir, root_name):
|
||||
if os.environ.get("D2N_QUIET"):
|
||||
return
|
||||
C = Colors
|
||||
print(
|
||||
f"{C.WARNING}Some python dependencies of {C.GREEN}{C.BOLD}{root_name}{C.ENDC}{C.WARNING} are installed in editable mode",
|
||||
file=sys.stderr,
|
||||
)
|
||||
for name, path in editables.items():
|
||||
if name == root_name:
|
||||
continue
|
||||
print(
|
||||
f" {C.BOLD}{C.CYAN}{name}{C.ENDC}{C.ENDC}\n"
|
||||
f" installed at: {C.PURPLE}{path}{C.ENDC}\n",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(
|
||||
f"{C.WARNING}To disable editable mode for a package, remove the corresponding entry from the 'editables' field in the dream2nix configuration file.{C.ENDC}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open(sys.argv[1], "r") as f:
|
||||
args = json.load(f)
|
||||
# print(json.dumps(args, indent=2), file=sys.stderr)
|
||||
unzip = args["unzip"]
|
||||
find_root = args["findRoot"]
|
||||
python_environment = Path(args["pyEnv"])
|
||||
root_name = args["rootName"]
|
||||
site_packages = args["sitePackages"]
|
||||
editables = args["editables"]
|
||||
|
||||
# directories to use
|
||||
root_dir = Path(run([find_root]))
|
||||
dream2nix_python_dir = root_dir / ".dream2nix" / "python"
|
||||
editables_dir = dream2nix_python_dir / "editables"
|
||||
bin_dir = dream2nix_python_dir / "bin"
|
||||
site_dir = dream2nix_python_dir / "site"
|
||||
|
||||
# remove dream2nix python dir if args changed
|
||||
if needs_update(args, dream2nix_python_dir):
|
||||
if dream2nix_python_dir.exists():
|
||||
shutil.rmtree(dream2nix_python_dir)
|
||||
else:
|
||||
pretty_print_editables(editables, root_dir, root_name)
|
||||
exit(0)
|
||||
|
||||
bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
editables_dir.mkdir(parents=True, exist_ok=True)
|
||||
site_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for name, path in editables.items():
|
||||
make_editable(
|
||||
python_environment,
|
||||
bin_dir,
|
||||
site_dir,
|
||||
editables_dir,
|
||||
site_packages,
|
||||
name,
|
||||
Path(path),
|
||||
root_dir,
|
||||
)
|
||||
|
||||
with open(site_dir / "sitecustomize.py", "w") as f:
|
||||
f.write(
|
||||
f"""import sys
|
||||
import site
|
||||
|
||||
try:
|
||||
import _sitecustomize
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
site.addsitedir("{site_dir}")
|
||||
|
||||
# addsitedir only supports appending to the path, not prepending.
|
||||
# As we already include a non-editable instance of each package
|
||||
# in our pyEnv, those would shadow the editables. So we move
|
||||
# the editables to the front of sys.path.
|
||||
for index, path in enumerate(sys.path):
|
||||
if path.startswith("{editables_dir}"):
|
||||
sys.path.insert(0, sys.path.pop(index))
|
||||
"""
|
||||
)
|
||||
|
||||
print(
|
||||
f"""
|
||||
export PYTHONPATH="{site_dir}:{python_environment / site_packages}:$PYTHONPATH"
|
||||
export PATH="{bin_dir}:${python_environment}/bin:$PATH"
|
||||
"""
|
||||
)
|
||||
|
||||
with open(dream2nix_python_dir / "editable-args.json", "w") as f:
|
||||
json.dump(args, f, indent=2)
|
||||
|
||||
pretty_print_editables(editables, root_dir, root_name)
|
@ -1,8 +1,6 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
dream2nix,
|
||||
packageSets,
|
||||
specialArgs,
|
||||
...
|
||||
}: let
|
||||
@ -34,6 +32,15 @@ in {
|
||||
description = "the names of the selected top-level dependencies";
|
||||
};
|
||||
|
||||
editables = l.mkOption {
|
||||
type = t.attrsOf t.str;
|
||||
};
|
||||
|
||||
editablesShellHook = l.mkOption {
|
||||
type = t.str;
|
||||
readOnly = true;
|
||||
};
|
||||
|
||||
# user interface
|
||||
env = l.mkOption {
|
||||
type = t.attrsOf t.str;
|
||||
@ -47,6 +54,7 @@ in {
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
pypiSnapshotDate = l.mkOption {
|
||||
type = t.nullOr t.str;
|
||||
description = ''
|
||||
|
@ -21,6 +21,8 @@
|
||||
python-dateutil
|
||||
pip
|
||||
];
|
||||
|
||||
meta.mainProgram = "fetch_pip_metadata";
|
||||
};
|
||||
in
|
||||
package
|
||||
|
@ -157,6 +157,7 @@ def lock_entry_from_report_entry(install, project_root: Path):
|
||||
info = lock_info_fallback(download_info)
|
||||
|
||||
return name, dict(
|
||||
is_direct=install["is_direct"],
|
||||
version=install["metadata"]["version"],
|
||||
**info,
|
||||
)
|
||||
|
@ -1,100 +1,75 @@
|
||||
from pathlib import Path
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
import lock_file_from_report as l
|
||||
|
||||
|
||||
def test_url_no_hash():
|
||||
install = dict(
|
||||
def metadata(**kwargs):
|
||||
return dict(
|
||||
metadata=dict(
|
||||
name="test",
|
||||
version="0.0.0",
|
||||
),
|
||||
download_info=dict(url="https://example.com"),
|
||||
is_direct=False,
|
||||
**kwargs,
|
||||
)
|
||||
expected = "test", dict(
|
||||
|
||||
|
||||
def expect(install, sha256=None):
|
||||
return "test", dict(
|
||||
type="url",
|
||||
url=install["download_info"]["url"],
|
||||
version=install["metadata"]["version"],
|
||||
sha256=None,
|
||||
sha256=sha256,
|
||||
is_direct=install["is_direct"],
|
||||
)
|
||||
|
||||
|
||||
def test_url_no_hash():
|
||||
install = metadata(
|
||||
download_info=dict(url="https://example.com"),
|
||||
)
|
||||
expected = expect(install)
|
||||
assert l.lock_entry_from_report_entry(install, Path("foo")) == expected
|
||||
|
||||
|
||||
def test_url_with_hash():
|
||||
install = dict(
|
||||
metadata=dict(
|
||||
name="test",
|
||||
version="0.0.0",
|
||||
),
|
||||
install = metadata(
|
||||
download_info=dict(
|
||||
url="https://example.com",
|
||||
archive_info=dict(hash=f"sha256=example_hash"),
|
||||
),
|
||||
)
|
||||
expected = "test", dict(
|
||||
type="url",
|
||||
url=install["download_info"]["url"],
|
||||
version=install["metadata"]["version"],
|
||||
sha256="example_hash",
|
||||
)
|
||||
expected = expect(install, sha256="example_hash")
|
||||
assert l.lock_entry_from_report_entry(install, Path("foo")) == expected
|
||||
|
||||
|
||||
def test_path_external():
|
||||
install = dict(
|
||||
metadata=dict(
|
||||
name="test",
|
||||
version="0.0.0",
|
||||
),
|
||||
install = metadata(
|
||||
download_info=dict(
|
||||
url="/foo/bar",
|
||||
),
|
||||
)
|
||||
expected = "test", dict(
|
||||
type="url",
|
||||
url=install["download_info"]["url"],
|
||||
version=install["metadata"]["version"],
|
||||
sha256=None,
|
||||
)
|
||||
expected = expect(install)
|
||||
assert l.lock_entry_from_report_entry(install, Path("foo")) == expected
|
||||
|
||||
|
||||
def test_path_in_repo(git_repo_path):
|
||||
install = dict(
|
||||
metadata=dict(
|
||||
name="test",
|
||||
version="0.0.0",
|
||||
),
|
||||
install = metadata(
|
||||
download_info=dict(
|
||||
url=git_repo_path.name,
|
||||
),
|
||||
)
|
||||
expected = "test", dict(
|
||||
type="url",
|
||||
url=install["download_info"]["url"],
|
||||
version=install["metadata"]["version"],
|
||||
sha256=None,
|
||||
)
|
||||
expected = expect(install)
|
||||
assert l.lock_entry_from_report_entry(install, Path("foo")) == expected
|
||||
|
||||
|
||||
def test_path_in_nix_store():
|
||||
install = dict(
|
||||
metadata=dict(
|
||||
name="test",
|
||||
version="0.0.0",
|
||||
),
|
||||
install = metadata(
|
||||
download_info=dict(
|
||||
url="/nix/store/test",
|
||||
),
|
||||
)
|
||||
expected = "test", dict(
|
||||
type="url",
|
||||
url=install["download_info"]["url"],
|
||||
version=install["metadata"]["version"],
|
||||
sha256=None,
|
||||
)
|
||||
expected = expect(install)
|
||||
assert l.lock_entry_from_report_entry(install, Path("foo")) == expected
|
||||
|
||||
|
||||
@ -117,6 +92,7 @@ def test_git(monkeypatch):
|
||||
"name": "mypy",
|
||||
"version": "1.7.0+dev.df4717ee2cbbeb9e47fbd0e60edcaa6f81bbd7bb",
|
||||
},
|
||||
"is_direct": True,
|
||||
}
|
||||
expected = "mypy", {
|
||||
"rev": "df4717ee2cbbeb9e47fbd0e60edcaa6f81bbd7bb",
|
||||
@ -124,5 +100,6 @@ def test_git(monkeypatch):
|
||||
"type": "git",
|
||||
"url": "https://github.com/python/mypy",
|
||||
"version": "1.7.0+dev.df4717ee2cbbeb9e47fbd0e60edcaa6f81bbd7bb",
|
||||
"is_direct": True,
|
||||
}
|
||||
assert l.lock_entry_from_report_entry(install, Path("foo")) == expected
|
||||
|
@ -31,6 +31,7 @@ def test_simple():
|
||||
name="test",
|
||||
version="0.0.0",
|
||||
),
|
||||
is_direct=False,
|
||||
download_info=dict(url="https://example.com"),
|
||||
)
|
||||
],
|
||||
@ -42,6 +43,7 @@ def test_simple():
|
||||
sha256=None,
|
||||
url="https://example.com",
|
||||
version="0.0.0",
|
||||
is_direct=False,
|
||||
)
|
||||
),
|
||||
targets=dict(
|
||||
@ -64,6 +66,7 @@ def test_multiple_requested():
|
||||
version="0.0.0",
|
||||
),
|
||||
download_info=dict(url="https://example.com"),
|
||||
is_direct=False,
|
||||
),
|
||||
dict(
|
||||
requested=True,
|
||||
@ -71,6 +74,7 @@ def test_multiple_requested():
|
||||
name="bar",
|
||||
version="0.0.0",
|
||||
),
|
||||
is_direct=True,
|
||||
download_info=dict(url="https://example.com"),
|
||||
),
|
||||
],
|
||||
@ -82,12 +86,14 @@ def test_multiple_requested():
|
||||
sha256=None,
|
||||
url="https://example.com",
|
||||
version="0.0.0",
|
||||
is_direct=False,
|
||||
),
|
||||
bar=dict(
|
||||
type="url",
|
||||
sha256=None,
|
||||
url="https://example.com",
|
||||
version="0.0.0",
|
||||
is_direct=True,
|
||||
),
|
||||
),
|
||||
targets=dict(
|
||||
|
Loading…
Reference in New Issue
Block a user