1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
#!/usr/bin/env vpython3
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Emits a formatted, optionally filtered view of the list of flags.
"""
from __future__ import print_function
import argparse
import os
import re
import sys
import flags_utils
DEPOT_TOOLS_PATH = os.path.join(flags_utils.ROOT_PATH, 'third_party',
'depot_tools')
sys.path.append(DEPOT_TOOLS_PATH)
import owners_client
def keep_never_expires(flags):
"""Filter flags to contain only flags that never expire.
>>> keep_never_expires([{'expiry_milestone': -1}, {'expiry_milestone': 2}])
[{'expiry_milestone': -1}]
"""
return [f for f in flags if f['expiry_milestone'] == -1]
def resolve_owners(flags):
"""Resolves sets of owners for every flag in the provided list.
Given a list of flags, for each flag, resolves owners for that flag. Resolving
owners means, for each entry in a flag's owners list:
* Turning owners files references into the transitive set of owners listed in
those files
* Turning bare usernames into @chromium.org email addresses
* Passing any other type of entry through unmodified
"""
owners_db = owners_client.GetCodeOwnersClient(
host="chromium-review.googlesource.com",
project="chromium/src",
branch="main")
new_flags = []
for f in flags:
new_flag = f.copy()
new_owners = set()
for o in f['owners']:
# Assume any filepath is to an OWNERS file.
if '/' in o:
new_owners.update(set(owners_db.ListBestOwners(re.sub('//', '', o))))
elif '@' not in o:
new_owners.add(o + '@chromium.org')
else:
new_owners.add(o)
new_flag['resolved_owners'] = sorted(new_owners)
new_flags.append(new_flag)
return new_flags
def find_unused(flags):
FLAG_FILES = [
'chrome/browser/about_flags.cc',
'ios/chrome/browser/flags/about_flags.mm',
]
flag_files_data = [open(f, 'r', encoding='utf-8').read() for f in FLAG_FILES]
unused_flags = []
for flag in flags:
# Search for the name in quotes.
needle = '"%s"' % flag['name']
if not any([needle in data for data in flag_files_data]):
unused_flags.append(flag)
return unused_flags
def filter_by_owners(flags, owners):
"""Given a list of owners, returns all flags which have any owner appearing
in the list. The `owners` arg is a list of owners.
Need exact match and need to include @google.com or @chromium.org in the
argument. This is because the owner with ldap only is extended with
@chromium.org automatically via resolve_owners function.
TODO(zhangwenyu): Support filter by ldap.
>>> f1 = {'name': 'f_1', 'owners': ['b@g.com']}
>>> f1['resolved_owners'] = ['b@g.com']
>>> f2 = {'name': 'f_2', 'owners': ['z']}
>>> f2['resolved_owners'] = ['z@c.org']
>>> f3 = {'name': 'f_3', 'owners': ['c@g.com', 'd@g.com']}
>>> f3['resolved_owners'] = ['c@g.com', 'd@g.com']
>>> filter_by_owners([f1, f2, f3], ['b@g.com'])
[{'name': 'f_1', 'owners': ['b@g.com'], 'resolved_owners': ['b@g.com']}]
>>> filter_by_owners([f1, f2, f3], ['z@c.org'])
[{'name': 'f_2', 'owners': ['z'], 'resolved_owners': ['z@c.org']}]
>>> filter_by_owners([f1, f2, f3], ['z']) # Filter by ldap not supported.
[]
>>> filter_by_owners([f1, f2, f3], ['b@g.co']) # Need exact match.
[]
>>> filter_by_owners([f1, f2, f3], ['b@g.com', 'z@c.org'])
[{'name': 'f_1', 'owners': ['b@g.com'], 'resolved_owners': ['b@g.com']}, {'name': 'f_2', 'owners': ['z'], 'resolved_owners': ['z@c.org']}]
>>> filter_by_owners([f1, f2, f3], ['c@g.com', 'd@g.com'])
[{'name': 'f_3', 'owners': ['c@g.com', 'd@g.com'], 'resolved_owners': ['c@g.com', 'd@g.com']}]
"""
# A helper function to check if there is any intersection between flag's
# owners and targeted owners.
def matches_any_owner(flag):
return set(flag['resolved_owners']) & set(owners)
return list(filter(matches_any_owner, flags))
def print_flags(flags, verbose):
"""Prints the supplied list of flags.
In verbose mode, prints name, expiry, and owner list; in non-verbose mode,
prints just the name. Verbose mode is actually tab-separated values, with
commas used as separators within individual fields - this is the format the
rest of the flags automation consumes most readily.
>>> f1 = {'name': 'foo', 'expiry_milestone': 73, 'owners': ['bar', 'baz']}
>>> f1['resolved_owners'] = ['bar@c.org', 'baz@c.org']
>>> f2 = {'name': 'bar', 'expiry_milestone': 74, 'owners': ['//quxx/OWNERS']}
>>> f2['resolved_owners'] = ['quxx@c.org']
>>> print_flags([f1], False)
foo
>>> print_flags([f1], True) # doctest: +NORMALIZE_WHITESPACE
foo 73 bar,baz bar@c.org,baz@c.org
>>> print_flags([f2], False)
bar
>>> print_flags([f2], True) # doctest: +NORMALIZE_WHITESPACE
bar 74 //quxx/OWNERS quxx@c.org
"""
for f in flags:
if verbose:
print('%s\t%d\t%s\t%s' % (f['name'], f['expiry_milestone'], ','.join(
f['owners']), ','.join(f['resolved_owners'])))
else:
print(f['name'])
def main():
import doctest
doctest.testmod()
parser = argparse.ArgumentParser(description=__doc__)
group = parser.add_mutually_exclusive_group()
group.add_argument('-n', '--never-expires', action='store_true')
group.add_argument('-e', '--expired-by', type=int)
group.add_argument('-u', '--find-unused', action='store_true')
# The -o argument could be a single owner or multiple owners joined by ','.
group.add_argument('-o', '--has-owner', type=str)
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('--testonly', action='store_true')
args = parser.parse_args()
if args.testonly:
return
flags = flags_utils.load_metadata()
if args.expired_by:
flags = flags_utils.keep_expired_by(flags, args.expired_by)
if args.never_expires:
flags = keep_never_expires(flags)
if args.find_unused:
flags = find_unused(flags)
flags = resolve_owners(flags)
# Filter by owner after resolving owners completed, so it understands
# owners file.
if args.has_owner:
owners = [o.strip() for o in args.has_owner.split(',')]
flags = filter_by_owners(flags, owners)
print_flags(flags, args.verbose)
if __name__ == '__main__':
main()
|