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