diff options
-rw-r--r-- | SConstruct | 1 | ||||
-rwxr-xr-x | buildscripts/scons.py | 13 | ||||
-rw-r--r-- | etc/pip/components/compile.req | 2 | ||||
-rw-r--r-- | site_scons/mongo/pip_requirements.py | 73 |
4 files changed, 88 insertions, 1 deletions
diff --git a/SConstruct b/SConstruct index d70e0a7e6b2..4a585c15e17 100644 --- a/SConstruct +++ b/SConstruct @@ -1,4 +1,5 @@ # -*- mode: python; -*- + import atexit import copy import datetime diff --git a/buildscripts/scons.py b/buildscripts/scons.py index 6143dc7e3b6..534fca3264e 100755 --- a/buildscripts/scons.py +++ b/buildscripts/scons.py @@ -14,7 +14,18 @@ if not os.path.exists(SCONS_DIR): print("Could not find SCons in '%s'" % (SCONS_DIR)) sys.exit(1) -sys.path = [SCONS_DIR] + sys.path +SITE_TOOLS_DIR = os.path.join(MONGODB_ROOT, 'site_scons') + +sys.path = [SCONS_DIR, SITE_TOOLS_DIR] + sys.path + +# pylint: disable=C0413 +from mongo.pip_requirements import verify_requirements, MissingRequirements + +try: + verify_requirements('etc/pip/compile-requirements.txt') +except MissingRequirements as ex: + print(ex) + sys.exit(1) try: import SCons.Script diff --git a/etc/pip/components/compile.req b/etc/pip/components/compile.req index 34a8ff66e0f..7c1e8f4f45d 100644 --- a/etc/pip/components/compile.req +++ b/etc/pip/components/compile.req @@ -2,3 +2,5 @@ Cheetah3 # src/mongo/base/generate_error_codes.py psutil regex +requirements_parser +setuptools
\ No newline at end of file diff --git a/site_scons/mongo/pip_requirements.py b/site_scons/mongo/pip_requirements.py new file mode 100644 index 00000000000..5fd9b947b02 --- /dev/null +++ b/site_scons/mongo/pip_requirements.py @@ -0,0 +1,73 @@ +# -*- mode: python; -*- + +# Try to keep this modules imports minimum and only +# import python standard modules, because this module +# should be used for finding such external modules or +# missing dependencies. +import sys + + +class MissingRequirements(Exception): + """Raised when when verify_requirements() detects missing requirements.""" + pass + + +def verify_requirements(requirements_file: str, silent: bool = False): + """Check if the modules in a pip requirements file are installed. + This allows for a more friendly user message with guidance on how to + resolve the missing dependencies. + Args: + requirements_file: path to a pip requirements file. + silent: True if the function should print. + Raises: + MissingRequirements if any requirements are missing + """ + + def verbose(*args, **kwargs): + if not silent: + print(*args, **kwargs) + + def raiseSuggestion(ex, pip_pkg): + raise MissingRequirements( + f"{ex}\n" + f"Try running:\n" + f" {sys.executable} -m pip install {pip_pkg}" + ) from ex + + # Import the prequisites for this function, providing hints on failure. + try: + import requirements + except ModuleNotFoundError as ex: + raiseSuggestion(ex, "requirements_parser") + + try: + import pkg_resources + except ModuleNotFoundError as ex: + raiseSuggestion(ex, "setuptools") + + verbose("Checking required python packages...") + + # Reduce a pip requirements file to its PEP 508 requirement specifiers. + with open(requirements_file) as fd: + pip_lines = [p.line for p in requirements.parse(fd)] + + # The PEP 508 requirement specifiers can be parsed by the `pkg_resources`. + pkg_requirements = list(pkg_resources.parse_requirements(pip_lines)) + + verbose("Requirements list:") + for req in sorted(set([str(req) for req in pkg_requirements])): + verbose(f" {req}") + + # Resolve all the requirements at once. + # This should help expose dependency hell among the requirements. + try: + dists = pkg_resources.working_set.resolve(pkg_requirements) + except pkg_resources.ResolutionError as ex: + raiseSuggestion( + ex, + f"-r {requirements_file}") + + + verbose("Resolved to these distributions:") + for dist in sorted(set([f" {dist.key} {dist.version}" for dist in dists])): + verbose(dist) |