chromium/tools/flags/generate_expired_list.py

#!/usr/bin/env python
# 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.


"""Generates the list of expired flags as a C++ source file.

This program generates a data structure representing the set of flags that
expired before or as of the current Chromium milestone. Specifically, it reads
the flag metadata JSON file and emits a structure mapping flag internal names to
expiration milestones. This data structure is then linked into the built
Chromium, to be used to decide whether to show or hide a given flag in the flags
UI.

This program can be run with no arguments to run its own unit tests.
"""

from __future__ import print_function

import flags_utils
import os
import sys


ROOT_PATH = os.path.join(os.path.dirname(__file__), '..', '..')


def get_chromium_version():
  """Parses the Chromium version out of //chrome/VERSION."""
  with open(os.path.join(ROOT_PATH, 'chrome', 'VERSION'),
            encoding='utf-8') as f:
    for line in f.readlines():
      key, value = line.strip().split('=')
      if key == 'MAJOR':
        return int(value)
  return None


def gen_file_header(prog_name, meta_name):
  """Returns the header for the generated expiry list file.

  The generated header contains at least:
  * A copyright message on the first line
  * A reference to this program (prog_name)
  * A reference to the input metadata file
  >>> 'The Chromium Authors' in gen_file_header('foo', 'bar')
  True
  >>> '/progname' in gen_file_header('/progname', '/dataname')
  True
  >>> '/dataname' in gen_file_header('/progname', '/dataname')
  True
  """
  return """// 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.

// This is a generated file. Do not edit it! It was generated by:
//   {prog_name}
// and it was generated from:
//   {meta_name}

#include "chrome/browser/expired_flags_list.h"

namespace flags {{
const ExpiredFlag kExpiredFlags[] = {{
""".format(prog_name=prog_name, meta_name=meta_name)


def gen_file_footer():
  return """
  {nullptr, 0},
};

}  // namespace flags"""


def gen_file_body(flags, mstone):
  """Generates the body of the flag expiration list.

  Flags appear in the list only if:
  * Their expiration mstone is not -1
  * Either the chrome version is unknown OR
  * Their expiration mstone is <= the chrome version

  >>> flags = [ { 'name': 'foo', 'expiry_milestone': 1 } ]
  >>> flags.append({ 'name': 'bar', 'expiry_milestone': 2 })
  >>> flags.append({ 'name': 'baz', 'expiry_milestone': -1 })
  >>> gen_file_body(flags, 1)
  '  {"foo", 1},'
  >>> gen_file_body(flags, 2)
  '  {"foo", 1},\\n  {"bar", 2},'
  >>> gen_file_body(flags, None)
  '  {"foo", 1},\\n  {"bar", 2},'
  """
  if mstone != None:
    flags = flags_utils.keep_expired_by(flags, mstone)
  output = []
  for f in flags:
    if f['expiry_milestone'] != -1:
      name, expiry = f['name'], f['expiry_milestone']
      output.append('  {"' + name + '", ' + str(expiry) + '},')
  return '\n'.join(output)


def gen_expiry_file(program_name, metadata_name):
  output = gen_file_header(program_name, metadata_name)
  output += gen_file_body(flags_utils.load_metadata(), get_chromium_version())
  output += gen_file_footer()
  return output


def write_if_changed(filename, contents):
  """Write contents into the named file if the file's content is different.

  This avoids updating the mtime if the file's contents haven't changed,
  which helps reduce spurious rebuilds.
  """
  try:
    with open(filename, 'r', encoding='utf-8') as f:
      current = f.read()
  except FileNotFoundError:
    # If the file doesn't exist yet, we'll always need to rewrite the file.
    current = None
  if not current or contents != current:
    with open(filename, 'w', encoding='utf-8') as f:
      f.write(contents)


def main():
  import doctest
  doctest.testmod()

  if len(sys.argv) < 3:
    print('{}: only ran tests'.format(sys.argv[0]))
    return

  output = gen_expiry_file(sys.argv[0], sys.argv[1])
  write_if_changed(sys.argv[2], output)


if __name__ == '__main__':
  main()