| OLD | NEW |
| 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 """Utility for checking and processing licensing information in third_party | 6 """Utility for checking and processing licensing information in third_party |
| 7 directories. | 7 directories. |
| 8 | 8 |
| 9 Usage: licenses.py <command> | 9 Usage: licenses.py <command> |
| 10 | 10 |
| 11 Commands: | 11 Commands: |
| 12 scan scan third_party directories, verifying that we have licensing info | 12 scan scan third_party directories, verifying that we have licensing info |
| 13 credits generate about:credits on stdout | 13 credits generate about:credits on stdout |
| 14 | 14 |
| 15 (You can also import this as a module.) | 15 (You can also import this as a module.) |
| 16 """ | 16 """ |
| 17 | 17 |
| 18 import argparse | 18 import argparse |
| 19 import cgi | 19 import cgi |
| 20 import os | 20 import os |
| 21 import shutil |
| 21 import subprocess | 22 import subprocess |
| 22 import sys | 23 import sys |
| 24 import tempfile |
| 23 | 25 |
| 24 # TODO(agrieve): Move build_utils.WriteDepFile into a non-android directory. | 26 # TODO(agrieve): Move build_utils.WriteDepFile into a non-android directory. |
| 25 _REPOSITORY_ROOT = os.path.dirname(os.path.dirname(__file__)) | 27 _REPOSITORY_ROOT = os.path.dirname(os.path.dirname(__file__)) |
| 26 sys.path.append(os.path.join(_REPOSITORY_ROOT, 'build/android/gyp/util')) | 28 sys.path.append(os.path.join(_REPOSITORY_ROOT, 'build/android/gyp/util')) |
| 27 import build_utils | 29 import build_utils |
| 28 | 30 |
| 29 | 31 |
| 30 # Paths from the root of the tree to directories to skip. | 32 # Paths from the root of the tree to directories to skip. |
| 31 PRUNE_PATHS = set([ | 33 PRUNE_PATHS = set([ |
| 32 # Placeholder directory only, not third-party code. | 34 # Placeholder directory only, not third-party code. |
| (...skipping 442 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 475 else: | 477 else: |
| 476 raise RuntimeError("Unsupported platform '%s'." % sys.platform) | 478 raise RuntimeError("Unsupported platform '%s'." % sys.platform) |
| 477 | 479 |
| 478 return os.path.join(_REPOSITORY_ROOT, 'buildtools', subdir, exe) | 480 return os.path.join(_REPOSITORY_ROOT, 'buildtools', subdir, exe) |
| 479 | 481 |
| 480 | 482 |
| 481 def FindThirdPartyDeps(gn_out_dir, gn_target): | 483 def FindThirdPartyDeps(gn_out_dir, gn_target): |
| 482 if not gn_out_dir: | 484 if not gn_out_dir: |
| 483 raise RuntimeError("--gn-out-dir is required if --gn-target is used.") | 485 raise RuntimeError("--gn-out-dir is required if --gn-target is used.") |
| 484 | 486 |
| 485 gn_deps = subprocess.check_output([_GnBinary(), "desc", gn_out_dir, | 487 # Generate gn project in temp directory and use it to find dependencies. |
| 486 gn_target, | 488 # Current gn directory cannot be used when we run this script in a gn action |
| 487 "deps", "--as=buildfile", "--all"]) | 489 # rule, because gn doesn't allow recursive invocations due to potential side |
| 490 # effects. |
| 491 tmp_dir = None |
| 492 try: |
| 493 tmp_dir = tempfile.mkdtemp(dir = gn_out_dir) |
| 494 shutil.copy(os.path.join(gn_out_dir, "args.gn"), tmp_dir) |
| 495 subprocess.check_output([_GnBinary(), "gen", tmp_dir]) |
| 496 gn_deps = subprocess.check_output([ |
| 497 _GnBinary(), "desc", tmp_dir, gn_target, |
| 498 "deps", "--as=buildfile", "--all"]) |
| 499 finally: |
| 500 if tmp_dir and os.path.exists(tmp_dir): |
| 501 shutil.rmtree(tmp_dir) |
| 502 |
| 488 third_party_deps = set() | 503 third_party_deps = set() |
| 489 for build_dep in gn_deps.split(): | 504 for build_dep in gn_deps.split(): |
| 490 if ("third_party" in build_dep and | 505 if ("third_party" in build_dep and |
| 491 os.path.basename(build_dep) == "BUILD.gn"): | 506 os.path.basename(build_dep) == "BUILD.gn"): |
| 492 third_party_deps.add(os.path.dirname(build_dep)) | 507 third_party_deps.add(os.path.dirname(build_dep)) |
| 493 return third_party_deps | 508 return third_party_deps |
| 494 | 509 |
| 495 | 510 |
| 496 def ScanThirdPartyDirs(root=None): | 511 def ScanThirdPartyDirs(root=None): |
| 497 """Scan a list of directories and report on any problems we find.""" | 512 """Scan a list of directories and report on any problems we find.""" |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 607 # practice however. | 622 # practice however. |
| 608 license_file_list = (entry['license_file'] for entry in entries) | 623 license_file_list = (entry['license_file'] for entry in entries) |
| 609 license_file_list = (os.path.relpath(p) for p in license_file_list) | 624 license_file_list = (os.path.relpath(p) for p in license_file_list) |
| 610 license_file_list = sorted(set(license_file_list)) | 625 license_file_list = sorted(set(license_file_list)) |
| 611 build_utils.WriteDepfile(depfile, output_file, | 626 build_utils.WriteDepfile(depfile, output_file, |
| 612 license_file_list + ['build.ninja']) | 627 license_file_list + ['build.ninja']) |
| 613 | 628 |
| 614 return True | 629 return True |
| 615 | 630 |
| 616 | 631 |
| 632 def _ReadFile(path): |
| 633 """Reads a file from disk. |
| 634 Args: |
| 635 path: The path of the file to read, relative to the root of the |
| 636 repository. |
| 637 Returns: |
| 638 The contents of the file as a string. |
| 639 """ |
| 640 with open(os.path.join(_REPOSITORY_ROOT, path), 'rb') as f: |
| 641 return f.read() |
| 642 |
| 643 |
| 644 def GenerateLicenseFile(output_file, gn_out_dir, gn_target): |
| 645 """Generate a plain-text LICENSE file which can be used when you ship a part |
| 646 of Chromium code (specified by gn_target) as a stand-alone library |
| 647 (e.g., //ios/web_view). |
| 648 |
| 649 The LICENSE file contains licenses of both Chromium and third-party |
| 650 libraries which gn_target depends on. """ |
| 651 |
| 652 third_party_dirs = FindThirdPartyDeps(gn_out_dir, gn_target) |
| 653 |
| 654 # Start with Chromium's LICENSE file. |
| 655 content = [_ReadFile('LICENSE')] |
| 656 |
| 657 # Add necessary third_party. |
| 658 for directory in sorted(third_party_dirs): |
| 659 metadata = ParseDir( |
| 660 directory, _REPOSITORY_ROOT, require_license_file=True) |
| 661 content.append('-' * 20) |
| 662 content.append(directory.split('/')[-1]) |
| 663 content.append('-' * 20) |
| 664 license_file = metadata['License File'] |
| 665 if license_file and license_file != NOT_SHIPPED: |
| 666 content.append(_ReadFile(license_file)) |
| 667 |
| 668 content_text = '\n'.join(content) |
| 669 |
| 670 if output_file: |
| 671 with open(output_file, 'w') as output: |
| 672 output.write(content_text) |
| 673 else: |
| 674 print content_text |
| 675 |
| 676 return True |
| 677 |
| 678 |
| 617 def main(): | 679 def main(): |
| 618 parser = argparse.ArgumentParser() | 680 parser = argparse.ArgumentParser() |
| 619 parser.add_argument('--file-template', | 681 parser.add_argument('--file-template', |
| 620 help='Template HTML to use for the license page.') | 682 help='Template HTML to use for the license page.') |
| 621 parser.add_argument('--entry-template', | 683 parser.add_argument('--entry-template', |
| 622 help='Template HTML to use for each license.') | 684 help='Template HTML to use for each license.') |
| 623 parser.add_argument('--target-os', | 685 parser.add_argument('--target-os', |
| 624 help='OS that this build is targeting.') | 686 help='OS that this build is targeting.') |
| 625 parser.add_argument('--gn-out-dir', | 687 parser.add_argument('--gn-out-dir', |
| 626 help='GN output directory for scanning dependencies.') | 688 help='GN output directory for scanning dependencies.') |
| 627 parser.add_argument('--gn-target', | 689 parser.add_argument('--gn-target', |
| 628 help='GN target to scan for dependencies.') | 690 help='GN target to scan for dependencies.') |
| 629 parser.add_argument('command', choices=['help', 'scan', 'credits']) | 691 parser.add_argument('command', |
| 692 choices=['help', 'scan', 'credits', 'license_file']) |
| 630 parser.add_argument('output_file', nargs='?') | 693 parser.add_argument('output_file', nargs='?') |
| 631 build_utils.AddDepfileOption(parser) | 694 build_utils.AddDepfileOption(parser) |
| 632 args = parser.parse_args() | 695 args = parser.parse_args() |
| 633 | 696 |
| 634 if args.command == 'scan': | 697 if args.command == 'scan': |
| 635 if not ScanThirdPartyDirs(): | 698 if not ScanThirdPartyDirs(): |
| 636 return 1 | 699 return 1 |
| 637 elif args.command == 'credits': | 700 elif args.command == 'credits': |
| 638 if not GenerateCredits(args.file_template, args.entry_template, | 701 if not GenerateCredits(args.file_template, args.entry_template, |
| 639 args.output_file, args.target_os, | 702 args.output_file, args.target_os, |
| 640 args.gn_out_dir, args.gn_target, args.depfile): | 703 args.gn_out_dir, args.gn_target, args.depfile): |
| 641 return 1 | 704 return 1 |
| 705 elif args.command == 'license_file': |
| 706 if not GenerateLicenseFile( |
| 707 args.output_file, args.gn_out_dir, args.gn_target): |
| 708 return 1 |
| 642 else: | 709 else: |
| 643 print __doc__ | 710 print __doc__ |
| 644 return 1 | 711 return 1 |
| 645 | 712 |
| 646 | 713 |
| 647 if __name__ == '__main__': | 714 if __name__ == '__main__': |
| 648 sys.exit(main()) | 715 sys.exit(main()) |
| OLD | NEW |