PRESUBMIT.py revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Presubmit script for changes affecting extensions.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)for more details about the presubmit API built into gcl.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import fnmatch
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)API_PATH = os.path.join(EXTENSIONS_PATH, 'api')
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEMPLATES_PATH = os.path.join(DOCS_PATH, 'templates')
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)PRIVATE_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'private')
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)PUBLIC_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'public')
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)INTROS_PATH = os.path.join(TEMPLATES_PATH, 'intros')
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ARTICLES_PATH = os.path.join(TEMPLATES_PATH, 'articles')
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)LOCAL_PUBLIC_TEMPLATES_PATH = os.path.join('docs',
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                           'templates',
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                           'public')
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _ReadFile(filename):
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  with open(filename) as f:
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return f.read()
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _ListFilesInPublic():
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  all_files = []
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH):
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    all_files.extend(
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):]
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for filename in files)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return all_files
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _UnixName(name):
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  name = os.path.splitext(name)[0]
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return s2.replace('.', '_').lower()
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _FindMatchingTemplates(template_name, template_path_list):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  matches = []
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  unix_name = _UnixName(template_name)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for template in template_path_list:
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if unix_name == _UnixName(template.split(os.sep)[-1]):
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # The docserver expects clean (extensionless) template URLs, so we strip
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # extensions here when generating the list of matches.
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      matches.append(os.path.splitext(template)[0])
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return matches
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _SanitizeAPIName(name, api_path):
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not api_path.endswith(os.sep):
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    api_path += os.sep
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_')
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if 'experimental' in filename:
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    filename = 'experimental_' + filename.replace('experimental_', '')
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return filename
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _CreateIntegrationTestArgs(affected_files):
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         for name in affected_files) or
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          for name in affected_files)):
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return ['-a']
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  args = []
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for name in affected_files:
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)):
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      args.extend(_FindMatchingTemplates(name.split(os.sep)[-1],
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         _ListFilesInPublic()))
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if fnmatch.fnmatch(name, '%s*' % API_PATH):
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH),
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         _ListFilesInPublic()))
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return args
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _CheckHeadingIDs(input_api):
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ids_re = re.compile('<h[23].*id=.*?>')
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  headings_re = re.compile('<h[23].*?>')
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bad_files = []
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for name in input_api.AbsoluteLocalPaths():
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not os.path.exists(name):
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      continue
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)):
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      contents = input_api.ReadFile(name)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (len(re.findall(headings_re, contents)) !=
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          len(re.findall(ids_re, contents))):
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bad_files.append(name)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return bad_files
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _CheckLinks(input_api, output_api, results):
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  for affected_file in input_api.AffectedFiles():
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    name = affected_file.LocalPath()
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    absolute_path = affected_file.AbsoluteLocalPath()
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not os.path.exists(absolute_path):
1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      continue
1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        fnmatch.fnmatch(name, '%s*' % API_PATH)):
1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      contents = _ReadFile(absolute_path)
1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      args = []
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if input_api.platform == 'win32':
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        args = [input_api.python_executable]
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      args.extend([os.path.join('docs', 'server2', 'link_converter.py'),
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                   '-o',
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                   '-f',
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                   absolute_path])
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      output = input_api.subprocess.check_output(
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          args,
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          cwd=input_api.PresubmitLocalPath(),
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          universal_newlines=True)
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if output != contents:
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        changes = ''
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        for i, (line1, line2) in enumerate(
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            zip(contents.split('\n'), output.split('\n'))):
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          if line1 != line2:
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            changes = ('%s\nLine %d:\n-%s\n+%s\n' %
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                (changes, i + 1, line1, line2))
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if changes:
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          results.append(output_api.PresubmitPromptWarning(
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'File %s may have an old-style <a> link to an API page. Please '
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'run docs/server2/link_converter.py to convert the link[s], or '
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'convert them manually.\n\nSuggested changes are: %s' %
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              (name, changes)))
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _CheckChange(input_api, output_api):
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  results = [
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      output_api.PresubmitError('File %s needs an id for each heading.' % name)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for name in _CheckHeadingIDs(input_api)]
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    integration_test = []
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # From depot_tools/presubmit_canned_checks.py:529
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if input_api.platform == 'win32':
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      integration_test = [input_api.python_executable]
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    integration_test.append(
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.path.join('docs', 'server2', 'integration_test.py'))
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths()))
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    input_api.subprocess.check_call(integration_test,
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                    cwd=input_api.PresubmitLocalPath())
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except input_api.subprocess.CalledProcessError:
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    results.append(output_api.PresubmitError('IntegrationTest failed!'))
14968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
15068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  # TODO(kalman): Re-enable this check, or decide to delete it forever. Now
15168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  # that we have multiple directories it no longer works.
15268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  # See http://crbug.com/297178.
15368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  #_CheckLinks(input_api, output_api, results)
15468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return results
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CheckChangeOnUpload(input_api, output_api):
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  results = []
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  results += _CheckChange(input_api, output_api)
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return results
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CheckChangeOnCommit(input_api, output_api):
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return _CheckChange(input_api, output_api)
165