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
|
# 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/.
# Given a Python script and arguments describing the output file, and
# the arguments that can be used to generate the output file, call the
# script's |main| method with appropriate arguments.
import argparse
import importlib.util
import os
import sys
import traceback
import buildconfig
import six
from mozbuild.action.util import log_build_task
from mozbuild.makeutil import Makefile
from mozbuild.pythonutil import iter_modules_in_path
from mozbuild.util import FileAvoidWrite
def main(argv):
parser = argparse.ArgumentParser(
"Generate a file from a Python script", add_help=False
)
parser.add_argument(
"--locale", metavar="locale", type=six.text_type, help="The locale in use."
)
parser.add_argument(
"python_script",
metavar="python-script",
type=six.text_type,
help="The Python script to run",
)
parser.add_argument(
"method_name",
metavar="method-name",
type=six.text_type,
help="The method of the script to invoke",
)
parser.add_argument(
"output_file",
metavar="output-file",
type=six.text_type,
help="The file to generate",
)
parser.add_argument(
"dep_file",
metavar="dep-file",
type=six.text_type,
help="File to write any additional make dependencies to",
)
parser.add_argument(
"dep_target",
metavar="dep-target",
type=six.text_type,
help="Make target to use in the dependencies file",
)
parser.add_argument(
"additional_arguments",
metavar="arg",
nargs=argparse.REMAINDER,
help="Additional arguments to the script's main() method",
)
args = parser.parse_args(argv)
kwargs = {}
if args.locale:
kwargs["locale"] = args.locale
script = args.python_script
# Permit the script to import modules from the same directory in which it
# resides. The justification for doing this is that if we were invoking
# the script as:
#
# python script arg1...
#
# then importing modules from the script's directory would come for free.
# Since we're invoking the script in a roundabout way, we provide this
# bit of convenience.
sys.path.append(os.path.dirname(script))
spec = importlib.util.spec_from_file_location("script", script)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
method = args.method_name
if not hasattr(module, method):
print(
'Error: script "{0}" is missing a {1} method'.format(script, method),
file=sys.stderr,
)
return 1
ret = 1
try:
with FileAvoidWrite(args.output_file, readmode="rb") as output:
try:
ret = module.__dict__[method](
output, *args.additional_arguments, **kwargs
)
except Exception:
# Ensure that we don't overwrite the file if the script failed.
output.avoid_writing_to_file()
raise
# The following values indicate a statement of success:
# - a set() (see below)
# - 0
# - False
# - None
#
# Everything else is an error (so scripts can conveniently |return
# 1| or similar). If a set is returned, the elements of the set
# indicate additional dependencies that will be listed in the deps
# file. Python module imports are automatically included as
# dependencies.
if isinstance(ret, set):
deps = set(six.ensure_text(s) for s in ret)
# The script succeeded, so reset |ret| to indicate that.
ret = None
else:
deps = set()
# Only write out the dependencies if the script was successful
if not ret:
# Add dependencies on any python modules that were imported by
# the script.
deps |= set(
six.ensure_text(s)
for s in iter_modules_in_path(
buildconfig.topsrcdir, buildconfig.topobjdir
)
)
# Add dependencies on any buildconfig items that were accessed
# by the script.
deps |= set(six.ensure_text(s) for s in buildconfig.get_dependencies())
mk = Makefile()
mk.create_rule([args.dep_target]).add_dependencies(deps)
with FileAvoidWrite(args.dep_file) as dep_file:
mk.dump(dep_file)
else:
# Ensure that we don't overwrite the file if the script failed.
output.avoid_writing_to_file()
except IOError as e:
print('Error opening file "{0}"'.format(e.filename), file=sys.stderr)
traceback.print_exc()
return 1
return ret
if __name__ == "__main__":
sys.exit(log_build_task(main, sys.argv[1:]))
|