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 def _ReadFile(filename): 29 with open(filename) as f: 30 return f.read() 31 32 def _ListFilesInPublic(): 33 all_files = [] 34 for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH): 35 all_files.extend( 36 os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):] 37 for filename in files) 38 return all_files 39 40 def _UnixName(name): 41 name = os.path.splitext(name)[0] 42 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name) 43 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1) 44 return s2.replace('.', '_').lower() 45 46 def _FindMatchingTemplates(template_name, template_path_list): 47 matches = [] 48 unix_name = _UnixName(template_name) 49 for template in template_path_list: 50 if unix_name == _UnixName(template.split(os.sep)[-1]): 51 matches.append(template) 52 return matches 53 54 def _SanitizeAPIName(name, api_path): 55 if not api_path.endswith(os.sep): 56 api_path += os.sep 57 filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_') 58 if 'experimental' in filename: 59 filename = 'experimental_' + filename.replace('experimental_', '') 60 return filename 61 62 def _CreateIntegrationTestArgs(affected_files): 63 if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH) 64 for name in affected_files) or 65 any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH) 66 for name in affected_files)): 67 return ['-a'] 68 args = [] 69 for name in affected_files: 70 if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or 71 fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or 72 fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)): 73 args.extend(_FindMatchingTemplates(name.split(os.sep)[-1], 74 _ListFilesInPublic())) 75 if fnmatch.fnmatch(name, '%s*' % API_PATH): 76 args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH), 77 _ListFilesInPublic())) 78 return args 79 80 def _CheckHeadingIDs(input_api): 81 ids_re = re.compile('<h[23].*id=.*?>') 82 headings_re = re.compile('<h[23].*?>') 83 bad_files = [] 84 for name in input_api.AbsoluteLocalPaths(): 85 if not os.path.exists(name): 86 continue 87 if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or 88 fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)): 89 contents = input_api.ReadFile(name) 90 if (len(re.findall(headings_re, contents)) != 91 len(re.findall(ids_re, contents))): 92 bad_files.append(name) 93 return bad_files 94 95 def _CheckLinks(input_api, output_api, results): 96 for affected_file in input_api.AffectedFiles(): 97 name = affected_file.LocalPath() 98 absolute_path = affected_file.AbsoluteLocalPath() 99 if not os.path.exists(absolute_path): 100 continue 101 if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or 102 fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or 103 fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH) or 104 fnmatch.fnmatch(name, '%s*' % API_PATH)): 105 contents = _ReadFile(absolute_path) 106 args = [] 107 if input_api.platform == 'win32': 108 args = [input_api.python_executable] 109 args.extend([os.path.join('docs', 'server2', 'link_converter.py'), 110 '-o', 111 '-f', 112 absolute_path]) 113 output = input_api.subprocess.check_output( 114 args, 115 cwd=input_api.PresubmitLocalPath(), 116 universal_newlines=True) 117 if output != contents: 118 changes = '' 119 for i, (line1, line2) in enumerate( 120 zip(contents.split('\n'), output.split('\n'))): 121 if line1 != line2: 122 changes = ('%s\nLine %d:\n-%s\n+%s\n' % 123 (changes, i + 1, line1, line2)) 124 if changes: 125 results.append(output_api.PresubmitPromptWarning( 126 'File %s may have an old-style <a> link to an API page. Please ' 127 'run docs/server2/link_converter.py to convert the link[s], or ' 128 'convert them manually.\n\nSuggested changes are: %s' % 129 (name, changes))) 130 131 def _CheckChange(input_api, output_api): 132 results = [ 133 output_api.PresubmitError('File %s needs an id for each heading.' % name) 134 for name in _CheckHeadingIDs(input_api)] 135 try: 136 integration_test = [] 137 # From depot_tools/presubmit_canned_checks.py:529 138 if input_api.platform == 'win32': 139 integration_test = [input_api.python_executable] 140 integration_test.append( 141 os.path.join('docs', 'server2', 'integration_test.py')) 142 integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths())) 143 input_api.subprocess.check_call(integration_test, 144 cwd=input_api.PresubmitLocalPath()) 145 except input_api.subprocess.CalledProcessError: 146 results.append(output_api.PresubmitError('IntegrationTest failed!')) 147 148 # TODO(kalman): Re-enable this check, or decide to delete it forever. Now 149 # that we have multiple directories it no longer works. 150 # See http://crbug.com/297178. 151 #_CheckLinks(input_api, output_api, results) 152 153 return results 154 155 def CheckChangeOnUpload(input_api, output_api): 156 return _CheckChange(input_api, output_api) 157 158 def CheckChangeOnCommit(input_api, output_api): 159 return _CheckChange(input_api, output_api) 160