Chromium Code Reviews| 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 |