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/.
10 "Print an error and terminate configure."
15 @imports(_from="mozbuild.configure", _import="ConfigureError")
16 def 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)
23 # A wrapper to obtain a process' output and return code.
24 # Returns a tuple (retcode, stdout, stderr).
26 @imports("subprocess")
27 @imports(_from="mozbuild.shellutil", _import="quote")
28 @imports(_from="mozbuild.util", _import="system_encoding")
29 def get_cmd_output(*args, **kwargs):
30 log.debug("Executing: `%s`", quote(*args))
31 proc = subprocess.Popen(
33 stdout=subprocess.PIPE,
34 stderr=subprocess.PIPE,
35 # On Python 2 on Windows, close_fds prevents the process from inheriting
36 # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra
37 # file descriptors, which is what we want.
38 close_fds=os.name != "nt",
39 encoding=system_encoding,
43 stdout, stderr = proc.communicate()
44 return proc.wait(), stdout, stderr
47 # A wrapper to obtain a process' output that returns the output generated
48 # by running the given command if it exits normally, and streams that
49 # output to log.debug and calls die or the given error callback if it
51 @imports(_from="mozbuild.configure.util", _import="LineIO")
52 @imports(_from="mozbuild.shellutil", _import="quote")
53 def check_cmd_output(*args, **kwargs):
54 onerror = kwargs.pop("onerror", None)
56 with log.queue_debug():
57 retcode, stdout, stderr = get_cmd_output(*args, **kwargs)
59 with LineIO(lambda l: log.debug("| %s", l)) as o:
63 log.debug("The command returned non-zero exit status %d.", retcode)
64 for out, desc in ((stdout, "output"), (stderr, "error output")):
66 log.debug("Its %s was:", desc)
67 with LineIO(lambda l: log.debug("| %s", l)) as o:
71 die("Command `%s` failed with exit status %d." % (quote(*args), retcode))
75 def is_absolute_or_relative(path):
76 if os.altsep and os.altsep in path:
81 @imports(_import="mozpack.path", _as="mozpath")
83 return mozpath.normsep(path)
87 @imports(_from="ctypes", _import="wintypes")
88 @imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType")
89 def windows_binary_type(path):
90 """Obtain the type of a binary on Windows.
92 Returns WindowsBinaryType constant.
94 GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
95 GetBinaryTypeW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)]
96 GetBinaryTypeW.restype = wintypes.BOOL
98 bin_type = wintypes.DWORD()
99 res = GetBinaryTypeW(path, ctypes.byref(bin_type))
101 die("could not obtain binary type of %s" % path)
103 if bin_type.value == 0:
104 return WindowsBinaryType("win32")
105 elif bin_type.value == 6:
106 return WindowsBinaryType("win64")
107 # If we see another binary type, something is likely horribly wrong.
109 die("unsupported binary type on %s: %s" % (path, bin_type))
113 @imports(_from="ctypes", _import="wintypes")
114 def get_GetShortPathNameW():
115 GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
116 GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
117 GetShortPathNameW.restype = wintypes.DWORD
118 return GetShortPathNameW
124 @imports(_from="mozbuild.shellutil", _import="quote")
125 def normalize_path():
126 # Until the build system can properly handle programs that need quoting,
127 # transform those paths into their short version on Windows (e.g.
129 if platform.system() == "Windows":
130 GetShortPathNameW = get_GetShortPathNameW()
132 def normalize_path(path):
134 if quote(path) == path:
138 out = ctypes.create_unicode_buffer(size)
139 needed = GetShortPathNameW(path, out, size)
143 "GetShortPathName returned a long path name: `%s`. "
144 "Use `fsutil file setshortname' "
145 "to create a short name "
146 "for any components of this path "
150 return normsep(out.value)
155 def normalize_path(path):
158 return normalize_path
161 normalize_path = normalize_path()
164 # Locates the given program using which, or returns the given path if it
166 # The `paths` parameter may be passed to search the given paths instead of
169 @imports(_from="os", _import="pathsep")
170 @imports(_from="os", _import="environ")
171 @imports(_from="mozfile", _import="which")
172 def find_program(file, paths=None, allow_spaces=False):
173 def which_normalize(file, path, exts):
174 path = which(file, path=path, exts=exts)
178 return normalize_path(path)
181 # The following snippet comes from `which` itself, with a slight
182 # modification to use lowercase extensions, because it's confusing rustup
183 # (on top of making results not really appealing to the eye).
185 # Windows has the concept of a list of extensions (PATHEXT env var).
186 if sys.platform.startswith("win"):
187 exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)]
188 # If '.exe' is not in exts then obviously this is Win9x and
189 # or a bogus PATHEXT, then use a reasonable default.
190 if ".exe" not in exts:
191 exts = [".com", ".exe", ".bat"]
195 if is_absolute_or_relative(file):
196 return which_normalize(
197 os.path.basename(file), path=os.path.dirname(file), exts=exts
201 if not isinstance(paths, (list, tuple)):
203 "Paths provided to find_program must be a list of strings, " "not %r",
206 paths = pathsep.join(paths)
208 return which_normalize(file, path=paths, exts=exts)
212 @imports(_from="mozbuild.configure.util", _import="LineIO")
213 @imports(_from="tempfile", _import="mkstemp")
214 @imports(_import="subprocess")
215 def try_invoke_compiler(
216 configure_cache, compiler, language, source, flags=None, onerror=None, wrapper=[]
218 compiler_path = compiler[0]
219 compiler = wrapper + compiler
220 use_cache = configure_cache is not None
222 if use_cache and compiler_path not in configure_cache.version_checked_compilers:
224 version_info = subprocess.check_output(
225 [compiler_path, "--version"],
228 except subprocess.CalledProcessError:
229 # There's no sane way to use the cache without the version details, so
230 # we need to avoid both reads from and writes to the cache.
235 if version_info != configure_cache.setdefault(compiler_path, {}).get(
238 configure_cache[compiler_path].clear()
240 configure_cache[compiler_path]["version"] = version_info
241 configure_cache.version_checked_compilers.add(compiler_path)
246 key = " ".join(compiler) + language + source + (" ".join(flags) or "")
248 if key in configure_cache[compiler_path]:
249 return configure_cache[compiler_path][key]
251 if not isinstance(flags, (list, tuple)):
252 die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
259 fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
261 source = source.encode("ascii", "replace")
263 log.debug("Creating `%s` with content:", path)
264 with LineIO(lambda l: log.debug("| %s", l)) as out:
269 cmd = compiler + [path] + list(flags)
270 kwargs = {"onerror": onerror}
271 val = check_cmd_output(*cmd, **kwargs)
273 configure_cache[compiler_path][key] = val
287 # Get values out of the Windows registry. This function can only be called on
289 # The `pattern` argument is a string starting with HKEY_ and giving the full
290 # "path" of the registry key to get the value for, with backslash separators.
291 # The string can contains wildcards ('*').
292 # The result of this functions is an enumerator yielding tuples for each
293 # match. Each of these tuples contains the key name matching wildcards
294 # followed by the value.
296 # The `get_32_and_64_bit` argument is a boolean, if True then it will return the
297 # values from the 32-bit and 64-bit registry views. This defaults to False,
298 # which will return the view depending on the bitness of python.
301 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
302 # r'Windows Kits\Installed Roots\KitsRoot*')
304 # ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
305 # ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
307 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
308 # r'Windows Kits\Installed Roots\KitsRoot8.1')
310 # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
312 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
313 # r'Windows Kits\Installed Roots\KitsRoot8.1',
314 # get_32_and_64_bit=True)
316 # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
317 # (r'C:\Program Files\Windows Kits\8.1\',)
319 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
320 # r'Windows Kits\*\KitsRoot*')
322 # ('Installed Roots', 'KitsRoot81',
323 # r'C:\Program Files (x86)\Windows Kits\8.1\')
324 # ('Installed Roots', 'KitsRoot10',
325 # r'C:\Program Files (x86)\Windows Kits\10\')
327 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
328 # r'VisualStudio\VC\*\x86\*\Compiler')
330 # ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
331 # ('19.0', 'x64', r'C:\...\amd64\cl.exe')
332 # ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
333 @imports(_import="winreg")
334 @imports(_from="__builtin__", _import="WindowsError")
335 @imports(_from="fnmatch", _import="fnmatch")
336 def get_registry_values(pattern, get_32_and_64_bit=False):
337 def enum_helper(func, key):
346 def get_keys(key, pattern, access_mask):
348 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
351 for k in enum_helper(winreg.EnumKey, s):
352 if fnmatch(k, pattern[-1]):
354 yield k, winreg.OpenKey(s, k, 0, access_mask)
358 def get_values(key, pattern, access_mask):
360 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
363 for k, v, t in enum_helper(winreg.EnumValue, s):
364 if fnmatch(k, pattern[-1]):
367 def split_pattern(pattern):
377 def get_all_values(keys, pattern, access_mask):
378 for i, p in enumerate(pattern):
380 for base_key in keys:
381 matches = base_key[:-1]
382 base_key = base_key[-1]
383 if i == len(pattern) - 1:
384 want_name = "*" in p[-1]
385 for name, value in get_values(base_key, p, access_mask):
386 yield matches + ((name, value) if want_name else (value,))
388 for name, k in get_keys(base_key, p, access_mask):
389 next_keys.append(matches + (name, k))
392 pattern = pattern.split("\\")
393 assert pattern[0].startswith("HKEY_")
394 keys = [(getattr(winreg, pattern[0]),)]
395 pattern = list(split_pattern(pattern[1:]))
396 if get_32_and_64_bit:
397 for match in get_all_values(
398 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY
401 for match in get_all_values(
402 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY
406 for match in get_all_values(keys, pattern, winreg.KEY_READ):
410 @imports(_from="mozbuild.configure.util", _import="Version", _as="_Version")
412 "A version number that can be compared usefully."
416 # Denotes a deprecated option. Combines option() and @depends:
417 # @deprecated_option('--option')
420 # @deprecated_option() takes the same arguments as option(), except `help`.
421 # The function may handle the option like a typical @depends function would,
422 # but it is recommended it emits a deprecation error message suggesting an
423 # alternative option to use if there is one.
425 def deprecated_option(*args, **kwargs):
426 assert "help" not in kwargs
427 kwargs["help"] = "Deprecated"
428 opt = option(*args, **kwargs)
429 kwargs = {k: v for k, v in kwargs.items() if k == "when"}
432 @depends(opt.option, **kwargs)
433 def deprecated(value):
434 if value.origin != "default":
442 # Turn an object into an object that can be used as an argument to @depends.
443 # The given object can be a literal value, a function that takes no argument,
444 # or, for convenience, a @depends function.
446 @imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
448 if isinstance(obj, SandboxDependsFunction):
450 return depends(when=True)(obj)
453 always = dependable(True)
454 never = dependable(False)
457 # Create a decorator that will only execute the body of a function
458 # if the passed function returns True when passed all positional
461 def depends_tmpl(eval_args_fn, *args, **kwargs):
463 assert len(kwargs) == 1
464 when = kwargs["when"]
469 @depends(*args, when=when)
471 if eval_args_fn(args):
479 # Like @depends, but the decorated function is only called if one of the
480 # arguments it would be called with has a positive value (bool(value) is True)
482 def depends_if(*args, **kwargs):
483 return depends_tmpl(any, *args, **kwargs)
486 # Like @depends, but the decorated function is only called if all of the
487 # arguments it would be called with have a positive value.
489 def depends_all(*args, **kwargs):
490 return depends_tmpl(all, *args, **kwargs)
493 # A template providing a shorthand for setting a variable. The created
494 # option will only be settable with imply_option.
495 # It is expected that a project-specific moz.configure will call imply_option
496 # to set a value other than the default.
497 # If required, the set_as_define argument will additionally cause the variable
498 # to be set using set_define.
500 def project_flag(env=None, set_as_define=False, **kwargs):
502 configure_error("A project_flag must be passed a variable name to set.")
504 opt = option(env=env, possible_origins=("implied",), **kwargs)
507 def option_implementation(value):
513 set_config(env, option_implementation)
515 set_define(env, option_implementation)
519 @imports(_from="mozbuild.configure.constants", _import="RaiseErrorOnUse")
520 def obsolete_config(name, *, replacement):
521 set_config(name, RaiseErrorOnUse(f"{name} is obsolete. Use {replacement} instead."))
524 # Hacks related to old-configure
525 # ==============================
529 def old_configure_assignments():
534 def add_old_configure_assignment(var, value, when=None):
535 var = dependable(var)
536 value = dependable(value)
538 @depends(old_configure_assignments, var, value, when=when)
539 @imports(_from="mozbuild.shellutil", _import="quote")
540 def add_assignment(assignments, var, value):
541 if var is None or value is None:
544 assignments.append((var, "1"))
546 assignments.append((var, ""))
548 if isinstance(value, (list, tuple)):
549 value = quote(*value)
550 assignments.append((var, str(value)))