| #!/usr/bin/env python |
| # |
| # Copyright 2020 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Helps generate enums.xml from ProductionSupportedFlagList. |
| |
| This is only a best-effort attempt to generate enums.xml values for the |
| LoginCustomFlags enum. You need to verify this script picks the right string |
| value for the new features and double check the hash value by running |
| "AboutFlagsHistogramTest.*". |
| """ |
| |
| from __future__ import print_function |
| |
| import argparse |
| import os |
| import re |
| import hashlib |
| import ctypes |
| import xml.etree.ElementTree as ET |
| import logging |
| import sys |
| |
| _CHROMIUM_SRC = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) |
| sys.path.append(os.path.join(_CHROMIUM_SRC, 'third_party', 'catapult', 'devil')) |
| from devil.utils import logging_common # pylint: disable=wrong-import-position |
| |
| _FLAG_LIST_FILE = os.path.join(_CHROMIUM_SRC, 'android_webview', 'java', 'src', |
| 'org', 'chromium', 'android_webview', 'common', |
| 'ProductionSupportedFlagList.java') |
| _ENUMS_XML_FILE = os.path.join(_CHROMIUM_SRC, 'tools', 'metrics', 'histograms', |
| 'enums.xml') |
| |
| # This script tries to guess the commandline switch/base::Feature name from the |
| # generated Java constant (assuming the constant name follows typical |
| # conventions), but sometimes the script generates the incorrect name. |
| # In this case, you can teach the |
| # script the right name is by editing this dictionary. The perk of editing |
| # here instead of fixing enums.xml by hand is this script *should* generate the |
| # correct hash value once you add the right name, so you can just rerun the |
| # script to get the correct set of enum entries. |
| # |
| # Keys are the names autogenerated by this script's logic, values are the |
| # base::Feature/switch string names as they would appear in Java/C++ code. |
| KNOWN_MISTAKES = { |
| # 'AutogeneratedName': 'CorrectName', |
| 'WebViewAccelerateSmallCanvases': 'WebviewAccelerateSmallCanvases', |
| 'EnableSharedImageForWebView': 'EnableSharedImageForWebview', |
| } |
| |
| |
| def GetSwitchId(label): |
| """Generate a hash consistent with flags_ui::GetSwitchUMAId().""" |
| digest = hashlib.md5(label).hexdigest() |
| first_eight_bytes = digest[:16] |
| long_value = int(first_eight_bytes, 16) |
| signed_32bit = ctypes.c_int(long_value).value |
| return signed_32bit |
| |
| |
| def _Capitalize(value): |
| value = value[0].upper() + value[1:].lower() |
| if value == 'Webview': |
| value = 'WebView' |
| return value |
| |
| |
| def FormatName(name, convert_to_pascal_case): |
| """Converts name to the correct format. |
| |
| If name is shouty-case (ex. 'SOME_NAME') like a Java constant, then: |
| * it converts to pascal case (camel case, with the first letter capitalized) |
| if convert_to_pascal_case == True (ex. 'SomeName') |
| * it converts to hyphenates name and converts to lower case (ex. |
| 'some-name') |
| raises |
| ValueError if name contains quotation marks like a Java literal (ex. |
| '"SomeName"') |
| """ |
| has_quotes_re = re.compile(r'".*"') |
| if has_quotes_re.match(name): |
| raise ValueError('String literals are not supported (got {})'.format(name)) |
| name = re.sub(r'^[^.]+\.', '', name) |
| sections = name.split('_') |
| |
| if convert_to_pascal_case: |
| sections = [_Capitalize(section) for section in sections] |
| return ''.join(sections) |
| |
| sections = [section.lower() for section in sections] |
| return '-'.join(sections) |
| |
| |
| def ConvertNameIfNecessary(name): |
| """Fixes any names which are known to be autogenerated incorrectly.""" |
| if name in KNOWN_MISTAKES.keys(): |
| return KNOWN_MISTAKES.get(name) |
| return name |
| |
| |
| class Flag(object): |
| """Simplified python equivalent of the Flag java class. |
| |
| See //android_webview/java/src/org/chromium/android_webview/common/Flag.java |
| """ |
| |
| def __init__(self, name, is_base_feature): |
| self.name = name |
| self.is_base_feature = is_base_feature |
| |
| |
| class EnumValue(object): |
| def __init__(self, label): |
| self.label = label |
| self.value = GetSwitchId(label) |
| |
| def ToXml(self): |
| return '<int value="{value}" label="{label}"/>'.format(value=self.value, |
| label=self.label) |
| |
| |
| def _GetExistingFlagLabels(): |
| with open(_ENUMS_XML_FILE) as f: |
| root = ET.fromstring(f.read()) |
| all_enums = root.find('enums') |
| login_custom_flags = all_enums.find('enum[@name="LoginCustomFlags"]') |
| return [item.get('label') for item in login_custom_flags] |
| |
| |
| def _RemoveDuplicates(enums, existing_labels): |
| return [enum for enum in enums if enum.label not in existing_labels] |
| |
| |
| def ExtractFlagsFromJavaLines(lines): |
| flags = [] |
| |
| hanging_name_re = re.compile( |
| r'(?:\s*Flag\.(?:baseFeature|commandLine)\()?(\S+),') |
| pending_feature = False |
| pending_commandline = False |
| |
| for line in lines: |
| if 'baseFeature(' in line: |
| pending_feature = True |
| if 'commandLine(' in line: |
| pending_commandline = True |
| |
| if pending_feature and pending_commandline: |
| raise RuntimeError('Somehow this is both a baseFeature and commandLine ' |
| 'switch: ({})'.format(line)) |
| |
| # This means we saw Flag.baseFeature() or Flag.commandLine() on this or a |
| # previous line but haven't found that flag's name yet. Check if we can |
| # find a name in this line. |
| if pending_feature or pending_commandline: |
| m = hanging_name_re.search(line) |
| if m: |
| name = m.group(1) |
| try: |
| formatted_name = FormatName(name, pending_feature) |
| formatted_name = ConvertNameIfNecessary(formatted_name) |
| flags.append(Flag(formatted_name, pending_feature)) |
| pending_feature = False |
| pending_commandline = False |
| except ValueError: |
| logging.warning('String literals are not supported, skipping %s', |
| name) |
| return flags |
| |
| |
| def _GetMissingWebViewEnums(): |
| with open(_FLAG_LIST_FILE, 'r') as f: |
| lines = f.readlines() |
| flags = ExtractFlagsFromJavaLines(lines) |
| |
| enums = [] |
| for flag in flags: |
| if flag.is_base_feature: |
| enums.append(EnumValue(flag.name + ':enabled')) |
| enums.append(EnumValue(flag.name + ':disabled')) |
| else: |
| enums.append(EnumValue(flag.name)) |
| |
| existing_labels = set(_GetExistingFlagLabels()) |
| enums_to_add = _RemoveDuplicates(enums, existing_labels) |
| return enums_to_add |
| |
| |
| def CheckMissingWebViewEnums(input_api, output_api): |
| """A presubmit check to find missing flag enums.""" |
| sources = input_api.AffectedSourceFiles( |
| lambda affected_file: input_api.FilterSourceFile( |
| affected_file, |
| files_to_check=(r'.*\bProductionSupportedFlagList\.java$', ))) |
| if not sources: |
| return [] |
| |
| enums_to_add = _GetMissingWebViewEnums() |
| if not enums_to_add: |
| return [] |
| |
| script_path = '//android_webview/tools/PRESUBMIT.py' |
| enums_path = '//tools/metrics/histograms/enums.xml' |
| xml_strs = sorted([' ' + enum.ToXml() for enum in enums_to_add]) |
| |
| return [ |
| output_api.PresubmitPromptWarning(""" |
| It looks like new flags have been added to ProductionSupportedFlagList but the |
| labels still need to be added to LoginCustomFlags enum in {enums_path}. |
| If you believe this |
| warning is correct, please update enums.xml by pasting the following lines under |
| LoginCustomFlags and running `git-cl format` to correctly sort the changes: |
| |
| {xml_strs} |
| |
| You can run this check again by running the {script_path} tool. |
| |
| If you believe this warning is a false positive, you can silence this warning by |
| updating KNOWN_MISTAKES in {script_path}. |
| """.format(xml_strs='\n'.join(xml_strs), |
| enums_path=enums_path, |
| script_path=script_path)) |
| ] |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| |
| logging_common.AddLoggingArguments(parser) |
| args = parser.parse_args() |
| logging_common.InitializeLogging(args) |
| |
| enums_to_add = _GetMissingWebViewEnums() |
| |
| xml_strs = sorted([' ' + enum.ToXml() for enum in enums_to_add]) |
| if not xml_strs: |
| print('enums.xml is already up-to-date!') |
| return |
| |
| message = """\ |
| This is a best-effort attempt to generate missing enums.xml entries. Please |
| double-check this picked the correct labels for your new features (labels are |
| case-sensitive!), add these to enums.xml, run `git-cl format`, and then follow |
| these steps as a final check: |
| |
| https://chromium.googlesource.com/chromium/src/+/master/tools/metrics/histograms/README.md#flag-histograms |
| |
| If any labels were generated incorrectly, please edit this script and change |
| KNOWN_MISTAKES. |
| """ |
| print(message) |
| |
| for xml_str in xml_strs: |
| print(xml_str) |
| |
| |
| if __name__ == '__main__': |
| main() |