| Index: tools/checkteamtags/owners_file_tags.py
|
| diff --git a/tools/checkteamtags/owners_file_tags.py b/tools/checkteamtags/owners_file_tags.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fc8d1f57ebc5ecb8a8d315aa2f0819f75e34061f
|
| --- /dev/null
|
| +++ b/tools/checkteamtags/owners_file_tags.py
|
| @@ -0,0 +1,85 @@
|
| +# Copyright (c) 2017 The Chromium 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 os
|
| +import re
|
| +
|
| +from collections import defaultdict
|
| +
|
| +
|
| +def parse(filename):
|
| + """Searches the file for lines that start with `# TEAM:` or `# COMPONENT:`.
|
| +
|
| + Args:
|
| + filename (str): path to the file to parse.
|
| + Returns:
|
| + (team (str), component(str)): The team and component found in the file, the
|
| + last one of each if multiple, None if missing.
|
| + """
|
| + team = None
|
| + component = None
|
| + team_regex = re.compile('\s*#\s*TEAM\s*:\s*(\S+)')
|
| + component_regex = re.compile('\s*#\s*COMPONENT\s*:\s*(\S+)')
|
| + with open(filename) as f:
|
| + for line in f:
|
| + team_matches = team_regex.match(line)
|
| + if team_matches:
|
| + team = team_matches.group(1)
|
| + component_matches = component_regex.match(line)
|
| + if component_matches:
|
| + component = component_matches.group(1)
|
| + return team, component
|
| +
|
| +
|
| +def aggregate_components_from_owners(root):
|
| + """Traverses the given dir and parse OWNERS files for team and component tags.
|
| +
|
| + Args:
|
| + root (str): the path to the src directory.
|
| +
|
| + Returns:
|
| + A pair (data, warnings) where data is a dict of the form
|
| + {'component-to-team': {'Component1': 'team1@chr...', ...},
|
| + 'dir-to-component': {'/path/to/1': 'Component1', ...}}
|
| + and warnings is a list of strings.
|
| + """
|
| + warnings = []
|
| + component_to_team = defaultdict(set)
|
| + dir_to_component = {}
|
| + for dirname, _, files in os.walk(root):
|
| + # Proofing against windows casing oddities.
|
| + owners_file_names = [f for f in files if f.upper() == 'OWNERS']
|
| + if owners_file_names:
|
| + owners_full_path = os.path.join(dirname, owners_file_names[0])
|
| + owners_rel_path = os.path.relpath(owners_full_path, root)
|
| + team, component = parse(owners_full_path)
|
| + if component:
|
| + dir_to_component[os.path.relpath(dirname, root)] = component
|
| + if team:
|
| + component_to_team[component].add(team)
|
| + else:
|
| + warnings.append('%s has no COMPONENT tag' % owners_rel_path)
|
| + mappings = {'component-to-team': component_to_team,
|
| + 'dir-to-component': dir_to_component}
|
| + errors = validate_one_team_per_component(mappings)
|
| + return unwrap(mappings), warnings, errors
|
| +
|
| +
|
| +def validate_one_team_per_component(m):
|
| + """Validates that each component is associated with at most 1 team."""
|
| + errors = []
|
| + # TODO(robertocn): Validate the component names: crbug.com/679540
|
| + component_to_team = m['component-to-team']
|
| + for c in component_to_team:
|
| + if len(component_to_team[c]) > 1:
|
| + errors.append('Component %s has more than one team assigned to it: %s' % (
|
| + c, ', '.join(list(component_to_team[c]))))
|
| + return errors
|
| +
|
| +
|
| +def unwrap(mappings):
|
| + """Remove the set() wrapper around values in component-to-team mapping."""
|
| + for c in mappings['component-to-team']:
|
| + mappings['component-to-team'][c] = mappings['component-to-team'][c].pop()
|
| + return mappings
|
|
|