list_flags.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #!/usr/bin/env python3
  2. # Copyright 2019 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Emits a formatted, optionally filtered view of the list of flags.
  6. """
  7. from __future__ import print_function
  8. import argparse
  9. import os
  10. import re
  11. import sys
  12. ROOT_PATH = os.path.join(os.path.dirname(__file__), '..', '..')
  13. PYJSON5_PATH = os.path.join(ROOT_PATH, 'third_party', 'pyjson5', 'src')
  14. DEPOT_TOOLS_PATH = os.path.join(ROOT_PATH, 'third_party', 'depot_tools')
  15. sys.path.append(PYJSON5_PATH)
  16. sys.path.append(DEPOT_TOOLS_PATH)
  17. import json5
  18. import owners
  19. def load_metadata():
  20. flags_path = os.path.join(ROOT_PATH, 'chrome', 'browser',
  21. 'flag-metadata.json')
  22. return json5.load(open(flags_path))
  23. def keep_expired_by(flags, mstone):
  24. """Filter flags to contain only flags that expire by mstone.
  25. Only flags that either never expire or have an expiration milestone <= mstone
  26. are in the returned list.
  27. >>> keep_expired_by([{'expiry_milestone': 3}], 2)
  28. []
  29. >>> keep_expired_by([{'expiry_milestone': 3}], 3)
  30. [{'expiry_milestone': 3}]
  31. >>> keep_expired_by([{'expiry_milestone': -1}], 3)
  32. []
  33. """
  34. return [f for f in flags if -1 != f['expiry_milestone'] <= mstone]
  35. def keep_never_expires(flags):
  36. """Filter flags to contain only flags that never expire.
  37. >>> keep_never_expires([{'expiry_milestone': -1}, {'expiry_milestone': 2}])
  38. [{'expiry_milestone': -1}]
  39. """
  40. return [f for f in flags if f['expiry_milestone'] == -1]
  41. def resolve_owners(flags):
  42. """Resolves sets of owners for every flag in the provided list.
  43. Given a list of flags, for each flag, resolves owners for that flag. Resolving
  44. owners means, for each entry in a flag's owners list:
  45. * Turning owners files references into the transitive set of owners listed in
  46. those files
  47. * Turning bare usernames into @chromium.org email addresses
  48. * Passing any other type of entry through unmodified
  49. """
  50. owners_db = owners.Database(ROOT_PATH, open, os.path)
  51. new_flags = []
  52. for f in flags:
  53. new_flag = f.copy()
  54. new_owners = []
  55. for o in f['owners']:
  56. if o.startswith('//') or '/' in o:
  57. new_owners += owners_db.owners_rooted_at_file(re.sub('//', '', o))
  58. elif '@' not in o:
  59. new_owners.append(o + '@chromium.org')
  60. else:
  61. new_owners.append(o)
  62. new_flag['resolved_owners'] = new_owners
  63. new_flags.append(new_flag)
  64. return new_flags
  65. def find_unused(flags):
  66. FLAG_FILES = [
  67. 'chrome/browser/about_flags.cc',
  68. 'ios/chrome/browser/flags/about_flags.mm',
  69. ]
  70. flag_files_data = [open(f, 'r', encoding='utf-8').read() for f in FLAG_FILES]
  71. unused_flags = []
  72. for flag in flags:
  73. # Search for the name in quotes.
  74. needle = '"%s"' % flag['name']
  75. if not any([needle in data for data in flag_files_data]):
  76. unused_flags.append(flag)
  77. return unused_flags
  78. def print_flags(flags, verbose):
  79. """Prints the supplied list of flags.
  80. In verbose mode, prints name, expiry, and owner list; in non-verbose mode,
  81. prints just the name. Verbose mode is actually tab-separated values, with
  82. commas used as separators within individual fields - this is the format the
  83. rest of the flags automation consumes most readily.
  84. >>> f1 = {'name': 'foo', 'expiry_milestone': 73, 'owners': ['bar', 'baz']}
  85. >>> f1['resolved_owners'] = ['bar@c.org', 'baz@c.org']
  86. >>> f2 = {'name': 'bar', 'expiry_milestone': 74, 'owners': ['//quxx/OWNERS']}
  87. >>> f2['resolved_owners'] = ['quxx@c.org']
  88. >>> print_flags([f1], False)
  89. foo
  90. >>> print_flags([f1], True) # doctest: +NORMALIZE_WHITESPACE
  91. foo 73 bar,baz bar@c.org,baz@c.org
  92. >>> print_flags([f2], False)
  93. bar
  94. >>> print_flags([f2], True) # doctest: +NORMALIZE_WHITESPACE
  95. bar 74 //quxx/OWNERS quxx@c.org
  96. """
  97. for f in flags:
  98. if verbose:
  99. print('%s\t%d\t%s\t%s' % (f['name'], f['expiry_milestone'], ','.join(
  100. f['owners']), ','.join(f['resolved_owners'])))
  101. else:
  102. print(f['name'])
  103. def main():
  104. import doctest
  105. doctest.testmod()
  106. parser = argparse.ArgumentParser(description=__doc__)
  107. group = parser.add_mutually_exclusive_group()
  108. group.add_argument('-n', '--never-expires', action='store_true')
  109. group.add_argument('-e', '--expired-by', type=int)
  110. group.add_argument('-u', '--find-unused', action='store_true')
  111. parser.add_argument('-v', '--verbose', action='store_true')
  112. parser.add_argument('--testonly', action='store_true')
  113. args = parser.parse_args()
  114. if args.testonly:
  115. return
  116. flags = load_metadata()
  117. if args.expired_by:
  118. flags = keep_expired_by(flags, args.expired_by)
  119. if args.never_expires:
  120. flags = keep_never_expires(flags)
  121. if args.find_unused:
  122. flags = find_unused(flags)
  123. flags = resolve_owners(flags)
  124. print_flags(flags, args.verbose)
  125. if __name__ == '__main__':
  126. main()