]>
Commit | Line | Data |
---|---|---|
1e59de90 | 1 | # coding: utf-8 |
11fdf7f2 | 2 | # Copyright 2017 Steven Watanabe |
1e59de90 | 3 | # Copyright 2020 René Ferdinand Rivera Morell |
11fdf7f2 TL |
4 | # |
5 | # Distributed under the Boost Software License, Version 1.0. | |
1e59de90 TL |
6 | # (See accompanying file LICENSE.txt or copy at |
7 | # https://www.bfgroup.xyz/b2/LICENSE.txt) | |
11fdf7f2 | 8 | |
92f5a8d4 TL |
9 | from __future__ import print_function |
10 | ||
11fdf7f2 TL |
11 | import sys |
12 | import os | |
13 | import re | |
20effc67 | 14 | import fnmatch |
11fdf7f2 TL |
15 | |
16 | # Represents a sequence of arguments that must appear | |
17 | # in a fixed order. | |
18 | class ordered: | |
19 | def __init__(self, *args): | |
20 | self.args = args | |
21 | def match(self, command_line, pos, outputs): | |
22 | for p in self.args: | |
23 | res = try_match(command_line, pos, p, outputs) | |
24 | if res is None: | |
25 | return | |
26 | pos = res | |
27 | return pos | |
28 | ||
29 | # Represents a sequence of arguments that can appear | |
30 | # in any order. | |
31 | class unordered: | |
32 | def __init__(self, *args): | |
33 | self.args = list(args) | |
34 | def match(self, command_line, pos, outputs): | |
35 | unmatched = self.args[:] | |
36 | while len(unmatched) > 0: | |
37 | res = try_match_one(command_line, pos, unmatched, outputs) | |
38 | if res is None: | |
39 | return | |
40 | pos = res | |
41 | return pos | |
42 | ||
43 | # Represents a single input file. | |
44 | # If id is set, then the file must have been created | |
45 | # by a prior use of output_file. | |
46 | # If source is set, then the file must be that source file. | |
47 | class input_file: | |
48 | def __init__(self, id=None, source=None): | |
49 | assert((id is None) ^ (source is None)) | |
50 | self.id = id | |
51 | self.source = source | |
52 | def check(self, path): | |
53 | if path.startswith("-"): | |
54 | return | |
55 | if self.id is not None: | |
56 | try: | |
57 | with open(path, "r") as f: | |
58 | data = f.read() | |
59 | if data == make_file_contents(self.id): | |
60 | return True | |
61 | else: | |
62 | return | |
63 | except: | |
64 | return | |
65 | elif self.source is not None: | |
66 | if self.source == path: | |
67 | return True | |
68 | else: | |
69 | return | |
70 | assert(False) | |
71 | def match(self, command_line, pos, outputs): | |
72 | if self.check(command_line[pos]): | |
73 | return pos + 1 | |
74 | ||
75 | # Matches an output file. | |
76 | # If the full pattern is matched, The | |
77 | # file will be created. | |
78 | class output_file: | |
79 | def __init__(self, id): | |
80 | self.id = id | |
81 | def match(self, command_line, pos, outputs): | |
82 | if command_line[pos].startswith("-"): | |
83 | return | |
84 | outputs.append((command_line[pos], self.id)) | |
85 | return pos + 1 | |
86 | ||
20effc67 TL |
87 | class arg_file: |
88 | def __init__(self, id): | |
89 | self.id = id | |
90 | def match(self, command_line, pos, outputs): | |
91 | if command_line[pos].startswith("-"): | |
92 | return | |
93 | if fnmatch.fnmatch(command_line[pos], self.id): | |
94 | return pos + 1 | |
95 | else: | |
96 | return | |
97 | ||
11fdf7f2 TL |
98 | # Matches the directory containing an input_file |
99 | class target_path(object): | |
100 | def __init__(self, id): | |
101 | self.tester = input_file(id=id) | |
102 | def match(self, command_line, pos, outputs): | |
103 | arg = command_line[pos] | |
104 | if arg.startswith("-"): | |
105 | return | |
106 | try: | |
107 | for path in os.listdir(arg): | |
108 | if self.tester.check(os.path.join(arg, path)): | |
109 | return pos + 1 | |
110 | except: | |
92f5a8d4 | 111 | return |
11fdf7f2 TL |
112 | |
113 | # Matches a single argument, which is composed of a prefix and a path | |
114 | # for example arguments of the form -ofilename. | |
115 | class arg(object): | |
116 | def __init__(self, prefix, a): | |
117 | # The prefix should be a string, a should be target_path or input_file. | |
118 | self.prefix = prefix | |
119 | self.a = a | |
120 | def match(self, command_line, pos, outputs): | |
121 | s = command_line[pos] | |
122 | if s.startswith(self.prefix) and try_match([s[len(self.prefix):]], 0, self.a, outputs) == 1: | |
123 | return pos + 1 | |
124 | ||
1e59de90 TL |
125 | # |
126 | class opt(object): | |
127 | def __init__(self, *args): | |
128 | self.args = args | |
129 | def match(self, command_line, pos, outputs): | |
130 | for p in self.args: | |
131 | res = try_match_one(command_line, pos, p, outputs) | |
132 | if res is not None: | |
133 | pos = res | |
134 | return pos | |
135 | ||
11fdf7f2 TL |
136 | # Given a file id, returns a string that will be |
137 | # written to the file to allow it to be recognized. | |
138 | def make_file_contents(id): | |
139 | return id | |
140 | ||
141 | # Matches a single pattern from a list. | |
142 | # If it succeeds, the matching pattern | |
143 | # is removed from the list. | |
144 | # Returns the index after the end of the match | |
145 | def try_match_one(command_line, pos, patterns, outputs): | |
146 | for p in patterns: | |
147 | tmp = outputs[:] | |
148 | res = try_match(command_line, pos, p, tmp) | |
149 | if res is not None: | |
150 | outputs[:] = tmp | |
151 | patterns.remove(p) | |
152 | return res | |
153 | ||
154 | # returns the end of the match if any | |
155 | def try_match(command_line, pos, pattern, outputs): | |
156 | if pos == len(command_line): | |
157 | return | |
158 | elif type(pattern) is str: | |
159 | if pattern == command_line[pos]: | |
160 | return pos + 1 | |
161 | else: | |
162 | return pattern.match(command_line, pos, outputs) | |
163 | ||
164 | known_patterns = [] | |
165 | program_name = None | |
166 | ||
167 | # Registers a command | |
168 | # The arguments should be a sequence of: | |
169 | # str, ordered, unordered, arg, input_file, output_file, target_path | |
170 | # kwarg: stdout is text that will be printed on success. | |
171 | def command(*args, **kwargs): | |
172 | global known_patterns | |
173 | global program_name | |
174 | stdout = kwargs.get("stdout", None) | |
175 | pattern = ordered(*args) | |
176 | known_patterns += [(pattern, stdout)] | |
177 | if program_name is None: | |
178 | program_name = args[0] | |
179 | else: | |
180 | assert(program_name == args[0]) | |
181 | ||
182 | # Use this to filter the recognized commands, based on the properties | |
183 | # passed to b2. | |
184 | def allow_properties(*args): | |
185 | try: | |
186 | return all(a in os.environ["B2_PROPERTIES"].split(" ") for a in args) | |
187 | except KeyError: | |
188 | return True | |
189 | ||
190 | # Use this in the stdout argument of command to print the command | |
191 | # for running another script. | |
192 | def script(name): | |
193 | return os.path.join(os.path.dirname(__file__), "bin", re.sub('\.py$', '', name)) | |
194 | ||
195 | def match(command_line): | |
196 | for (p, stdout) in known_patterns: | |
197 | outputs = [] | |
198 | if try_match(command_line, 0, p, outputs) == len(command_line): | |
199 | return (stdout, outputs) | |
200 | ||
201 | # Every mock program should call this after setting up all the commands. | |
202 | def main(): | |
203 | command_line = [program_name] + sys.argv[1:] | |
204 | result = match(command_line) | |
205 | if result is not None: | |
206 | (stdout, outputs) = result | |
207 | if stdout is not None: | |
92f5a8d4 | 208 | print(stdout) |
11fdf7f2 TL |
209 | for (file,id) in outputs: |
210 | with open(file, "w") as f: | |
211 | f.write(make_file_contents(id)) | |
212 | exit(0) | |
213 | else: | |
20effc67 | 214 | print("ERROR on command: %s"%(" ".join(command_line))) |
11fdf7f2 TL |
215 | exit(1) |
216 | ||
217 | # file should be the name of a file in the same directory | |
218 | # as this. Must be called after verify_setup | |
219 | def verify_file(filename): | |
220 | global known_files | |
221 | if filename not in known_files: | |
222 | known_files.add(filename) | |
223 | srcdir = os.path.dirname(__file__) | |
224 | execfile(os.path.join(srcdir, filename), {}) | |
225 | ||
226 | def verify_setup(): | |
227 | """Override the behavior of most module components | |
228 | in order to detect whether they are being used correctly.""" | |
229 | global main | |
230 | global allow_properties | |
231 | global output_file | |
232 | global input_file | |
233 | global target_path | |
234 | global script | |
235 | global command | |
236 | global verify_errors | |
237 | global output_ids | |
238 | global input_ids | |
239 | global known_files | |
240 | def allow_properties(*args): | |
241 | return True | |
242 | def main(): | |
243 | pass | |
244 | def output_file(id): | |
245 | global output_ids | |
246 | global verify_error | |
247 | if id in output_ids: | |
248 | verify_error("duplicate output_file: %s" % id) | |
249 | output_ids.add(id) | |
250 | def input_file(id=None, source=None): | |
251 | if id is not None: | |
252 | input_ids.add(id) | |
253 | def target_path(id): | |
254 | input_ids.add(id) | |
255 | def script(filename): | |
256 | verify_file(filename) | |
257 | def command(*args, **kwargs): | |
258 | pass | |
259 | verify_errors = [] | |
260 | output_ids = set() | |
261 | input_ids = set() | |
262 | known_files = set() | |
263 | ||
264 | def verify_error(message): | |
265 | global verify_errors | |
266 | verify_errors += [message] | |
267 | ||
268 | def verify_finalize(): | |
269 | for id in input_ids: | |
270 | if not id in output_ids: | |
271 | verify_error("Input file does not exist: %s" % id) | |
272 | for error in verify_errors: | |
92f5a8d4 | 273 | print("error: %s" % error) |
11fdf7f2 TL |
274 | if len(verify_errors) != 0: |
275 | return 1 | |
276 | else: | |
277 | return 0 | |
278 | ||
279 | def verify(): | |
280 | srcdir = os.path.dirname(__file__) | |
281 | if srcdir == '': | |
282 | srcdir = '.' | |
283 | verify_setup() | |
284 | for f in os.listdir(srcdir): | |
285 | if re.match(r"(gcc|clang|darwin|intel)-.*\.py", f): | |
286 | verify_file(f) | |
287 | exit(verify_finalize()) |