| #!/usr/bin/env fuchsia-vendored-python |
| # Copyright 2022 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import json |
| import sys |
| |
| |
| # Removes |prefix| from |line|. |
| # A removeprefix() function is not available until 3.9. |
| def removeprefix(line, prefix): |
| if line.startswith(prefix): |
| return line[len(prefix) :] |
| return line |
| |
| |
| # Create new dict/lists containers that are hashable, but first remove any |
| # |prefixes| from nested strings. |
| def make_hashable(item, prefixes): |
| if isinstance(item, dict): |
| item = hashabledict(item, prefixes) |
| elif isinstance(item, list): |
| item = hashablelist(item, prefixes) |
| elif isinstance(item, str): |
| for prefix in prefixes: |
| item = removeprefix(item, prefix) |
| return item |
| |
| |
| # A list that is hashable so it can be added to a set. |
| class hashablelist(list): |
| def __init__(self, list, prefixes): |
| for v in list: |
| self.append(make_hashable(v, prefixes)) |
| |
| def __hash__(self): |
| return hash(tuple(sorted(self))) |
| |
| def __eq__(self, other): |
| return sorted(self) == sorted(other) |
| |
| |
| # A dictionary that is hashable so it can be added to a set. |
| class hashabledict(dict): |
| def __init__(self, dict, prefixes): |
| for k, v in dict.items(): |
| self[k] = make_hashable(v, prefixes) |
| |
| def __hash__(self): |
| return hash(tuple(sorted(self.items()))) |
| |
| |
| # Loads a set of items from a json file and optionally, uses |key| to get the list. |
| # Any |prefixes| will be stripped from strings in each item. |
| def load_set_from_json(log, key, prefixes): |
| items = json.load(log) |
| if key: |
| items = items.get(key, []) |
| return set(make_hashable(i, prefixes) for i in items) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Compare two json lists generated by different assembly invocations" |
| "and assert that they are functionally equivalent" |
| ) |
| parser.add_argument( |
| "--reference", type=argparse.FileType("r"), required=True |
| ) |
| parser.add_argument( |
| "--comparison", type=argparse.FileType("r"), required=True |
| ) |
| parser.add_argument("--stamp", type=argparse.FileType("w"), required=True) |
| parser.add_argument("--strip-prefix", nargs="*") |
| parser.add_argument("--list-key", type=str) |
| args = parser.parse_args() |
| |
| reference = load_set_from_json( |
| args.reference, args.list_key, args.strip_prefix |
| ) |
| comparison = load_set_from_json( |
| args.comparison, args.list_key, args.strip_prefix |
| ) |
| |
| # Checks for missing commands. |
| missing = reference.difference(comparison) |
| extra = comparison.difference(reference) |
| if missing: |
| print("Items in the reference were missing from the comparison") |
| for cmd in missing: |
| print("Missing: " + str(cmd)) |
| if extra: |
| print("Items in the comparison were not found in the reference") |
| for cmd in extra: |
| print("Extra: " + str(cmd)) |
| if missing or extra: |
| return len(missing) + len(extra) |
| |
| # Writes an empty file because GN requires targets to have an output. |
| with args.stamp as stamp: |
| stamp.write("") |
| |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |