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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
|
# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""
PRESUBMIT_VERSION = '2.0.0'
from enum import Enum
import os
from pathlib import Path
import sys
import tempfile
from typing import Any, Callable, List, Type
# Cannot be called CheckType because by convention PRESUBMIT will try to call
# anything with a Check prefix as a function.
class HistogramsPresubmitCheckType(Enum):
"""Unique identifiers for the checks in this files.
As this file contains multiple checks, we need to have unique identifiers for
each of them to identify proper result set in the cache. This enum defines
all the unique identifiers for the checks in this file.
"""
BOOLS_ARE_ENUMS = 1
ALL_ALLOWLIST_HISTOGRAMS_PRESENT = 2
FORMATTING_VALIDATION = 3
_CACHE_FILE_PATH = os.path.join(tempfile.gettempdir(),
'histograms_presubmit_cache.json')
def _RunCheckWithCache(check_method: Callable[[Type, Type, Any], List[Any]],
check_id: int, input_api: type, output_api: type,
cache_file_path: str, *args, **kwargs):
"""Runs a check method with caching support.
Args:
check_method: The method that executes actual checks, must accept input_api
and output_api as first two arguments and will get past the rest generic
arguments (args, kwargs).
check_id: Unique identifier for the check used as a key for the cache. The
same type of check must always use the same id.
input_api: The input api type, generally provided by the PRESUBMIT system.
output_api: The output api type, generally provided by the PRESUBMIT system.
cache_file_path: The path of the cache file to be used.
*args: The extra args to pass to the check method (see: check_method).
**kwargs: The extra kwargs to pass to the check method (see: check_method).
"""
# As the path for import is relative to InputApi importing is done within
# the function that already has a reference to the InputApi.
sys.path.append(input_api.PresubmitLocalPath())
import presubmit_caching_support
cache = presubmit_caching_support.PresubmitCache(
cache_file_path, input_api.PresubmitLocalPath())
cached_result = cache.RetrieveResultFromCache(check_id)
if cached_result is not None:
sys.stdout.write(f'Using cached result for {check_id}\n')
return cached_result
new_result = check_method(input_api, output_api, *args, **kwargs)
cache.StoreResultInCache(check_id, new_result)
return new_result
def GetPrettyPrintErrors(input_api, output_api, cwd, rel_path, results):
"""Runs pretty-print command for specified file."""
args = [
input_api.python3_executable,
os.path.join(input_api.PresubmitLocalPath(), 'pretty_print.py'), rel_path,
'--presubmit', '--non-interactive'
]
exit_code = input_api.subprocess.call(args, cwd=cwd)
if exit_code != 0:
error_msg = ('%s is not formatted correctly; run `git cl format` to fix.' %
rel_path)
results.append(output_api.PresubmitError(error_msg))
def GetTokenErrors(input_api, output_api, cwd, rel_path, results):
"""Validates histogram tokens in specified file."""
exit_code = input_api.subprocess.call([
input_api.python3_executable,
os.path.join(input_api.PresubmitLocalPath(), 'validate_token.py'),
rel_path
],
cwd=cwd)
if exit_code != 0:
error_msg = (
'%s contains histogram(s) using <variants> not defined in the file, '
'please run validate_token.py %s to fix.' % (rel_path, rel_path))
results.append(output_api.PresubmitError(error_msg))
def GetValidateHistogramsError(input_api: Type, output_api: Type, cwd: str,
xml_paths_override: List[str],
results: List[Any]):
"""Validates histograms format using validate_format.py tool.
This validates things like:
- Histograms files are valid XMLs.
- Histograms namespaces only span one file
- Tokens used in histograms are registered.
Args:
input_api: An input_api instance that contains information about changes.
output_api: An output_api instance to create results of the PRESUBMIT check.
cwd: Work directory to run the python process in.
xml_paths: A list of paths to the xml files to validate or None to use the
default set of production xml files.
results: The list of output_api objects to append the check warnings to.
"""
validate_format_argv = [
input_api.python3_executable,
os.path.join(input_api.PresubmitLocalPath(), 'validate_format.py'),
]
if xml_paths_override is not None:
validate_format_argv.append('--xml_paths')
validate_format_argv.extend(xml_paths_override)
exit_code = input_api.subprocess.call(validate_format_argv, cwd=cwd)
if exit_code != 0:
error_msg = (
'Histograms are not well-formatted; please run %s/validate_format.py '
'and fix the reported errors.' % cwd)
results.append(output_api.PresubmitError(error_msg))
def _GetValidateHistogramsIndexError(input_api: Type, output_api: Type,
cwd: str, results: List[Any]):
"""Validates if index file is up-to-date with current state of the tree using
validate_histograms_index.py tool.
Args:
input_api: An input_api instance that contains information about changes.
output_api: An output_api instance to create results of the PRESUBMIT check.
cwd: Work directory to run the python process in.
results: The list of output_api objects to append the check warnings to.
"""
exit_code = input_api.subprocess.call([
input_api.python3_executable,
os.path.join(input_api.PresubmitLocalPath(),
'validate_histograms_index.py')
],
cwd=cwd)
if exit_code != 0:
error_msg = ('Histograms index file is not up-to-date. Please run '
'%s/histogram_paths.py to update it' % cwd)
results.append(output_api.PresubmitError(error_msg))
def ValidateSingleFile(input_api, output_api, file_obj, cwd, results,
allow_test_paths):
"""Does corresponding validations if histograms.xml or enums.xml is changed.
Args:
input_api: An input_api instance that contains information about changes.
output_api: An output_api instance to create results of the PRESUBMIT check.
file_obj: A file object of one of the changed files.
cwd: Path to current working directory.
results: The returned variable which is a list of output_api results.
allow_testing_paths: A boolean that determines if the test_data directory
changes should be validated. If it's False, all the files under
`test_data` directory will be ignored. This is needed as the `test_data`
xmls contains intentional errors to trip the presubmit checks and we want
to trip those presubmits in checks, but a the same time due to the nature
of how the input_api gives all changed files in the directory, we don't
want "production" checks to trip over those mistakes.
Returns:
A boolean that True if a histograms.xml or enums.xml file is changed.
"""
p = file_obj.AbsoluteLocalPath()
# Only do PRESUBMIT checks when |p| is under |cwd|.
if input_api.os_path.commonprefix([p, cwd]) != cwd:
return False
filepath = input_api.os_path.relpath(p, cwd)
if not allow_test_paths and 'test_data' in filepath:
return False
# If the changed file is histograms.xml or histogram_suffixes_list.xml,
# pretty-print it.
elif ('histograms.xml' in filepath
or 'histogram_suffixes_list.xml' in filepath):
GetPrettyPrintErrors(input_api, output_api, cwd, filepath, results)
GetTokenErrors(input_api, output_api, cwd, filepath, results)
return True
# If the changed file is enums.xml, pretty-print it.
elif 'enums.xml' in filepath:
GetPrettyPrintErrors(input_api, output_api, cwd, filepath, results)
return True
return False
def CheckHistogramFormatting(input_api,
output_api,
cache_file_path=_CACHE_FILE_PATH,
allow_test_paths=False,
xml_paths_override=None):
"""Checks that histograms.xml is pretty-printed and well-formatted.
This function is a wrapper around
ExecuteCheckHistogramFormatting that adds caching support.
"""
return _RunCheckWithCache(ExecuteCheckHistogramFormatting,
HistogramsPresubmitCheckType.FORMATTING_VALIDATION,
input_api, output_api, cache_file_path,
allow_test_paths, xml_paths_override)
# Note: Execute convention in this file comes from the fact that PRESUBMIT
# will try to call anything with a Check prefix as a function. As we want to
# avoid this and at the same we want to add a caching support, we are using
# Execute prefix for executing the checks on cache miss.
def ExecuteCheckHistogramFormatting(input_api, output_api, allow_test_paths,
xml_paths_override):
"""Checks that histograms.xml is pretty-printed and well-formatted.
This is a method that is called by the PRESUBMIT system and those it
represents a production check rather then a test one. This is why we
set allow_test_paths to False by default.
"""
results = []
cwd = input_api.PresubmitLocalPath()
xml_changed = False
# Only for changed files, do corresponding checks if the file is
# histograms.xml or enums.xml.
for file_obj in input_api.AffectedFiles(include_deletes=False):
is_changed = ValidateSingleFile(input_api, output_api, file_obj, cwd,
results, allow_test_paths)
xml_changed = xml_changed or is_changed
# Run validate_format.py if there were modified xml files.
if xml_changed:
GetValidateHistogramsError(input_api, output_api, cwd, xml_paths_override,
results)
# Always run validate_histograms_index.py the condiditon when we need it is
# relatively complex and given that this is a fast check (<100ms) it's easier
# to just always make that check.
_GetValidateHistogramsIndexError(input_api, output_api, cwd, results)
return results
def CheckWebViewHistogramsAllowlistOnUpload(input_api,
output_api,
cache_file_path=_CACHE_FILE_PATH,
allowlist_path_override=None,
xml_paths_override=None):
"""Checks that HistogramsAllowlist.java contains valid histograms.
This function is a wrapper around
ExecuteCheckWebViewHistogramsAllowlistOnUpload that adds caching support.
"""
return _RunCheckWithCache(
ExecuteCheckWebViewHistogramsAllowlistOnUpload,
HistogramsPresubmitCheckType.ALL_ALLOWLIST_HISTOGRAMS_PRESENT, input_api,
output_api, cache_file_path, allowlist_path_override, xml_paths_override)
# Note: Execute convention in this file comes from the fact that PRESUBMIT
# will try to call anything with a Check prefix as a function. As we want to
# avoid this and at the same we want to add a caching support, we are using
# Execute prefix for executing the checks on cache miss.
def ExecuteCheckWebViewHistogramsAllowlistOnUpload(input_api, output_api,
allowlist_path_override,
xml_paths_override):
"""Checks that HistogramsAllowlist.java contains valid histograms."""
xml_filter = lambda f: Path(f.LocalPath()).suffix == '.xml'
xml_files = input_api.AffectedFiles(include_deletes=False,
file_filter=xml_filter)
if not xml_files:
return []
sys.path.append(input_api.PresubmitLocalPath())
from histogram_paths import ALL_XMLS
from histograms_allowlist_check import check_histograms_allowlist
from histograms_allowlist_check import WellKnownAllowlistPath
xml_files_paths = ALL_XMLS
if xml_paths_override is not None:
xml_files_paths = xml_paths_override
xml_files = [open(f, encoding='utf-8') for f in xml_files_paths]
src_path = os.path.join(input_api.PresubmitLocalPath(), '..', '..', '..')
allowlist_path = os.path.join(
src_path, WellKnownAllowlistPath.ANDROID_WEBVIEW.relative_path())
if allowlist_path_override is not None:
allowlist_path = allowlist_path_override
result = check_histograms_allowlist(output_api, allowlist_path, xml_files)
for f in xml_files:
f.close()
return result
def CheckBooleansAreEnums(input_api,
output_api,
cache_file_path=_CACHE_FILE_PATH):
"""Checks that histograms that use Booleans do not use units.
This function is a wrapper around ExecuteCheckBooleansAreEnums that adds
caching support.
"""
return _RunCheckWithCache(ExecuteCheckBooleansAreEnums,
HistogramsPresubmitCheckType.BOOLS_ARE_ENUMS,
input_api, output_api, cache_file_path)
# Note: Execute convention in this file comes from the fact that PRESUBMIT
# will try to call anything with a Check prefix as a function. As we want to
# avoid this and at the same we want to add a caching support, we are using
# Execute prefix for executing the checks on cache miss.
def ExecuteCheckBooleansAreEnums(input_api, output_api):
"""Checks that histograms that use Booleans do not use units."""
results = []
cwd = input_api.PresubmitLocalPath()
inclusion_pattern = input_api.re.compile(r'units="[Bb]oolean')
units_warning = """
You are using 'units' for a boolean histogram, but you should be using
'enum' instead."""
# Only for changed files, do corresponding checks if the file is
# histograms.xml or enums.xml.
for affected_file in input_api.AffectedFiles(include_deletes=False):
filepath = input_api.os_path.relpath(affected_file.AbsoluteLocalPath(), cwd)
if 'histograms.xml' in filepath:
for line_number, line in affected_file.ChangedContents():
if inclusion_pattern.search(line):
results.append('%s:%s\n\t%s' % (filepath, line_number, line.strip()))
# If a histograms.xml file was changed, check for units="[Bb]oolean".
if results:
return [output_api.PresubmitPromptOrNotify(units_warning, results)]
return results
|