summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/action/node.py
blob: fca0745b809af15ca45a569341dcb538f17aa99f (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
# 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/.

import pipes
import subprocess
import sys

import buildconfig
import six

SCRIPT_ALLOWLIST = [buildconfig.topsrcdir + "/devtools/client/shared/build/build.js"]

ALLOWLIST_ERROR = """
%s is not
in SCRIPT_ALLOWLIST in python/mozbuild/mozbuild/action/node.py.
Using NodeJS from moz.build is currently in beta, and node
scripts to be executed need to be added to the allowlist and
reviewed by a build peer so that we can get a better sense of
how support should evolve. (To consult a build peer, raise a
question in the #build channel at https://chat.mozilla.org.)
"""


def is_script_in_allowlist(script_path):
    if script_path in SCRIPT_ALLOWLIST:
        return True

    return False


def execute_node_cmd(node_cmd_list):
    """Execute the given node command list.

    Arguments:
    node_cmd_list -- a list of the command and arguments to be executed

    Returns:
    The set of dependencies which should trigger this command to be re-run.
    This is ultimately returned to the build system for use by the backend
    to ensure that incremental rebuilds happen when any dependency changes.

    The node script is expected to output lines for all of the dependencies
    to stdout, each prefixed by the string "dep:".  These lines will make up
    the returned set of dependencies.  Any line not so-prefixed will simply be
    printed to stderr instead.
    """

    try:
        printable_cmd = " ".join(pipes.quote(arg) for arg in node_cmd_list)
        print('Executing "{}"'.format(printable_cmd), file=sys.stderr)
        sys.stderr.flush()

        # We need to redirect stderr to a pipe because
        # https://github.com/nodejs/node/issues/14752 causes issues with make.
        proc = subprocess.Popen(
            node_cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )

        stdout, stderr = proc.communicate()
        retcode = proc.wait()

        if retcode != 0:
            print(stderr, file=sys.stderr)
            sys.stderr.flush()
            sys.exit(retcode)

        # Process the node script output
        #
        # XXX Starting with an empty list means that node scripts can
        # (intentionally or inadvertently) remove deps.  Do we want this?
        deps = []
        for line in stdout.splitlines():
            line = six.ensure_text(line)
            if "dep:" in line:
                deps.append(line.replace("dep:", ""))
            else:
                print(line, file=sys.stderr)
                sys.stderr.flush()

        return set(deps)

    except subprocess.CalledProcessError as err:
        # XXX On Mac (and elsewhere?) "OSError: [Errno 13] Permission denied"
        # (at least sometimes) means "node executable not found".  Can we
        # disambiguate this from real "Permission denied" errors so that we
        # can log such problems more clearly?
        print(
            """Failed with %s.  Be sure to check that your mozconfig doesn't
            have --disable-nodejs in it.  If it does, try removing that line and
            building again."""
            % str(err),
            file=sys.stderr,
        )
        sys.exit(1)


def generate(output, node_script, *files):
    """Call the given node_script to transform the given modules.

    Arguments:
    output -- a dummy file, used by the build system.  Can be ignored.
    node_script -- the script to be executed.  Must be in the SCRIPT_ALLOWLIST
    files -- files to be transformed, will be passed to the script as arguments

    Returns:
    The set of dependencies which should trigger this command to be re-run.
    This is ultimately returned to the build system for use by the backend
    to ensure that incremental rebuilds happen when any dependency changes.
    """

    node_interpreter = buildconfig.substs.get("NODEJS")
    if not node_interpreter:
        print(
            """NODEJS not set.  Be sure to check that your mozconfig doesn't
            have --disable-nodejs in it.  If it does, try removing that line
            and building again.""",
            file=sys.stderr,
        )
        sys.exit(1)

    node_script = six.ensure_text(node_script)
    if not isinstance(node_script, six.text_type):
        print(
            "moz.build file didn't pass a valid node script name to execute",
            file=sys.stderr,
        )
        sys.exit(1)

    if not is_script_in_allowlist(node_script):
        print(ALLOWLIST_ERROR % (node_script), file=sys.stderr)
        sys.exit(1)

    node_cmd_list = [node_interpreter, node_script]
    node_cmd_list.extend(files)

    return execute_node_cmd(node_cmd_list)