File: list_flags.py

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (185 lines) | stat: -rwxr-xr-x 6,325 bytes parent folder | download | duplicates (4)
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()