diff --git a/lib/internal/python/selectWheel.nix b/lib/internal/python/selectWheel.nix new file mode 100644 index 00000000..f671fcc3 --- /dev/null +++ b/lib/internal/python/selectWheel.nix @@ -0,0 +1,147 @@ +# MIT Licensed by @adisbladis +# this is vendored from poetry2nix atm, because it uses pyprojects pep599 instead of poetryLib, +# to get going while testing the design. +# FIXME: just port selectWheel to pyproject.nix +# https://github.com/nix-community/poetry2nix/blame/master/pep425.nix +{ + lib, + stdenv, + pep599, + python, + isLinux ? stdenv.isLinux, +}: let + inherit (lib.strings) escapeRegex hasPrefix hasSuffix hasInfix splitString removeSuffix; + targetMachine = pep599.manyLinuxTargetMachines.${stdenv.targetPlatform.parsed.cpu.name} or null; + + pythonVer = let + ver = builtins.splitVersion python.version; + major = builtins.elemAt ver 0; + minor = builtins.elemAt ver 1; + tags = ["cp" "py"]; + in {inherit major minor tags;}; + # Builds with and without pymalloc (m) are ABI compatible since python 3.8 (bpo-36707) + abiTag = + "cp${pythonVer.major}${pythonVer.minor}" + + lib.optionalString (builtins.compareVersions python.version "3.8" < 0) "m"; + + # + # Parses wheel file returning an attribute set + # + toWheelAttrs = str: let + entries' = splitString "-" str; + el = builtins.length entries'; + entryAt = builtins.elemAt entries'; + + # Hack: Remove version "suffixes" like 2.11.4-1 + entries = + if el == 6 + then [ + (entryAt 0) # name + (entryAt 1) # version + # build tag is skipped + (entryAt (el - 3)) # python version + (entryAt (el - 2)) # abi + (entryAt (el - 1)) # platform + ] + else entries'; + p = removeSuffix ".whl" (builtins.elemAt entries 4); + in { + pkgName = builtins.elemAt entries 0; + pkgVer = builtins.elemAt entries 1; + pyVer = builtins.elemAt entries 2; + abi = builtins.elemAt entries 3; + platform = p; + }; + + # + # Builds list of acceptable osx wheel files + # + # accepted versions in descending order of preference + # list of wheel files to select from + findBestMatches = versions: candidates: let + v = lib.lists.head versions; + vs = lib.lists.tail versions; + in + if (builtins.length versions == 0) + then [] + else (builtins.filter (file: hasInfix v file) candidates) ++ (findBestMatches vs candidates); + + # x = "cpXX" | "py2" | "py3" | "py2.py3" + isPyVersionCompatible = pyver @ { + major, + minor, + tags, + }: x: let + isCompat = m: + builtins.elem m.tag tags + && m.major == major + && builtins.compareVersions minor m.minor >= 0; + parseMarker = v: let + tag = builtins.substring 0 2 v; + major = builtins.substring 2 1 v; + end = builtins.substring 3 3 v; + minor = + if builtins.stringLength end > 0 + then end + else "0"; + in {inherit major minor tag;}; + markers = splitString "." x; + in + lib.lists.any isCompat (map parseMarker markers); + + # + # Selects the best matching wheel file from a list of files + # + selectWheel = files: let + filesWithoutSources = builtins.filter (file: hasSuffix ".whl" file) files; + isPyAbiCompatible = pyabi: x: + x + == "none" + || hasPrefix pyabi x + || hasPrefix x pyabi + || ( + # The CPython stable ABI is abi3 as in the shared library suffix. + python.passthru.implementation + == "cpython" + && builtins.elemAt (lib.splitString "." python.version) 0 == "3" + && x == "abi3" + ); + withPython = ver: abi: x: (isPyVersionCompatible ver x.pyVer) && (isPyAbiCompatible abi x.abi); + withPlatform = + if isLinux + then + if targetMachine != null + then + # See PEP 600 for details. + ( + p: + builtins.match "any|(linux|manylinux(1|2010|2014))_${escapeRegex targetMachine}|manylinux_[0-9]+_[0-9]+_${escapeRegex targetMachine}" p != null + ) + else (p: p == "any") + else if stdenv.isDarwin + then + if stdenv.targetPlatform.isAarch64 + then (p: p == "any" || (hasInfix "macosx" p && lib.lists.any (e: hasSuffix e p) ["arm64" "aarch64"])) + else (p: p == "any" || (hasInfix "macosx" p && hasSuffix "x86_64" p)) + else (p: p == "any"); + withPlatforms = x: lib.lists.any withPlatform (splitString "." x.platform); + filterWheel = file: let + f = toWheelAttrs file; + in + (withPython pythonVer abiTag f) && (withPlatforms f); + filtered = builtins.filter filterWheel filesWithoutSources; + choose = files: let + osxMatches = ["12_0" "11_0" "10_15" "10_14" "10_12" "10_11" "10_10" "10_9" "10_8" "10_7" "any"]; + linuxMatches = ["manylinux1_" "manylinux2010_" "manylinux2014_" "manylinux_" "linux_" "any"]; + chooseLinux = x: lib.head (findBestMatches linuxMatches x); + chooseOSX = x: lib.head (findBestMatches osxMatches x); + in + if isLinux + then chooseLinux files + else chooseOSX files; + in + if (builtins.length filtered == 0) + then null + else choose filtered; +in + selectWheel diff --git a/modules/dream2nix/WIP-python-pdm/default.nix b/modules/dream2nix/WIP-python-pdm/default.nix index 48ae781b..7a321da0 100644 --- a/modules/dream2nix/WIP-python-pdm/default.nix +++ b/modules/dream2nix/WIP-python-pdm/default.nix @@ -10,9 +10,16 @@ libpyproject = import (dream2nix.inputs.pyproject-nix + "/lib") {inherit lib;}; + selectWheel = import ../../../lib/internal/python/selectWheel.nix { + inherit lib; + pep599 = libpyproject.pep599; + python = config.deps.python3; + stdenv = config.deps.stdenv; + }; + lock_data = lib.importTOML config.pdm.lockfile; environ = libpyproject.pep508.mkEnviron config.deps.python3; - selector = libpdm.preferWheelSelector; + selector = config.pdm.sourceSelector; pyproject = libpdm.loadPdmPyProject (lib.importTOML config.pdm.pyproject); @@ -33,16 +40,20 @@ in { ../core/deps ./interface.nix ]; + pdm.sourceSelector = lib.mkDefault selectWheel; deps = {nixpkgs, ...}: { inherit (nixpkgs) curl jq stdenvNoCC + python3 + stdenv ; }; commonModule = { options.sourceSelector = import ./sourceSelectorOption.nix {inherit lib;}; + # TODO: per dependency selector isn't yet respected config.sourceSelector = lib.mkOptionDefault config.pdm.sourceSelector; }; groups = let