1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Python module to find feature names in source code. 5 6These functions are declared in a separate module to allow multiprocessing to 7correctly unpickle the called functions again. 8""" 9 10import glob 11import itertools 12import multiprocessing 13import pathlib 14import re 15 16BASE_FEATURE_PATTERN = br'BASE_FEATURE\((.*?),(.*?),(.*?)\);' 17BASE_FEATURE_RE = re.compile(BASE_FEATURE_PATTERN, 18 flags=re.MULTILINE + re.DOTALL) 19 20# Only search these directories for flags. If your flag is outside these root 21# directories, then add the directory here. 22DIRECTORIES_TO_SEARCH = [ 23 'android_webview', 24 'apps', 25 'ash', 26 'base', 27 'cc', 28 'chrome', 29 'chromecast', 30 'chromeos', 31 'clank', 32 'components', 33 'content', 34 'courgette', 35 'crypto', 36 'dbus', 37 'device', 38 'extensions', 39 'fuchsia_web', 40 'gin', 41 'google_apis', 42 'gpu', 43 'headless', 44 'infra', 45 'internal', 46 'ios', 47 'ipc', 48 'media', 49 'mojo', 50 'native_client', 51 'native_client_sdk', 52 'net', 53 'pdf', 54 'ppapi', 55 'printing', 56 'remoting', 57 'rlz', 58 'sandbox', 59 'services', 60 'skia', 61 'sql', 62 'storage', 63 # third_party/blink handled separately in FindDeclaredFeatures 64 'ui', 65 'url', 66 'v8', 67 'webkit', 68 'weblayer', 69] 70 71 72def _FindFeaturesInFile(filepath): 73 # Work on bytes to avoid utf-8 decode errors outside feature declarations 74 file_contents = pathlib.Path(filepath).read_bytes() 75 matches = BASE_FEATURE_RE.finditer(file_contents) 76 # Remove whitespace and surrounding " from the second argument 77 # which is the feature name. 78 return [m.group(2).strip().strip(b'"').decode('utf-8') for m in matches] 79 80 81def FindDeclaredFeatures(input_api): 82 """Finds all declared feature names in the source code. 83 84 This function will scan all *.cc and *.mm files and look for features 85 defined with the BASE_FEATURE macro. It will extract the feature names. 86 87 Args: 88 input_api: InputApi instance for opening files 89 Returns: 90 Set of defined feature names in the source tree. 91 """ 92 # Features are supposed to be defined in .cc files. 93 # Iterate over the search folders in the root. 94 root = pathlib.Path(input_api.change.RepositoryRoot()) 95 glob_patterns = [ 96 str(p / pathlib.Path('**/*.cc')) for p in root.iterdir() 97 if p.is_dir() and p.name in DIRECTORIES_TO_SEARCH 98 ] 99 100 # blink is the only directory in third_party that should be searched. 101 blink_glob = str(root / pathlib.Path('third_party/blink/**/*.cc')) 102 glob_patterns.append(blink_glob) 103 104 # Additional features for iOS can be found in mm files in the ios directory. 105 mm_glob = str(root / pathlib.Path('ios/**/*.mm')) 106 glob_patterns.append(mm_glob) 107 108 # Create glob iterators that lazily go over the files to search 109 glob_iterators = [ 110 glob.iglob(pattern, recursive=True) for pattern in glob_patterns 111 ] 112 113 # Limit to 4 processes - the disk accesses becomes a bottleneck with just a 114 # few processes, but splitting the searching across multiple CPUs does yield 115 # a benefit of a few seconds. 116 # The exact batch size does not seem to matter much, as long as it is >> 1. 117 pool = multiprocessing.Pool(4) 118 found_features = pool.imap_unordered(_FindFeaturesInFile, 119 itertools.chain(*glob_iterators), 1000) 120 pool.close() 121 pool.join() 122 123 feature_names = set() 124 for feature_list in found_features: 125 feature_names.update(feature_list) 126 return feature_names 127