diff options
author | Chris Matthews <cmatthews5@apple.com> | 2018-02-01 00:33:16 +0000 |
---|---|---|
committer | Chris Matthews <cmatthews5@apple.com> | 2018-02-01 00:33:16 +0000 |
commit | 6daa080af05867359429a461d7cde1109a321f51 (patch) | |
tree | 8f0dc38a150596e90ef0d051674cec9c5f58dd2d | |
parent | bcc3c05c58f79666e90ac61c4b361372c562fe9e (diff) |
Support for checking pip packages
git-svn-id: https://llvm.org/svn/llvm-project/zorg/trunk@323937 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | dep/dep.py | 60 | ||||
-rw-r--r-- | dep/tests/assets/pip_output.json | 422 | ||||
-rw-r--r-- | dep/tests/test_dep.py | 43 |
3 files changed, 518 insertions, 7 deletions
@@ -502,11 +502,71 @@ class Sdk(Dependency): return "{} {}".format(self.str_kind, self.version) +class Pip(Dependency): + """Verify and Inject pip package dependencies.""" + + # pip <package> <operator> <version>. Operator may not have spaces around it. + pip_re = re.compile(r'(?P<command>\w+)\s+(?P<package>\w+)\s*(?P<operator>>=|<=|==)\s*(?P<version_text>[\d.-_]+)') + + def __init__(self, line, kind): + # type: (Line, Text) -> None + """Parse and verify pip package is installed. + + :param line: the Line with the deceleration of the dependency. + :param kind: the detected dependency kind. + """ + super(Pip, self).__init__(line, kind) + self.command = None + self.operator = None + self.package = None + self.version = None + self.version_text = None + self.installed_version = None + + def parse(self): + """Parse this dependency.""" + text = self.line.text + match = self.pip_re.match(text) + if not match: + raise MalformedDependency("Expression does not compile in {}: {}".format(self.__class__.__name__, + self.line)) + self.__dict__.update(match.groupdict()) + + self.version = Version(self.version_text) + + def verify(self): + """Verify the packages in pip match this dependency.""" + try: + pip_package_config = json.loads(subprocess.check_output(["/usr/bin/env", + "python", "-m", "pip", "list", "--format=json"])) + except (subprocess.CalledProcessError, OSError): + raise MissingDependencyError(self, "Cannot find pip") + + installed = {p['name']: p['version'] for p in pip_package_config} # type: Dict[Text, Text] + + package = installed.get(self.package) + + if not package: + # The package is not installed at all. + raise MissingDependencyError(self, "not in package list") + self.installed_version = Version(package) + return check_version(self.installed_version, self.operator, self.version) + + def inject(self): + """Not implemented.""" + raise NotImplementedError() + + def __str__(self): + """Dependency kind, package and version, for printing in error messages.""" + return "{} {} {}".format(self.str_kind, self.package, self.version) + + dependencies_implementations = {'brew': Brew, 'os_version': HostOSVersion, 'config_manager': ConMan, 'xcode': Xcode, 'sdk': Sdk, + 'pip': Pip, } diff --git a/dep/tests/assets/pip_output.json b/dep/tests/assets/pip_output.json new file mode 100644 index 00000000..9de433b4 --- /dev/null +++ b/dep/tests/assets/pip_output.json @@ -0,0 +1,422 @@ +[ + { + "version": "0.10.2", + "name": "altgraph" + }, + { + "version": "1.2.0", + "name": "aniso8601" + }, + { + "version": "0.24.0", + "name": "asn1crypto" + }, + { + "version": "17.3.0", + "name": "attrs" + }, + { + "version": "1.4", + "name": "backports.functools-lru-cache" + }, + { + "version": "3.1.4", + "name": "bcrypt" + }, + { + "version": "0.5.0", + "name": "bdist-mpkg" + }, + { + "version": "0.3", + "name": "bonjour-py" + }, + { + "version": "1.11.2", + "name": "cffi" + }, + { + "version": "6.7", + "name": "click" + }, + { + "version": "0.5.5", + "name": "contextlib2" + }, + { + "version": "1.0", + "name": "CoreNLP" + }, + { + "version": "2.1.4", + "name": "cryptography" + }, + { + "version": "1.1.6", + "name": "enum34" + }, + { + "version": "1.14.0", + "name": "Fabric" + }, + { + "version": "0.12.2", + "name": "Flask" + }, + { + "version": "0.3.4", + "name": "Flask-RESTful" + }, + { + "version": "0.12", + "name": "Flask-WTF" + }, + { + "version": "1.0.2", + "name": "funcsigs" + }, + { + "version": "2.6", + "name": "idna" + }, + { + "version": "0.2.5", + "name": "inflect" + }, + { + "version": "1.0.19", + "name": "ipaddress" + }, + { + "version": "16.1", + "name": "irc" + }, + { + "version": "0.24", + "name": "itsdangerous" + }, + { + "version": "1.4.3", + "name": "jaraco.classes" + }, + { + "version": "1.5.2", + "name": "jaraco.collections" + }, + { + "version": "1.17", + "name": "jaraco.functools" + }, + { + "version": "2.1", + "name": "jaraco.itertools" + }, + { + "version": "1.5", + "name": "jaraco.logging" + }, + { + "version": "1.1.2", + "name": "jaraco.stream" + }, + { + "version": "1.9.2", + "name": "jaraco.text" + }, + { + "version": "2.10", + "name": "Jinja2" + }, + { + "version": "1.0", + "name": "LanguageModeling" + }, + { + "version": "0.6.0.dev0", + "name": "lit" + }, + { + "version": "0.4.2.dev0", + "name": "LNT" + }, + { + "version": "1.5.1", + "name": "macholib" + }, + { + "version": "1.0", + "name": "MarkupSafe" + }, + { + "version": "1.3.1", + "name": "matplotlib" + }, + { + "version": "2.0.0", + "name": "mock" + }, + { + "version": "0.10.4", + "name": "modulegraph" + }, + { + "version": "4.0.1", + "name": "more-itertools" + }, + { + "version": "1.8.0rc1", + "name": "numpy" + }, + { + "version": "2.4.0", + "name": "paramiko" + }, + { + "version": "3.1.1", + "name": "pbr" + }, + { + "version": "9.0.1", + "name": "pip" + }, + { + "version": "0.6.0", + "name": "pluggy" + }, + { + "version": "1.5.2", + "name": "py" + }, + { + "version": "0.7.3", + "name": "py2app" + }, + { + "version": "0.4.2", + "name": "pyasn1" + }, + { + "version": "2.18", + "name": "pycparser" + }, + { + "version": "1.2.1", + "name": "PyNaCl" + }, + { + "version": "2.5.1", + "name": "pyobjc-core" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Accounts" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-AddressBook" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-AppleScriptKit" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-AppleScriptObjC" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Automator" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-CFNetwork" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Cocoa" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Collaboration" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-CoreData" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-CoreLocation" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-CoreText" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-DictionaryServices" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-EventKit" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-ExceptionHandling" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-FSEvents" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-InputMethodKit" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-InstallerPlugins" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-InstantMessage" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-LatentSemanticMapping" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-LaunchServices" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Message" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-OpenDirectory" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-PreferencePanes" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-PubSub" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-QTKit" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Quartz" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-ScreenSaver" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-ScriptingBridge" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-SearchKit" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-ServiceManagement" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-Social" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-SyncServices" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-SystemConfiguration" + }, + { + "version": "2.5.1", + "name": "pyobjc-framework-WebKit" + }, + { + "version": "0.13.1", + "name": "pyOpenSSL" + }, + { + "version": "2.0.1", + "name": "pyparsing" + }, + { + "version": "3.3.1", + "name": "pytest" + }, + { + "version": "1.6.3", + "name": "pytest-mock" + }, + { + "version": "1.5", + "name": "python-dateutil" + }, + { + "version": "0.3.7", + "name": "python-gnupg" + }, + { + "version": "2013.7", + "name": "pytz" + }, + { + "version": "3.12", + "name": "PyYAML" + }, + { + "version": "6.4.0", + "name": "raven" + }, + { + "version": "0.13.0b1", + "name": "scipy" + }, + { + "version": "38.2.4", + "name": "setuptools" + }, + { + "version": "1.11.0", + "name": "six" + }, + { + "version": "1.1.11", + "name": "SQLAlchemy" + }, + { + "version": "1.9", + "name": "tempora" + }, + { + "version": "3.6.2", + "name": "typing" + }, + { + "version": "0.12.2", + "name": "Werkzeug" + }, + { + "version": "2.0.2", + "name": "WTForms" + }, + { + "version": "0.6.4", + "name": "xattr" + }, + { + "version": "4.1.1", + "name": "zope.interface" + } +]
\ No newline at end of file diff --git a/dep/tests/test_dep.py b/dep/tests/test_dep.py index 5f249871..0fd41422 100644 --- a/dep/tests/test_dep.py +++ b/dep/tests/test_dep.py @@ -7,6 +7,7 @@ import json import os import sys import pytest +import subprocess import dep from dep import Line, Brew, Version, MissingDependencyError, ConMan, HostOSVersion, Xcode, Sdk @@ -189,36 +190,64 @@ def test_sdk_version_requirement(mocker): b.verify_and_act() line = Line("foo.c", 10, "sdk iphoneos == 2.0", "test") - bad = Sdk(line, "sdk") + bad = dep.Sdk(line, "sdk") bad.parse() with pytest.raises(MissingDependencyError): bad.verify_and_act() - b = Sdk(Line("foo.c", 11, "sdk iphoneos == 1.0", "test"), "sdk") + b = dep.Sdk(Line("foo.c", 11, "sdk iphoneos == 1.0", "test"), "sdk") b.parse() b.verify_and_act() - b = Sdk(Line("foo.c", 11, "sdk iphoneos <= 1.0", "test"), "sdk") + b = dep.Sdk(Line("foo.c", 11, "sdk iphoneos <= 1.0", "test"), "sdk") b.parse() b.verify_and_act() - b = Sdk(Line("foo.c", 11, "sdk iphonesimulator <= 1.0", "test"), "sdk") + b = dep.Sdk(Line("foo.c", 11, "sdk iphonesimulator <= 1.0", "test"), "sdk") b.parse() b.verify_and_act() - b = Sdk(Line("foo.c", 11, "sdk iwash == 1.0", "test"), "sdk") + b = dep.Sdk(Line("foo.c", 11, "sdk iwash == 1.0", "test"), "sdk") b.parse() # TODO handle unversioned SDKs. with pytest.raises(MissingDependencyError): b.verify_and_act() - b = Sdk(Line("foo.c", 11, "sdk macosx == 10.12", "test"), "sdk") + b = dep.Sdk(Line("foo.c", 11, "sdk macosx == 10.12", "test"), "sdk") b.parse() b.verify_and_act() - b = Sdk(Line("foo.c", 11, "sdk macosx == 10.11", "test"), "sdk") + b = dep.Sdk(Line("foo.c", 11, "sdk macosx == 10.11", "test"), "sdk") b.parse() b.verify_and_act() +def test_pip_requirement(mocker): + """Detailed check of a pip packages dependency.""" + line = Line("foo.c", 10, "pip pytest <= 3.3.1", "test") + b = dep.Pip(line, "pip") + b.parse() + assert b.operator == "<=" + assert b.command == "pip" + assert b.package == "pytest" + assert b.version_text == "3.3.1" + mocker.patch('dep.subprocess.check_output') + dep.subprocess.check_output.return_value = open(here + '/assets/pip_output.json').read() + b.verify_and_act() + assert dep.subprocess.check_output.called + + b = dep.Pip(Line("foo.c", 10, "pip pytest == 3.3.1", "test"), "pip") + b.parse() + b.verify_and_act() + + b = dep.Pip(Line("foo.c", 10, "pip pytest <= 3.3.0", "test"), "pip") + b.parse() + with pytest.raises(MissingDependencyError): + b.verify_and_act() + + mocker.patch('dep.subprocess.check_output') + no_pip = "/usr/bin/python: No module named pip" + dep.subprocess.check_output.side_effect = subprocess.CalledProcessError(1, [], output=no_pip) + with pytest.raises(MissingDependencyError): + b.verify_and_act() |