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 |