checks.configure (7732B)
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 # Templates implementing some generic checks. 8 # ============================================================== 9 10 # Declare some exceptions. This is cumbersome, but since we shouldn't need a 11 # lot of them, let's stack them all here. When adding a new one, put it in the 12 # _declare_exceptions template, and add it to the return statement. Then 13 # destructure in the assignment below the function declaration. 14 15 16 @template 17 @imports(_from="__builtin__", _import="Exception") 18 def _declare_exceptions(): 19 class FatalCheckError(Exception): 20 """An exception to throw from a function decorated with @checking. 21 It will result in calling die() with the given message. 22 Debugging messages emitted from the decorated function will also be 23 printed out.""" 24 25 return (FatalCheckError,) 26 27 28 (FatalCheckError,) = _declare_exceptions() 29 30 del _declare_exceptions 31 32 # Helper to display "checking" messages 33 # @checking('for foo') 34 # def foo(): 35 # return 'foo' 36 # is equivalent to: 37 # def foo(): 38 # log.info('checking for foo... ') 39 # ret = foo 40 # log.info(ret) 41 # return ret 42 # This can be combined with e.g. @depends: 43 # @depends(some_option) 44 # @checking('for something') 45 # def check(value): 46 # ... 47 # An optional callback can be given, that will be used to format the returned 48 # value when displaying it. 49 50 51 @template 52 def checking(what, callback=None): 53 def decorator(func): 54 def wrapped(*args, **kwargs): 55 log.info("checking %s... ", what) 56 with log.queue_debug(): 57 error, ret = None, None 58 try: 59 ret = func(*args, **kwargs) 60 except FatalCheckError as e: 61 error = str(e) 62 display_ret = callback(ret) if callback else ret 63 if display_ret is True: 64 log.info("yes") 65 elif display_ret is False or display_ret is None: 66 log.info("no") 67 else: 68 log.info(display_ret) 69 if error is not None: 70 die(error) 71 return ret 72 73 return wrapped 74 75 return decorator 76 77 78 # Template to check for programs in $PATH. 79 # - `var` is the name of the variable that will be set with `set_config` when 80 # the program is found. 81 # - `progs` is a list (or tuple) of program names that will be searched for. 82 # It can also be a reference to a @depends function that returns such a 83 # list. If the list is empty and there is no input, the check is skipped. 84 # - `what` is a human readable description of what is being looked for. It 85 # defaults to the lowercase version of `var`. 86 # - `input` is a string reference to an existing option or a reference to a 87 # @depends function resolving to explicit input for the program check. 88 # The default is to create an option for the environment variable `var`. 89 # This argument allows to use a different kind of option (possibly using a 90 # configure flag), or doing some pre-processing with a @depends function. 91 # - `allow_missing` indicates whether not finding the program is an error. 92 # - `paths` is a list of paths or @depends function returning a list of paths 93 # that will cause the given path(s) to be searched rather than $PATH. Input 94 # paths may either be individual paths or delimited by os.pathsep, to allow 95 # passing $PATH (for example) as an element. 96 # - `bootstrap` is a path relative to the bootstrap root path (e.g ~/.mozbuild) 97 # where to find the program if it's bootstrapped. 98 # - `validate` is a callback function that takes a path and returns True if 99 # the program at that location is appropriate or not, or False if not. 100 # when the callback returns False, check_prog ignores the program and goes 101 # on to the next from the `progs` list. 102 # 103 # - `bootstrap_search_path` is not an argument that users of the template are 104 # supposed to pass. See the override of check_prog in top-level moz.configure. 105 # 106 # The simplest form is: 107 # check_prog('PROG', ('a', 'b')) 108 # This will look for 'a' or 'b' in $PATH, and set_config PROG to the one 109 # it can find. If PROG is already set from the environment or command line, 110 # use that value instead. 111 @template 112 @imports(_from="mozshellutil", _import="quote") 113 def check_prog( 114 var, 115 progs, 116 what=None, 117 input=None, 118 allow_missing=False, 119 paths=None, 120 allow_spaces=False, 121 bootstrap=None, 122 when=None, 123 validate=None, 124 bootstrap_search_path=None, 125 ): 126 if input is not None: 127 # Wrap input with type checking and normalization. 128 @depends(input, when=when) 129 def input(value): 130 if not value: 131 return 132 if isinstance(value, str): 133 return (value,) 134 if isinstance(value, (tuple, list)) and len(value) == 1: 135 return value 136 configure_error( 137 "input must resolve to a tuple or a list with a " 138 "single element, or a string" 139 ) 140 141 else: 142 option( 143 env=var, 144 nargs=1, 145 when=when, 146 help="Path to %s" % (what or "the %s program" % var.lower()), 147 ) 148 input = var 149 what = what or var.lower() 150 151 # Trick to make a @depends function out of an immediate value. 152 progs = dependable(progs) 153 paths = dependable(paths) 154 allow_missing = dependable(allow_missing) 155 156 if bootstrap: 157 if input is var: 158 # A when is needed when depending on an option, so normalize 159 # to a function that can used without. 160 has_input = depends(input, when=when)(lambda x: x) 161 else: 162 has_input = input 163 # We don't want to bootstrap when an explicit value was given as input. 164 if when: 165 bootstrap_when = depends(when, has_input)(lambda w, i: w and not i) 166 else: 167 bootstrap_when = depends(has_input)(lambda i: not i) 168 paths = bootstrap_search_path(bootstrap, paths, when=bootstrap_when) 169 170 # Avoid displaying the "Checking for" message when the inputs are such 171 # that we don't actually want anything to be checked. It is a bit 172 # convoluted because of how `when` works. 173 # We first wrap all the inputs except allow_missing (which doesn't count 174 # for whether to display the "Checking for" message). 175 @depends_if(input, progs, paths, when=when) 176 def inputs(input, progs, paths): 177 if progs is None: 178 progs = () 179 180 if not isinstance(progs, (tuple, list)): 181 configure_error("progs must resolve to a list or tuple!") 182 183 return namespace(value=input, progs=progs, paths=paths) 184 185 @depends(inputs, allow_missing, when=inputs) 186 @checking("for %s" % what, lambda x: quote(x) if x else "not found") 187 def check(inputs, allow_missing): 188 value = inputs.value 189 progs = inputs.progs 190 paths = inputs.paths 191 192 for prog in value or progs: 193 log.debug("%s: Looking for %s", var.lower(), quote(prog)) 194 result = find_program(prog, paths, allow_spaces) 195 if validate and result and not validate(result): 196 log.debug("%s: %s found but didn't work", var.lower(), quote(result)) 197 continue 198 if result: 199 return result 200 201 if not allow_missing or value: 202 raise FatalCheckError("Cannot find %s" % what) 203 204 set_config(var, check) 205 206 return check