Add PEP508 marker support

This is still experimental and may contain bugs
This commit is contained in:
adisbladis 2019-11-16 12:57:43 +00:00
parent be7dab861a
commit ff8d58af7d
No known key found for this signature in database
GPG key ID: 110BFAD44C6249B7
2 changed files with 204 additions and 15 deletions

View file

@ -1,7 +1,6 @@
{ {
pkgs ? import <nixpkgs> { }, pkgs ? import <nixpkgs> { },
lib ? pkgs.lib, lib ? pkgs.lib,
python ? pkgs.python3,
}: }:
let let
@ -16,7 +15,7 @@ let
satisfiesSemver = (import ./semver.nix {inherit lib;}).satisfies; satisfiesSemver = (import ./semver.nix {inherit lib;}).satisfies;
# Check Python version is compatible with package # Check Python version is compatible with package
isCompatible = pythonVersions: let isCompatible = pythonVersion: pythonVersions: let
operators = { operators = {
"||" = cond1: cond2: cond1 || cond2; "||" = cond1: cond2: cond1 || cond2;
"," = cond1: cond2: cond1 && cond2; # , means && "," = cond1: cond2: cond1 && cond2; # , means &&
@ -27,7 +26,7 @@ let
operator = if isOperator then (builtins.elemAt v 0) else acc.operator; operator = if isOperator then (builtins.elemAt v 0) else acc.operator;
in if isOperator then (acc // {inherit operator;}) else { in if isOperator then (acc // {inherit operator;}) else {
inherit operator; inherit operator;
state = operators."${operator}" acc.state (satisfiesSemver python.version v); state = operators."${operator}" acc.state (satisfiesSemver pythonVersion v);
}) })
{ {
operator = ","; operator = ",";
@ -47,12 +46,18 @@ let
isBdist = f: builtins.match "^.*?whl$" f.file != null; isBdist = f: builtins.match "^.*?whl$" f.file != null;
isSdist = f: ! isBdist f; isSdist = f: ! isBdist f;
mkEvalPep508 = import ./pep508.nix {
inherit lib;
stdenv = pkgs.stdenv;
};
mkPoetryPackage = { mkPoetryPackage = {
src, src,
pyproject ? src + "/pyproject.toml", pyproject ? src + "/pyproject.toml",
poetrylock ? src + "/poetry.lock", poetrylock ? src + "/poetry.lock",
overrides ? defaultPoetryOverrides, overrides ? defaultPoetryOverrides,
meta ? {}, meta ? {},
python ? pkgs.python3,
... ...
}@attrs: let }@attrs: let
pyProject = importTOML pyproject; pyProject = importTOML pyproject;
@ -63,6 +68,8 @@ let
specialAttrs = [ "pyproject" "poetrylock" "overrides" ]; specialAttrs = [ "pyproject" "poetrylock" "overrides" ];
passedAttrs = builtins.removeAttrs attrs specialAttrs; passedAttrs = builtins.removeAttrs attrs specialAttrs;
evalPep508 = mkEvalPep508 python;
# Create an overriden version of pythonPackages # Create an overriden version of pythonPackages
# #
# We need to avoid mixing multiple versions of pythonPackages in the same # We need to avoid mixing multiple versions of pythonPackages in the same
@ -85,7 +92,10 @@ let
then "wheel" then "wheel"
else "setuptools"; else "setuptools";
in self.buildPythonPackage { # Filter derivations by their PEP508 markers
markerFiltered = if builtins.hasAttr "marker" pkgMeta then (! evalPep508 pkgMeta.marker) else false;
in if markerFiltered then null else self.buildPythonPackage {
pname = pkgMeta.name; pname = pkgMeta.name;
version = pkgMeta.version; version = pkgMeta.version;
@ -101,7 +111,7 @@ let
in builtins.map (dep: self."${dep}") dependencies; in builtins.map (dep: self."${dep}") dependencies;
meta = { meta = {
broken = ! isCompatible pkgMeta.python-versions; broken = ! isCompatible python.version pkgMeta.python-versions;
}; };
src = src =
@ -126,7 +136,7 @@ let
value = let value = let
drv = mkPoetryDep pkgMeta; drv = mkPoetryDep pkgMeta;
override = getAttrDefault pkgMeta.name overrides (_: _: drv: drv); override = getAttrDefault pkgMeta.name overrides (_: _: drv: drv);
in override self super drv; in if drv != null then (override self super drv) else null;
}) poetryLock.package; }) poetryLock.package;
in { in {
@ -134,13 +144,7 @@ let
pytest_xdist = super.pytest_xdist.overrideAttrs(old: { pytest_xdist = super.pytest_xdist.overrideAttrs(old: {
doInstallCheck = false; doInstallCheck = false;
}); });
} // builtins.listToAttrs lockPkgs // { } // builtins.listToAttrs lockPkgs;
# Temporary hacks (Missing support for markers)
enum34 = null;
functools32 = null;
typing = null;
futures = null;
};
in python.override { inherit packageOverrides; self = py; }; in python.override { inherit packageOverrides; self = py; };
pythonPackages = py.pkgs; pythonPackages = py.pkgs;
@ -160,9 +164,9 @@ let
# TODO: Only conditionally include poetry based on build-system # TODO: Only conditionally include poetry based on build-system
# buildInputs = mkInput "buildInputs" ([ pythonPackages.poetry ]); # buildInputs = mkInput "buildInputs" ([ pythonPackages.poetry ]);
buildInputs = mkInput "buildInputs" ([ pythonPackages.setuptools ]); buildInputs = mkInput "buildInputs" ([ pythonPackages.poetry ]);
propagatedBuildInputs = mkInput "propagatedBuildInputs" (getDeps "dependencies"); propagatedBuildInputs = mkInput "propagatedBuildInputs" (getDeps "dependencies") ++ ([ pythonPackages.setuptools ]);
checkInputs = mkInput "checkInputs" (getDeps "dev-dependencies"); checkInputs = mkInput "checkInputs" (getDeps "dev-dependencies");
passthru = { passthru = {

185
pep508.nix Normal file
View file

@ -0,0 +1,185 @@
{ lib, stdenv }: python:
let
# Like builtins.substring but with stop being offset instead of length
substr = start: stop: s: builtins.substring start (stop - start) s;
# Strip leading/trailing whitespace from string
stripStr = s: lib.elemAt (builtins.split "^ *| *$" s) 2;
findSubExpressionsFun = acc: c: (
if c == "(" then (
let
posNew = acc.pos + 1;
isOpen = acc.openP == 0;
startPos = if isOpen then posNew else acc.startPos;
exprs = if isOpen then acc.exprs else acc.exprs ++ [ (substr acc.exprPos (acc.pos - 1) acc.expr) ];
in acc // {
inherit exprs startPos;
pos = posNew;
openP = acc.openP + 1;
}
) else if c == ")" then (
let
openP = acc.openP - 1;
exprs = findSubExpressions (substr acc.startPos acc.pos acc.expr);
in acc // {
inherit openP;
pos = acc.pos + 1;
exprs = if openP == 0 then acc.exprs ++ [ exprs ] else acc.exprs;
exprPos = if openP == 0 then acc.pos + 1 else acc.exprPos;
}
) else acc // {pos = acc.pos + 1;}
);
# Make a tree out of expression groups (parens)
findSubExpressions = expr: let
acc = builtins.foldl' findSubExpressionsFun {
exprs = [];
expr = expr;
pos = 0;
openP = 0;
exprPos = 0;
startPos = 0;
} (lib.stringToCharacters expr);
in acc.exprs ++ [ (substr acc.exprPos acc.pos expr) ];
parseExpressions = exprs: let
splitCond = (s: builtins.map
(x: if builtins.typeOf x == "list" then (builtins.elemAt x 0) else x)
(builtins.split " (and|or) " s));
mapfn = expr: (
if (builtins.match "^ ?$" expr != null) then null # Filter empty
else if (builtins.elem expr [ "and" "or" ]) then {
type = "bool";
value = expr;
}
else {
type = "expr";
value = expr;
});
parsed = builtins.filter (x: x != null) (builtins.map mapfn (splitCond exprs));
in if builtins.typeOf exprs == "string" then parsed else builtins.map parseExpressions exprs;
# Transform individual expressions to structured expressions
# This function also performs variable substitution, replacing environment markers with their explicit values
transformExpressions = exprs: let
variables = {
os_name = "posix"; # TODO: Check other platforms
sys_platform = (
if stdenv.isLinux then "linux"
else if stdenv.isDarwin then "darwin"
else throw "Unsupported platform"
);
platform_machine = stdenv.platform.kernelArch;
platform_python_implementation = "CPython"; # Only CPython supported for now
platform_release = ""; # Field not reproducible
platform_system = (
if stdenv.isLinux then "Linux"
else if stdenv.isDarwin then "Darwin"
else throw "Unsupported platform"
);
platform_version = ""; # Field not reproducible
python_version = python.passthru.pythonVersion;
python_full_version = python.version;
implementation_name = "cpython"; # Only cpython supported for now
implementation_version = python.version;
extra = "";
};
substituteVar = value: if builtins.hasAttr value variables then (builtins.toJSON variables."${value}") else value;
processVar = value: builtins.foldl' (acc: v: v acc) value [
stripStr
substituteVar
];
in if builtins.typeOf exprs == "set" then (
if exprs.type == "expr" then (let
mVal = ''[a-zA-Z0-9\'"_\. ]+'';
mOp = "in|[!=<>]+";
e = stripStr exprs.value;
m = builtins.map stripStr (builtins.match ''^(${mVal}) *(${mOp}) *(${mVal})$'' e);
in {
type = "expr";
value = {
op = builtins.elemAt m 1;
values = [
(processVar (builtins.elemAt m 0))
(processVar (builtins.elemAt m 2))
];
};
}) else exprs
) else builtins.map transformExpressions exprs;
# Recursively eval all expressions
evalExpressions = exprs: let
unmarshal = v: (
# TODO: Handle single quoted values
if v == "True" then true
else if v == "False" then false
else builtins.fromJSON v
);
hasElem = needle: haystack: builtins.elem needle (builtins.filter (x: builtins.typeOf x == "string") (builtins.split " " haystack));
# TODO: Implement all operators
op = {
"<=" = x: y: (unmarshal x) <= (unmarshal y);
"<" = x: y: (unmarshal x) < (unmarshal y);
"!=" = x: y: x != y;
"==" = x: y: x == y;
">=" = x: y: (unmarshal x) >= (unmarshal y);
">" = x: y: (unmarshal x) > (unmarshal y);
"~=" = null;
"===" = null;
"in" = x: y: let
values = builtins.filter (x: builtins.typeOf x == "string") (builtins.split " " (unmarshal y));
in builtins.elem (unmarshal x) values;
};
in if builtins.typeOf exprs == "set" then (
if exprs.type == "expr" then (let
expr = exprs;
result = (op."${expr.value.op}") (builtins.elemAt expr.value.values 0) (builtins.elemAt expr.value.values 1);
in {
type = "value";
value = result;
}) else exprs
) else builtins.map evalExpressions exprs;
# Now that we have performed an eval all that's left to do is to concat the graph into a single bool
reduceExpressions = exprs: let
cond = {
"and" = x: y: x && y;
"or" = x: y: x || y;
};
reduceExpressionsFun = acc: v: (
if builtins.typeOf v == "set" then (
if v.type == "value" then (
acc // {
value = cond."${acc.cond}" acc.value v.value;
}
) else if v.type == "bool" then (
acc // {
cond = v.value;
}
) else throw "Unsupported type"
) else if builtins.typeOf v == "list" then (
builtins.foldl' reduceExpressionsFun acc v
) else throw "Unsupported type"
);
in (builtins.foldl' reduceExpressionsFun {
value = true;
cond = "and";
} exprs).value;
in e: builtins.foldl' (acc: v: v acc) e [
findSubExpressions
parseExpressions
transformExpressions
evalExpressions
reduceExpressions
]