mach-nix/mach_nix/requirements.py

103 lines
3.4 KiB
Python
Raw Normal View History

2020-11-18 18:09:56 +00:00
import re
2020-04-22 09:28:58 +00:00
from typing import Iterable
import distlib.markers
import pkg_resources
from distlib.markers import DEFAULT_CONTEXT
from pkg_resources._vendor.packaging.specifiers import SpecifierSet
from mach_nix.cache import cached
2020-04-22 09:28:58 +00:00
from mach_nix.versions import PyVer
version 2.2.0 (#60) # 2.2.0 (09 Aug 2020) Improved success rate, MacOS support, bugfixes, optimizations ### Features - Improved selection of wheel releases. MacOS is now supported and architectures besides x86_64 should be handled correctly. - Whenever mach-nix resolves dependencies, a visualization of the resulting dependency tree is printed on the terminal. - The dependency DB is now accessed through a caching layer which reduces the resolver's CPU time significantly for larger environments. - The python platform context is now generated from the nix build environment variable `system`. This should decrease the chance of impurities during dependency resolution. ### Fixes - The requires_python attribute of wheels was not respected. This lead to failing builds especially for older python versions. Now `requires_python` is part of the dependency graph and affects resolution. - Detecting the correct package name for python packages in nixpkgs often failed since the attribute names don't follow a fixed schema. This lead to a handful of different errors in different situations. Now the package names are extracted from the pypi `url` inside the `src` attribute which is much more reliable. For packages which are not fetched from pypi, the `pname` attribute is used as fallback. - Fixed bug which lead to the error `attribute 'sdist' missing` if a package from the nixpkgs provider was used which doesn't publish it's source on pypi. (For example `tensorflow`) ### Other Changes - Mach-nix now uses a revision of the nixpkgs-unstable branch instead of nixos-20.03 as base fo the tool and the nixpkgs provider. - Updated revision of the dependency DB
2020-08-09 13:24:12 +00:00
def context(py_ver: PyVer, platform: str, system: str):
2020-04-22 09:28:58 +00:00
context = DEFAULT_CONTEXT.copy()
context.update(dict(
version 2.2.0 (#60) # 2.2.0 (09 Aug 2020) Improved success rate, MacOS support, bugfixes, optimizations ### Features - Improved selection of wheel releases. MacOS is now supported and architectures besides x86_64 should be handled correctly. - Whenever mach-nix resolves dependencies, a visualization of the resulting dependency tree is printed on the terminal. - The dependency DB is now accessed through a caching layer which reduces the resolver's CPU time significantly for larger environments. - The python platform context is now generated from the nix build environment variable `system`. This should decrease the chance of impurities during dependency resolution. ### Fixes - The requires_python attribute of wheels was not respected. This lead to failing builds especially for older python versions. Now `requires_python` is part of the dependency graph and affects resolution. - Detecting the correct package name for python packages in nixpkgs often failed since the attribute names don't follow a fixed schema. This lead to a handful of different errors in different situations. Now the package names are extracted from the pypi `url` inside the `src` attribute which is much more reliable. For packages which are not fetched from pypi, the `pname` attribute is used as fallback. - Fixed bug which lead to the error `attribute 'sdist' missing` if a package from the nixpkgs provider was used which doesn't publish it's source on pypi. (For example `tensorflow`) ### Other Changes - Mach-nix now uses a revision of the nixpkgs-unstable branch instead of nixos-20.03 as base fo the tool and the nixpkgs provider. - Updated revision of the dependency DB
2020-08-09 13:24:12 +00:00
platform_version='', # remove impure platform_version
platform_release='', # remove impure kernel verison
platform_system=system[0].upper() + system[1:], # eg. Linux or Darwin
platform_machine=platform, # eg. x86_64
2020-04-22 09:28:58 +00:00
python_version=py_ver.python_version(),
python_full_version=py_ver.python_full_version()
2020-04-22 09:28:58 +00:00
))
return context
class Requirement(pkg_resources.Requirement):
2020-10-25 18:09:37 +00:00
def __init__(self, line, build=None):
self.build = build
2020-04-22 09:28:58 +00:00
super(Requirement, self).__init__(line)
self.name = self.name.lower().replace('_', '-')
self.specifier = SpecifierSet(','.join(f"{op}{ver}" for op, ver in self.specs))
2020-10-25 18:09:37 +00:00
def __hash__(self):
return hash((super().__hash__(), self.build))
2020-04-22 09:28:58 +00:00
def filter_reqs_by_eval_marker(reqs: Iterable[Requirement], context: dict, selected_extras=None):
2020-04-22 09:28:58 +00:00
# filter requirements relevant for current environment
for req in reqs:
if req.marker is None:
2020-04-22 09:28:58 +00:00
yield req
elif selected_extras:
for extra in selected_extras:
extra_context = context.copy()
extra_context['extra'] = extra
if distlib.markers.interpret(str(req.marker), extra_context):
yield req
else:
if distlib.markers.interpret(str(req.marker), context):
yield req
2020-04-22 09:28:58 +00:00
2020-11-17 11:29:44 +00:00
all_ops = {'==', '!=', '<=', '>=', '<', '>', '~=', ';'}
2020-10-25 18:09:37 +00:00
@cached(lambda args: tuple(args[0]) if isinstance(args[0], list) else args[0])
2020-04-22 09:28:58 +00:00
def parse_reqs(strs):
2020-10-25 18:09:37 +00:00
lines = iter(pkg_resources.yield_lines(strs))
for line in lines:
if ' #' in line:
line = line[:line.find(' #')]
if line.endswith('\\'):
line = line[:-2].strip()
try:
line += next(lines)
except StopIteration:
return
2020-11-17 11:29:44 +00:00
yield Requirement(*parse_reqs_line(line))
2020-10-25 18:09:37 +00:00
2020-11-17 11:29:44 +00:00
def parse_reqs_line(line):
2020-11-17 12:49:44 +00:00
build = None
line = line.strip(' ,')
splitted = line.split(' ')
# conda spec with build like "tensorflow-base 2.0.0 gpu_py36h0ec5d1f_0"
# or "hdf5 >=1.10.5,<1.10.6.0a0 mpi_mpich_*"
if len(splitted) == 3 \
2020-11-18 18:09:56 +00:00
and not splitted[1] in all_ops \
2020-11-17 12:49:44 +00:00
and not any(op in splitted[0]+splitted[2] for op in all_ops) \
and (
2020-11-18 18:09:56 +00:00
splitted[-1].isdigit()
or (len(splitted[-1]) > 1 and splitted[-1][-2] == '_')
or '*' in splitted[-1]
or not any(op in splitted[1] for op in all_ops)
2020-11-17 12:49:44 +00:00
):
name, ver_spec, build = splitted
if not any(op in ver_spec for op in all_ops):
ver_spec = f"=={ver_spec}"
line = f"{name}{ver_spec}"
# parse conda specifiers without operator like "requests 2.24.*"
elif len(splitted) == 2:
name, ver_spec = splitted
if not any(op in name + ver_spec for op in all_ops):
ver_spec = f"=={ver_spec}"
line = f"{name}{ver_spec}"
if build == "*":
2020-11-18 18:09:56 +00:00
build = None
2020-11-17 12:49:44 +00:00
return line, build