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