summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/pythonutil.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/pythonutil.py')
-rw-r--r--python/mozbuild/mozbuild/pythonutil.py107
1 files changed, 107 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/pythonutil.py b/python/mozbuild/mozbuild/pythonutil.py
new file mode 100644
index 0000000000..00b2deef62
--- /dev/null
+++ b/python/mozbuild/mozbuild/pythonutil.py
@@ -0,0 +1,107 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+import six
+import subprocess
+import sys
+
+from distutils.version import StrictVersion
+
+
+def iter_modules_in_path(*paths):
+ paths = [os.path.abspath(os.path.normcase(p)) + os.sep for p in paths]
+ for name, module in sys.modules.items():
+ if getattr(module, "__file__", None) is None:
+ continue
+ if module.__file__ is None:
+ continue
+ path = module.__file__
+
+ if path.endswith(".pyc"):
+ path = path[:-1]
+ path = os.path.abspath(os.path.normcase(path))
+
+ if any(path.startswith(p) for p in paths):
+ yield path
+
+
+def python_executable_version(exe):
+ """Determine the version of a Python executable by invoking it.
+
+ May raise ``subprocess.CalledProcessError`` or ``ValueError`` on failure.
+ """
+ program = "import sys; print('.'.join(map(str, sys.version_info[0:3])))"
+ out = six.ensure_text(
+ subprocess.check_output([exe, "-c", program], universal_newlines=True)
+ ).rstrip()
+ return StrictVersion(out)
+
+
+def _find_python_executable(major):
+ if major not in (2, 3):
+ raise ValueError("Expected a Python major version of 2 or 3")
+ min_versions = {2: "2.7.0", 3: "3.6.0"}
+
+ def ret(min_version=min_versions[major]):
+ from mozfile import which
+
+ prefix = min_version[0] + "."
+ if not min_version.startswith(prefix):
+ raise ValueError(
+ "min_version expected a %sx string, got %s" % (prefix, min_version)
+ )
+
+ min_version = StrictVersion(min_version)
+ major = min_version.version[0]
+
+ if sys.version_info.major == major:
+ our_version = StrictVersion("%s.%s.%s" % (sys.version_info[0:3]))
+
+ if our_version >= min_version:
+ # This will potentially return a virtualenv Python. It's probably
+ # OK for now...
+ return sys.executable, our_version.version
+
+ # Else fall back to finding another binary.
+
+ # https://www.python.org/dev/peps/pep-0394/ defines how the Python binary
+ # should be named. `python3` should refer to some Python 3, and
+ # `python2` to some Python 2. `python` may refer to a Python 2 or 3.
+ # `pythonX.Y` may exist.
+ #
+ # Since `python` is ambiguous and `pythonX` should always exist, we
+ # ignore `python` here. We instead look for the preferred `pythonX` first
+ # and fall back to `pythonX.Y` if it isn't found or doesn't meet our
+ # version requirements.
+ names = ["python%d" % major]
+
+ # Look for `pythonX.Y` down to our minimum version.
+ for minor in range(9, min_version.version[1] - 1, -1):
+ names.append("python%d.%d" % (major, minor))
+
+ for name in names:
+ exe = which(name)
+ if not exe:
+ continue
+
+ # We always verify we can invoke the executable and its version is
+ # sane.
+ try:
+ version = python_executable_version(exe)
+ except (subprocess.CalledProcessError, ValueError):
+ continue
+
+ if version >= min_version:
+ return exe, version.version
+
+ return None, None
+
+ return ret
+
+
+find_python2_executable = _find_python_executable(2)
+find_python3_executable = _find_python_executable(3)