summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct1
-rwxr-xr-xbuildscripts/scons.py13
-rw-r--r--etc/pip/components/compile.req2
-rw-r--r--site_scons/mongo/pip_requirements.py73
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)