blob: b981a2e02f53908facd7c7f5d2bce990d09b3aa8 [file] [log] [blame]
#!/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 json
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.
allowed_cts_deps(list(string)): A list of allowed deps found in //sdk/cts/allowed_cts_deps.gni"
allowed_cts_dirs(list(string)): A list of allowed directories found in //sdk/cts/allowed_cts_dirs.gni"
sdk_manifests(list(string)): A list of absolute paths to SDK manifest files.
Raises: ValueError if any parameter is empty, if root_build_dir does not exist, or if the sdk_manifests do not exist.
"""
def __init__(self, root_build_dir, cts_file, invoker_label, deps,
allowed_cts_deps, allowed_cts_dirs, sdk_manifests):
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')
if sdk_manifests:
for manifest in sdk_manifests:
if not os.path.isfile(manifest):
raise ValueError('manifest %s does not exist' % manifest)
self.sdk_manifests = sdk_manifests
else:
raise ValueError('sdk_manifests 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 (//third_party/dart-pkg/pub/*).
for allowed_dir in self.allowed_cts_dirs:
pattern = re.compile(allowed_dir)
if pattern.match(dep):
dep_found = True
# Check if dep is an SDK target.
if not dep_found:
dep_found = self.verify_dep_in_sdk(dep)
if not dep_found:
unaccepted_deps.append(dep)
return unaccepted_deps
def verify_dep_in_sdk(self, dep):
"""Verifies that a dependency is released in an SDK.
Looks for the dependency in the the list of SDK manifests.
Returns:
A boolean determining whether the dependency is released in an SDK.
"""
sdk_atom_labels = {}
for sdk_manifest in self.sdk_manifests:
try:
with open(sdk_manifest, 'r') as manifest:
data = json.load(manifest)
except json.JSONDecodeError:
continue
for atom in data['atoms']:
# SDK atoms are appended with one of the following and a
# toolchain, so we want to ignore them to match against the
# provided label.
match_label = re.compile("(?:(_sdk)|(_sdk_manifest)|(_sdk_legacy))\(.*\)")
label = re.sub(match_label, '', atom['gn-label'])
sdk_atom_labels[label] = atom['category']
return dep in sdk_atom_labels and sdk_atom_labels[dep] in [ 'partner', 'public' ]
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'
)
parser.add_argument(
'--sdk_manifests',
nargs='+',
required=True,
help='The list of paths to public and partner SDK manifests')
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, args.sdk_manifests)
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())