aboutsummaryrefslogtreecommitdiff
path: root/tools_webrtc/libs/generate_licenses.py
blob: cbb1514d3cb852d834c4f1a07d1f4619ad68fdaa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#!/usr/bin/env python3

#  Copyright 2016 The WebRTC project authors. All Rights Reserved.
#
#  Use of this source code is governed by a BSD-style license
#  that can be found in the LICENSE file in the root of the source
#  tree. An additional intellectual property rights grant can be found
#  in the file PATENTS.  All contributing project authors may
#  be found in the AUTHORS file in the root of the source tree.
"""Generates license markdown for a prebuilt version of WebRTC.

Licenses are taken from dependent libraries which are determined by
GN desc command `gn desc` on all targets specified via `--target` argument.

One can see all dependencies by invoking this command:
$ gn.py desc --all --format=json <out_directory> <target> | python -m json.tool
(see "deps" subarray)

Libraries are mapped to licenses via LIB_TO_LICENSES_DICT dictionary.

"""

import sys

import argparse
import json
import logging
import os
import re
import subprocess
try:
    # python 3.2+
    from html import escape
except ImportError:
    from cgi import escape

# Third_party library to licences mapping. Keys are names of the libraries
# (right after the `third_party/` prefix)
LIB_TO_LICENSES_DICT = {
    'abseil-cpp': ['third_party/abseil-cpp/LICENSE'],
    'android_ndk': ['third_party/android_ndk/NOTICE'],
    'android_sdk': ['third_party/android_sdk/LICENSE'],
    'auto': [
        'third_party/android_deps/libs/'
        'com_google_auto_service_auto_service/LICENSE'
    ],
    'bazel': ['third_party/bazel/LICENSE'],
    'boringssl': ['third_party/boringssl/src/LICENSE'],
    'crc32c': ['third_party/crc32c/src/LICENSE'],
    'errorprone': [
        'third_party/android_deps/libs/'
        'com_google_errorprone_error_prone_core/LICENSE'
    ],
    'fiat': ['third_party/boringssl/src/third_party/fiat/LICENSE'],
    'guava': ['third_party/android_deps/libs/com_google_guava_guava/LICENSE'],
    'ijar': ['third_party/ijar/LICENSE'],
    'jsoncpp': ['third_party/jsoncpp/LICENSE'],
    'libaom': ['third_party/libaom/source/libaom/LICENSE'],
    'libc++': ['buildtools/third_party/libc++/trunk/LICENSE.TXT'],
    'libc++abi': ['buildtools/third_party/libc++abi/trunk/LICENSE.TXT'],
    'libevent': ['base/third_party/libevent/LICENSE'],
    'libjpeg_turbo': ['third_party/libjpeg_turbo/LICENSE.md'],
    'libsrtp': ['third_party/libsrtp/LICENSE'],
    'libvpx': ['third_party/libvpx/source/libvpx/LICENSE'],
    'libyuv': ['third_party/libyuv/LICENSE'],
    'nasm': ['third_party/nasm/LICENSE'],
    'opus': ['third_party/opus/src/COPYING'],
    'pffft': ['third_party/pffft/LICENSE'],
    'protobuf': ['third_party/protobuf/LICENSE'],
    'rnnoise': ['third_party/rnnoise/COPYING'],
    'usrsctp': ['third_party/usrsctp/LICENSE'],
    'webrtc': ['LICENSE'],
    'zlib': ['third_party/zlib/LICENSE'],
    'base64': ['rtc_base/third_party/base64/LICENSE'],
    'sigslot': ['rtc_base/third_party/sigslot/LICENSE'],
    'portaudio': ['modules/third_party/portaudio/LICENSE'],
    'fft': ['modules/third_party/fft/LICENSE'],
    'g711': ['modules/third_party/g711/LICENSE'],
    'g722': ['modules/third_party/g722/LICENSE'],
    'ooura': ['common_audio/third_party/ooura/LICENSE'],
    'spl_sqrt_floor': ['common_audio/third_party/spl_sqrt_floor/LICENSE'],

    # TODO(bugs.webrtc.org/1110): Remove this hack. This is not a lib.
    # For some reason it is listed as so in _GetThirdPartyLibraries.
    'android_deps': [],
    # This is not a library but a collection of libraries.
    'androidx': [],

    # Compile time dependencies, no license needed:
    'yasm': [],
    'ow2_asm': [],
    'jdk': [],
}

# Third_party library _regex_ to licences mapping. Keys are regular expression
# with names of the libraries (right after the `third_party/` prefix)
LIB_REGEX_TO_LICENSES_DICT = {
    'android_deps:android_support_annotations.*': [
        'third_party/android_deps/libs/' +
        'com_android_support_support_annotations/LICENSE'
    ],

    # Internal dependencies, licenses are already included by other dependencies
    'android_deps:com_android_support_support_annotations.*': [],
}


def FindSrcDirPath():
    """Returns the abs path to the src/ dir of the project."""
    src_dir = os.path.dirname(os.path.abspath(__file__))
    while os.path.basename(src_dir) != 'src':
        src_dir = os.path.normpath(os.path.join(src_dir, os.pardir))
    return src_dir


SCRIPT_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
WEBRTC_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir))
SRC_DIR = FindSrcDirPath()
sys.path.append(os.path.join(SRC_DIR, 'build'))
import find_depot_tools

