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