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