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