mach (7311B)
1 #!/usr/bin/env python3 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 6 import os 7 import platform 8 import sys 9 import subprocess 10 import traceback 11 from textwrap import dedent, fill 12 13 MIN_PYTHON_VERSION = (3, 9) 14 MAX_PYTHON_VERSION_TO_CONSIDER = (3, 12) 15 16 17 def load_mach(dir_path, mach_path, args): 18 # Defer import of "importlib.util" until after Python version check has happened 19 # so that Python 2 usages fail gracefully. 20 import importlib.util 21 22 spec = importlib.util.spec_from_file_location("mach_initialize", mach_path) 23 mach_initialize = importlib.util.module_from_spec(spec) 24 spec.loader.exec_module(mach_initialize) 25 return mach_initialize.initialize(dir_path, args) 26 27 28 def check_and_get_mach(dir_path, args): 29 initialize_paths = ( 30 # Run Thunderbird's mach_initialize.py if it exists 31 "comm/build/mach_initialize.py", 32 "build/mach_initialize.py", 33 # test package initialize 34 "tools/mach_initialize.py", 35 ) 36 for initialize_path in initialize_paths: 37 mach_path = os.path.join(dir_path, initialize_path) 38 if os.path.isfile(mach_path): 39 return load_mach(dir_path, mach_path, args) 40 return None 41 42 43 def find_alternate_python3_executables(): 44 for i in range(MIN_PYTHON_VERSION[1], MAX_PYTHON_VERSION_TO_CONSIDER[1] + 1): 45 potential_python_binary = f"python3.{i}" 46 if os.name == "nt": 47 potential_python_binary += ".exe" 48 49 try: 50 out = subprocess.run( 51 [potential_python_binary, "--version"], 52 stdout=subprocess.PIPE, 53 stderr=subprocess.PIPE, 54 encoding="UTF-8", 55 ) 56 57 binary_minor_version = int(out.stdout[9:11].strip(".")) 58 59 if binary_minor_version >= MIN_PYTHON_VERSION[1]: 60 yield potential_python_binary 61 62 except Exception: 63 pass 64 65 66 def try_alternate_python3_executables(args): 67 for potential_python_binary in find_alternate_python3_executables(): 68 try: 69 print( 70 f"We found '{potential_python_binary}' and will attempt to re-run Mach with it." 71 ) 72 os.execvp( 73 potential_python_binary, [potential_python_binary] + ["mach"] + args 74 ) 75 except Exception: 76 # We don't really care what goes wrong, just don't let it bubble up 77 # If we can't successfully launch with a different python3 binary 78 # we will just print the normal help messages. 79 pass 80 81 82 def main(args): 83 # Ensure we are running Python 3.9+. We run this check as soon as 84 # possible to avoid a cryptic import/usage error. 85 if sys.version_info < MIN_PYTHON_VERSION: 86 print( 87 f"Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]}+ is required to run mach." 88 ) 89 print("You are running Mach with Python {0}".format(platform.python_version())) 90 try_alternate_python3_executables(args) 91 if sys.platform.startswith("linux"): 92 print( 93 dedent( 94 """ 95 See https://firefox-source-docs.mozilla.org/setup/linux_build.html#install-python 96 for guidance on how to install Python on your system. 97 """ 98 ).strip() 99 ) 100 elif sys.platform.startswith("darwin"): 101 print( 102 dedent( 103 """ 104 See https://firefox-source-docs.mozilla.org/setup/macos_build.html 105 for guidance on how to prepare your system to build Firefox. Perhaps 106 you need to update Xcode, or install Python using brew? 107 """ 108 ).strip() 109 ) 110 elif "MOZILLABUILD" in os.environ and os.environ.get("TERM"): 111 print( 112 dedent( 113 """ 114 Python is provided by MozillaBuild; ensure your MozillaBuild installation is 115 up to date. See https://firefox-source-docs.mozilla.org/setup/windows_build.html#install-mozillabuild 116 for details. 117 """ 118 ).strip() 119 ) 120 elif sys.platform.startswith("win"): 121 print( 122 dedent( 123 """ 124 You probably want to be interacting with Mach from within MozillaBuild, see 125 https://firefox-source-docs.mozilla.org/setup/windows_build.html for details. 126 127 If you are deliberately using Mach from outside MozillaBuild, then see 128 https://firefox-source-docs.mozilla.org/mach/windows-usage-outside-mozillabuild.html#install-python 129 for guidance on installing native Python on your system. 130 """ 131 ).strip() 132 ) 133 else: 134 print( 135 dedent( 136 """ 137 We do not have specific instructions for your platform on how to 138 install Python. You may find Pyenv (https://github.com/pyenv/pyenv) 139 helpful, if your system package manager does not provide a way to 140 install a recent enough Python 3. 141 """ 142 ).strip() 143 ) 144 sys.exit(1) 145 146 # XCode python sets __PYVENV_LAUNCHER__, which overrides the executable 147 # used when a python subprocess is created. This is an issue when we want 148 # to run using our virtualenv python executables. 149 # In future Python relases, __PYVENV_LAUNCHER__ will be cleared before 150 # application code (mach) is started. 151 # https://github.com/python/cpython/pull/9516 152 os.environ.pop("__PYVENV_LAUNCHER__", None) 153 154 try: 155 mach = check_and_get_mach(os.path.dirname(os.path.realpath(__file__)), args) 156 if not mach: 157 print("Could not run mach: No mach source directory found.") 158 sys.exit(1) 159 sys.exit(mach.run(args)) 160 except (KeyboardInterrupt, SystemExit): 161 raise 162 except Exception as e: 163 if sys.version_info >= ( 164 MAX_PYTHON_VERSION_TO_CONSIDER[0], 165 MAX_PYTHON_VERSION_TO_CONSIDER[1] + 1, 166 ): 167 traceback.print_exc() 168 print() 169 print("---") 170 print() 171 print( 172 fill( 173 dedent( 174 f"""\ 175 Note that you are running Mach with Python 176 {platform.python_version()}, which is higher than the highest 177 known working version of Python for Mach. Consider running Mach 178 with Python {MAX_PYTHON_VERSION_TO_CONSIDER[0]}.{MAX_PYTHON_VERSION_TO_CONSIDER[1]} 179 or lower.""" 180 ) 181 ) 182 ) 183 184 try: 185 alternative = next(find_alternate_python3_executables()) 186 print() 187 print("Running the following command may solve your issue:") 188 print() 189 print(f" {alternative} {sys.argv[0]} {' '.join(args)}") 190 print() 191 except StopIteration: 192 pass 193 sys.exit(1) 194 else: 195 raise 196 197 198 if __name__ == "__main__": 199 main(sys.argv[1:])