summaryrefslogtreecommitdiffstats
path: root/build/moz.configure/checks.configure
blob: 73e8b0a886e258ff67229f755e11a2b504d5b73c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Templates implementing some generic checks.
# ==============================================================

# Declare some exceptions. This is cumbersome, but since we shouldn't need a
# lot of them, let's stack them all here. When adding a new one, put it in the
# _declare_exceptions template, and add it to the return statement. Then
# destructure in the assignment below the function declaration.


@template
@imports(_from="__builtin__", _import="Exception")
def _declare_exceptions():
    class FatalCheckError(Exception):
        """An exception to throw from a function decorated with @checking.
        It will result in calling die() with the given message.
        Debugging messages emitted from the decorated function will also be
        printed out."""

    return (FatalCheckError,)


(FatalCheckError,) = _declare_exceptions()

del _declare_exceptions

# Helper to display "checking" messages
#   @checking('for foo')
#   def foo():
#       return 'foo'
# is equivalent to:
#   def foo():
#       log.info('checking for foo... ')
#       ret = foo
#       log.info(ret)
#       return ret
# This can be combined with e.g. @depends:
#   @depends(some_option)
#   @checking('for something')
#   def check(value):
#       ...
# An optional callback can be given, that will be used to format the returned
# value when displaying it.


@template
def checking(what, callback=None):
    def decorator(func):
        def wrapped(*args, **kwargs):
            log.info("checking %s... ", what)
            with log.queue_debug():
                error, ret = None, None
                try:
                    ret = func(*args, **kwargs)
                except FatalCheckError as e:
                    error = str(e)
                display_ret = callback(ret) if callback else ret
                if display_ret is True:
                    log.info("yes")
                elif display_ret is False or display_ret is None:
                    log.info("no")
                else:
                    log.info(display_ret)
                if error is not None:
                    die(error)
            return ret

        return wrapped

    return decorator


# Template to check for programs in $PATH.
# - `var` is the name of the variable that will be set with `set_config` when
#   the program is found.
# - `progs` is a list (or tuple) of program names that will be searched for.
#   It can also be a reference to a @depends function that returns such a
#   list. If the list is empty and there is no input, the check is skipped.
# - `what` is a human readable description of what is being looked for. It
#   defaults to the lowercase version of `var`.
# - `input` is a string reference to an existing option or a reference to a
#   @depends function resolving to explicit input for the program check.
#   The default is to create an option for the environment variable `var`.
#   This argument allows to use a different kind of option (possibly using a
#   configure flag), or doing some pre-processing with a @depends function.
# - `allow_missing` indicates whether not finding the program is an error.
# - `paths` is a list of paths or @depends function returning a list of paths
#   that will cause the given path(s) to be searched rather than $PATH. Input
#   paths may either be individual paths or delimited by os.pathsep, to allow
#   passing $PATH (for example) as an element.
# - `bootstrap` is a path relative to the bootstrap root path (e.g ~/.mozbuild)
#   where to find the program if it's bootstrapped.
# - `validate` is a callback function that takes a path and returns True if
#   the program at that location is appropriate or not, or False if not.
#   when the callback returns False, check_prog ignores the program and goes
#   on to the next from the `progs` list.
#
# - `bootstrap_search_path` is not an argument that users of the template are
#   supposed to pass. See the override of check_prog in top-level moz.configure.
#
# The simplest form is:
#   check_prog('PROG', ('a', 'b'))
# This will look for 'a' or 'b' in $PATH, and set_config PROG to the one
# it can find. If PROG is already set from the environment or command line,
# use that value instead.
@template
@imports(_from="mozbuild.shellutil", _import="quote")
def check_prog(
    var,
    progs,
    what=None,
    input=None,
    allow_missing=False,
    paths=None,
    allow_spaces=False,
    bootstrap=None,
    when=None,
    validate=None,
    bootstrap_search_path=None,
):
    if input is not None:
        # Wrap input with type checking and normalization.
        @depends(input, when=when)
        def input(value):
            if not value:
                return
            if isinstance(value, str):
                return (value,)
            if isinstance(value, (tuple, list)) and len(value) == 1:
                return value
            configure_error(
                "input must resolve to a tuple or a list with a "
                "single element, or a string"
            )

    else:
        option(
            env=var,
            nargs=1,
            when=when,
            help="Path to %s" % (what or "the %s program" % var.lower()),
        )
        input = var
    what = what or var.lower()

    # Trick to make a @depends function out of an immediate value.
    progs = dependable(progs)
    paths = dependable(paths)
    allow_missing = dependable(allow_missing)

    if bootstrap:
        if input is var:
            # A when is needed when depending on an option, so normalize
            # to a function that can used without.
            has_input = depends(input, when=when)(lambda x: x)
        else:
            has_input = input
        # We don't want to bootstrap when an explicit value was given as input.
        if when:
            bootstrap_when = depends(when, has_input)(lambda w, i: w and not i)
        else:
            bootstrap_when = depends(has_input)(lambda i: not i)
        paths = bootstrap_search_path(bootstrap, paths, when=bootstrap_when)

    # Avoid displaying the "Checking for" message when the inputs are such
    # that we don't actually want anything to be checked. It is a bit
    # convoluted because of how `when` works.
    # We first wrap all the inputs except allow_missing (which doesn't count
    # for whether to display the "Checking for" message).
    @depends_if(input, progs, paths, when=when)
    def inputs(input, progs, paths):
        if progs is None:
            progs = ()

        if not isinstance(progs, (tuple, list)):
            configure_error("progs must resolve to a list or tuple!")

        return namespace(value=input, progs=progs, paths=paths)

    @depends(inputs, allow_missing, when=inputs)
    @checking("for %s" % what, lambda x: quote(x) if x else "not found")
    def check(inputs, allow_missing):
        value = inputs.value
        progs = inputs.progs
        paths = inputs.paths

        for prog in value or progs:
            log.debug("%s: Looking for %s", var.lower(), quote(prog))
            result = find_program(prog, paths, allow_spaces)
            if validate and result and not validate(result):
                log.debug("%s: %s found but didn't work", var.lower(), quote(result))
                continue
            if result:
                return result

        if not allow_missing or value:
            raise FatalCheckError("Cannot find %s" % what)

    set_config(var, check)

    return check