1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5from __future__ import absolute_import 6 7import os 8import subprocess 9import sys 10 11from distutils.version import ( 12 StrictVersion, 13) 14 15 16def iter_modules_in_path(*paths): 17 paths = [os.path.abspath(os.path.normcase(p)) + os.sep 18 for p in paths] 19 for name, module in sys.modules.items(): 20 if not hasattr(module, '__file__'): 21 continue 22 23 path = module.__file__ 24 25 if path.endswith('.pyc'): 26 path = path[:-1] 27 path = os.path.abspath(os.path.normcase(path)) 28 29 if any(path.startswith(p) for p in paths): 30 yield path 31 32 33def python_executable_version(exe): 34 """Determine the version of a Python executable by invoking it. 35 36 May raise ``subprocess.CalledProcessError`` or ``ValueError`` on failure. 37 """ 38 program = "import sys; print('.'.join(map(str, sys.version_info[0:3])))" 39 out = subprocess.check_output([exe, '-c', program]).rstrip() 40 return StrictVersion(out) 41 42 43def find_python3_executable(min_version='3.5.0'): 44 """Find a Python 3 executable. 45 46 Returns a tuple containing the the path to an executable binary and a 47 version tuple. Both tuple entries will be None if a Python executable 48 could not be resolved. 49 """ 50 import which 51 52 if not min_version.startswith('3.'): 53 raise ValueError('min_version expected a 3.x string, got %s' % 54 min_version) 55 56 min_version = StrictVersion(min_version) 57 58 if sys.version_info.major >= 3: 59 our_version = StrictVersion('%s.%s.%s' % (sys.version_info[0:3])) 60 61 if our_version >= min_version: 62 # This will potentially return a virtualenv Python. It's probably 63 # OK for now... 64 return sys.executable, our_version.version 65 66 # Else fall back to finding another binary. 67 68 # https://www.python.org/dev/peps/pep-0394/ defines how the Python binary 69 # should be named. `python3` should refer to some Python 3. `python` may 70 # refer to a Python 2 or 3. `pythonX.Y` may exist. 71 # 72 # Since `python` is ambiguous and `python3` should always exist, we 73 # ignore `python` here. We instead look for the preferred `python3` first 74 # and fall back to `pythonX.Y` if it isn't found or doesn't meet our 75 # version requirements. 76 names = ['python3'] 77 78 # Look for `python3.Y` down to our minimum version. 79 for minor in range(9, min_version.version[1] - 1, -1): 80 names.append('python3.%d' % minor) 81 82 for name in names: 83 try: 84 exe = which.which(name) 85 except which.WhichError: 86 continue 87 88 # We always verify we can invoke the executable and its version is 89 # sane. 90 try: 91 version = python_executable_version(exe) 92 except (subprocess.CalledProcessError, ValueError): 93 continue 94 95 if version >= min_version: 96 return exe, version.version 97 98 return None, None 99