| import json |
| |
| |
| # Parameters. |
| ALL_ERRORS = False |
| |
| |
| def _report_error(msg): |
| '''Report an error.''' |
| full_msg = 'ERROR: ' + msg |
| if ALL_ERRORS: |
| print(full_msg) |
| else: |
| raise RuntimeError(full_msg) |
| |
| |
| class Token(object): |
| pass |
| |
| |
| class Output(Token): |
| def __init__(self, path): |
| self.path = path |
| |
| |
| class Input(Token): |
| def __init__(self, path): |
| self.path = path |
| |
| |
| class OrderInput(Token): |
| def __init__(self, path): |
| self.path = path |
| |
| |
| class Colon(Token): |
| pass |
| |
| |
| class Append(Token): |
| pass |
| |
| |
| class Variable(Token): |
| def __init__(self, name): |
| self.name = name |
| |
| |
| class Word(Token): |
| def __init__(self, name): |
| self.name = name |
| |
| |
| def validate_depfile(depfile, expect_input=None): |
| '''Validate a depfile contains some information |
| |
| Returns `False` if the information is not found. |
| ''' |
| with open(depfile, 'r') as fin: |
| depfile_content = fin.read() |
| |
| real_lines = [] |
| join_line = False |
| for line in depfile_content.split('\n'): |
| # Join the line if needed. |
| if join_line: |
| line = real_lines.pop() + line |
| |
| # Detect line continuations. |
| join_line = line.endswith('\\') |
| # Strip line continuation characters. |
| if join_line: |
| line = line[:-1] |
| |
| # Add to the real line set. |
| real_lines.append(line) |
| |
| # Perform tokenization. |
| tokenized_lines = [] |
| for line in real_lines: |
| tokenized = [] |
| join_word = False |
| for word in line.split(' '): |
| if join_word: |
| word = tokenized.pop() + ' ' + word |
| |
| # Detect word joins. |
| join_word = word.endswith('\\') |
| # Strip escape character. |
| if join_word: |
| word = word[:-1] |
| |
| # Detect `:` at the end of a word. |
| if word.endswith(':'): |
| tokenized.append(word[:-1]) |
| word = word[-1] |
| # Detect `:` at the end of a word. |
| if word.endswith(':|'): |
| tokenized.append(word[:-2]) |
| word = word[-2] |
| |
| # Add word to the tokenized set. |
| tokenized.append(word) |
| |
| tokenized_lines.append(tokenized) |
| |
| # Parse. |
| ast = [] |
| for line in tokenized_lines: |
| kind = None |
| for token in line: |
| if token == ':': |
| kind = 'dependency' |
| elif token == '+=': |
| kind = 'append' |
| elif token == ':|': |
| kind = 'order-only' |
| if line == ['']: |
| kind = 'empty' |
| |
| if kind is None: |
| _report_error('unknown line kind: %s' % line) |
| |
| line_parse = [] |
| if kind == 'dependency': |
| after_colon = False |
| for token in line: |
| if token == ':': |
| after_colon = True |
| elif after_colon: |
| line_parse.append(Input(token)) |
| else: |
| line_parse.append(Output(token)) |
| elif kind == 'order-only': |
| after_op = False |
| for token in line: |
| if token == ':|': |
| after_op = True |
| elif after_op: |
| line_parse.append(OrderInput(token)) |
| else: |
| line_parse.append(Output(token)) |
| elif kind == 'append': |
| after_op = False |
| for token in line: |
| if token == '+=': |
| after_op = True |
| elif after_op: |
| line_parse.append(Word(token)) |
| else: |
| line_parse.append(Variable(token)) |
| |
| ast.append(line_parse) |
| |
| for node in ast: |
| for token in node: |
| if expect_input is not None: |
| # If the input is found, clear the expectation. |
| if isinstance(token, Input) and token.path == expect_input: |
| expect_input = None |
| |
| result = True |
| if expect_input: |
| _report_error('missing input: %s' % expect_input) |
| result = False |
| |
| return result |
| |
| |
| if __name__ == '__main__': |
| import sys |
| |
| depfile = None |
| have_expect = False |
| expect_input = None |
| |
| # Parse arguments. |
| args = sys.argv[1:] |
| while args: |
| # Take an argument. |
| arg = args.pop(0) |
| |
| # Flag to change how errors are reported. |
| if arg == '-A' or arg == '--all': |
| ALL_ERRORS = True |
| # Required arguments. |
| elif arg == '-d' or arg == '--depfile': |
| depfile = args.pop(0) |
| elif arg == '-i' or arg == '--expect-input': |
| expect_input = args.pop(0) |
| have_expect = True |
| |
| # Validate that we have the required arguments. |
| if depfile is None: |
| raise RuntimeError('missing "depfile" file') |
| if have_expect is None: |
| raise RuntimeError('missing an "expect" argument') |
| |
| # Do the actual work. |
| try: |
| is_ok = validate_depfile(depfile, expect_input=expect_input) |
| except BaseException as e: |
| _report_error('exception: %s' % e) |
| |
| # Fail if errors are found. |
| if not is_ok: |
| sys.exit(1) |