|  | #!/usr/bin/env python3.8 | 
|  | # Copyright 2020 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. | 
|  |  | 
|  | """A helper script for CTS dependency verification. | 
|  |  | 
|  | The Compatibility Test Suite (CTS) has strict rules on GN dependencies. A CTS target may only depend | 
|  | on other CTS targets and carefully selected dependencies listed in ALLOWED_CTS_DEPS. | 
|  |  | 
|  | The CTS creates a CTS file for each CTS target at GN gen time. This CTS file is used to indicate | 
|  | that a target is acceptable in CTS. Each CTS target verifies its own dependencies using this script. | 
|  | If a dependency does not have a CTS file, the script will look for that fully qualified dependency | 
|  | in ALLOWED_CTS_DEPS. If it does have a CTS file, verification of that target will be deferred such | 
|  | that the target may verify its own dependencies. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import os | 
|  | import re | 
|  | import sys | 
|  |  | 
|  | CTS_EXTENSION = '.this_is_cts' | 
|  |  | 
|  |  | 
|  | class VerifyCtsDeps: | 
|  | """Helper class to verify GN dependencies in CTS. | 
|  |  | 
|  | Args: | 
|  | root_build_dir (string): An absolute path to the GN's $root_build_dir. | 
|  | cts_file (string): The path to (including) the file to be generated. | 
|  | invoker_label (string): The label of the invoker of cts_element. | 
|  | deps (list(string)): A list of fully qualified GN labels. | 
|  |  | 
|  | Raises: ValueError if any parameter is empty or if root_build_dir does not exist. | 
|  | """ | 
|  |  | 
|  | def __init__(self, root_build_dir, cts_file, invoker_label, deps, allowed_cts_deps, allowed_cts_dirs): | 
|  | if root_build_dir and os.path.exists(root_build_dir): | 
|  | self.root_build_dir = root_build_dir | 
|  | else: | 
|  | raise ValueError('root_build_dir cannot be empty and must exist.') | 
|  |  | 
|  | if cts_file: | 
|  | self.cts_file = cts_file | 
|  | else: | 
|  | raise ValueError('cts_file cannot be empty.') | 
|  |  | 
|  | if invoker_label: | 
|  | self.invoker_label = invoker_label | 
|  | else: | 
|  | raise ValueError('invoker_label cannot be empty.') | 
|  |  | 
|  | if deps: | 
|  | self.deps = deps | 
|  | else: | 
|  | raise ValueError('deps cannot be empty.') | 
|  |  | 
|  | if allowed_cts_deps: | 
|  | self.allowed_cts_deps = allowed_cts_deps | 
|  | else: | 
|  | raise ValueError('allowed_cts_deps cannot be empty') | 
|  |  | 
|  | if allowed_cts_dirs: | 
|  | self.allowed_cts_dirs = allowed_cts_dirs | 
|  | else: | 
|  | raise ValueError('allowed_cts_dirs cannot be empty') | 
|  |  | 
|  | def get_file_path(self, dep): | 
|  | """Returns the path to a CTS file. | 
|  |  | 
|  | Args: | 
|  | dep (string): A GN label. | 
|  |  | 
|  | Returns: | 
|  | A string containing the absolute path to the target's CTS file. | 
|  | """ | 
|  |  | 
|  | # Remove the leading slashes from the label. | 
|  | dep = dep[2:] | 
|  |  | 
|  | # Get the target_name from the label. | 
|  | if ':' in dep: | 
|  | # zircon/public/lib/zxtest:zxtest | 
|  | dep, target_name = dep.split(':') | 
|  | elif '/' in dep: | 
|  | # zircon/public/lib/zxtest | 
|  | _, target_name = dep.rsplit('/', 1) | 
|  | else: | 
|  | # sdk | 
|  | target_name = dep | 
|  |  | 
|  | return self.root_build_dir + '/cts/' + dep + '/' + target_name + CTS_EXTENSION | 
|  |  | 
|  | def verify_deps(self): | 
|  | """Verifies the element's dependencies are allowed in CTS. | 
|  |  | 
|  | CTS has strict dependency rules. All dependencies must be in ALLOWED_CTS_DEPS or be CTS | 
|  | targets themselves. | 
|  |  | 
|  | Returns: | 
|  | A list of labels that are unaccepted in CTS. The list will be empty if all deps are allowed. | 
|  | """ | 
|  | unaccepted_deps = [] | 
|  | for dep in self.deps: | 
|  | dep_found = False | 
|  |  | 
|  | if dep in self.allowed_cts_deps or os.path.exists(self.get_file_path(dep)): | 
|  | dep_found = True | 
|  | else: | 
|  | # Dep isn't in the allow list and a CTS file doesn't exist. Check if | 
|  | # all targets in dep's directory are allowed (//sdk/*). | 
|  | for allowed_dir in self.allowed_cts_dirs: | 
|  | pattern = re.compile(allowed_dir) | 
|  | if pattern.match(dep): | 
|  | dep_found = True | 
|  |  | 
|  | if not dep_found: | 
|  | unaccepted_deps.append(dep) | 
|  |  | 
|  | return unaccepted_deps | 
|  |  | 
|  | def create_cts_dep_file(self): | 
|  | """Create a CTS file containing the verified dependencies. | 
|  |  | 
|  | Should only be run after dependencies are verified using verify_deps. | 
|  | """ | 
|  | target_gen_dir, _ = self.cts_file.rsplit('/', 1) | 
|  | if not os.path.exists(target_gen_dir): | 
|  | os.makedirs(target_gen_dir) | 
|  |  | 
|  | with open(self.cts_file, 'w') as f: | 
|  | for dep in self.deps: | 
|  | f.write('%s\n' % dep) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument( | 
|  | '--root_build_dir', | 
|  | required=True, | 
|  | help='Path to the GN $root_build_dir. The default is //out/default.') | 
|  | parser.add_argument( | 
|  | '--output', | 
|  | required=True, | 
|  | help='The path to (including) the file to be generated.') | 
|  | parser.add_argument( | 
|  | '--invoker_label', | 
|  | required=True, | 
|  | help='The label of the invoker of cts_element.') | 
|  | parser.add_argument( | 
|  | '--deps', | 
|  | nargs='+', | 
|  | required=True, | 
|  | help= | 
|  | 'A list of at least one GN label representing the target\'s dependencies.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--allowed_cts_deps', | 
|  | nargs='+', | 
|  | required=True, | 
|  | help='The list of allowed CTS dependencies in allowed_cts_deps.gni') | 
|  | parser.add_argument( | 
|  | '--allowed_cts_dirs', | 
|  | nargs='+', | 
|  | required=True, | 
|  | help='The list of allowed CTS dependency directories in allowed_cts_deps.gni') | 
|  | args = parser.parse_args() | 
|  | try: | 
|  | cts_element = VerifyCtsDeps(args.root_build_dir, | 
|  | args.output, | 
|  | args.invoker_label, | 
|  | args.deps, | 
|  | args.allowed_cts_deps, | 
|  | args.allowed_cts_dirs) | 
|  | except ValueError as e: | 
|  | print('ValueError: %s' % e) | 
|  | return 1 | 
|  |  | 
|  | unaccepted_deps = cts_element.verify_deps() | 
|  | if not unaccepted_deps: | 
|  | cts_element.create_cts_dep_file() | 
|  | else: | 
|  | print( | 
|  | 'The following dependencies of "%s" are not allowed in CTS: %s' % | 
|  | (args.invoker_label, unaccepted_deps)) | 
|  | return 1 | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |