summaryrefslogtreecommitdiffstats
path: root/build/moz.configure/checks.configure
blob: 8c2dbc0cc4c3aecc2e1937a21141e9c5ef12658b (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
# -*- 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 = e.message
                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.
#
# 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):
    if input:
        # Wrap input with type checking and normalization.
        @depends(input)
        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,
               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)

    @depends_if(input, progs, paths)
    @checking('for %s' % what, lambda x: quote(x) if x else 'not found')
    def check(value, progs, paths):
        if progs is None:
            progs = ()

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

        for prog in value or progs:
            log.debug('%s: Trying %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)

    @depends_if(check, progs)
    def normalized_for_config(value, progs):
        return ':' if value is None else value

    set_config(var, normalized_for_config)

    return check