aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Matthews <cmatthews5@apple.com>2018-02-01 00:33:16 +0000
committerChris Matthews <cmatthews5@apple.com>2018-02-01 00:33:16 +0000
commit6daa080af05867359429a461d7cde1109a321f51 (patch)
tree8f0dc38a150596e90ef0d051674cec9c5f58dd2d
parentbcc3c05c58f79666e90ac61c4b361372c562fe9e (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.py60
-rw-r--r--dep/tests/assets/pip_output.json422
-rw-r--r--dep/tests/test_dep.py43
3 files changed, 518 insertions, 7 deletions
diff --git a/dep/dep.py b/dep/dep.py
index 925e451b..8850fbec 100644
--- a/dep/dep.py
+++ b/dep/dep.py
@@ -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()