1# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- 2# vim: set filetype=python: 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 8@imports("sys") 9def die(*args): 10 "Print an error and terminate configure." 11 log.error(*args) 12 sys.exit(1) 13 14 15@imports(_from="mozbuild.configure", _import="ConfigureError") 16def configure_error(message): 17 """Raise a programming error and terminate configure. 18 Primarily for use in moz.configure templates to sanity check 19 their inputs from moz.configure usage.""" 20 raise ConfigureError(message) 21 22 23# A wrapper to obtain a process' output and return code. 24# Returns a tuple (retcode, stdout, stderr). 25@imports("os") 26@imports("six") 27@imports("subprocess") 28@imports(_from="mozbuild.shellutil", _import="quote") 29@imports(_from="mozbuild.util", _import="system_encoding") 30def get_cmd_output(*args, **kwargs): 31 log.debug("Executing: `%s`", quote(*args)) 32 proc = subprocess.Popen( 33 args, 34 stdout=subprocess.PIPE, 35 stderr=subprocess.PIPE, 36 # On Python 2 on Windows, close_fds prevents the process from inheriting 37 # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra 38 # file descriptors, which is what we want. 39 close_fds=os.name != "nt", 40 **kwargs 41 ) 42 stdout, stderr = proc.communicate() 43 # Normally we would set the `encoding` and `errors` arguments in the 44 # constructor to subprocess.Popen, but those arguments were added in 3.6 45 # and we need to support back to 3.5, so instead we need to do this 46 # nonsense. 47 stdout = six.ensure_text( 48 stdout, encoding=system_encoding, errors="replace" 49 ).replace("\r\n", "\n") 50 stderr = six.ensure_text( 51 stderr, encoding=system_encoding, errors="replace" 52 ).replace("\r\n", "\n") 53 return proc.wait(), stdout, stderr 54 55 56# A wrapper to obtain a process' output that returns the output generated 57# by running the given command if it exits normally, and streams that 58# output to log.debug and calls die or the given error callback if it 59# does not. 60@imports(_from="mozbuild.configure.util", _import="LineIO") 61@imports(_from="mozbuild.shellutil", _import="quote") 62def check_cmd_output(*args, **kwargs): 63 onerror = kwargs.pop("onerror", None) 64 65 with log.queue_debug(): 66 retcode, stdout, stderr = get_cmd_output(*args, **kwargs) 67 if retcode == 0: 68 return stdout 69 70 log.debug("The command returned non-zero exit status %d.", retcode) 71 for out, desc in ((stdout, "output"), (stderr, "error output")): 72 if out: 73 log.debug("Its %s was:", desc) 74 with LineIO(lambda l: log.debug("| %s", l)) as o: 75 o.write(out) 76 if onerror: 77 return onerror() 78 die("Command `%s` failed with exit status %d." % (quote(*args), retcode)) 79 80 81@imports("os") 82def is_absolute_or_relative(path): 83 if os.altsep and os.altsep in path: 84 return True 85 return os.sep in path 86 87 88@imports(_import="mozpack.path", _as="mozpath") 89def normsep(path): 90 return mozpath.normsep(path) 91 92 93@imports("ctypes") 94@imports(_from="ctypes", _import="wintypes") 95@imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType") 96def windows_binary_type(path): 97 """Obtain the type of a binary on Windows. 98 99 Returns WindowsBinaryType constant. 100 """ 101 GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW 102 GetBinaryTypeW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)] 103 GetBinaryTypeW.restype = wintypes.BOOL 104 105 bin_type = wintypes.DWORD() 106 res = GetBinaryTypeW(path, ctypes.byref(bin_type)) 107 if not res: 108 die("could not obtain binary type of %s" % path) 109 110 if bin_type.value == 0: 111 return WindowsBinaryType("win32") 112 elif bin_type.value == 6: 113 return WindowsBinaryType("win64") 114 # If we see another binary type, something is likely horribly wrong. 115 else: 116 die("unsupported binary type on %s: %s" % (path, bin_type)) 117 118 119@imports("ctypes") 120@imports(_from="ctypes", _import="wintypes") 121def get_GetShortPathNameW(): 122 GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW 123 GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] 124 GetShortPathNameW.restype = wintypes.DWORD 125 return GetShortPathNameW 126 127 128@template 129@imports("ctypes") 130@imports("platform") 131@imports(_from="mozbuild.shellutil", _import="quote") 132def normalize_path(): 133 # Until the build system can properly handle programs that need quoting, 134 # transform those paths into their short version on Windows (e.g. 135 # c:\PROGRA~1...). 136 if platform.system() == "Windows": 137 GetShortPathNameW = get_GetShortPathNameW() 138 139 def normalize_path(path): 140 path = normsep(path) 141 if quote(path) == path: 142 return path 143 size = 0 144 while True: 145 out = ctypes.create_unicode_buffer(size) 146 needed = GetShortPathNameW(path, out, size) 147 if size >= needed: 148 if " " in out.value: 149 die( 150 "GetShortPathName returned a long path name: `%s`. " 151 "Use `fsutil file setshortname' " 152 "to create a short name " 153 "for any components of this path " 154 "that have spaces.", 155 out.value, 156 ) 157 return normsep(out.value) 158 size = needed 159 160 else: 161 162 def normalize_path(path): 163 return normsep(path) 164 165 return normalize_path 166 167 168normalize_path = normalize_path() 169 170 171# Locates the given program using which, or returns the given path if it 172# exists. 173# The `paths` parameter may be passed to search the given paths instead of 174# $PATH. 175@imports("sys") 176@imports(_from="os", _import="pathsep") 177@imports(_from="os", _import="environ") 178@imports(_from="mozfile", _import="which") 179def find_program(file, paths=None): 180 # The following snippet comes from `which` itself, with a slight 181 # modification to use lowercase extensions, because it's confusing rustup 182 # (on top of making results not really appealing to the eye). 183 184 # Windows has the concept of a list of extensions (PATHEXT env var). 185 if sys.platform.startswith("win"): 186 exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)] 187 # If '.exe' is not in exts then obviously this is Win9x and 188 # or a bogus PATHEXT, then use a reasonable default. 189 if ".exe" not in exts: 190 exts = [".com", ".exe", ".bat"] 191 else: 192 exts = None 193 194 if is_absolute_or_relative(file): 195 path = which(os.path.basename(file), path=os.path.dirname(file), exts=exts) 196 return normalize_path(path) if path else None 197 198 if paths: 199 if not isinstance(paths, (list, tuple)): 200 die( 201 "Paths provided to find_program must be a list of strings, " "not %r", 202 paths, 203 ) 204 paths = pathsep.join(paths) 205 206 path = which(file, path=paths, exts=exts) 207 return normalize_path(path) if path else None 208 209 210@imports("os") 211@imports(_from="mozbuild.configure.util", _import="LineIO") 212@imports(_from="six", _import="ensure_binary") 213@imports(_from="tempfile", _import="mkstemp") 214def try_invoke_compiler(compiler, language, source, flags=None, onerror=None): 215 flags = flags or [] 216 217 if not isinstance(flags, (list, tuple)): 218 die("Flags provided to try_compile must be a list of strings, " "not %r", flags) 219 220 suffix = { 221 "C": ".c", 222 "C++": ".cpp", 223 }[language] 224 225 fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True) 226 try: 227 source = source.encode("ascii", "replace") 228 229 log.debug("Creating `%s` with content:", path) 230 with LineIO(lambda l: log.debug("| %s", l)) as out: 231 out.write(source) 232 233 os.write(fd, ensure_binary(source)) 234 os.close(fd) 235 cmd = compiler + [path] + list(flags) 236 kwargs = {"onerror": onerror} 237 return check_cmd_output(*cmd, **kwargs) 238 finally: 239 os.remove(path) 240 241 242def unique_list(l): 243 result = [] 244 for i in l: 245 if l not in result: 246 result.append(i) 247 return result 248 249 250# Get values out of the Windows registry. This function can only be called on 251# Windows. 252# The `pattern` argument is a string starting with HKEY_ and giving the full 253# "path" of the registry key to get the value for, with backslash separators. 254# The string can contains wildcards ('*'). 255# The result of this functions is an enumerator yielding tuples for each 256# match. Each of these tuples contains the key name matching wildcards 257# followed by the value. 258# 259# The `get_32_and_64_bit` argument is a boolean, if True then it will return the 260# values from the 32-bit and 64-bit registry views. This defaults to False, 261# which will return the view depending on the bitness of python. 262# 263# Examples: 264# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' 265# r'Windows Kits\Installed Roots\KitsRoot*') 266# yields e.g.: 267# ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\') 268# ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\') 269# 270# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' 271# r'Windows Kits\Installed Roots\KitsRoot8.1') 272# yields e.g.: 273# (r'C:\Program Files (x86)\Windows Kits\8.1\',) 274# 275# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' 276# r'Windows Kits\Installed Roots\KitsRoot8.1', 277# get_32_and_64_bit=True) 278# yields e.g.: 279# (r'C:\Program Files (x86)\Windows Kits\8.1\',) 280# (r'C:\Program Files\Windows Kits\8.1\',) 281# 282# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' 283# r'Windows Kits\*\KitsRoot*') 284# yields e.g.: 285# ('Installed Roots', 'KitsRoot81', 286# r'C:\Program Files (x86)\Windows Kits\8.1\') 287# ('Installed Roots', 'KitsRoot10', 288# r'C:\Program Files (x86)\Windows Kits\10\') 289# 290# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' 291# r'VisualStudio\VC\*\x86\*\Compiler') 292# yields e.g.: 293# ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe') 294# ('19.0', 'x64', r'C:\...\amd64\cl.exe') 295# ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe') 296@imports(_import="winreg") 297@imports(_from="__builtin__", _import="WindowsError") 298@imports(_from="fnmatch", _import="fnmatch") 299def get_registry_values(pattern, get_32_and_64_bit=False): 300 def enum_helper(func, key): 301 i = 0 302 while True: 303 try: 304 yield func(key, i) 305 except WindowsError: 306 break 307 i += 1 308 309 def get_keys(key, pattern, access_mask): 310 try: 311 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask) 312 except WindowsError: 313 return 314 for k in enum_helper(winreg.EnumKey, s): 315 if fnmatch(k, pattern[-1]): 316 try: 317 yield k, winreg.OpenKey(s, k, 0, access_mask) 318 except WindowsError: 319 pass 320 321 def get_values(key, pattern, access_mask): 322 try: 323 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask) 324 except WindowsError: 325 return 326 for k, v, t in enum_helper(winreg.EnumValue, s): 327 if fnmatch(k, pattern[-1]): 328 yield k, v 329 330 def split_pattern(pattern): 331 subpattern = [] 332 for p in pattern: 333 subpattern.append(p) 334 if "*" in p: 335 yield subpattern 336 subpattern = [] 337 if subpattern: 338 yield subpattern 339 340 def get_all_values(keys, pattern, access_mask): 341 for i, p in enumerate(pattern): 342 next_keys = [] 343 for base_key in keys: 344 matches = base_key[:-1] 345 base_key = base_key[-1] 346 if i == len(pattern) - 1: 347 want_name = "*" in p[-1] 348 for name, value in get_values(base_key, p, access_mask): 349 yield matches + ((name, value) if want_name else (value,)) 350 else: 351 for name, k in get_keys(base_key, p, access_mask): 352 next_keys.append(matches + (name, k)) 353 keys = next_keys 354 355 pattern = pattern.split("\\") 356 assert pattern[0].startswith("HKEY_") 357 keys = [(getattr(winreg, pattern[0]),)] 358 pattern = list(split_pattern(pattern[1:])) 359 if get_32_and_64_bit: 360 for match in get_all_values( 361 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY 362 ): 363 yield match 364 for match in get_all_values( 365 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY 366 ): 367 yield match 368 else: 369 for match in get_all_values(keys, pattern, winreg.KEY_READ): 370 yield match 371 372 373@imports(_from="mozbuild.configure.util", _import="Version", _as="_Version") 374def Version(v): 375 "A version number that can be compared usefully." 376 return _Version(v) 377 378 379# Denotes a deprecated option. Combines option() and @depends: 380# @deprecated_option('--option') 381# def option(value): 382# ... 383# @deprecated_option() takes the same arguments as option(), except `help`. 384# The function may handle the option like a typical @depends function would, 385# but it is recommended it emits a deprecation error message suggesting an 386# alternative option to use if there is one. 387 388 389@template 390def deprecated_option(*args, **kwargs): 391 assert "help" not in kwargs 392 kwargs["help"] = "Deprecated" 393 opt = option(*args, **kwargs) 394 395 def decorator(func): 396 @depends(opt.option) 397 def deprecated(value): 398 if value.origin != "default": 399 return func(value) 400 401 return deprecated 402 403 return decorator 404 405 406# Turn an object into an object that can be used as an argument to @depends. 407# The given object can be a literal value, a function that takes no argument, 408# or, for convenience, a @depends function. 409@template 410@imports(_from="inspect", _import="isfunction") 411@imports(_from="mozbuild.configure", _import="SandboxDependsFunction") 412def dependable(obj): 413 if isinstance(obj, SandboxDependsFunction): 414 return obj 415 if isfunction(obj): 416 return depends(when=True)(obj) 417 # Depend on --help to make lint happy if the dependable is used as an input 418 # to an option(). 419 return depends("--help", when=True)(lambda _: obj) 420 421 422always = dependable(True) 423never = dependable(False) 424 425 426# Create a decorator that will only execute the body of a function 427# if the passed function returns True when passed all positional 428# arguments. 429@template 430def depends_tmpl(eval_args_fn, *args, **kwargs): 431 if kwargs: 432 assert len(kwargs) == 1 433 when = kwargs["when"] 434 else: 435 when = None 436 437 def decorator(func): 438 @depends(*args, when=when) 439 def wrapper(*args): 440 if eval_args_fn(args): 441 return func(*args) 442 443 return wrapper 444 445 return decorator 446 447 448# Like @depends, but the decorated function is only called if one of the 449# arguments it would be called with has a positive value (bool(value) is True) 450@template 451def depends_if(*args, **kwargs): 452 return depends_tmpl(any, *args, **kwargs) 453 454 455# Like @depends, but the decorated function is only called if all of the 456# arguments it would be called with have a positive value. 457@template 458def depends_all(*args, **kwargs): 459 return depends_tmpl(all, *args, **kwargs) 460 461 462# Hacks related to old-configure 463# ============================== 464 465 466@dependable 467def old_configure_assignments(): 468 return [] 469 470 471@template 472def add_old_configure_assignment(var, value, when=None): 473 var = dependable(var) 474 value = dependable(value) 475 476 @depends(old_configure_assignments, var, value, when=when) 477 @imports(_from="mozbuild.shellutil", _import="quote") 478 def add_assignment(assignments, var, value): 479 if var is None or value is None: 480 return 481 if value is True: 482 assignments.append((var, "1")) 483 elif value is False: 484 assignments.append((var, "")) 485 else: 486 if isinstance(value, (list, tuple)): 487 value = quote(*value) 488 assignments.append((var, str(value))) 489