| # Copyright 2014 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. | 
 |  | 
 | """Presubmit script for mojo | 
 |  | 
 | See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts | 
 | for more details about the presubmit API built into depot_tools. | 
 | """ | 
 |  | 
 | import os.path | 
 | import re | 
 |  | 
 | # NOTE: The EDK allows all external paths, so doesn't need a whitelist. | 
 | _PACKAGE_WHITELISTED_EXTERNAL_PATHS = { | 
 |     "SDK": ["//build/module_args/mojo.gni", | 
 |             "//build/module_args/dart.gni", | 
 |             "//testing/gtest", | 
 |             "//third_party/cython"], | 
 |     "services": ["//build/module_args/mojo.gni", | 
 |                  "//testing/gtest"], | 
 | } | 
 |  | 
 | # These files are not part of the exported package. | 
 | _PACKAGE_IGNORED_BUILD_FILES = { | 
 |     "SDK": {}, | 
 |     "EDK": {}, | 
 |     "services": {"mojo/services/BUILD.gn", "mojo/services/mojo_services.gni"}, | 
 | } | 
 |  | 
 |  | 
 | _PACKAGE_PATH_PREFIXES = {"SDK": "mojo/public/", | 
 |                           "EDK": "mojo/edk/", | 
 |                           "services": "mojo/services"} | 
 |  | 
 | # TODO(etiennej): python_binary_source_set added due to crbug.com/443147 | 
 | _PACKAGE_SOURCE_SET_TYPES = {"SDK": ["mojo_sdk_source_set", | 
 |                                      "python_binary_source_set"], | 
 |                              "EDK": ["mojo_edk_source_set"], | 
 |                              "services": ["mojo_sdk_source_set"]} | 
 |  | 
 | _ILLEGAL_EXTERNAL_PATH_WARNING_MESSAGE = \ | 
 |     "Found disallowed external paths within SDK buildfiles." | 
 |  | 
 | _ILLEGAL_SERVICES_ABSOLUTE_PATH_WARNING_MESSAGE = \ | 
 |     "Found references to services' public buildfiles via absolute paths " \ | 
 |     "within services' public buildfiles." | 
 |  | 
 | _ILLEGAL_EDK_ABSOLUTE_PATH_WARNING_MESSAGE = \ | 
 |     "Found references to the EDK via absolute paths within EDK buildfiles." | 
 |  | 
 | _ILLEGAL_SDK_ABSOLUTE_PATH_WARNING_MESSAGE_TEMPLATE = \ | 
 |     "Found references to the SDK via absolute paths within %s buildfiles." | 
 |  | 
 | _ILLEGAL_SDK_ABSOLUTE_PATH_WARNING_MESSAGES = { | 
 |   "SDK": _ILLEGAL_SDK_ABSOLUTE_PATH_WARNING_MESSAGE_TEMPLATE % "SDK", | 
 |   "EDK": _ILLEGAL_SDK_ABSOLUTE_PATH_WARNING_MESSAGE_TEMPLATE % "EDK", | 
 |   "services": _ILLEGAL_SDK_ABSOLUTE_PATH_WARNING_MESSAGE_TEMPLATE | 
 |       % "services' public", | 
 | } | 
 |  | 
 | _INCORRECT_SOURCE_SET_TYPE_WARNING_MESSAGE_TEMPLATE = \ | 
 |     "All source sets in %s must be constructed via %s." | 
 |  | 
 | _INCORRECT_SOURCE_SET_TYPE_WARNING_MESSAGES = { | 
 |   "SDK": _INCORRECT_SOURCE_SET_TYPE_WARNING_MESSAGE_TEMPLATE | 
 |       % ("the SDK", _PACKAGE_SOURCE_SET_TYPES["SDK"]), | 
 |   "EDK": _INCORRECT_SOURCE_SET_TYPE_WARNING_MESSAGE_TEMPLATE | 
 |       % ("the EDK", _PACKAGE_SOURCE_SET_TYPES["EDK"]), | 
 |   "services": _INCORRECT_SOURCE_SET_TYPE_WARNING_MESSAGE_TEMPLATE | 
 |       % ("services' client libs", _PACKAGE_SOURCE_SET_TYPES["services"]), | 
 | } | 
 |  | 
 | def _IsBuildFileWithinPackage(f, package): | 
 |   """Returns whether |f| specifies a GN build file within |package|.""" | 
 |   assert package in _PACKAGE_PATH_PREFIXES | 
 |   package_path_prefix = _PACKAGE_PATH_PREFIXES[package] | 
 |  | 
 |   if not f.LocalPath().startswith(package_path_prefix): | 
 |     return False | 
 |   if (not f.LocalPath().endswith("/BUILD.gn") and | 
 |       not f.LocalPath().endswith(".gni")): | 
 |     return False | 
 |   if f.LocalPath() in _PACKAGE_IGNORED_BUILD_FILES[package]: | 
 |     return False | 
 |   return True | 
 |  | 
 | def _AffectedBuildFilesWithinPackage(input_api, package): | 
 |   """Returns all the affected build files within |package|.""" | 
 |   return [f for f in input_api.AffectedFiles() | 
 |       if _IsBuildFileWithinPackage(f, package)] | 
 |  | 
 | def _FindIllegalAbsolutePathsInBuildFiles(input_api, package): | 
 |   """Finds illegal absolute paths within the build files in | 
 |   |input_api.AffectedFiles()| that are within |package|. | 
 |   An illegal absolute path within the SDK or a service's SDK is one that is to | 
 |   the SDK itself or a non-whitelisted external path. An illegal absolute path | 
 |   within the EDK is one that is to the SDK or the EDK. | 
 |   Returns any such references in a list of (file_path, line_number, | 
 |   referenced_path) tuples.""" | 
 |   illegal_references = [] | 
 |   for f in _AffectedBuildFilesWithinPackage(input_api, package): | 
 |     for line_num, line in f.ChangedContents(): | 
 |       # Determine if this is a reference to an absolute path. | 
 |       m = re.search(r'"(//[^"]*)"', line) | 
 |       if not m: | 
 |         continue | 
 |       referenced_path = m.group(1) | 
 |  | 
 |       if not referenced_path.startswith("//mojo"): | 
 |         # In the EDK, all external absolute paths are allowed. | 
 |         if package == "EDK": | 
 |           continue | 
 |  | 
 |         dart_reference_allowed = (f.LocalPath() == "mojo/public/mojo_sdk.gni" or | 
 |             os.path.dirname(f.LocalPath()) == "mojo/public/platform/dart") | 
 |  | 
 |         if referenced_path.startswith("//dart") and dart_reference_allowed: | 
 |           continue | 
 |  | 
 |         # Determine if this is a whitelisted external path. | 
 |         if referenced_path in _PACKAGE_WHITELISTED_EXTERNAL_PATHS[package]: | 
 |           continue | 
 |  | 
 |       illegal_references.append((f.LocalPath(), line_num, referenced_path)) | 
 |  | 
 |   return illegal_references | 
 |  | 
 | def _PathReferenceInBuildFileWarningItem(build_file, line_num, referenced_path): | 
 |   """Returns a string expressing a warning item that |referenced_path| is | 
 |   referenced at |line_num| in |build_file|.""" | 
 |   return "%s, line %d (%s)" % (build_file, line_num, referenced_path) | 
 |  | 
 | def _IncorrectSourceSetTypeWarningItem(build_file, line_num): | 
 |   """Returns a string expressing that the error occurs at |line_num| in | 
 |   |build_file|.""" | 
 |   return "%s, line %d" % (build_file, line_num) | 
 |  | 
 | def _CheckNoIllegalAbsolutePathsInBuildFiles(input_api, output_api, package): | 
 |   """Makes sure that the BUILD.gn files within |package| do not reference the | 
 |   SDK/EDK via absolute paths, and do not reference disallowed external | 
 |   dependencies.""" | 
 |   sdk_references = [] | 
 |   edk_references = [] | 
 |   external_deps_references = [] | 
 |   services_references = [] | 
 |  | 
 |   # Categorize any illegal references. | 
 |   illegal_references = _FindIllegalAbsolutePathsInBuildFiles(input_api, package) | 
 |   for build_file, line_num, referenced_path in illegal_references: | 
 |     reference_string = _PathReferenceInBuildFileWarningItem(build_file, | 
 |                                                             line_num, | 
 |                                                             referenced_path) | 
 |     if referenced_path.startswith("//mojo/public"): | 
 |       sdk_references.append(reference_string) | 
 |     elif package == "SDK": | 
 |       external_deps_references.append(reference_string) | 
 |     elif package == "services": | 
 |       if referenced_path.startswith("//mojo/services"): | 
 |         services_references.append(reference_string) | 
 |       else: | 
 |         external_deps_references.append(reference_string) | 
 |     elif referenced_path.startswith("//mojo/edk"): | 
 |       edk_references.append(reference_string) | 
 |  | 
 |   # Package up categorized illegal references into results. | 
 |   results = [] | 
 |   if sdk_references: | 
 |     results.extend([output_api.PresubmitError( | 
 |         _ILLEGAL_SDK_ABSOLUTE_PATH_WARNING_MESSAGES[package], | 
 |         items=sdk_references)]) | 
 |  | 
 |   if external_deps_references: | 
 |     assert package == "SDK" or package == "services" | 
 |     results.extend([output_api.PresubmitError( | 
 |         _ILLEGAL_EXTERNAL_PATH_WARNING_MESSAGE, | 
 |         items=external_deps_references)]) | 
 |  | 
 |   if services_references: | 
 |     assert package == "services" | 
 |     results.extend([output_api.PresubmitError( | 
 |         _ILLEGAL_SERVICES_ABSOLUTE_PATH_WARNING_MESSAGE, | 
 |         items=services_references)]) | 
 |  | 
 |   if edk_references: | 
 |     assert package == "EDK" | 
 |     results.extend([output_api.PresubmitError( | 
 |         _ILLEGAL_EDK_ABSOLUTE_PATH_WARNING_MESSAGE, | 
 |         items=edk_references)]) | 
 |  | 
 |   return results | 
 |  | 
 | def _CheckSourceSetsAreOfCorrectType(input_api, output_api, package): | 
 |   """Makes sure that the BUILD.gn files always use the correct wrapper type for | 
 |   |package|, which can be one of ["SDK", "EDK"], to construct source_set | 
 |   targets.""" | 
 |   assert package in _PACKAGE_SOURCE_SET_TYPES | 
 |   required_source_set_type = _PACKAGE_SOURCE_SET_TYPES[package] | 
 |  | 
 |   problems = [] | 
 |   for f in _AffectedBuildFilesWithinPackage(input_api, package): | 
 |     if f.LocalPath() == "mojo/public/tools/bindings/mojom.gni": | 
 |       continue | 
 |     for line_num, line in f.ChangedContents(): | 
 |       m = re.search(r"[a-z_]*source_set\(", line) | 
 |       if not m: | 
 |         continue | 
 |       source_set_type = m.group(0)[:-1] | 
 |       if source_set_type in required_source_set_type: | 
 |         continue | 
 |       problems.append(_IncorrectSourceSetTypeWarningItem(f.LocalPath(), | 
 |                                                          line_num)) | 
 |  | 
 |   if not problems: | 
 |     return [] | 
 |   return [output_api.PresubmitError( | 
 |       _INCORRECT_SOURCE_SET_TYPE_WARNING_MESSAGES[package], | 
 |       items=problems)] | 
 |  | 
 | def _CheckChangePylintsClean(input_api, output_api): | 
 |   # Additional python module paths (we're in src/mojo/); not everyone needs | 
 |   # them, but it's easiest to add them to everyone's path. | 
 |   # For jinja2 (and markupsafe): | 
 |   third_party_path = os.path.join( | 
 |       input_api.PresubmitLocalPath(), "public", "third_party") | 
 |   # For the bindings generator: | 
 |   mojo_public_bindings_pylib_path = os.path.join( | 
 |       input_api.PresubmitLocalPath(), "public", "tools", "bindings", "pylib") | 
 |   # For the python bindings: | 
 |   mojo_python_bindings_path = os.path.join( | 
 |       input_api.PresubmitLocalPath(), "public", "python") | 
 |   # For the python bindings tests: | 
 |   mojo_python_bindings_tests_path = os.path.join( | 
 |       input_api.PresubmitLocalPath(), "python", "tests") | 
 |   # For the roll tools scripts: | 
 |   mojo_roll_tools_path = os.path.join( | 
 |       input_api.PresubmitLocalPath(), "tools", "roll") | 
 |   # For all mojo/tools scripts: | 
 |   mopy_path = os.path.join(input_api.PresubmitLocalPath(), "tools") | 
 |   # For all mojo/devtools scripts: | 
 |   devtools_path = os.path.join(input_api.PresubmitLocalPath(), "devtools") | 
 |   # TODO(vtl): Don't lint these files until the (many) problems are fixed | 
 |   # (possibly by deleting/rewriting some files). | 
 |   temporary_black_list = ( | 
 |       r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]pylib[\\\/]mojom[\\\/]" | 
 |           r"generate[\\\/].+\.py$", | 
 |       r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]generators[\\\/].+\.py$") | 
 |   black_list = input_api.DEFAULT_BLACK_LIST + temporary_black_list + ( | 
 |       # Imported from Android tools, we might want not to fix the warnings | 
 |       # raised for it to make it easier to compare the code with the original. | 
 |       r".*\bdevtools[\\\/]common[\\\/]android_stack_parser[\\\/].+\.py$",) | 
 |  | 
 |   results = [] | 
 |   pylint_extra_paths = [ | 
 |       third_party_path, | 
 |       mojo_public_bindings_pylib_path, | 
 |       mojo_python_bindings_path, | 
 |       mojo_python_bindings_tests_path, | 
 |       mojo_roll_tools_path, | 
 |       mopy_path, | 
 |       devtools_path | 
 |   ] | 
 |   results.extend(input_api.canned_checks.RunPylint( | 
 |       input_api, output_api, extra_paths_list=pylint_extra_paths, | 
 |       black_list=black_list)) | 
 |   return results | 
 |  | 
 | def _BuildFileChecks(input_api, output_api): | 
 |   """Performs checks on SDK, EDK, and services' public buildfiles.""" | 
 |   results = [] | 
 |   for package in ["SDK", "EDK", "services"]: | 
 |     results.extend(_CheckNoIllegalAbsolutePathsInBuildFiles(input_api, | 
 |                                                             output_api, | 
 |                                                             package)) | 
 |     results.extend(_CheckSourceSetsAreOfCorrectType(input_api, | 
 |                                                     output_api, | 
 |                                                     package)) | 
 |   return results | 
 |  | 
 | def _CommonChecks(input_api, output_api): | 
 |   """Checks common to both upload and commit.""" | 
 |   results = [] | 
 |   results.extend(_BuildFileChecks(input_api, output_api)) | 
 |   return results | 
 |  | 
 | def CheckChangeOnUpload(input_api, output_api): | 
 |   results = [] | 
 |   results.extend(_CommonChecks(input_api, output_api)) | 
 |   results.extend(_CheckChangePylintsClean(input_api, output_api)) | 
 |   return results | 
 |  | 
 | def CheckChangeOnCommit(input_api, output_api): | 
 |   results = [] | 
 |   results.extend(_CommonChecks(input_api, output_api)) | 
 |   return results |