checks.configure 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. # Templates implementing some generic checks.
  6. # ==============================================================
  7. # Declare some exceptions. This is cumbersome, but since we shouldn't need a
  8. # lot of them, let's stack them all here. When adding a new one, put it in the
  9. # _declare_exceptions template, and add it to the return statement. Then
  10. # destructure in the assignment below the function declaration.
  11. @template
  12. @imports(_from='__builtin__', _import='Exception')
  13. def _declare_exceptions():
  14. class FatalCheckError(Exception):
  15. '''An exception to throw from a function decorated with @checking.
  16. It will result in calling die() with the given message.
  17. Debugging messages emitted from the decorated function will also be
  18. printed out.'''
  19. return (FatalCheckError,)
  20. (FatalCheckError,) = _declare_exceptions()
  21. del _declare_exceptions
  22. # Helper to display "checking" messages
  23. # @checking('for foo')
  24. # def foo():
  25. # return 'foo'
  26. # is equivalent to:
  27. # def foo():
  28. # log.info('checking for foo... ')
  29. # ret = foo
  30. # log.info(ret)
  31. # return ret
  32. # This can be combined with e.g. @depends:
  33. # @depends(some_option)
  34. # @checking('for something')
  35. # def check(value):
  36. # ...
  37. # An optional callback can be given, that will be used to format the returned
  38. # value when displaying it.
  39. @template
  40. def checking(what, callback=None):
  41. def decorator(func):
  42. def wrapped(*args, **kwargs):
  43. log.info('checking %s... ', what)
  44. with log.queue_debug():
  45. error, ret = None, None
  46. try:
  47. ret = func(*args, **kwargs)
  48. except FatalCheckError as e:
  49. error = e.message
  50. display_ret = callback(ret) if callback else ret
  51. if display_ret is True:
  52. log.info('yes')
  53. elif display_ret is False or display_ret is None:
  54. log.info('no')
  55. else:
  56. log.info(display_ret)
  57. if error is not None:
  58. die(error)
  59. return ret
  60. return wrapped
  61. return decorator
  62. # Template to check for programs in $PATH.
  63. # - `var` is the name of the variable that will be set with `set_config` when
  64. # the program is found.
  65. # - `progs` is a list (or tuple) of program names that will be searched for.
  66. # It can also be a reference to a @depends function that returns such a
  67. # list. If the list is empty and there is no input, the check is skipped.
  68. # - `what` is a human readable description of what is being looked for. It
  69. # defaults to the lowercase version of `var`.
  70. # - `input` is a string reference to an existing option or a reference to a
  71. # @depends function resolving to explicit input for the program check.
  72. # The default is to create an option for the environment variable `var`.
  73. # This argument allows to use a different kind of option (possibly using a
  74. # configure flag), or doing some pre-processing with a @depends function.
  75. # - `allow_missing` indicates whether not finding the program is an error.
  76. # - `paths` is a list of paths or @depends function returning a list of paths
  77. # that will cause the given path(s) to be searched rather than $PATH. Input
  78. # paths may either be individual paths or delimited by os.pathsep, to allow
  79. # passing $PATH (for example) as an element.
  80. #
  81. # The simplest form is:
  82. # check_prog('PROG', ('a', 'b'))
  83. # This will look for 'a' or 'b' in $PATH, and set_config PROG to the one
  84. # it can find. If PROG is already set from the environment or command line,
  85. # use that value instead.
  86. @template
  87. @imports(_from='mozbuild.shellutil', _import='quote')
  88. def check_prog(var, progs, what=None, input=None, allow_missing=False,
  89. paths=None):
  90. if input:
  91. # Wrap input with type checking and normalization.
  92. @depends(input)
  93. def input(value):
  94. if not value:
  95. return
  96. if isinstance(value, str):
  97. return (value,)
  98. if isinstance(value, (tuple, list)) and len(value) == 1:
  99. return value
  100. configure_error('input must resolve to a tuple or a list with a '
  101. 'single element, or a string')
  102. else:
  103. option(env=var, nargs=1,
  104. help='Path to %s' % (what or 'the %s program' % var.lower()))
  105. input = var
  106. what = what or var.lower()
  107. # Trick to make a @depends function out of an immediate value.
  108. progs = dependable(progs)
  109. paths = dependable(paths)
  110. @depends_if(input, progs, paths)
  111. @checking('for %s' % what, lambda x: quote(x) if x else 'not found')
  112. def check(value, progs, paths):
  113. if progs is None:
  114. progs = ()
  115. if not isinstance(progs, (tuple, list)):
  116. configure_error('progs must resolve to a list or tuple!')
  117. for prog in value or progs:
  118. log.debug('%s: Trying %s', var.lower(), quote(prog))
  119. result = find_program(prog, paths)
  120. if result:
  121. return result
  122. if not allow_missing or value:
  123. raise FatalCheckError('Cannot find %s' % what)
  124. @depends_if(check, progs)
  125. def normalized_for_config(value, progs):
  126. return ':' if value is None else value
  127. set_config(var, normalized_for_config)
  128. return check