| # 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", |
| "//third_party/khronos"], |
| "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"}, |
| } |
| |
| |
| _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 |
| |
| # 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): |
| 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 ply and jinja2: |
| third_party_path = os.path.join( |
| input_api.PresubmitLocalPath(), "..", "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 |