chromium/chrome/PRESUBMIT.py

# Copyright 2011 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Presubmit script for changes affecting chrome/

See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details about the presubmit API built into depot_tools.
"""


import re

INCLUDE_CPP_FILES_ONLY = (
  r'.*\.(cc|h)$',
)

INCLUDE_SOURCE_FILES_ONLY = (
  r'.*\.(c|cc|cpp|h|m|mm)$',
)

EXCLUDE = (
  # Objective C confuses everything.
  r'.*cocoa.*',
  r'.*_mac\.(cc|h)$',
  r'.*_mac_.*',
  # All the messages files do weird multiple include trickery
  r'.*_messages.*\.h$',
  # Autogenerated window resources files are off limits
  r'.*resource.h$',
  # Header trickery
  r'.*-inl\.h$',
  # Has safe printf usage that cpplint complains about
  r'safe_browsing_util\.cc$',
)

def _CheckChangeLintsClean(input_api, output_api):
  """Makes sure that the chrome/ code is cpplint clean."""
  files_to_skip = input_api.DEFAULT_FILES_TO_SKIP + EXCLUDE
  sources = lambda x: input_api.FilterSourceFile(
    x, files_to_check=INCLUDE_CPP_FILES_ONLY, files_to_skip=files_to_skip)
  return input_api.canned_checks.CheckChangeLintsClean(
      input_api, output_api, sources)


def _CheckNoContentUnitTestsInChrome(input_api, output_api):
  """Makes sure that no unit tests from content/ are included in unit_tests."""
  problems = []
  for f in input_api.AffectedFiles():
    if not f.LocalPath().endswith('BUILD.gn'):
      continue

    for line_num, line in f.ChangedContents():
      m = re.search(r"'(.*\/content\/.*unittest.*)'", line)
      if m:
        problems.append(m.group(1))

  if not problems:
    return []
  return [output_api.PresubmitPromptWarning(
      'Unit tests located in content/ should be added to the ' +
      'content_unittests target.',
      items=problems)]


def _CheckNoIsAppleBuildFlagsInChromeFile(input_api, f):
  """Check for IS_APPLE in a given file in chrome/."""
  preprocessor_statement = input_api.re.compile(r'^\s*#')
  apple_buildflag = input_api.re.compile(r'BUILDFLAG\(IS_APPLE\)')
  results = []
  for lnum, line in f.ChangedContents():
    if preprocessor_statement.search(line) and apple_buildflag.search(line):
      results.append('    %s:%d' % (f.LocalPath(), lnum))

  return results


def _CheckNoIsAppleBuildFlagsInChrome(input_api, output_api):
  """Check for IS_APPLE which isn't used in chrome/."""
  apple_buildflags = []
  def SourceFilter(affected_file):
    return input_api.FilterSourceFile(affected_file, INCLUDE_SOURCE_FILES_ONLY,
                                      input_api.DEFAULT_FILES_TO_SKIP)
  for f in input_api.AffectedSourceFiles(SourceFilter):
    apple_buildflags.extend(_CheckNoIsAppleBuildFlagsInChromeFile(input_api, f))

  if not apple_buildflags:
    return []

  return [output_api.PresubmitError(
      'IS_APPLE is not used in chrome/ but found in:\n', apple_buildflags)]


def _CheckNoIsIOSBuildFlagsInChromeFile(input_api, f):
  """Check for IS_IOS in a given file in chrome/."""
  preprocessor_statement = input_api.re.compile(r'^\s*#')
  ios_buildflag = input_api.re.compile(r'BUILDFLAG\(IS_IOS\)')
  results = []
  for lnum, line in f.ChangedContents():
    if preprocessor_statement.search(line) and ios_buildflag.search(line):
      results.append('    %s:%d' % (f.LocalPath(), lnum))

  return results


def _CheckNoIsIOSBuildFlagsInChrome(input_api, output_api):
  """Check for IS_IOS which isn't used in chrome/."""
  ios_buildflags = []
  def SourceFilter(affected_file):
    return input_api.FilterSourceFile(affected_file, INCLUDE_SOURCE_FILES_ONLY,
                                      input_api.DEFAULT_FILES_TO_SKIP)
  for f in input_api.AffectedSourceFiles(SourceFilter):
    ios_buildflags.extend(_CheckNoIsIOSBuildFlagsInChromeFile(input_api, f))

  if not ios_buildflags:
    return []

  return [output_api.PresubmitError(
      'IS_IOS is not used in chrome/ but found in:\n', ios_buildflags)]


def _CheckBreakingInstallerVersionBumpNeeded(input_api, output_api):
  files = []
  breaking_version_installer_updated = False

  def _FilterFile(affected_file):
    return input_api.FilterSourceFile(
        affected_file,
        files_to_check=input_api.DEFAULT_FILES_TO_CHECK + (r'.*\.release',))
  for f in input_api.AffectedSourceFiles(_FilterFile):
    # Normalize the local path to Linux-style path separators so that the path
    # comparisons work on Windows as well.
    local_path = f.LocalPath().replace('\\', '/')
    breaking_version_installer_updated |= (local_path ==
    'chrome/installer/setup/last_breaking_installer_version.cc')
    if (local_path == 'chrome/installer/mini_installer/chrome.release' or
        local_path.startswith('chrome/test/mini_installer')):
      files.append(local_path)

  if files and not breaking_version_installer_updated:
    return [output_api.PresubmitPromptWarning('''
Update chrome/installer/setup/last_breaking_installer_version.cc if the changes
found in the following files might break make downgrades not possible beyond
this browser's version.''', items=files)]

  if not files and breaking_version_installer_updated:
    return [output_api.PresubmitPromptWarning('''
No installer breaking changes detected but
chrome/installer/setup/last_breaking_installer_version.cc was updated. Please
update chrome/installer/PRESUBMIT.py if more files need to be watched for
breaking installer changes.''')]

  return []


def _CommonChecks(input_api, output_api):
  """Checks common to both upload and commit."""
  results = []
  results.extend(_CheckNoContentUnitTestsInChrome(input_api, output_api))
  results.extend(_CheckNoIsAppleBuildFlagsInChrome(input_api, output_api))
  results.extend(_CheckNoIsIOSBuildFlagsInChrome(input_api, output_api))
  results.extend(_CheckBreakingInstallerVersionBumpNeeded(input_api,
                 output_api))
  return results


def CheckChangeOnUpload(input_api, output_api):
  results = []
  results.extend(_CommonChecks(input_api, output_api))
  results.extend(_CheckChangeLintsClean(input_api, output_api))
  return results


def CheckChangeOnCommit(input_api, output_api):
  results = []
  results.extend(_CommonChecks(input_api, output_api))
  return results