OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 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 """Checks third-party licenses for the purposes of the Android WebView build. | 6 """Checks third-party licenses for the purposes of the Android WebView build. |
7 | 7 |
8 The Android tree includes a snapshot of Chromium in order to power the system | 8 The Android tree includes a snapshot of Chromium in order to power the system |
9 WebView. This tool checks that all code uses open-source licenses compatible | 9 WebView. This tool checks that all code uses open-source licenses compatible |
10 with Android, and that we meet the requirements of those licenses. It can also | 10 with Android, and that we meet the requirements of those licenses. It can also |
11 be used to generate an Android NOTICE file for the third-party code. | 11 be used to generate an Android NOTICE file for the third-party code. |
12 | 12 |
13 It makes use of src/tools/licenses.py and the README.chromium files on which | 13 It makes use of src/tools/licenses.py and the README.chromium files on which |
14 it depends. It also makes use of a data file, third_party_files_whitelist.txt, | 14 it depends. It also makes use of a data file, third_party_files_whitelist.txt, |
15 which whitelists indicidual files which contain third-party code but which | 15 which whitelists indicidual files which contain third-party code but which |
16 aren't in a third-party directory with a README.chromium file. | 16 aren't in a third-party directory with a README.chromium file. |
17 """ | 17 """ |
18 | 18 |
19 import glob | 19 import glob |
20 import imp | 20 import imp |
21 import json | |
21 import multiprocessing | 22 import multiprocessing |
22 import optparse | 23 import optparse |
23 import os | 24 import os |
24 import re | 25 import re |
25 import sys | 26 import sys |
26 import textwrap | 27 import textwrap |
27 | 28 |
28 | 29 |
29 REPOSITORY_ROOT = os.path.abspath(os.path.join( | 30 REPOSITORY_ROOT = os.path.abspath(os.path.join( |
30 os.path.dirname(__file__), '..', '..')) | 31 os.path.dirname(__file__), '..', '..')) |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
154 '\n'.join(sorted(unknown)) | 155 '\n'.join(sorted(unknown)) |
155 if missing: | 156 if missing: |
156 print 'The following files are whitelisted, but do not exist.\n%s' % \ | 157 print 'The following files are whitelisted, but do not exist.\n%s' % \ |
157 '\n'.join(sorted(missing)) | 158 '\n'.join(sorted(missing)) |
158 if stale: | 159 if stale: |
159 print 'The following files are whitelisted unnecessarily. You must ' \ | 160 print 'The following files are whitelisted unnecessarily. You must ' \ |
160 'remove the following files from the whitelist.\n%s' % \ | 161 'remove the following files from the whitelist.\n%s' % \ |
161 '\n'.join(sorted(stale)) | 162 '\n'.join(sorted(stale)) |
162 | 163 |
163 if unknown: | 164 if unknown: |
164 return ScanResult.Errors | 165 code = ScanResult.Errors |
165 elif stale or missing: | 166 elif stale or missing: |
166 return ScanResult.Warnings | 167 code = ScanResult.Warnings |
167 else: | 168 else: |
168 return ScanResult.Ok | 169 code = ScanResult.Ok |
170 | |
171 problem_paths = sorted(set(unknown + missing + stale)) | |
mnaganov (inactive)
2015/02/17 11:27:09
How exactly will this list be used? Will any human
mnaganov (inactive)
2015/02/17 11:28:35
Looked at the bug -- if it is only for scripts tha
Paweł Hajdan Jr.
2015/02/17 14:09:36
Yes, JSON is for machine-readable processing.
Con
| |
172 return (code, problem_paths) | |
169 | 173 |
170 | 174 |
171 def _ReadFile(full_path, mode='rU'): | 175 def _ReadFile(full_path, mode='rU'): |
172 """Reads a file from disk. This emulates presubmit InputApi.ReadFile func. | 176 """Reads a file from disk. This emulates presubmit InputApi.ReadFile func. |
173 Args: | 177 Args: |
174 full_path: The path of the file to read. | 178 full_path: The path of the file to read. |
175 Returns: | 179 Returns: |
176 The contents of the file as a string. | 180 The contents of the file as a string. |
177 """ | 181 """ |
178 | 182 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
234 that all non third-party code doesn't contain external copyrighted code. | 238 that all non third-party code doesn't contain external copyrighted code. |
235 Returns: | 239 Returns: |
236 ScanResult.Ok if everything is in order; | 240 ScanResult.Ok if everything is in order; |
237 ScanResult.Warnings if there are non-fatal problems (e.g. stale whitelist | 241 ScanResult.Warnings if there are non-fatal problems (e.g. stale whitelist |
238 entries) | 242 entries) |
239 ScanResult.Errors otherwise. | 243 ScanResult.Errors otherwise. |
240 """ | 244 """ |
241 | 245 |
242 third_party_dirs = _FindThirdPartyDirs() | 246 third_party_dirs = _FindThirdPartyDirs() |
243 | 247 |
248 problem_paths = [] | |
249 | |
244 # First, check designated third-party directories using src/tools/licenses.py. | 250 # First, check designated third-party directories using src/tools/licenses.py. |
245 all_licenses_valid = True | 251 all_licenses_valid = True |
246 for path in sorted(third_party_dirs): | 252 for path in sorted(third_party_dirs): |
247 try: | 253 try: |
248 licenses.ParseDir(path, REPOSITORY_ROOT) | 254 licenses.ParseDir(path, REPOSITORY_ROOT) |
249 except licenses.LicenseError, e: | 255 except licenses.LicenseError, e: |
250 if not (path in known_issues.KNOWN_ISSUES): | 256 if not (path in known_issues.KNOWN_ISSUES): |
251 print 'Got LicenseError "%s" while scanning %s' % (e, path) | 257 print 'Got LicenseError "%s" while scanning %s' % (e, path) |
258 problem_paths.append(path) | |
252 all_licenses_valid = False | 259 all_licenses_valid = False |
253 | 260 |
254 # Second, check for non-standard license text. | 261 # Second, check for non-standard license text. |
255 whitelisted_files = copyright_scanner.LoadWhitelistedFilesList(InputApi()) | 262 whitelisted_files = copyright_scanner.LoadWhitelistedFilesList(InputApi()) |
256 licenses_check = _CheckLicenseHeaders(third_party_dirs, whitelisted_files) | 263 licenses_check, more_problem_paths = _CheckLicenseHeaders( |
264 third_party_dirs, whitelisted_files) | |
257 | 265 |
258 return licenses_check if all_licenses_valid else ScanResult.Errors | 266 problem_paths.extend(more_problem_paths) |
267 | |
268 return (licenses_check if all_licenses_valid else ScanResult.Errors, | |
269 problem_paths) | |
259 | 270 |
260 | 271 |
261 class TemplateEntryGenerator(object): | 272 class TemplateEntryGenerator(object): |
262 def __init__(self): | 273 def __init__(self): |
263 self._generate_licenses_file_list_only = False | 274 self._generate_licenses_file_list_only = False |
264 self._toc_index = 0 | 275 self._toc_index = 0 |
265 | 276 |
266 def SetGenerateLicensesFileListOnly(self, generate_licenses_file_list_only): | 277 def SetGenerateLicensesFileListOnly(self, generate_licenses_file_list_only): |
267 self._generate_licenses_file_list_only = generate_licenses_file_list_only | 278 self._generate_licenses_file_list_only = generate_licenses_file_list_only |
268 | 279 |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
343 | 354 |
344 def main(): | 355 def main(): |
345 class FormatterWithNewLines(optparse.IndentedHelpFormatter): | 356 class FormatterWithNewLines(optparse.IndentedHelpFormatter): |
346 def format_description(self, description): | 357 def format_description(self, description): |
347 paras = description.split('\n') | 358 paras = description.split('\n') |
348 formatted_paras = [textwrap.fill(para, self.width) for para in paras] | 359 formatted_paras = [textwrap.fill(para, self.width) for para in paras] |
349 return '\n'.join(formatted_paras) + '\n' | 360 return '\n'.join(formatted_paras) + '\n' |
350 | 361 |
351 parser = optparse.OptionParser(formatter=FormatterWithNewLines(), | 362 parser = optparse.OptionParser(formatter=FormatterWithNewLines(), |
352 usage='%prog [options]') | 363 usage='%prog [options]') |
364 parser.add_option('--json', help='Path to JSON output file') | |
353 parser.description = (__doc__ + | 365 parser.description = (__doc__ + |
354 '\nCommands:\n' | 366 '\nCommands:\n' |
355 ' scan Check licenses.\n' | 367 ' scan Check licenses.\n' |
356 ' notice_deps Generate the list of dependencies for ' | 368 ' notice_deps Generate the list of dependencies for ' |
357 'Android NOTICE file.\n' | 369 'Android NOTICE file.\n' |
358 ' notice [file] Generate Android NOTICE file on ' | 370 ' notice [file] Generate Android NOTICE file on ' |
359 'stdout or into |file|.\n' | 371 'stdout or into |file|.\n' |
360 ' incompatible_directories Scan for incompatibly' | 372 ' incompatible_directories Scan for incompatibly' |
361 ' licensed directories.\n' | 373 ' licensed directories.\n' |
362 ' all_incompatible_directories Scan for incompatibly' | 374 ' all_incompatible_directories Scan for incompatibly' |
363 ' licensed directories (even those in' | 375 ' licensed directories (even those in' |
364 ' known_issues.py).\n' | 376 ' known_issues.py).\n' |
365 ' display_copyrights Display autorship on the files' | 377 ' display_copyrights Display autorship on the files' |
366 ' using names provided via stdin.\n') | 378 ' using names provided via stdin.\n') |
367 (_, args) = parser.parse_args() | 379 (options, args) = parser.parse_args() |
368 if len(args) < 1: | 380 if len(args) < 1: |
369 parser.print_help() | 381 parser.print_help() |
370 return ScanResult.Errors | 382 return ScanResult.Errors |
371 | 383 |
372 if args[0] == 'scan': | 384 if args[0] == 'scan': |
373 scan_result = _Scan() | 385 scan_result, problem_paths = _Scan() |
374 if scan_result == ScanResult.Ok: | 386 if scan_result == ScanResult.Ok: |
375 print 'OK!' | 387 print 'OK!' |
388 if options.json: | |
389 with open(options.json, 'w') as f: | |
390 json.dump(problem_paths, f) | |
376 return scan_result | 391 return scan_result |
377 elif args[0] == 'notice_deps': | 392 elif args[0] == 'notice_deps': |
378 # 'set' is used to eliminate duplicate references to the same license file. | 393 # 'set' is used to eliminate duplicate references to the same license file. |
379 print ' '.join( | 394 print ' '.join( |
380 sorted(set(GenerateNoticeFile(generate_licenses_file_list_only=True)))) | 395 sorted(set(GenerateNoticeFile(generate_licenses_file_list_only=True)))) |
381 return ScanResult.Ok | 396 return ScanResult.Ok |
382 elif args[0] == 'notice': | 397 elif args[0] == 'notice': |
383 notice_file_contents = GenerateNoticeFile() | 398 notice_file_contents = GenerateNoticeFile() |
384 if len(args) == 1: | 399 if len(args) == 1: |
385 print notice_file_contents | 400 print notice_file_contents |
386 else: | 401 else: |
387 with open(args[1], 'w') as output_file: | 402 with open(args[1], 'w') as output_file: |
388 output_file.write(notice_file_contents) | 403 output_file.write(notice_file_contents) |
389 return ScanResult.Ok | 404 return ScanResult.Ok |
390 elif args[0] == 'incompatible_directories': | 405 elif args[0] == 'incompatible_directories': |
391 return _ProcessIncompatibleResult(GetUnknownIncompatibleDirectories()) | 406 return _ProcessIncompatibleResult(GetUnknownIncompatibleDirectories()) |
392 elif args[0] == 'all_incompatible_directories': | 407 elif args[0] == 'all_incompatible_directories': |
393 return _ProcessIncompatibleResult(GetIncompatibleDirectories()) | 408 return _ProcessIncompatibleResult(GetIncompatibleDirectories()) |
394 elif args[0] == 'display_copyrights': | 409 elif args[0] == 'display_copyrights': |
395 files = sys.stdin.read().splitlines() | 410 files = sys.stdin.read().splitlines() |
396 for f, c in \ | 411 for f, c in \ |
397 zip(files, copyright_scanner.FindCopyrights(InputApi(), '.', files)): | 412 zip(files, copyright_scanner.FindCopyrights(InputApi(), '.', files)): |
398 print f, '\t', ' / '.join(sorted(c)) | 413 print f, '\t', ' / '.join(sorted(c)) |
399 return ScanResult.Ok | 414 return ScanResult.Ok |
400 parser.print_help() | 415 parser.print_help() |
401 return ScanResult.Errors | 416 return ScanResult.Errors |
402 | 417 |
403 if __name__ == '__main__': | 418 if __name__ == '__main__': |
404 sys.exit(main()) | 419 sys.exit(main()) |
OLD | NEW |