1#!/usr/bin/env python3
2"""
3This script can be used to generate LIC_FILES_CHKSUM in chromium.inc.
4
5It uses Chromium's own tools/licenses/licenses.py script to scan for third_party
6directories and license files. This means its output is generated on a
7best-effort basis, as some directories are non-compliant upstream or may not be
8found. It might also include directories which are not used in a
9Yocto/OpenEmbedded build.
10"""
11
12import argparse
13import hashlib
14import os
15import sys
16
17# These are directories that are known to cause licenses.LicenseError to be
18# thrown because but should not cause a failure in this script for
19# different reasons.
20SKIPPED_DIRECTORIES = (
21    # These directories are not part of the Chromium tarballs (upstream's
22    # export_tarball.py declares them "non-essential" or plain test
23    # directories, and their README.chromium in the git repositories mark
24    # them as NOT_SHIPPED).
25    'chrome/test/data',
26    'third_party/hunspell_dictionaries',
27
28    # android_protobuf is checked out and used only in Android builds, so a
29    # LicenseError will be thrown because README.chromium will point to a
30    # file that is not present in a Chromium tarball.
31    'third_party/android_protobuf',
32
33    # Starting with M61, Chromium is shipping its own pinned version of
34    # depot_tools. It's only part of the build and the directory structure
35    # does not follow the standard. Skip it.
36    'third_party/depot_tools',
37
38    # M63 and later: we do not consume win_build_output.
39    'third_party/win_build_output',
40
41    # M67: third_party/fuchsia-sdk has no LICENSE file. This is not used in
42    # Linux builds though.
43    # https://bugs.chromium.org/p/chromium/issues/detail?id=847821
44    'third_party/fuchsia-sdk',
45)
46
47
48def find_chromium_licenses(chromium_root):
49    """Look for license files in a Chromium checkout and return a set with all
50    files that are actually shipped and used in the final Chromium binary."""
51    try:
52        import licenses
53    except ImportError:
54        raise ImportError('Failed to import licenses.py. Make sure %s '
55                          'contains tools/licenses/licenses.py.' % chromium_root)
56
57    # Make sure the main Chromium LICENSE file is always present.
58    license_files = set([os.path.join(chromium_root, 'LICENSE')])
59
60    for d in licenses.FindThirdPartyDirs(licenses.PRUNE_PATHS, chromium_root):
61        if d in SKIPPED_DIRECTORIES:
62            continue
63        try:
64            metadata = licenses.ParseDir(d, chromium_root)
65        except licenses.LicenseError as e:
66            print('Exception in directory %s: %s' % (d, e))
67            if input('Ignore (y)? ') == 'y':
68                continue
69            raise
70        # We are not interested in licenses for projects that are not marked as
71        # used in the final product (ie. they might be optional development
72        # aids, or only used in a build).
73        if metadata['License File'] != licenses.NOT_SHIPPED:
74            license_files.add(metadata['License File'])
75    return license_files
76
77
78def print_license_list(chromium_root, output_file):
79    """Print a list of Chromium license paths and checksums in a format
80    suitable for use in a Yocto recipe."""
81    licenses = {}
82    for license_file in find_chromium_licenses(chromium_root):
83        with open(license_file, 'rb') as file_handle:
84            license_hash = hashlib.md5(file_handle.read()).hexdigest()
85        license_relpath = os.path.relpath(license_file, chromium_root)
86        licenses[license_relpath] = license_hash
87    with open(output_file, 'w') as out:
88        out.write('LIC_FILES_CHKSUM = "\\\n')
89        for f in sorted(licenses):
90            out.write('    file://${S}/%s;md5=%s \\\n' % (f, licenses[f]))
91        out.write('    "\n')
92
93
94if __name__ == '__main__':
95    parser = argparse.ArgumentParser()
96    parser.add_argument('chromium_root',
97                        help='Path to the root directory of a Chromium '
98                        'checkout or extracted tarball.')
99    parser.add_argument('output_file',
100                        help='File to write the output to (it will be '
101                        'overwritten)')
102    args = parser.parse_args()
103
104    tools_licenses_dir = os.path.join(args.chromium_root, 'tools/licenses')
105    if not os.path.isdir(tools_licenses_dir):
106        print('%s does not look like a valid directory.' % tools_licenses_dir)
107        sys.exit(1)
108    sys.path = [tools_licenses_dir] + sys.path
109
110    print_license_list(args.chromium_root, args.output_file)
111