THIRD_PARTY_LIB_SIMPLE_NAME_REGEX = r'^.*/third_party/([\w\-+]+).*$'
THIRD_PARTY_LIB_REGEX_TEMPLATE = r'^.*/third_party/%s$'


class LicenseBuilder(object):
    def __init__(self,
                 buildfile_dirs,
                 targets,
                 lib_to_licenses_dict=None,
                 lib_regex_to_licenses_dict=None):
        if lib_to_licenses_dict is None:
            lib_to_licenses_dict = LIB_TO_LICENSES_DICT

        if lib_regex_to_licenses_dict is None:
            lib_regex_to_licenses_dict = LIB_REGEX_TO_LICENSES_DICT

        self.buildfile_dirs = buildfile_dirs
        self.targets = targets
        self.lib_to_licenses_dict = lib_to_licenses_dict
        self.lib_regex_to_licenses_dict = lib_regex_to_licenses_dict

        self.common_licenses_dict = self.lib_to_licenses_dict.copy()
        self.common_licenses_dict.update(self.lib_regex_to_licenses_dict)

    @staticmethod
    def _ParseLibraryName(dep):
        """Returns library name after third_party

    Input one of:
    //a/b/third_party/libname:c
    //a/b/third_party/libname:c(//d/e/f:g)
    //a/b/third_party/libname/c:d(//e/f/g:h)

    Outputs libname or None if this is not a third_party dependency.
    """
        groups = re.match(THIRD_PARTY_LIB_SIMPLE_NAME_REGEX, dep)
        return groups.group(1) if groups else None

    def _ParseLibrary(self, dep):
        """Returns library simple or regex name that matches `dep` after third_party

    This method matches `dep` dependency against simple names in
    LIB_TO_LICENSES_DICT and regular expression names in
    LIB_REGEX_TO_LICENSES_DICT keys

    Outputs matched dict key or None if this is not a third_party dependency.
    """
        libname = LicenseBuilder._ParseLibraryName(dep)

        for lib_regex in self.lib_regex_to_licenses_dict:
            if re.match(THIRD_PARTY_LIB_REGEX_TEMPLATE % lib_regex, dep):
                return lib_regex

        return libname

    @staticmethod
    def _RunGN(buildfile_dir, target):
        cmd = [
            sys.executable,
            os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gn.py'),
            'desc',
            '--all',
            '--format=json',
            os.path.abspath(buildfile_dir),
            target,
        ]
        logging.debug('Running: %r', cmd)
        output_json = subprocess.check_output(cmd, cwd=WEBRTC_ROOT).decode('UTF-8')
        logging.debug('Output: %s', output_json)
        return output_json

    def _GetThirdPartyLibraries(self, buildfile_dir, target):
        output = json.loads(LicenseBuilder._RunGN(buildfile_dir, target))
        libraries = set()
        for described_target in output.values():
            third_party_libs = (self._ParseLibrary(dep)
                                for dep in described_target['deps'])
            libraries |= set(lib for lib in third_party_libs if lib)
        return libraries

    def GenerateLicenseText(self, output_dir):
        # Get a list of third_party libs from gn. For fat libraries we must consider
        # all architectures, hence the multiple buildfile directories.
        third_party_libs = set()
        for buildfile in self.buildfile_dirs:
            for target in self.targets:
                third_party_libs |= self._GetThirdPartyLibraries(
                    buildfile, target)
        assert len(third_party_libs) > 0

        missing_licenses = third_party_libs - set(
            self.common_licenses_dict.keys())
        if missing_licenses:
            error_msg = 'Missing licenses for following third_party targets: %s' % \
                        ', '.join(sorted(missing_licenses))
            logging.error(error_msg)
            raise Exception(error_msg)

        # Put webrtc at the front of the list.
        license_libs = sorted(third_party_libs)
        license_libs.insert(0, 'webrtc')

        logging.info('List of licenses: %s', ', '.join(license_libs))

        # Generate markdown.
        output_license_file = open(os.path.join(output_dir, 'LICENSE.md'),
                                   'w+')
        for license_lib in license_libs:
            if len(self.common_licenses_dict[license_lib]) == 0:
                logging.info(
                    'Skipping compile time or internal dependency: %s',
                    license_lib)
                continue  # Compile time dependency

            output_license_file.write('# %s\n' % license_lib)
            output_license_file.write('```\n')
            for path in self.common_licenses_dict[license_lib]:
                license_path = os.path.join(WEBRTC_ROOT, path)
                with open(license_path, 'r') as license_file:
                    license_text = escape(license_file.read(), quote=True)
                    output_license_file.write(license_text)
                    output_license_file.write('\n')
            output_license_file.write('```\n\n')

        output_license_file.close()


def main():
    parser = argparse.ArgumentParser(description='Generate WebRTC LICENSE.md')
    parser.add_argument('--verbose',
                        action='store_true',
                        default=False,
                        help='Debug logging.')
    parser.add_argument('--target',
                        required=True,
                        action='append',
                        default=[],
                        help='Name of the GN target to generate a license for')
    parser.add_argument('output_dir',
                        help='Directory to output LICENSE.md to.')
    parser.add_argument('buildfile_dirs',
                        nargs='+',
                        help='Directories containing gn generated ninja files')
    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)

    builder = LicenseBuilder(args.buildfile_dirs, args.target)
    builder.GenerateLicenseText(args.output_dir)


if __name__ == '__main__':
    sys.exit(main())