Bug 1700051: part 30) Narrow scope of `newOffset`. r=smaug
[gecko.git] / build / moz.configure / util.configure
blob8f35e89c73e20e7b6e532b991ac2c4a952ba1d54
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/.
8 @imports("sys")
9 def die(*args):
10     "Print an error and terminate configure."
11     log.error(*args)
12     sys.exit(1)
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).
25 @imports("os")
26 @imports("six")
27 @imports("subprocess")
28 @imports(_from="mozbuild.shellutil", _import="quote")
29 @imports(_from="mozbuild.util", _import="system_encoding")
30 def 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
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")
62 def check_cmd_output(*args, **kwargs):
63     onerror = kwargs.pop("onerror", None)
65     with log.queue_debug():
66         retcode, stdout, stderr = get_cmd_output(*args, **kwargs)
67         if retcode == 0:
68             return stdout
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))
81 @imports("os")
82 def is_absolute_or_relative(path):
83     if os.altsep and os.altsep in path:
84         return True
85     return os.sep in path
88 @imports(_import="mozpack.path", _as="mozpath")
89 def normsep(path):
90     return mozpath.normsep(path)
93 @imports("ctypes")
94 @imports(_from="ctypes", _import="wintypes")
95 @imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType")
96 def windows_binary_type(path):
97     """Obtain the type of a binary on Windows.
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
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)
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))
119 @imports("ctypes")
120 @imports(_from="ctypes", _import="wintypes")
121 def 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
128 @template
129 @imports("ctypes")
130 @imports("platform")
131 @imports(_from="mozbuild.shellutil", _import="quote")
132 def 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()
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
160     else:
162         def normalize_path(path):
163             return normsep(path)
165     return normalize_path
168 normalize_path = normalize_path()
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")
179 def 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).
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
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
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)
206     path = which(file, path=paths, exts=exts)
207     return normalize_path(path) if path else None
210 @imports("os")
211 @imports(_from="mozbuild.configure.util", _import="LineIO")
212 @imports(_from="six", _import="ensure_binary")
213 @imports(_from="tempfile", _import="mkstemp")
214 def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
215     flags = flags or []
217     if not isinstance(flags, (list, tuple)):
218         die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
220     suffix = {
221         "C": ".c",
222         "C++": ".cpp",
223     }[language]
225     fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
226     try:
227         source = source.encode("ascii", "replace")
229         log.debug("Creating `%s` with content:", path)
230         with LineIO(lambda l: log.debug("| %s", l)) as out:
231             out.write(source)
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)
242 def unique_list(l):
243     result = []
244     for i in l:
245         if l not in result:
246             result.append(i)
247     return result
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.
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.
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\')
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\',)
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\',)
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\')
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")
299 def 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
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
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
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
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
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
373 @imports(_from="mozbuild.configure.util", _import="Version", _as="_Version")
374 def Version(v):
375     "A version number that can be compared usefully."
376     return _Version(v)
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.
389 @template
390 def deprecated_option(*args, **kwargs):
391     assert "help" not in kwargs
392     kwargs["help"] = "Deprecated"
393     opt = option(*args, **kwargs)
395     def decorator(func):
396         @depends(opt.option)
397         def deprecated(value):
398             if value.origin != "default":
399                 return func(value)
401         return deprecated
403     return decorator
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")
412 def 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)
422 always = dependable(True)
423 never = dependable(False)
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
430 def depends_tmpl(eval_args_fn, *args, **kwargs):
431     if kwargs:
432         assert len(kwargs) == 1
433         when = kwargs["when"]
434     else:
435         when = None
437     def decorator(func):
438         @depends(*args, when=when)
439         def wrapper(*args):
440             if eval_args_fn(args):
441                 return func(*args)
443         return wrapper
445     return decorator
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
451 def depends_if(*args, **kwargs):
452     return depends_tmpl(any, *args, **kwargs)
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
458 def depends_all(*args, **kwargs):
459     return depends_tmpl(all, *args, **kwargs)
462 # Hacks related to old-configure
463 # ==============================
466 @dependable
467 def old_configure_assignments():
468     return []
471 @template
472 def add_old_configure_assignment(var, value, when=None):
473     var = dependable(var)
474     value = dependable(value)
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)))