Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(526)

Side by Side Diff: tools/checkperms/checkperms.py

Issue 10088002: Remove over 100 lines from checkperms.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Implemented quicker and dirtier svn implementation Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Makes sure files have the right permissions. 6 """Makes sure files have the right permissions.
7 7
8 Some developers have broken SCM configurations that flip the svn:executable 8 Some developers have broken SCM configurations that flip the svn:executable
9 permission on for no good reason. Unix developers who run ls --color will then 9 permission on for no good reason. Unix developers who run ls --color will then
10 see .cc files in green and get confused. 10 see .cc files in green and get confused.
11 11
12 To ignore a particular file, add it to WHITELIST_FILES. 12 To ignore a particular file, add it to WHITELIST_FILES.
13 To ignore a particular extension, add it to WHITELIST_EXTENSIONS. 13 To ignore a particular extension, add it to WHITELIST_EXTENSIONS.
14 To ignore whatever regexps your heart desires, add it WHITELIST_REGEX. 14 To ignore a particular path, add it WHITELIST_PATHS.
15 15
16 Note that all directory separators must be slashes (Unix-style) and not 16 Note that all directory separators must be slashes (Unix-style) and not
17 backslashes. All directories should be relative to the source root and all 17 backslashes. All directories should be relative to the source root and all
18 file paths should be only lowercase. 18 file paths should be only lowercase.
19 """ 19 """
20 20
21 import logging
21 import optparse 22 import optparse
22 import os 23 import os
23 import pipes
24 import re
25 import stat 24 import stat
25 import subprocess
26 import sys 26 import sys
27 27
28 #### USER EDITABLE SECTION STARTS HERE #### 28 #### USER EDITABLE SECTION STARTS HERE ####
29 29
30 # Files with these extensions are allowed to have executable permissions. 30 # Files with these extensions are allowed to have executable permissions.
31 WHITELIST_EXTENSIONS = [ 31 WHITELIST_EXTENSIONS = (
32 'bash', 32 'bat',
33 'bat', 33 'dll',
34 'dll', 34 'dylib',
35 'dylib', 35 'exe',
36 'exe', 36 )
37 'pl',
38 'py',
39 'rb',
40 'sed',
41 'sh',
42 ]
43
44 # Files that end the following paths are whitelisted too.
45 WHITELIST_FILES = [
46 '/build/gyp_chromium',
47 '/build/linux/dump_app_syms',
48 '/build/linux/pkg-config-wrapper',
49 '/build/mac/strip_from_xcode',
50 '/build/mac/strip_save_dsym',
51 '/chrome/installer/mac/pkg-dmg',
52 '/chrome/test/data/webrtc/linux/peerconnection_server',
53 '/chrome/tools/build/linux/chrome-wrapper',
54 '/chrome/tools/build/mac/build_app_dmg',
55 '/chrome/tools/build/mac/clean_up_old_versions',
56 '/chrome/tools/build/mac/dump_product_syms',
57 '/chrome/tools/build/mac/generate_localizer',
58 '/chrome/tools/build/mac/make_sign_sh',
59 '/chrome/tools/build/mac/verify_order',
60 '/o3d/build/gyp_o3d',
61 '/o3d/gypbuild',
62 '/o3d/installer/linux/debian.in/rules',
63 '/third_party/icu/source/runconfigureicu',
64 '/third_party/gold/gold32',
65 '/third_party/gold/gold64',
66 '/third_party/gold/ld',
67 '/third_party/gold/ld.bfd',
68 '/third_party/lcov/bin/gendesc',
69 '/third_party/lcov/bin/genhtml',
70 '/third_party/lcov/bin/geninfo',
71 '/third_party/lcov/bin/genpng',
72 '/third_party/lcov/bin/lcov',
73 '/third_party/lcov/bin/mcov',
74 '/third_party/lcov-1.9/bin/gendesc',
75 '/third_party/lcov-1.9/bin/genhtml',
76 '/third_party/lcov-1.9/bin/geninfo',
77 '/third_party/lcov-1.9/bin/genpng',
78 '/third_party/lcov-1.9/bin/lcov',
79 '/third_party/libxml/linux/xml2-config',
80 '/third_party/lzma_sdk/executable/7za.exe',
81 '/third_party/swig/linux/swig',
82 '/third_party/tcmalloc/chromium/src/pprof',
83 '/tools/deep_memory_profiler/dmprof',
84 '/tools/git/post-checkout',
85 '/tools/git/post-merge',
86 '/tools/ld_bfd/ld',
87 ]
88 37
89 # File names that are always whitelisted. (These are all autoconf spew.) 38 # File names that are always whitelisted. (These are all autoconf spew.)
90 WHITELIST_FILENAMES = set(( 39 WHITELIST_FILENAMES = set((
91 'config.guess', 40 'config.guess',
92 'config.sub', 41 'config.sub',
93 'configure', 42 'configure',
94 'depcomp', 43 'depcomp',
95 'install-sh', 44 'install-sh',
96 'missing', 45 'missing',
97 'mkinstalldirs', 46 'mkinstalldirs',
47 'naclsdk',
98 'scons', 48 'scons',
99 'naclsdk',
100 )) 49 ))
101 50
102 # File paths that contain these regexps will be whitelisted as well. 51 # File paths that contain these regexps will be whitelisted as well.
103 WHITELIST_REGEX = [ 52 WHITELIST_PATHS = (
104 re.compile('/third_party/openssl/'), 53 # TODO(maruel): Detect ELF files.
105 re.compile('/third_party/sqlite/'), 54 'chrome/test/data/webrtc/linux/peerconnection_server',
106 re.compile('/third_party/xdg-utils/'), 55 'chrome/installer/mac/sign_app.sh.in',
107 re.compile('/third_party/yasm/source/patched-yasm/config'), 56 'chrome/installer/mac/sign_versioned_dir.sh.in',
108 re.compile('/third_party/ffmpeg/tools'), 57 'native_client_sdk/src/build_tools/sdk_tools/third_party/',
109 ] 58 'out/',
59 # TODO(maruel): Fix these.
60 'third_party/android_testrunner/',
61 'third_party/closure_linter/',
62 'third_party/devscripts/licensecheck.pl.vanilla',
63 'third_party/hyphen/',
64 'third_party/jemalloc/',
65 'third_party/lcov-1.9/contrib/galaxy/conglomerate_functions.pl',
66 'third_party/lcov-1.9/contrib/galaxy/gen_makefile.sh',
67 'third_party/lcov/contrib/galaxy/conglomerate_functions.pl',
68 'third_party/lcov/contrib/galaxy/gen_makefile.sh',
69 'third_party/libevent/autogen.sh',
70 'third_party/libevent/test/test.sh',
71 'third_party/libxml/linux/xml2-config',
72 'third_party/libxml/src/ltmain.sh',
73 'third_party/mesa/',
74 'third_party/protobuf/',
75 'third_party/python_gflags/gflags.py',
76 'third_party/sqlite/',
77 'third_party/talloc/script/mksyms.sh',
78 'third_party/tcmalloc/',
79 'third_party/tlslite/setup.py',
80 )
110 81
111 #### USER EDITABLE SECTION ENDS HERE #### 82 #### USER EDITABLE SECTION ENDS HERE ####
112 83
113 WHITELIST_EXTENSIONS_REGEX = re.compile(r'\.(%s)' % 84
114 '|'.join(WHITELIST_EXTENSIONS)) 85 def capture(cmd, cwd):
115 86 """Returns the output of a command.
116 WHITELIST_FILES_REGEX = re.compile(r'(%s)$' % '|'.join(WHITELIST_FILES)) 87
117 88 Ignores the error code or stderr.
118 # Set to true for more output. This is set by the command line options.
119 VERBOSE = False
120
121 # Using forward slashes as directory separators, ending in a forward slash.
122 # Set by the command line options.
123 BASE_DIRECTORY = ''
124
125 # The default if BASE_DIRECTORY is not set on the command line.
126 DEFAULT_BASE_DIRECTORY = '../../..'
127
128 # The directories which contain the sources managed by git.
129 GIT_SOURCE_DIRECTORY = set()
130
131 # The SVN repository url.
132 SVN_REPO_URL = ''
133
134 # Whether we are using SVN or GIT.
135 IS_SVN = True
136
137 # Executable permission mask
138 EXECUTABLE_PERMISSION = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
139
140
141 def IsWhiteListed(file_path):
142 """Returns True if file_path is in our whitelist of files to ignore."""
143 if WHITELIST_EXTENSIONS_REGEX.match(os.path.splitext(file_path)[1]):
144 return True
145 if WHITELIST_FILES_REGEX.search(file_path):
146 return True
147 if os.path.basename(file_path) in WHITELIST_FILENAMES:
148 return True
149 for regex in WHITELIST_REGEX:
150 if regex.search(file_path):
151 return True
152 return False
153
154
155 def CheckFile(file_path):
156 """Checks file_path's permissions.
157
158 Args:
159 file_path: The file path to check.
160
161 Returns:
162 Either a string describing the error if there was one, or None if the file
163 checked out OK.
164 """ 89 """
165 if VERBOSE: 90 logging.debug(' '.join(cmd))
166 print 'Checking file: ' + file_path 91 env = os.environ.copy()
167 92 env['LANGUAGE'] = 'en_US.UTF-8'
Lei Zhang 2012/04/25 04:01:02 I thought $LANG is the right variable to use here.
M-A Ruel 2012/04/27 16:49:17 svn ls output is localized. :(
168 file_path_lower = file_path.lower() 93 p = subprocess.Popen(
169 if IsWhiteListed(file_path_lower): 94 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env)
95 return p.communicate()[0]
96
97
98 def get_svn_info(dir_path):
99 """Returns svn meta-data for a svn checkout."""
100 if not os.path.isdir(dir_path):
101 return {}
102 out = capture(['svn', 'info', '.', '--non-interactive'], dir_path)
103 return dict(l.split(': ', 1) for l in out.splitlines() if l)
104
105
106 def get_svn_url(dir_path):
107 return get_svn_info(dir_path).get('Repository Root')
108
109
110 def get_svn_root(dir_path):
111 """Returns the svn checkout root or None."""
112 svn_url = get_svn_url(dir_path)
113 if not svn_url:
170 return None 114 return None
171 115 logging.info('svn url: %s' % svn_url)
172 # Not whitelisted, stat the file and check permissions. 116 while True:
173 try: 117 parent = os.path.dirname(dir_path)
174 st_mode = os.stat(file_path).st_mode 118 if parent == dir_path:
175 except IOError, e: 119 return None
176 return 'Failed to stat file: %s' % e 120 if svn_url != get_svn_url(parent):
177 except OSError, e: 121 return dir_path
178 return 'Failed to stat file: %s' % e 122 dir_path = parent
179 123
180 if EXECUTABLE_PERMISSION & st_mode: 124
181 # Look if the file starts with #!/ 125 def get_git_root(dir_path):
182 with open(file_path, 'rb') as f: 126 """Returns the git checkout root or None."""
183 if f.read(3) == '#!/': 127 root = capture(['git', 'rev-parse', '--show-toplevel'], dir_path).strip()
184 # That's fine. 128 if root:
185 return None 129 return root
186 # TODO(maruel): Check that non-executable file do not start with a shebang. 130
187 error = 'Contains executable permission' 131
188 if VERBOSE: 132 class ApiBase(object):
189 return '%s: %06o' % (error, st_mode) 133 def __init__(self, root_dir, bare_output):
190 return error 134 self.root_dir = root_dir
191 return None 135 self.bare_output = bare_output
192 136 self.count = 0
193 137
194 def ShouldCheckDirectory(dir_path): 138 @staticmethod
195 """Determine if we should check the content of dir_path.""" 139 def is_whitelisted(rel_path):
196 if not IS_SVN: 140 """Returns True if rel_path is in our whitelist of files to ignore."""
197 return dir_path in GIT_SOURCE_DIRECTORY 141 rel_path = rel_path.lower()
198 repo_url = GetSvnRepositoryRoot(dir_path) 142 return (
199 if not repo_url: 143 os.path.splitext(rel_path)[1][1:] in WHITELIST_EXTENSIONS or
200 return False 144 os.path.basename(rel_path) in WHITELIST_FILENAMES or
201 return repo_url == SVN_REPO_URL 145 rel_path.startswith(WHITELIST_PATHS))
202 146
203 147 @staticmethod
204 def CheckDirectory(dir_path): 148 def has_executable_bit(full_path):
205 """Check the files in dir_path; recursively check its subdirectories.""" 149 """Returns if any executable bit is set."""
206 # Collect a list of all files and directories to check. 150 permission = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
207 files_to_check = [] 151 return bool(permission & os.stat(full_path).st_mode)
208 dirs_to_check = [] 152
209 success = True 153 @staticmethod
210 contents = os.listdir(dir_path) 154 def has_shebang(full_path):
211 for cur in contents: 155 """Returns if the file starts with #!/.
212 full_path = os.path.join(dir_path, cur) 156
213 if os.path.isdir(full_path) and ShouldCheckDirectory(full_path): 157 file_path is the absolute path to the file.
214 dirs_to_check.append(full_path) 158 """
215 elif os.path.isfile(full_path): 159 with open(full_path, 'rb') as f:
216 files_to_check.append(full_path) 160 return f.read(3) == '#!/'
217 161
218 # First check all files in this directory. 162 def check_file(self, rel_path):
219 for cur_file in files_to_check: 163 """Checks file_path's permissions and returns an error if it is
220 file_status = CheckFile(cur_file) 164 inconsistent.
221 if file_status is not None: 165 """
222 print 'ERROR in %s\n%s' % (cur_file, file_status) 166 logging.debug('check_file(%s)' % rel_path)
223 success = False 167 self.count += 1
224 168
225 # Next recurse into the subdirectories. 169 full_path = os.path.join(self.root_dir, rel_path)
226 for cur_dir in dirs_to_check: 170 try:
227 if not CheckDirectory(cur_dir): 171 bit = self.has_executable_bit(full_path)
228 success = False 172 except OSError:
229 return success 173 # It's faster to catch exception than call os.path.islink(). Chromium
230 174 # tree happens to have invalid symlinks under
231 175 # third_party/openssl/openssl/test/.
232 def GetGitSourceDirectory(root): 176 return None
233 """Returns a set of the directories to be checked. 177 shebang = self.has_shebang(full_path)
234 178 if bit != shebang:
235 Args: 179 if self.bare_output:
236 root: The repository root where a .git directory must exist. 180 return rel_path
237 181 if bit:
238 Returns: 182 return '%s: Has executable bit but not shebang' % rel_path
239 A set of directories which contain sources managed by git. 183 else:
184 return '%s: Has shebang but not executable bit' % rel_path
185
186 def check_dir(self, rel_path):
187 return self.check(rel_path)
188
189 def check(self, start_dir):
190 """Check the files in start_dir, recursively check its subdirectories."""
191 errors = []
192 items = self.list_dir(start_dir)
193 logging.info('check(%s) -> %d' % (start_dir, len(items)))
194 for item in items:
195 full_path = os.path.join(self.root_dir, start_dir, item)
196 rel_path = full_path[len(self.root_dir) + 1:]
197 if self.is_whitelisted(rel_path):
198 continue
199 if os.path.isdir(full_path):
200 # Depth first.
201 errors.extend(self.check_dir(rel_path))
202 else:
203 error = self.check_file(rel_path)
204 if error:
205 errors.append(error)
206 return errors
207
208 def list_dir(self, start_dir):
209 """Lists all the files and directory inside start_dir."""
210 return sorted(
211 x for x in os.listdir(os.path.join(self.root_dir, start_dir))
212 if not x.startswith('.')
213 )
214
215
216 class ApiSvnQuick(ApiBase):
M-A Ruel 2012/04/25 01:30:11 With this implementation, linux/svn/warm gets down
Lei Zhang 2012/04/25 04:01:02 I'm fine with this.
217 """Returns all files in svn-versioned directories, independent of the fact if
218 they are versionned.
219
220 Uses svn info in each directory to determine which directories should be
221 crawled.
240 """ 222 """
241 git_source_directory = set() 223 def __init__(self, *args):
242 popen_out = os.popen('cd %s && git ls-files --full-name .' % 224 super(ApiSvnQuick, self).__init__(*args)
243 pipes.quote(root)) 225 self.url = get_svn_info(self.root_dir)['Repository Root']
244 for line in popen_out: 226
245 dir_path = os.path.join(root, os.path.dirname(line)) 227 def check_dir(self, rel_path):
246 git_source_directory.add(dir_path) 228 if get_svn_url(os.path.join(self.root_dir, rel_path)) != self.url:
247 git_source_directory.add(root) 229 return []
248 return git_source_directory 230 return super(ApiSvnQuick, self).check_dir(rel_path)
249 231
250 232
251 def GetSvnRepositoryRoot(dir_path): 233 class ApiAllFilesAtOnceBase(ApiBase):
252 """Returns the repository root for a directory. 234 _files = None
253 235
254 Args: 236 def list_dir(self, start_dir):
255 dir_path: A directory where a .svn subdirectory should exist. 237 """Lists all the files and directory inside start_dir."""
256 238 if self._files is None:
257 Returns: 239 self._files = sorted(self._get_all_files())
258 The svn repository that contains dir_path or None. 240 if not self.bare_output:
241 print 'Found %s files' % len(self._files)
242 start_dir = start_dir[len(self.root_dir) + 1:]
243 return [
244 x[len(start_dir):] for x in self._files if x.startswith(start_dir)
245 ]
246
247 def _get_all_files(self):
248 """Lists all the files and directory inside self._root_dir."""
249 raise NotImplementedError()
250
251
252 class ApiSvn(ApiAllFilesAtOnceBase):
253 """Returns all the subversion controlled files.
254
255 Warning: svn ls is abnormally slow.
259 """ 256 """
260 svn_dir = os.path.join(dir_path, '.svn') 257 def _get_all_files(self):
261 if not os.path.isdir(svn_dir): 258 cmd = ['svn', 'ls', '--non-interactive', '--recursive']
262 return None 259 return (
263 popen_out = os.popen('cd %s && svn info' % pipes.quote(dir_path)) 260 x for x in capture(cmd, self.root_dir).splitlines()
264 for line in popen_out: 261 if not x.endswith(os.path.sep))
265 if line.startswith('Repository Root: '): 262
266 return line[len('Repository Root: '):].rstrip() 263
267 return None 264 class ApiGit(ApiAllFilesAtOnceBase):
268 265 def _get_all_files(self):
269 266 return capture(['git', 'ls-files'], cwd=self.root_dir).splitlines()
270 def main(argv): 267
268
269 def get_scm(dir_path, bare):
270 """Returns a properly configured ApiBase instance."""
271 dir_path = os.path.abspath(dir_path)
272 root = get_svn_root(dir_path)
273 if root:
274 if not bare:
275 print('Found subversion checkout at %s' % root)
276 return ApiSvnQuick(root, bare)
277 root = get_git_root(dir_path)
278 if root:
279 if not bare:
280 print('Found git repository at %s' % root)
281 return ApiGit(root, bare)
282
283 # Returns a non-scm aware checker.
284 if not bare:
285 print('Failed to determine the SCM for %s' % dir_path)
286 return ApiBase(dir_path, bare)
287
288
289 def main():
271 usage = """Usage: python %prog [--root <root>] [tocheck] 290 usage = """Usage: python %prog [--root <root>] [tocheck]
272 tocheck Specifies the directory, relative to root, to check. This defaults 291 tocheck Specifies the directory, relative to root, to check. This defaults
273 to "." so it checks everything. 292 to "." so it checks everything.
274 293
275 Examples: 294 Examples:
276 python checkperms.py 295 python %prog
277 python checkperms.py --root /path/to/source chrome""" 296 python %prog --root /path/to/source chrome"""
278 297
279 option_parser = optparse.OptionParser(usage=usage) 298 parser = optparse.OptionParser(usage=usage)
280 option_parser.add_option('--root', dest='base_directory', 299 parser.add_option(
281 default=DEFAULT_BASE_DIRECTORY, 300 '--root',
282 help='Specifies the repository root. This defaults ' 301 help='Specifies the repository root. This defaults '
283 'to %default relative to the script file, which ' 302 'to the checkout repository root')
284 'will normally be the repository root.') 303 parser.add_option(
285 option_parser.add_option('-v', '--verbose', action='store_true', 304 '-v', '--verbose', action='count', default=0, help='Print debug logging')
286 help='Print debug logging') 305 parser.add_option(
287 options, args = option_parser.parse_args() 306 '--bare',
288 307 action='store_true',
289 global VERBOSE 308 default=False,
290 if options.verbose: 309 help='Prints the bare filename triggering the checks')
291 VERBOSE = True 310 options, args = parser.parse_args()
292 311
293 # Optional base directory of the repository. 312 levels = [logging.ERROR, logging.INFO, logging.DEBUG]
294 global BASE_DIRECTORY 313 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)])
295 if (not options.base_directory or 314
296 options.base_directory == DEFAULT_BASE_DIRECTORY): 315 if len(args) > 1:
297 BASE_DIRECTORY = os.path.abspath( 316 parser.error('Too many arguments used')
298 os.path.join(os.path.abspath(argv[0]), DEFAULT_BASE_DIRECTORY)) 317
318 # Guess again the SCM used.
319 api = get_scm(options.root or '.', options.bare)
320 if args:
321 start_dir = args[0]
299 else: 322 else:
300 BASE_DIRECTORY = os.path.abspath(argv[2]) 323 start_dir = api.root_dir
301 324
302 # Figure out which directory we have to check. 325 errors = api.check(start_dir)
303 if not args: 326
304 # No directory to check specified, use the repository root. 327 if not options.bare:
305 start_dir = BASE_DIRECTORY 328 print 'Processed %s files' % api.count
306 elif len(args) == 1: 329
307 # Directory specified. Start here. It's supposed to be relative to the 330 if errors:
308 # base directory. 331 if not options.bare:
309 start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0])) 332 print '\nFAILED\n'
310 else: 333 print '\n'.join(errors)
311 # More than one argument, we don't handle this.
312 option_parser.print_help()
313 return 1 334 return 1
314 335 if not options.bare:
315 print 'Using base directory:', BASE_DIRECTORY 336 print '\nSUCCESS\n'
316 print 'Checking directory:', start_dir
317
318 BASE_DIRECTORY = BASE_DIRECTORY.replace('\\', '/')
319 start_dir = start_dir.replace('\\', '/')
320
321 success = True
322 if os.path.exists(os.path.join(BASE_DIRECTORY, '.svn')):
323 global SVN_REPO_URL
324 SVN_REPO_URL = GetSvnRepositoryRoot(BASE_DIRECTORY)
325 if not SVN_REPO_URL:
326 print 'Cannot determine the SVN repo URL'
327 success = False
328 elif os.path.exists(os.path.join(BASE_DIRECTORY, '.git')):
329 global IS_SVN
330 IS_SVN = False
331 global GIT_SOURCE_DIRECTORY
332 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY)
333 if not GIT_SOURCE_DIRECTORY:
334 print 'Cannot determine the list of GIT directories'
335 success = False
336 else:
337 print 'Cannot determine the SCM used in %s' % BASE_DIRECTORY
338 success = False
339
340 if success:
341 success = CheckDirectory(start_dir)
342 if not success:
343 print '\nFAILED\n'
344 return 1
345 print '\nSUCCESS\n'
346 return 0 337 return 0
347 338
348 339
349 if '__main__' == __name__: 340 if '__main__' == __name__:
350 sys.exit(main(sys.argv)) 341 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698