chromium/chrome/android/webapk/PRESUBMIT.py

# Copyright 2017 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/android/webapk/shell_apk:webapk

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

This presubmit checks for three rules:
1. If anything in the webapk/libs/common or the webapk/shell_apk directories
has changed (excluding test files), $CURRENT_VERSION_VARIABLE should be updated.
2. If $REQUEST_UPDATE_FOR_VERSION_VARIABLE in
$REQUEST_UPDATE_FOR_VERSION_LOCAL_PATH is changed, the variable change should
be the only change in the CL.
3. If a file in a res/ directory has been added, its' file name should be
unique to the res/ directory.
  res/values/dimens.xml and res/values-v17/dimens.xml -> OK
  res/values/dimens.xml and libs/common/res_splash/values/dimens.xml -> BAD
This requirement is needed to upload the resources to the Google storage
build bucket.
"""


CURRENT_VERSION_VARIABLE = 'current_shell_apk_version'
CURRENT_VERSION_LOCAL_PATH = 'shell_apk/current_version/current_version.gni'

REQUEST_UPDATE_FOR_VERSION_VARIABLE = 'request_update_for_shell_apk_version'
REQUEST_UPDATE_FOR_VERSION_LOCAL_PATH = (
    'shell_apk/request_update_for_version.gni')

TRIGGER_CURRENT_VERSION_UPDATE_LOCAL_PATHS = [
    'libs/common/src/',
    'libs/common/res_splash/',
    'shell_apk/AndroidManifest.xml',
    'shell_apk/res/',
    'shell_apk/res_template',
    'shell_apk/src/',
]

RES_DIR_LOCAL_PATHS = [
    'shell_apk/res',
    'shell_apk/res_template',
    'libs/common/res_splash'
]

def _DoChangedContentsContain(changed_contents, key):
  for _, line in changed_contents:
    if key in line:
      return True
  return False


def _FindFileNamesInDirectory(input_api, dir_path, search_file_names):
  """
  Searches the directory recursively for files with the passed-in file name
  (not file path) set. Returns the file names of any matches.
  """
  matches = []
  for _, _, file_names in input_api.os_walk(dir_path):
    for file_name in file_names:
      if file_name in search_file_names:
        matches.append(file_name)
  return matches


def _CheckVersionVariableChanged(input_api, version_file_local_path,
                                 variable_name):
  for f in input_api.AffectedFiles():
    local_path = input_api.os_path.relpath(
        f.AbsoluteLocalPath(),
        input_api.PresubmitLocalPath()).replace('\\', '/')
    if local_path == version_file_local_path:
      return _DoChangedContentsContain(f.ChangedContents(), variable_name)

  return False


def _CheckChromeUpdateTriggerRule(input_api, output_api):
  """
  Check that if |request_update_for_shell_apk_version| is updated it is the
  only change in the CL.
  """
  if _CheckVersionVariableChanged(input_api,
                                  REQUEST_UPDATE_FOR_VERSION_LOCAL_PATH,
                                  REQUEST_UPDATE_FOR_VERSION_VARIABLE):
    if (len(input_api.AffectedFiles()) != 1 or
        len(input_api.AffectedFiles()[0].ChangedContents()) != 1):
      return [
        output_api.PresubmitError(
            '{} in {} must be updated in a standalone CL.'.format(
                REQUEST_UPDATE_FOR_VERSION_VARIABLE,
                REQUEST_UPDATE_FOR_VERSION_LOCAL_PATH))
        ]
  return []


def _CheckCurrentVersionIncreaseRule(input_api, output_api):
  """
  Check that if a file in $WAM_MINT_TRIGGER_LOCAL_PATHS is updated that
  |template_shell_apk_version| is updated as well.
  """
  files_requiring_version_increase = []
  for f in input_api.AffectedFiles():
    if f.ChangedContents():
      local_path = input_api.os_path.relpath(
          f.AbsoluteLocalPath(),
          input_api.PresubmitLocalPath()).replace('\\', '/')
      for trigger_local_path in TRIGGER_CURRENT_VERSION_UPDATE_LOCAL_PATHS:
        if local_path.startswith(trigger_local_path):
          files_requiring_version_increase.append(local_path)

  if not files_requiring_version_increase:
    return []

  if not _CheckVersionVariableChanged(input_api, CURRENT_VERSION_LOCAL_PATH,
                                      CURRENT_VERSION_VARIABLE):
    return [output_api.PresubmitError(
        '{} in {} needs to updated due to changes in:'.format(
            CURRENT_VERSION_VARIABLE, CURRENT_VERSION_LOCAL_PATH),
        items=files_requiring_version_increase)]

  return []


def _CheckNoOverlappingFileNamesInResourceDirsRule(input_api, output_api):
  """
  Checks that if a file has been added to a res/ directory that its file name
  is unique to the res/ directory.
    res/values/dimens.xml and res/values-v17/dimens.xml -> OK
    res/values/dimens.xml and libs/common/res_splash/values/dimens.xml -> BAD
  """
  res_dir_file_names_map = {}
  for f in input_api.AffectedFiles():
    local_path = input_api.os_path.relpath(
        f.AbsoluteLocalPath(),
        input_api.PresubmitLocalPath()).replace('\\', '/')
    for res_dir_local_path in RES_DIR_LOCAL_PATHS:
      if local_path.startswith(res_dir_local_path):
        file_name = input_api.os_path.basename(local_path)
        res_dir_file_names_map.setdefault(res_dir_local_path, set()).add(
            file_name)
        break

  if len(res_dir_file_names_map) == 0:
    return []

  overlapping_file_names = set()
  for res_dir, file_names in res_dir_file_names_map.items():
    for other_res_dir, other_file_names in res_dir_file_names_map.items():
      if res_dir == other_res_dir:
        continue

      # Check for affected files with identical name in |other_res_dir|.
      overlapping_file_names |= (file_names & other_file_names)

      # Check for existing files with identical name in |other_res_dir|.
      overlapping_file_names.update(
          _FindFileNamesInDirectory(input_api, other_res_dir, file_names))

  if len(overlapping_file_names) > 0:
    error_msg = ('Resources in different top level res/ directories {} should '
                 'have different names:').format(RES_DIR_LOCAL_PATHS)
    return [output_api.PresubmitError(error_msg,
                                      items=list(overlapping_file_names))]
  return []

def _CommonChecks(input_api, output_api):
  """Checks common to both upload and commit."""
  result = []
  result.extend(_CheckChromeUpdateTriggerRule(input_api, output_api))
  result.extend(_CheckCurrentVersionIncreaseRule(input_api, output_api))
  result.extend(_CheckNoOverlappingFileNamesInResourceDirsRule(input_api,
                                                               output_api))

  return result


def CheckChangeOnUpload(input_api, output_api):
  return _CommonChecks(input_api, output_api)


def CheckChangeOnCommit(input_api, output_api):
  return _CommonChecks(input_api, output_api)