From d36d930802ddf5f7e6cd4ef39d40d3872d53afcd Mon Sep 17 00:00:00 2001 From: Richard Ipsum Date: Thu, 6 Nov 2014 17:16:41 +0000 Subject: move resolve stuff into resolve_version_constraints --- exts/pip.find_deps | 238 ------------------------------------------------ exts/pip_find_deps.py | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 238 deletions(-) delete mode 100755 exts/pip.find_deps create mode 100755 exts/pip_find_deps.py diff --git a/exts/pip.find_deps b/exts/pip.find_deps deleted file mode 100755 index 8cd8364..0000000 --- a/exts/pip.find_deps +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python -# -# Find the build and runtime dependencies for a given Python package -# -# Copyright (C) 2014 Codethink Limited -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from __future__ import print_function - -import sys -import subprocess -import os - -import pkg_resources -import xmlrpclib - -DEBUG = False - -# TODO: I'm guessing these things are probably standard somewhere -def warn(*args, **kwargs): - print('%s:' % sys.argv[0], *args, file=sys.stderr, **kwargs) - -def error(*args, **kwargs): - warn(*args, **kwargs) - sys.exit(1) - -def debug(s): - if DEBUG: - print(s) - -# TODO: this is copied from pip_lorry, it should be shared. -def specs_satisfied(version, specs): - def mapping_error(op): - # We parse ops with requirements-parser, so any invalid user input - # should be detected there. This really guards against - # the pip developers adding some new operation to a requirement. - error("Invalid op in spec: %s" % op) - - opmap = {'==' : lambda x, y: x == y, '!=' : lambda x, y: x != y, - '<=' : lambda x, y: x <= y, '>=' : lambda x, y: x >= y, - '<': lambda x, y: x < y, '>' : lambda x, y: x > y} - - def get_op_func(op): - return opmap[op] if op in opmap else lambda x, y: mapping_error(op) - - return all([get_op_func(op)(version, sv) for (op, sv) in specs]) - -def conflict(specs, spec): - # Return whether spec_x conflicts with spec_y - # examples: ('==', '0.1') conflicts with ('==', '0.2') - - # ('==', '0.1') conflicts with ('<', 0.1) - # ('==', '0.1') conflicts with ('>', 0.1) - # ('==', '0.1') conflicts with ('!=', 0.1) - - # ('<', '0.1') conflicts with ('>', '0.1') - # ('<=', 0.1) conflicts with ('>=', 0.2) - # ('<', '0.1') conflicts with ('>', '0.2') - pass - -class Dependency(object): - def __init__(self): - # non-inclusive boundaries - self.less_than = None - self.greater_than = None - - self.absolute = None - self.excludes = [] - - # A list of conclicting specs for this dependency - self.conflicts = [] - - def is_unbounded(self): - return self.less_than == None and self.greater_than == None - - def _in_less_than(self, version): - return True if self.less_than == None else version < self.less_than - - def _in_greater_than(self, version): - return True if self.greater_than == None else version > self.greater_than - - def in_bounds(self, version): - return self.is_unbounded() or (self._in_less_than(version) - and self._in_greater_than(version)) - - def get_bounds_conflict(self, version): - if not self._in_less_than(version): - return ('<', str(self.less_than)) - elif not self._in_greater_than(version): - return ('>', str(self.greater_than)) - else: - return None - - def is_unconstrained(self): - return (self.is_unbounded() and self.excludes == [] - and self.absolute == None) - - def set_absolute_version(self, version): - self.absolute = version - - -def check_eqs(dep, version): - if dep.is_unconstrained(): - dep.set_absolute_version(version) - elif version not in dep.excludes: - if dep.absolute in [version, None]: - if dep.in_bounds(version): - dep.set_absolute_version(version) - else: - warn('conflict! == %s conflicts with %s' - % (version, - dep.get_bounds_conflict(version))) - dep.conflicts.append((('==', version), - dep.get_bounds_conflict(version))) - - else: - warn('conflict! == %s conflicts with == %s' - % (version, dep.absolute)) - dep.conflicts.append(('==', version), ('!=', dep.absolute)) - elif version in dep.excludes: - warn('conflict! == %s conflicts with != %s' - % (version, version)) - dep.conflicts.append((('==', version), ('!=', version))) - elif version > dep.less_than: - # conflict - warn('conflict! == %s conflicts with < %s' - % (version, dep.less_than)) - dep.conflicts.append((('==', version), ('<', dep.less_than))) - elif version < dep.greater_than: - # conflict - warn('conflict! == %s conflicts with > %s' - % (version, dep.greater_than)) - dep.conflicts.append(('==', version), ('>', dep.greater_than)) - -def check_lt(dep, version): - if dep.is_unconstrained(): - dep.less_than = version - elif dep.greater_than >= version: - # conflict #(our minimum version is greater - # than this greater_than version) - warn('conflict! > %s conflicts with < %s' - % (dep.greater_than, version)) - dep.conflicts.append((('>', dep.greater_than), ('<', version))) - else: - dep.less_than = version - -def check_gt(dep, version): - if dep.is_unconstrained(): - dep.greater_than = version - elif dep.less_than <= version: - # conflict (our maximum version is less than this - # less_than version) - warn('conflict! < %s conflicts with > %s' - % (dep.less_than, version)) - dep.conflicts.append((('<', dep.less_than), ('>', version))) - -def find_build_deps(source, name, version=None): - debug('source: %s' % source) - debug('name: %s' % name) - debug('version: %s' % version) - - # This amounts to running python setup.py egg_info and checking - # the resulting egg_info dir for a file called setup_requires.txt - - # So it's $name.egg_info - p = subprocess.Popen(['python', 'setup.py', 'egg_info'], cwd=source, - stdout=subprocess.PIPE) - - if p.wait() != 0: - error('egg_info command failed') - - egg_dir = '%s.egg-info' % name - build_deps_file = os.path.join(source, egg_dir, 'setup_requires.txt') - - build_deps = {} - - # Check whether there's a setup_requires.txt - if not os.path.isfile(build_deps_file): - print('%s has no build dependencies' % name) - else: - with open(build_deps_file) as f: - for r in pkg_resources.parse_requirements(f): - print('%s %s' % (r.project_name, r.specs)) - - if r.project_name not in build_deps: - build_deps[r.project_name] = Dependency() - - for (op, version) in r.specs: - version = pkg_resources.parse_version(version) - - #if no_conflict(requirements_versions_map[r.project_name], s): - # requirements_versions_map[r.project_name] += s - dep = build_deps[r.project_name] - - print('dep.excludes: %s' % str(dep.excludes)) - - # TODO: replace with function table - if op == '==': - check_eqs(dep, version) - elif op == '!=': - dep.excludes.append(version) - # TODO: chk_eqs needed here - elif op == '<': - check_lt(dep, version) - elif op == '>': - check_gt(dep, version) - - # Resolve versions - #client = xmlrpclib.ServerProxy(PYPI_URL) - #releases = client.package_releases(requirement.name) - - return build_deps - -if len(sys.argv) not in [3, 4]: - print('usage: %s PACKAGE_SOURCE_DIR NAME [VERSION]' % sys.argv[0]) - sys.exit(1) - -# Ignore the issue of dependency conflicts for now - -# First, given a source return build dependencies in json from - -for name, dep in find_build_deps(*sys.argv[1:]).iteritems(): - print('%s less_than: %s greater_than: %s absolute: %s excludes: %s' - % (name, str(dep.less_than), str(dep.greater_than), - str(dep.absolute), dep.excludes)) - print('conflicts: %s' % str(dep.conflicts)) \ No newline at end of file diff --git a/exts/pip_find_deps.py b/exts/pip_find_deps.py new file mode 100755 index 0000000..3666afe --- /dev/null +++ b/exts/pip_find_deps.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# +# Find the build and runtime dependencies for a given Python package +# +# Copyright (C) 2014 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import print_function + +import sys +import subprocess +import os + +import pkg_resources +import xmlrpclib + +DEBUG = False + +# TODO: I'm guessing these things are probably standard somewhere +def warn(*args, **kwargs): + print('%s:' % sys.argv[0], *args, file=sys.stderr, **kwargs) + +def error(*args, **kwargs): + warn(*args, **kwargs) + sys.exit(1) + +def debug(s): + if DEBUG: + print(s) + +# TODO: this is copied from pip_lorry, it should be shared. +def specs_satisfied(version, specs): + def mapping_error(op): + # We parse ops with requirements-parser, so any invalid user input + # should be detected there. This really guards against + # the pip developers adding some new operation to a requirement. + error("Invalid op in spec: %s" % op) + + opmap = {'==' : lambda x, y: x == y, '!=' : lambda x, y: x != y, + '<=' : lambda x, y: x <= y, '>=' : lambda x, y: x >= y, + '<': lambda x, y: x < y, '>' : lambda x, y: x > y} + + def get_op_func(op): + return opmap[op] if op in opmap else lambda x, y: mapping_error(op) + + return all([get_op_func(op)(version, sv) for (op, sv) in specs]) + +def conflict(specs, spec): + # Return whether spec_x conflicts with spec_y + # examples: ('==', '0.1') conflicts with ('==', '0.2') + + # ('==', '0.1') conflicts with ('<', 0.1) + # ('==', '0.1') conflicts with ('>', 0.1) + # ('==', '0.1') conflicts with ('!=', 0.1) + + # ('<', '0.1') conflicts with ('>', '0.1') + # ('<=', 0.1) conflicts with ('>=', 0.2) + # ('<', '0.1') conflicts with ('>', '0.2') + pass + +class Dependency(object): + def __init__(self): + # non-inclusive boundaries + self.less_than = None + self.greater_than = None + + self.absolute = None + self.excludes = [] + + # A list of conclicting specs for this dependency + self.conflicts = [] + + def is_unbounded(self): + return self.less_than == None and self.greater_than == None + + def _in_less_than(self, version): + return True if self.less_than == None else version < self.less_than + + def _in_greater_than(self, version): + return True if self.greater_than == None else version > self.greater_than + + def in_bounds(self, version): + return self.is_unbounded() or (self._in_less_than(version) + and self._in_greater_than(version)) + + def get_bounds_conflict(self, version): + if not self._in_less_than(version): + return ('<', str(self.less_than)) + elif not self._in_greater_than(version): + return ('>', str(self.greater_than)) + else: + return None + + def is_unconstrained(self): + return (self.is_unbounded() and self.excludes == [] + and self.absolute == None) + + def set_absolute_version(self, version): + self.absolute = version + + +def check_eqs(dep, version): + if dep.is_unconstrained(): + dep.set_absolute_version(version) + elif version not in dep.excludes: + if dep.absolute in [version, None]: + if dep.in_bounds(version): + dep.set_absolute_version(version) + else: + warn('conflict! == %s conflicts with %s' + % (version, + dep.get_bounds_conflict(version))) + dep.conflicts.append((('==', version), + dep.get_bounds_conflict(version))) + + else: + warn('conflict! == %s conflicts with == %s' + % (version, dep.absolute)) + dep.conflicts.append(('==', version), ('!=', dep.absolute)) + elif version in dep.excludes: + warn('conflict! == %s conflicts with != %s' + % (version, version)) + dep.conflicts.append((('==', version), ('!=', version))) + elif version > dep.less_than: + # conflict + warn('conflict! == %s conflicts with < %s' + % (version, dep.less_than)) + dep.conflicts.append((('==', version), ('<', dep.less_than))) + elif version < dep.greater_than: + # conflict + warn('conflict! == %s conflicts with > %s' + % (version, dep.greater_than)) + dep.conflicts.append(('==', version), ('>', dep.greater_than)) + +def check_lt(dep, version): + if dep.is_unconstrained(): + dep.less_than = version + elif dep.greater_than >= version: + # conflict #(our minimum version is greater + # than this greater_than version) + warn('conflict! > %s conflicts with < %s' + % (dep.greater_than, version)) + dep.conflicts.append((('>', dep.greater_than), ('<', version))) + else: + dep.less_than = version + +def check_gt(dep, version): + if dep.is_unconstrained(): + dep.greater_than = version + elif dep.less_than <= version: + # conflict (our maximum version is less than this + # less_than version) + warn('conflict! < %s conflicts with > %s' + % (dep.less_than, version)) + dep.conflicts.append((('<', dep.less_than), ('>', version))) + +def resolve_version_constraints(requirements): + build_deps = {} + + for r in requirements: + print('%s %s' % (r.project_name, r.specs)) + + if r.project_name not in build_deps: + build_deps[r.project_name] = Dependency() + + for (op, version) in r.specs: + version = pkg_resources.parse_version(version) + + #if no_conflict(requirements_versions_map[r.project_name], s): + # requirements_versions_map[r.project_name] += s + dep = build_deps[r.project_name] + + print('dep.excludes: %s' % str(dep.excludes)) + + # TODO: replace with function table + if op == '==': + check_eqs(dep, version) + elif op == '!=': + dep.excludes.append(version) + # TODO: chk_eqs needed here + elif op == '<': + check_lt(dep, version) + elif op == '>': + check_gt(dep, version) + + # Resolve versions + #client = xmlrpclib.ServerProxy(PYPI_URL) + #releases = client.package_releases(requirement.name) + return build_deps + +def find_build_deps(source, name, version=None): + debug('source: %s' % source) + debug('name: %s' % name) + debug('version: %s' % version) + + # This amounts to running python setup.py egg_info and checking + # the resulting egg_info dir for a file called setup_requires.txt + + # So it's $name.egg_info + p = subprocess.Popen(['python', 'setup.py', 'egg_info'], cwd=source, + stdout=subprocess.PIPE) + + if p.wait() != 0: + error('egg_info command failed') + + egg_dir = '%s.egg-info' % name + build_deps_file = os.path.join(source, egg_dir, 'setup_requires.txt') + + build_deps = {} + + # Check whether there's a setup_requires.txt + if not os.path.isfile(build_deps_file): + print('%s has no build dependencies' % name) + else: + with open(build_deps_file) as f: + build_deps = resolve_version_constraints( + pkg_resources.parse_requirements(f)) + + return build_deps + +if len(sys.argv) not in [3, 4]: + print('usage: %s PACKAGE_SOURCE_DIR NAME [VERSION]' % sys.argv[0]) + sys.exit(1) + +# Ignore the issue of dependency conflicts for now + +# First, given a source return build dependencies in json from + +for name, dep in find_build_deps(*sys.argv[1:]).iteritems(): + print('%s less_than: %s greater_than: %s absolute: %s excludes: %s' + % (name, str(dep.less_than), str(dep.greater_than), + str(dep.absolute), dep.excludes)) + print('conflicts: %s' % str(dep.conflicts)) \ No newline at end of file -- cgit v1.2.1