summaryrefslogtreecommitdiffstats
path: root/build/moz.configure/checks.configure
blob: 9269addbb8c8f9e3bc3a0d58ad40c1fa6280a74d (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
# -*- 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.
# - `paths_have_priority` means that any programs found early in the PATH
#   will be prioritized over programs found later in the PATH.  The default is
#   False, meaning that any of the programs earlier in the program list will be
#   given priority, no matter where in the PATH they are found.
#
# 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,
    paths_have_priority=False,
    when=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)

    # 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

        if paths_have_priority:
            for path in paths:
                for prog in value or progs:
                    log.debug("%s: Looking for %s", var.lower(), quote(prog))
                    result = find_program(prog, [path])
                    if result:
                        return result
        else:
            for prog in value or progs:
                log.debug("%s: Looking for %s", var.lower(), quote(prog))
                result = find_program(prog, paths)
                if result:
                    return result

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

    set_config(var, check)

    return check