util.configure 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
  2. # This Source Code Form is subject to the terms of the Mozilla Public
  3. # License, v. 2.0. If a copy of the MPL was not distributed with this
  4. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  5. @imports('sys')
  6. def die(*args):
  7. 'Print an error and terminate configure.'
  8. log.error(*args)
  9. sys.exit(1)
  10. @imports(_from='mozbuild.configure', _import='ConfigureError')
  11. def configure_error(message):
  12. '''Raise a programming error and terminate configure.
  13. Primarily for use in moz.configure templates to sanity check
  14. their inputs from moz.configure usage.'''
  15. raise ConfigureError(message)
  16. # A wrapper to obtain a process' output that returns the output generated
  17. # by running the given command if it exits normally, and streams that
  18. # output to log.debug and calls die or the given error callback if it
  19. # does not.
  20. @imports('subprocess')
  21. @imports('sys')
  22. @imports(_from='mozbuild.configure.util', _import='LineIO')
  23. @imports(_from='mozbuild.shellutil', _import='quote')
  24. def check_cmd_output(*args, **kwargs):
  25. onerror = kwargs.pop('onerror', None)
  26. with log.queue_debug():
  27. log.debug('Executing: `%s`', quote(*args))
  28. proc = subprocess.Popen(args, stdout=subprocess.PIPE,
  29. stderr=subprocess.PIPE)
  30. stdout, stderr = proc.communicate()
  31. retcode = proc.wait()
  32. if retcode == 0:
  33. return stdout
  34. log.debug('The command returned non-zero exit status %d.',
  35. retcode)
  36. for out, desc in ((stdout, 'output'), (stderr, 'error output')):
  37. if out:
  38. log.debug('Its %s was:', desc)
  39. with LineIO(lambda l: log.debug('| %s', l)) as o:
  40. o.write(out)
  41. if onerror:
  42. return onerror()
  43. die('Command `%s` failed with exit status %d.' %
  44. (quote(*args), retcode))
  45. @imports('os')
  46. def is_absolute_or_relative(path):
  47. if os.altsep and os.altsep in path:
  48. return True
  49. return os.sep in path
  50. @imports(_import='mozpack.path', _as='mozpath')
  51. def normsep(path):
  52. return mozpath.normsep(path)
  53. @imports('ctypes')
  54. @imports(_from='ctypes', _import='wintypes')
  55. @imports(_from='mozbuild.configure.constants', _import='WindowsBinaryType')
  56. def windows_binary_type(path):
  57. """Obtain the type of a binary on Windows.
  58. Returns WindowsBinaryType constant.
  59. """
  60. GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
  61. GetBinaryTypeW.argtypes = [wintypes.LPWSTR, wintypes.POINTER(wintypes.DWORD)]
  62. GetBinaryTypeW.restype = wintypes.BOOL
  63. bin_type = wintypes.DWORD()
  64. res = GetBinaryTypeW(path, ctypes.byref(bin_type))
  65. if not res:
  66. die('could not obtain binary type of %s' % path)
  67. if bin_type.value == 0:
  68. return WindowsBinaryType('win32')
  69. elif bin_type.value == 6:
  70. return WindowsBinaryType('win64')
  71. # If we see another binary type, something is likely horribly wrong.
  72. else:
  73. die('unsupported binary type on %s: %s' % (path, bin_type))
  74. @imports('ctypes')
  75. @imports(_from='ctypes', _import='wintypes')
  76. def get_GetShortPathNameW():
  77. GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
  78. GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR,
  79. wintypes.DWORD]
  80. GetShortPathNameW.restype = wintypes.DWORD
  81. return GetShortPathNameW
  82. @template
  83. @imports('ctypes')
  84. @imports('platform')
  85. @imports(_from='mozbuild.shellutil', _import='quote')
  86. def normalize_path():
  87. # Until the build system can properly handle programs that need quoting,
  88. # transform those paths into their short version on Windows (e.g.
  89. # c:\PROGRA~1...).
  90. if platform.system() == 'Windows':
  91. GetShortPathNameW = get_GetShortPathNameW()
  92. def normalize_path(path):
  93. path = normsep(path)
  94. if quote(path) == path:
  95. return path
  96. size = 0
  97. while True:
  98. out = ctypes.create_unicode_buffer(size)
  99. needed = GetShortPathNameW(path, out, size)
  100. if size >= needed:
  101. if ' ' in out.value:
  102. die("GetShortPathName returned a long path name. "
  103. "Are 8dot3 filenames disabled?")
  104. return normsep(out.value)
  105. size = needed
  106. else:
  107. def normalize_path(path):
  108. return normsep(path)
  109. return normalize_path
  110. normalize_path = normalize_path()
  111. # Locates the given program using which, or returns the given path if it
  112. # exists.
  113. # The `paths` parameter may be passed to search the given paths instead of
  114. # $PATH.
  115. @imports(_from='which', _import='which')
  116. @imports(_from='which', _import='WhichError')
  117. @imports('itertools')
  118. @imports(_from='os', _import='pathsep')
  119. def find_program(file, paths=None):
  120. try:
  121. if is_absolute_or_relative(file):
  122. return normalize_path(which(os.path.basename(file),
  123. [os.path.dirname(file)]))
  124. if paths:
  125. if not isinstance(paths, (list, tuple)):
  126. die("Paths provided to find_program must be a list of strings, "
  127. "not %r", paths)
  128. paths = list(itertools.chain(
  129. *(p.split(pathsep) for p in paths if p)))
  130. return normalize_path(which(file, path=paths))
  131. except WhichError:
  132. return None
  133. @imports('os')
  134. @imports('subprocess')
  135. @imports(_from='mozbuild.configure.util', _import='LineIO')
  136. @imports(_from='tempfile', _import='mkstemp')
  137. def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
  138. flags = flags or []
  139. if not isinstance(flags, (list, tuple)):
  140. die("Flags provided to try_compile must be a list of strings, "
  141. "not %r", paths)
  142. suffix = {
  143. 'C': '.c',
  144. 'C++': '.cpp',
  145. }[language]
  146. fd, path = mkstemp(prefix='conftest.', suffix=suffix)
  147. try:
  148. source = source.encode('ascii', 'replace')
  149. log.debug('Creating `%s` with content:', path)
  150. with LineIO(lambda l: log.debug('| %s', l)) as out:
  151. out.write(source)
  152. os.write(fd, source)
  153. os.close(fd)
  154. cmd = compiler + list(flags) + [path]
  155. kwargs = {'onerror': onerror}
  156. return check_cmd_output(*cmd, **kwargs)
  157. finally:
  158. os.remove(path)
  159. def unique_list(l):
  160. result = []
  161. for i in l:
  162. if l not in result:
  163. result.append(i)
  164. return result
  165. # Get values out of the Windows registry. This function can only be called on
  166. # Windows.
  167. # The `pattern` argument is a string starting with HKEY_ and giving the full
  168. # "path" of the registry key to get the value for, with backslash separators.
  169. # The string can contains wildcards ('*').
  170. # The result of this functions is an enumerator yielding tuples for each
  171. # match. Each of these tuples contains the key name matching wildcards
  172. # followed by the value.
  173. #
  174. # Examples:
  175. # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
  176. # r'Windows Kits\Installed Roots\KitsRoot*')
  177. # yields e.g.:
  178. # ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
  179. # ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
  180. #
  181. # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
  182. # r'Windows Kits\Installed Roots\KitsRoot8.1')
  183. # yields e.g.:
  184. # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
  185. #
  186. # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
  187. # r'Windows Kits\*\KitsRoot*')
  188. # yields e.g.:
  189. # ('Installed Roots', 'KitsRoot81',
  190. # r'C:\Program Files (x86)\Windows Kits\8.1\')
  191. # ('Installed Roots', 'KitsRoot10',
  192. # r'C:\Program Files (x86)\Windows Kits\10\')
  193. #
  194. # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
  195. # r'VisualStudio\VC\*\x86\*\Compiler')
  196. # yields e.g.:
  197. # ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
  198. # ('19.0', 'x64', r'C:\...\amd64\cl.exe')
  199. # ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
  200. @imports(_import='_winreg', _as='winreg')
  201. @imports(_from='__builtin__', _import='WindowsError')
  202. @imports(_from='fnmatch', _import='fnmatch')
  203. def get_registry_values(pattern):
  204. def enum_helper(func, key):
  205. i = 0
  206. while True:
  207. try:
  208. yield func(key, i)
  209. except WindowsError:
  210. break
  211. i += 1
  212. def get_keys(key, pattern):
  213. try:
  214. s = winreg.OpenKey(key, '\\'.join(pattern[:-1]))
  215. except WindowsError:
  216. return
  217. for k in enum_helper(winreg.EnumKey, s):
  218. if fnmatch(k, pattern[-1]):
  219. try:
  220. yield k, winreg.OpenKey(s, k)
  221. except WindowsError:
  222. pass
  223. def get_values(key, pattern):
  224. try:
  225. s = winreg.OpenKey(key, '\\'.join(pattern[:-1]))
  226. except WindowsError:
  227. return
  228. for k, v, t in enum_helper(winreg.EnumValue, s):
  229. if fnmatch(k, pattern[-1]):
  230. yield k, v
  231. def split_pattern(pattern):
  232. subpattern = []
  233. for p in pattern:
  234. subpattern.append(p)
  235. if '*' in p:
  236. yield subpattern
  237. subpattern = []
  238. if subpattern:
  239. yield subpattern
  240. pattern = pattern.split('\\')
  241. assert pattern[0].startswith('HKEY_')
  242. keys = [(getattr(winreg, pattern[0]),)]
  243. pattern = list(split_pattern(pattern[1:]))
  244. for i, p in enumerate(pattern):
  245. next_keys = []
  246. for base_key in keys:
  247. matches = base_key[:-1]
  248. base_key = base_key[-1]
  249. if i == len(pattern) - 1:
  250. want_name = '*' in p[-1]
  251. for name, value in get_values(base_key, p):
  252. yield matches + ((name, value) if want_name else (value,))
  253. else:
  254. for name, k in get_keys(base_key, p):
  255. next_keys.append(matches + (name, k))
  256. keys = next_keys
  257. @imports(_from='mozbuild.configure.util', _import='Version', _as='_Version')
  258. def Version(v):
  259. 'A version number that can be compared usefully.'
  260. return _Version(v)
  261. # Denotes a deprecated option. Combines option() and @depends:
  262. # @deprecated_option('--option')
  263. # def option(value):
  264. # ...
  265. # @deprecated_option() takes the same arguments as option(), except `help`.
  266. # The function may handle the option like a typical @depends function would,
  267. # but it is recommended it emits a deprecation error message suggesting an
  268. # alternative option to use if there is one.
  269. @template
  270. def deprecated_option(*args, **kwargs):
  271. assert 'help' not in kwargs
  272. kwargs['help'] = 'Deprecated'
  273. opt = option(*args, **kwargs)
  274. def decorator(func):
  275. @depends(opt.option)
  276. def deprecated(value):
  277. if value.origin != 'default':
  278. return func(value)
  279. return deprecated
  280. return decorator
  281. # from mozbuild.util import ReadOnlyNamespace as namespace
  282. @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
  283. def namespace(**kwargs):
  284. return ReadOnlyNamespace(**kwargs)
  285. # Turn an object into an object that can be used as an argument to @depends.
  286. # The given object can be a literal value, a function that takes no argument,
  287. # or, for convenience, a @depends function.
  288. @template
  289. @imports(_from='inspect', _import='isfunction')
  290. @imports(_from='mozbuild.configure', _import='SandboxDependsFunction')
  291. def dependable(obj):
  292. if isinstance(obj, SandboxDependsFunction):
  293. return obj
  294. if isfunction(obj):
  295. return depends(when=True)(obj)
  296. return depends(when=True)(lambda: obj)
  297. always = dependable(True)
  298. never = dependable(False)
  299. # Some @depends function return namespaces, and one could want to use one
  300. # specific attribute from such a namespace as a "value" given to functions
  301. # such as `set_config`. But those functions do not take immediate values.
  302. # The `delayed_getattr` function allows access to attributes from the result
  303. # of a @depends function in a non-immediate manner.
  304. # @depends('--option')
  305. # def option(value)
  306. # return namespace(foo=value)
  307. # set_config('FOO', delayed_getattr(option, 'foo')
  308. @template
  309. def delayed_getattr(func, key):
  310. @depends(func)
  311. def result(value):
  312. # The @depends function we're being passed may have returned
  313. # None, or an object that simply doesn't have the wanted key.
  314. # In that case, just return None.
  315. return getattr(value, key, None)
  316. return result
  317. # Like @depends, but the decorated function is only called if one of the
  318. # arguments it would be called with has a positive value (bool(value) is True)
  319. @template
  320. def depends_if(*args):
  321. def decorator(func):
  322. @depends(*args)
  323. def wrapper(*args):
  324. if any(arg for arg in args):
  325. return func(*args)
  326. return wrapper
  327. return decorator
  328. # Like @depends_if, but a distinguished value passed as a keyword argument
  329. # "when" is truth tested instead of every argument. This value is not passed
  330. # to the function if it is called.
  331. @template
  332. def depends_when(*args, **kwargs):
  333. if not len(kwargs) == 1 and kwargs.get('when'):
  334. die('depends_when requires a single keyword argument, "when"')
  335. when = kwargs['when']
  336. if not when:
  337. return depends(*args)
  338. def decorator(fn):
  339. @depends(when, *args)
  340. def wrapper(val, *args):
  341. if val:
  342. return fn(*args)
  343. return wrapper
  344. return decorator
  345. # Hacks related to old-configure
  346. # ==============================
  347. @dependable
  348. def old_configure_assignments():
  349. return []
  350. @dependable
  351. def extra_old_configure_args():
  352. return []
  353. @template
  354. def add_old_configure_assignment(var, value):
  355. var = dependable(var)
  356. value = dependable(value)
  357. @depends(old_configure_assignments, var, value)
  358. @imports(_from='mozbuild.shellutil', _import='quote')
  359. def add_assignment(assignments, var, value):
  360. if var is None or value is None:
  361. return
  362. if value is True:
  363. assignments.append('%s=1' % var)
  364. elif value is False:
  365. assignments.append('%s=' % var)
  366. else:
  367. if isinstance(value, (list, tuple)):
  368. value = quote(*value)
  369. assignments.append('%s=%s' % (var, quote(str(value))))
  370. @template
  371. def add_old_configure_arg(arg):
  372. @depends(extra_old_configure_args, arg)
  373. def add_arg(args, arg):
  374. if arg:
  375. args.append(arg)