configure.py (11964B)
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 5 import itertools 6 import logging 7 import os 8 import pprint 9 import sys 10 import textwrap 11 from collections.abc import Iterable 12 13 base_dir = os.path.abspath(os.path.dirname(__file__)) 14 sys.path.insert(0, os.path.join(base_dir, "python", "mach")) 15 sys.path.insert(0, os.path.join(base_dir, "python", "mozboot")) 16 sys.path.insert(0, os.path.join(base_dir, "python", "mozbuild")) 17 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "packaging")) 18 sys.path.insert(0, os.path.join(base_dir, "testing", "mozbase", "mozfile")) 19 sys.path.insert(0, os.path.join(base_dir, "testing", "mozbase", "mozshellutil")) 20 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "six")) 21 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "looseversion")) 22 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "filelock")) 23 import mozpack.path as mozpath 24 from mach.requirements import MachEnvRequirements 25 from mach.site import ( 26 CommandSiteManager, 27 ExternalPythonSite, 28 MachSiteManager, 29 MozSiteMetadata, 30 SitePackagesSource, 31 ) 32 from mozbuild.backend.configenvironment import PartialConfigEnvironment 33 from mozbuild.configure import TRACE, ConfigureSandbox 34 from mozbuild.pythonutil import iter_modules_in_path 35 from mozbuild.util import FileAvoidWrite 36 37 if "MOZ_CONFIGURE_BUILDSTATUS" in os.environ: 38 39 def buildstatus(message): 40 print("BUILDSTATUS", message) 41 42 else: 43 44 def buildstatus(message): 45 return 46 47 48 def main(argv): 49 # Check for CRLF line endings. 50 with open(__file__) as fh: 51 data = fh.read() 52 if "\r" in data: 53 print( 54 "\n ***\n" 55 " * The source tree appears to have Windows-style line endings.\n" 56 " *\n" 57 " * If using Git, Git is likely configured to use Windows-style\n" 58 " * line endings.\n" 59 " *\n" 60 " * To convert the working copy to UNIX-style line endings, run\n" 61 " * the following:\n" 62 " *\n" 63 " * $ git config core.autocrlf false\n" 64 " * $ git config core.eof lf\n" 65 " * $ git rm --cached -r .\n" 66 " * $ git reset --hard\n" 67 " *\n" 68 " * If not using Git, the tool you used to obtain the source\n" 69 " * code likely converted files to Windows line endings. See\n" 70 " * usage information for that tool for more.\n" 71 " ***", 72 file=sys.stderr, 73 ) 74 return 1 75 76 config = {} 77 78 sandbox = ConfigureSandbox(config, os.environ, argv) 79 80 if not sandbox._help: 81 # This limitation has mostly to do with GNU make. Since make can't represent 82 # variables with spaces without correct quoting and many paths are used 83 # without proper quoting, using paths with spaces commonly results in 84 # targets or dependencies being treated as multiple paths. This, of course, 85 # undermines the ability for make to perform up-to-date checks and makes 86 # the build system not work very efficiently. In theory, a non-make build 87 # backend will make this limitation go away. But there is likely a long tail 88 # of things that will need fixing due to e.g. lack of proper path quoting. 89 topsrcdir = os.path.realpath(os.path.dirname(__file__)) 90 if len(topsrcdir.split()) > 1: 91 print( 92 "Source directory cannot be located in a path with spaces: %s" 93 % topsrcdir, 94 file=sys.stderr, 95 ) 96 return 1 97 topobjdir = os.path.realpath(os.curdir) 98 if len(topobjdir.split()) > 1: 99 print( 100 "Object directory cannot be located in a path with spaces: %s" 101 % topobjdir, 102 file=sys.stderr, 103 ) 104 return 1 105 106 # Do not allow topobjdir == topsrcdir 107 if os.path.samefile(topsrcdir, topobjdir): 108 print( 109 " ***\n" 110 " * Building directly in the main source directory is not allowed.\n" 111 " *\n" 112 " * To build, you must run configure from a separate directory\n" 113 " * (referred to as an object directory).\n" 114 " *\n" 115 " * If you are building with a mozconfig, you will need to change your\n" 116 " * mozconfig to point to a different object directory.\n" 117 " ***", 118 file=sys.stderr, 119 ) 120 return 1 121 buildstatus("START_configure activate virtualenv") 122 _activate_build_virtualenv() 123 buildstatus("END_configure activate virtualenv") 124 125 clobber_file = "CLOBBER" 126 if not os.path.exists(clobber_file): 127 # Simply touch the file. 128 with open(clobber_file, "a"): 129 pass 130 131 if os.environ.get("MOZ_CONFIGURE_TRACE"): 132 sandbox._logger.setLevel(TRACE) 133 134 buildstatus("START_configure read moz.configure") 135 sandbox.include_file(os.path.join(os.path.dirname(__file__), "moz.configure")) 136 buildstatus("END_configure read moz.configure") 137 buildstatus("START_configure run moz.configure") 138 sandbox.run() 139 buildstatus("END_configure run moz.configure") 140 141 if sandbox._help: 142 return 0 143 144 buildstatus("START_configure config.status") 145 logging.getLogger("moz.configure").info("Creating config.status") 146 try: 147 js_config = config.copy() 148 pwd = os.getcwd() 149 try: 150 os.makedirs("js/src", exist_ok=True) 151 os.chdir("js/src") 152 # The build system frontend expects $objdir/js/src/config.status 153 # to have $objdir/js/src as topobjdir. 154 # We want forward slashes on all platforms. 155 js_config["TOPOBJDIR"] += "/js/src" 156 ret = config_status(js_config, execute=False) 157 if ret: 158 return ret 159 finally: 160 os.chdir(pwd) 161 return config_status(config) 162 finally: 163 buildstatus("END_configure config.status") 164 165 166 def check_unicode(obj): 167 """Recursively check that all strings in the object are unicode strings.""" 168 if isinstance(obj, dict): 169 result = True 170 for k, v in obj.items(): 171 if not check_unicode(k): 172 print("%s key is not unicode." % k, file=sys.stderr) 173 result = False 174 elif not check_unicode(v): 175 print("%s value is not unicode." % k, file=sys.stderr) 176 result = False 177 return result 178 if isinstance(obj, bytes): 179 return False 180 if isinstance(obj, str): 181 return True 182 if isinstance(obj, Iterable): 183 return all(check_unicode(o) for o in obj) 184 return True 185 186 187 def config_status(config, execute=True): 188 # Sanitize config data to feed config.status 189 # Ideally, all the backend and frontend code would handle the booleans, but 190 # there are so many things involved, that it's easier to keep config.status 191 # untouched for now. 192 def sanitize_config(v): 193 if v is True: 194 return "1" 195 if v is False: 196 return "" 197 # Serialize types that look like lists and tuples as lists. 198 if not isinstance(v, (bytes, str, dict)) and isinstance(v, Iterable): 199 return list(v) 200 return v 201 202 sanitized_config = {} 203 sanitized_config["substs"] = { 204 k: sanitize_config(v) 205 for k, v in config.items() 206 if k 207 not in ( 208 "DEFINES", 209 "TOPSRCDIR", 210 "TOPOBJDIR", 211 "CONFIG_STATUS_DEPS", 212 ) 213 } 214 sanitized_config["defines"] = { 215 k: sanitize_config(v) for k, v in config["DEFINES"].items() 216 } 217 sanitized_config["topsrcdir"] = config["TOPSRCDIR"] 218 sanitized_config["topobjdir"] = config["TOPOBJDIR"] 219 sanitized_config["mozconfig"] = config.get("MOZCONFIG") 220 221 if not check_unicode(sanitized_config): 222 print("Configuration should be all unicode.", file=sys.stderr) 223 print("Please file a bug for the above.", file=sys.stderr) 224 return 1 225 226 # Create config.status. Eventually, we'll want to just do the work it does 227 # here, when we're able to skip configure tests/use cached results/not rely 228 # on autoconf. 229 with open("config.status", "w", encoding="utf-8") as fh: 230 fh.write( 231 textwrap.dedent( 232 """\ 233 #!%(python)s 234 # coding=utf-8 235 from mozbuild.configure.constants import * 236 """ 237 ) 238 % {"python": config["PYTHON3"]} 239 ) 240 for k, v in sorted(sanitized_config.items()): 241 fh.write("%s = " % k) 242 pprint.pprint(v, stream=fh, indent=4) 243 fh.write( 244 "__all__ = ['topobjdir', 'topsrcdir', 'defines', 'substs', 'mozconfig']" 245 ) 246 247 if execute: 248 fh.write( 249 textwrap.dedent( 250 """ 251 if __name__ == '__main__': 252 from mozbuild.config_status import config_status 253 args = dict([(name, globals()[name]) for name in __all__]) 254 config_status(**args) 255 """ 256 ) 257 ) 258 259 partial_config = PartialConfigEnvironment(config["TOPOBJDIR"]) 260 partial_config.write_vars(sanitized_config) 261 262 # Write out a file so the build backend knows to re-run configure when 263 # relevant Python changes. Use FileAvoidWrite to only write if the 264 # deps_content has changed to avoid invalidating Gradle's configuration cache 265 with FileAvoidWrite("config_status_deps.in") as fh: 266 for f in sorted( 267 itertools.chain( 268 config["CONFIG_STATUS_DEPS"], 269 iter_modules_in_path(config["TOPOBJDIR"], config["TOPSRCDIR"]), 270 ) 271 ): 272 fh.write("%s\n" % mozpath.normpath(f)) 273 274 # Other things than us are going to run this file, so we need to give it 275 # executable permissions. 276 os.chmod("config.status", 0o755) 277 if execute: 278 from mozbuild.config_status import config_status 279 280 return config_status(args=[], **sanitized_config) 281 return 0 282 283 284 def _activate_build_virtualenv(): 285 """Ensure that the build virtualenv is activated 286 287 configure.py may be executed through Mach, or via "./configure, make". 288 In the first case, the build virtualenv should already be activated. 289 In the second case, we're likely being executed with the system Python, and must 290 prepare the virtualenv and activate it ourselves. 291 """ 292 293 version = ".".join(str(i) for i in sys.version_info[0:3]) 294 print(f"Using Python {version} from {sys.executable}") 295 296 active_site = MozSiteMetadata.from_runtime() 297 if active_site and active_site.site_name == "build": 298 # We're already running within the "build" virtualenv, no additional work is 299 # needed. 300 return 301 302 # We're using the system python (or are nested within a non-build mach-managed 303 # virtualenv), so we should activate the build virtualenv as expected by the rest of 304 # configure. 305 306 topsrcdir = os.path.realpath(os.path.dirname(__file__)) 307 308 mach_site = MachSiteManager( 309 topsrcdir, 310 None, 311 MachEnvRequirements(), 312 ExternalPythonSite(sys.executable), 313 SitePackagesSource.NONE, 314 ) 315 mach_site.activate() 316 317 from mach.util import get_virtualenv_base_dir 318 319 build_site = CommandSiteManager.from_environment( 320 topsrcdir, 321 None, 322 "build", 323 get_virtualenv_base_dir(topsrcdir), 324 ) 325 if not build_site.ensure(): 326 print("Created Python 3 virtualenv") 327 build_site.activate() 328 329 330 if __name__ == "__main__": 331 sys.exit(main(sys.argv))