Home | History | Annotate | Download | only in extensions
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Presubmit script for changes affecting extensions.
      6 
      7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
      8 for more details about the presubmit API built into gcl.
      9 """
     10 import fnmatch
     11 import os
     12 import re
     13 
     14 EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
     15 DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
     16 SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
     17 API_PATH = os.path.join(EXTENSIONS_PATH, 'api')
     18 TEMPLATES_PATH = os.path.join(DOCS_PATH, 'templates')
     19 PRIVATE_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'private')
     20 PUBLIC_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'public')
     21 INTROS_PATH = os.path.join(TEMPLATES_PATH, 'intros')
     22 ARTICLES_PATH = os.path.join(TEMPLATES_PATH, 'articles')
     23 
     24 LOCAL_PUBLIC_TEMPLATES_PATH = os.path.join('docs',
     25                                            'templates',
     26                                            'public')
     27 
     28 EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS = ('.md', '.html')
     29 
     30 def _ReadFile(filename):
     31   with open(filename) as f:
     32     return f.read()
     33 
     34 def _ListFilesInPublic():
     35   all_files = []
     36   for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH):
     37     all_files.extend(
     38         os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):]
     39         for filename in files)
     40   return all_files
     41 
     42 def _UnixName(name):
     43   name = os.path.splitext(name)[0]
     44   s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
     45   s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
     46   return s2.replace('.', '_').lower()
     47 
     48 def _FindMatchingTemplates(template_name, template_path_list):
     49   matches = []
     50   unix_name = _UnixName(template_name)
     51   for template in template_path_list:
     52     if unix_name == _UnixName(template.split(os.sep)[-1]):
     53       basename, ext = os.path.splitext(template)
     54       # The docserver expects clean (extensionless) template URLs, so we
     55       # strip some extensions here when generating the list of matches.
     56       if ext in EXTENSIONS_TO_REMOVE_FOR_CLEAN_URLS:
     57         matches.append(basename)
     58       else:
     59         matches.append(template)
     60   return matches
     61 
     62 def _SanitizeAPIName(name, api_path):
     63   if not api_path.endswith(os.sep):
     64     api_path += os.sep
     65   filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_')
     66   if 'experimental' in filename:
     67     filename = 'experimental_' + filename.replace('experimental_', '')
     68   return filename
     69 
     70 def _CreateIntegrationTestArgs(affected_files):
     71   if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH)
     72          for name in affected_files) or
     73       any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH)
     74           for name in affected_files)):
     75     return ['-a']
     76   args = []
     77   for name in affected_files:
     78     if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
     79         fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
     80         fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)):
     81       args.extend(_FindMatchingTemplates(name.split(os.sep)[-1],
     82                                          _ListFilesInPublic()))
     83     if fnmatch.fnmatch(name, '%s*' % API_PATH):
     84       args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH),
     85                                          _ListFilesInPublic()))
     86   return args
     87 
     88 def _CheckHeadingIDs(input_api):
     89   ids_re = re.compile('<h[23].*id=.*?>')
     90   headings_re = re.compile('<h[23].*?>')
     91   bad_files = []
     92   for name in input_api.AbsoluteLocalPaths():
     93     if not os.path.exists(name):
     94       continue
     95     if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or
     96         fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)):
     97       contents = input_api.ReadFile(name)
     98       if (len(re.findall(headings_re, contents)) !=
     99           len(re.findall(ids_re, contents))):
    100         bad_files.append(name)
    101   return bad_files
    102 
    103 def _CheckLinks(input_api, output_api, results):
    104   for affected_file in input_api.AffectedFiles():
    105     name = affected_file.LocalPath()
    106     absolute_path = affected_file.AbsoluteLocalPath()
    107     if not os.path.exists(absolute_path):
    108       continue
    109     if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
    110         fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
    111         fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or
    112         fnmatch.fnmatch(name, '%s*' % API_PATH)):
    113       contents = _ReadFile(absolute_path)
    114       args = []
    115       if input_api.platform == 'win32':
    116         args = [input_api.python_executable]
    117       args.extend([os.path.join('docs', 'server2', 'link_converter.py'),
    118                    '-o',
    119                    '-f',
    120                    absolute_path])
    121       output = input_api.subprocess.check_output(
    122           args,
    123           cwd=input_api.PresubmitLocalPath(),
    124           universal_newlines=True)
    125       if output != contents:
    126         changes = ''
    127         for i, (line1, line2) in enumerate(
    128             zip(contents.split('\n'), output.split('\n'))):
    129           if line1 != line2:
    130             changes = ('%s\nLine %d:\n-%s\n+%s\n' %
    131                 (changes, i + 1, line1, line2))
    132         if changes:
    133           results.append(output_api.PresubmitPromptWarning(
    134               'File %s may have an old-style <a> link to an API page. Please '
    135               'run docs/server2/link_converter.py to convert the link[s], or '
    136               'convert them manually.\n\nSuggested changes are: %s' %
    137               (name, changes)))
    138 
    139 def _CheckChange(input_api, output_api):
    140   results = [
    141       output_api.PresubmitError('File %s needs an id for each heading.' % name)
    142       for name in _CheckHeadingIDs(input_api)]
    143   try:
    144     integration_test = []
    145     # From depot_tools/presubmit_canned_checks.py:529
    146     if input_api.platform == 'win32':
    147       integration_test = [input_api.python_executable]
    148     integration_test.append(
    149         os.path.join('docs', 'server2', 'integration_test.py'))
    150     integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths()))
    151     input_api.subprocess.check_call(integration_test,
    152                                     cwd=input_api.PresubmitLocalPath())
    153   except input_api.subprocess.CalledProcessError:
    154     results.append(output_api.PresubmitError('IntegrationTest failed!'))
    155 
    156   # TODO(kalman): Re-enable this check, or decide to delete it forever. Now
    157   # that we have multiple directories it no longer works.
    158   # See http://crbug.com/297178.
    159   #_CheckLinks(input_api, output_api, results)
    160 
    161   return results
    162 
    163 def CheckChangeOnUpload(input_api, output_api):
    164   results = []
    165   results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
    166   results += _CheckChange(input_api, output_api)
    167   return results
    168 
    169 def CheckChangeOnCommit(input_api, output_api):
    170   return _CheckChange(input_api, output_api)
    171