checklicenses.py revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
1#!/usr/bin/env python
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
4# found in the LICENSE file.
5
6"""Makes sure that all files contain proper licensing information."""
7
8
9import json
10import optparse
11import os.path
12import subprocess
13import sys
14
15
16def PrintUsage():
17  print """Usage: python checklicenses.py [--root <root>] [tocheck]
18  --root   Specifies the repository root. This defaults to "../.." relative
19           to the script file. This will be correct given the normal location
20           of the script in "<root>/tools/checklicenses".
21
22  --ignore-suppressions  Ignores path-specific license whitelist. Useful when
23                         trying to remove a suppression/whitelist entry.
24
25  tocheck  Specifies the directory, relative to root, to check. This defaults
26           to "." so it checks everything.
27
28Examples:
29  python checklicenses.py
30  python checklicenses.py --root ~/chromium/src third_party"""
31
32
33WHITELISTED_LICENSES = [
34    'Anti-Grain Geometry',
35    'Apache (v2.0)',
36    'Apache (v2.0) BSD (2 clause)',
37    'Apache (v2.0) GPL (v2)',
38    'Apple MIT',  # https://fedoraproject.org/wiki/Licensing/Apple_MIT_License
39    'APSL (v2)',
40    'APSL (v2) BSD (4 clause)',
41    'BSD',
42    'BSD (2 clause)',
43    'BSD (2 clause) ISC',
44    'BSD (2 clause) MIT/X11 (BSD like)',
45    'BSD (3 clause)',
46    'BSD (3 clause) GPL (v2)',
47    'BSD (3 clause) ISC',
48    'BSD (3 clause) LGPL (v2 or later)',
49    'BSD (3 clause) LGPL (v2.1 or later)',
50    'BSD (3 clause) MIT/X11 (BSD like)',
51    'BSD (4 clause)',
52    'BSD-like',
53
54    # TODO(phajdan.jr): Make licensecheck not print BSD-like twice.
55    'BSD-like MIT/X11 (BSD like)',
56
57    'BSL (v1.0)',
58    'FreeType (BSD like)',
59    'FreeType (BSD like) with patent clause',
60    'GPL (v2) LGPL (v2.1 or later)',
61    'GPL (v2 or later) with Bison parser exception',
62    'GPL (v2 or later) with libtool exception',
63    'GPL (v3 or later) with Bison parser exception',
64    'GPL with Bison parser exception',
65    'Independent JPEG Group License',
66    'ISC',
67    'LGPL (unversioned/unknown version)',
68    'LGPL (v2)',
69    'LGPL (v2 or later)',
70    'LGPL (v2.1)',
71    'LGPL (v2.1 or later)',
72    'LGPL (v3 or later)',
73    'MIT/X11 (BSD like)',
74    'MIT/X11 (BSD like) LGPL (v2.1 or later)',
75    'MPL (v1.0) LGPL (v2 or later)',
76    'MPL (v1.1)',
77    'MPL (v1.1) BSD (3 clause) GPL (v2) LGPL (v2.1 or later)',
78    'MPL (v1.1) BSD (3 clause) LGPL (v2.1 or later)',
79    'MPL (v1.1) BSD-like',
80    'MPL (v1.1) BSD-like GPL (unversioned/unknown version)',
81    'MPL (v1.1) BSD-like GPL (v2) LGPL (v2.1 or later)',
82    'MPL (v1.1) GPL (v2)',
83    'MPL (v1.1) GPL (v2) LGPL (v2 or later)',
84    'MPL (v1.1) GPL (v2) LGPL (v2.1 or later)',
85    'MPL (v1.1) GPL (unversioned/unknown version)',
86    'MPL (v1.1) LGPL (v2 or later)',
87    'MPL (v1.1) LGPL (v2.1 or later)',
88    'MPL (v2.0)',
89    'Ms-PL',
90    'Public domain',
91    'Public domain BSD',
92    'Public domain BSD (3 clause)',
93    'Public domain BSD-like',
94    'Public domain LGPL (v2.1 or later)',
95    'libpng',
96    'zlib/libpng',
97    'SGI Free Software License B',
98    'University of Illinois/NCSA Open Source License (BSD like)',
99    ('University of Illinois/NCSA Open Source License (BSD like) '
100     'MIT/X11 (BSD like)'),
101]
102
103
104PATH_SPECIFIC_WHITELISTED_LICENSES = {
105    'base/third_party/icu': [  # http://crbug.com/98087
106        'UNKNOWN',
107    ],
108
109    # http://code.google.com/p/google-breakpad/issues/detail?id=450
110    'breakpad/src': [
111        'UNKNOWN',
112    ],
113
114    'chrome/common/extensions/docs/examples': [  # http://crbug.com/98092
115        'UNKNOWN',
116    ],
117    'courgette/third_party/bsdiff_create.cc': [  # http://crbug.com/98095
118        'UNKNOWN',
119    ],
120    'native_client': [  # http://crbug.com/98099
121        'UNKNOWN',
122    ],
123    'native_client/toolchain': [
124        'BSD GPL (v2 or later)',
125        'BSD (2 clause) GPL (v2 or later)',
126        'BSD (3 clause) GPL (v2 or later)',
127        'BSL (v1.0) GPL',
128        'BSL (v1.0) GPL (v3.1)',
129        'GPL',
130        'GPL (unversioned/unknown version)',
131        'GPL (v2)',
132        'GPL (v2 or later)',
133        'GPL (v3.1)',
134        'GPL (v3 or later)',
135    ],
136    'third_party/WebKit': [
137        'UNKNOWN',
138    ],
139
140    # http://code.google.com/p/angleproject/issues/detail?id=217
141    'third_party/angle': [
142        'UNKNOWN',
143    ],
144
145    # http://crbug.com/222828
146    # http://bugs.python.org/issue17514
147    'third_party/chromite/third_party/argparse.py': [
148        'UNKNOWN',
149    ],
150
151    # http://crbug.com/326117
152    # https://bitbucket.org/chrisatlee/poster/issue/21
153    'third_party/chromite/third_party/poster': [
154        'UNKNOWN',
155    ],
156
157    # http://crbug.com/333508
158    'third_party/clang_format/script': [
159        'UNKNOWN',
160    ],
161
162    'third_party/devscripts': [
163        'GPL (v2 or later)',
164    ],
165    'third_party/expat/files/lib': [  # http://crbug.com/98121
166        'UNKNOWN',
167    ],
168    'third_party/ffmpeg': [
169        'GPL',
170        'GPL (v2)',
171        'GPL (v2 or later)',
172        'UNKNOWN',  # http://crbug.com/98123
173    ],
174    'third_party/fontconfig': [
175        # https://bugs.freedesktop.org/show_bug.cgi?id=73401
176        'UNKNOWN',
177    ],
178    'third_party/freetype2': [ # http://crbug.com/177319
179        'UNKNOWN',
180    ],
181    'third_party/hunspell': [  # http://crbug.com/98134
182        'UNKNOWN',
183    ],
184    'third_party/iccjpeg': [  # http://crbug.com/98137
185        'UNKNOWN',
186    ],
187    'third_party/icu': [  # http://crbug.com/98301
188        'UNKNOWN',
189    ],
190    'third_party/lcov': [  # http://crbug.com/98304
191        'UNKNOWN',
192    ],
193    'third_party/lcov/contrib/galaxy/genflat.pl': [
194        'GPL (v2 or later)',
195    ],
196    'third_party/libc++/trunk/include/support/solaris': [
197        # http://llvm.org/bugs/show_bug.cgi?id=18291
198        'UNKNOWN',
199    ],
200    'third_party/libc++/trunk/src/support/solaris/xlocale.c': [
201        # http://llvm.org/bugs/show_bug.cgi?id=18291
202        'UNKNOWN',
203    ],
204    'third_party/libc++/trunk/test': [
205        # http://llvm.org/bugs/show_bug.cgi?id=18291
206        'UNKNOWN',
207    ],
208    'third_party/libevent': [  # http://crbug.com/98309
209        'UNKNOWN',
210    ],
211    'third_party/libjingle/source/talk': [  # http://crbug.com/98310
212        'UNKNOWN',
213    ],
214    'third_party/libjpeg_turbo': [  # http://crbug.com/98314
215        'UNKNOWN',
216    ],
217    'third_party/libpng': [  # http://crbug.com/98318
218        'UNKNOWN',
219    ],
220
221    # The following files lack license headers, but are trivial.
222    'third_party/libusb/src/libusb/os/poll_posix.h': [
223        'UNKNOWN',
224    ],
225
226    'third_party/libvpx/source': [  # http://crbug.com/98319
227        'UNKNOWN',
228    ],
229    'third_party/libxml': [
230        'UNKNOWN',
231    ],
232    'third_party/libxslt': [
233        'UNKNOWN',
234    ],
235    'third_party/lzma_sdk': [
236        'UNKNOWN',
237    ],
238    'third_party/mesa/src': [
239        'GPL (v2)',
240        'GPL (v3 or later)',
241        'MIT/X11 (BSD like) GPL (v3 or later) with Bison parser exception',
242        'UNKNOWN',  # http://crbug.com/98450
243    ],
244    'third_party/modp_b64': [
245        'UNKNOWN',
246    ],
247    'third_party/openmax_dl/dl' : [
248        'Khronos Group',
249    ],
250    'third_party/openssl': [  # http://crbug.com/98451
251        'UNKNOWN',
252    ],
253    'third_party/ots/tools/ttf-checksum.py': [  # http://code.google.com/p/ots/issues/detail?id=2
254        'UNKNOWN',
255    ],
256    'third_party/molokocacao': [  # http://crbug.com/98453
257        'UNKNOWN',
258    ],
259    'third_party/npapi/npspy': [
260        'UNKNOWN',
261    ],
262    'third_party/ocmock/OCMock': [  # http://crbug.com/98454
263        'UNKNOWN',
264    ],
265    'third_party/ply/__init__.py': [
266        'UNKNOWN',
267    ],
268    'third_party/protobuf': [  # http://crbug.com/98455
269        'UNKNOWN',
270    ],
271
272    # http://crbug.com/222831
273    # https://bitbucket.org/eliben/pyelftools/issue/12
274    'third_party/pyelftools': [
275        'UNKNOWN',
276    ],
277
278    'third_party/scons-2.0.1/engine/SCons': [  # http://crbug.com/98462
279        'UNKNOWN',
280    ],
281    'third_party/simplejson': [
282        'UNKNOWN',
283    ],
284    'third_party/skia': [  # http://crbug.com/98463
285        'UNKNOWN',
286    ],
287    'third_party/snappy/src': [  # http://crbug.com/98464
288        'UNKNOWN',
289    ],
290    'third_party/smhasher/src': [  # http://crbug.com/98465
291        'UNKNOWN',
292    ],
293    'third_party/speech-dispatcher/libspeechd.h': [
294        'GPL (v2 or later)',
295    ],
296    'third_party/sqlite': [
297        'UNKNOWN',
298    ],
299
300    # https://code.google.com/p/colorama/issues/detail?id=44
301    'tools/swarming_client/third_party/colorama': [
302        'UNKNOWN',
303    ],
304
305    # http://crbug.com/334668
306    # MIT license.
307    'tools/swarming_client/third_party/httplib2': [
308        'UNKNOWN',
309    ],
310
311    # http://crbug.com/334668
312    # Apache v2.0.
313    'tools/swarming_client/third_party/oauth2client': [
314        'UNKNOWN',
315    ],
316
317    # https://github.com/kennethreitz/requests/issues/1610
318    'tools/swarming_client/third_party/requests': [
319        'UNKNOWN',
320    ],
321
322    'third_party/swig/Lib/linkruntime.c': [  # http://crbug.com/98585
323        'UNKNOWN',
324    ],
325    'third_party/talloc': [
326        'GPL (v3 or later)',
327        'UNKNOWN',  # http://crbug.com/98588
328    ],
329    'third_party/tcmalloc': [
330        'UNKNOWN',  # http://crbug.com/98589
331    ],
332    'third_party/tlslite': [
333        'UNKNOWN',
334    ],
335    'third_party/webdriver': [  # http://crbug.com/98590
336        'UNKNOWN',
337    ],
338
339    # https://github.com/html5lib/html5lib-python/issues/125
340    # https://github.com/KhronosGroup/WebGL/issues/435
341    'third_party/webgl/src': [
342        'UNKNOWN',
343    ],
344
345    'third_party/webrtc': [  # http://crbug.com/98592
346        'UNKNOWN',
347    ],
348    'third_party/xdg-utils': [  # http://crbug.com/98593
349        'UNKNOWN',
350    ],
351    'third_party/yasm/source': [  # http://crbug.com/98594
352        'UNKNOWN',
353    ],
354    'third_party/zlib/contrib/minizip': [
355        'UNKNOWN',
356    ],
357    'third_party/zlib/trees.h': [
358        'UNKNOWN',
359    ],
360    'tools/emacs': [  # http://crbug.com/98595
361        'UNKNOWN',
362    ],
363    'tools/gyp/test': [
364        'UNKNOWN',
365    ],
366    'tools/python/google/__init__.py': [
367        'UNKNOWN',
368    ],
369    'tools/stats_viewer/Properties/AssemblyInfo.cs': [
370        'UNKNOWN',
371    ],
372    'tools/symsrc/pefile.py': [
373        'UNKNOWN',
374    ],
375    'tools/telemetry/third_party/pyserial': [
376        # https://sourceforge.net/p/pyserial/feature-requests/35/
377        'UNKNOWN',
378    ],
379    'v8/test/cctest': [  # http://crbug.com/98597
380        'UNKNOWN',
381    ],
382}
383
384
385def check_licenses(options, args):
386  # Figure out which directory we have to check.
387  if len(args) == 0:
388    # No directory to check specified, use the repository root.
389    start_dir = options.base_directory
390  elif len(args) == 1:
391    # Directory specified. Start here. It's supposed to be relative to the
392    # base directory.
393    start_dir = os.path.abspath(os.path.join(options.base_directory, args[0]))
394  else:
395    # More than one argument, we don't handle this.
396    PrintUsage()
397    return 1
398
399  print "Using base directory:", options.base_directory
400  print "Checking:", start_dir
401  print
402
403  licensecheck_path = os.path.abspath(os.path.join(options.base_directory,
404                                                   'third_party',
405                                                   'devscripts',
406                                                   'licensecheck.pl'))
407
408  licensecheck = subprocess.Popen([licensecheck_path,
409                                   '-l', '100',
410                                   '-r', start_dir],
411                                  stdout=subprocess.PIPE,
412                                  stderr=subprocess.PIPE)
413  stdout, stderr = licensecheck.communicate()
414  if options.verbose:
415    print '----------- licensecheck stdout -----------'
416    print stdout
417    print '--------- end licensecheck stdout ---------'
418  if licensecheck.returncode != 0 or stderr:
419    print '----------- licensecheck stderr -----------'
420    print stderr
421    print '--------- end licensecheck stderr ---------'
422    print "\nFAILED\n"
423    return 1
424
425  used_suppressions = set()
426  errors = []
427
428  for line in stdout.splitlines():
429    filename, license = line.split(':', 1)
430    filename = os.path.relpath(filename.strip(), options.base_directory)
431
432    # All files in the build output directory are generated one way or another.
433    # There's no need to check them.
434    if filename.startswith('out/'):
435      continue
436
437    # For now we're just interested in the license.
438    license = license.replace('*No copyright*', '').strip()
439
440    # Skip generated files.
441    if 'GENERATED FILE' in license:
442      continue
443
444    if license in WHITELISTED_LICENSES:
445      continue
446
447    if not options.ignore_suppressions:
448      matched_prefixes = [
449          prefix for prefix in PATH_SPECIFIC_WHITELISTED_LICENSES
450          if filename.startswith(prefix) and
451          license in PATH_SPECIFIC_WHITELISTED_LICENSES[prefix]]
452      if matched_prefixes:
453        used_suppressions.update(set(matched_prefixes))
454        continue
455
456    errors.append({'filename': filename, 'license': license})
457
458  if options.json:
459    with open(options.json, 'w') as f:
460      json.dump(errors, f)
461
462  if errors:
463    for error in errors:
464      print "'%s' has non-whitelisted license '%s'" % (
465          error['filename'], error['license'])
466    print "\nFAILED\n"
467    print "Please read",
468    print "http://www.chromium.org/developers/adding-3rd-party-libraries"
469    print "for more info how to handle the failure."
470    print
471    print "Please respect OWNERS of checklicenses.py. Changes violating"
472    print "this requirement may be reverted."
473
474    # Do not print unused suppressions so that above message is clearly
475    # visible and gets proper attention. Too much unrelated output
476    # would be distracting and make the important points easier to miss.
477
478    return 1
479
480  print "\nSUCCESS\n"
481
482  if not len(args):
483    unused_suppressions = set(
484        PATH_SPECIFIC_WHITELISTED_LICENSES.iterkeys()).difference(
485            used_suppressions)
486    if unused_suppressions:
487      print "\nNOTE: unused suppressions detected:\n"
488      print '\n'.join(unused_suppressions)
489
490  return 0
491
492
493def main():
494  default_root = os.path.abspath(
495      os.path.join(os.path.dirname(__file__), '..', '..'))
496  option_parser = optparse.OptionParser()
497  option_parser.add_option('--root', default=default_root,
498                           dest='base_directory',
499                           help='Specifies the repository root. This defaults '
500                           'to "../.." relative to the script file, which '
501                           'will normally be the repository root.')
502  option_parser.add_option('-v', '--verbose', action='store_true',
503                           default=False, help='Print debug logging')
504  option_parser.add_option('--ignore-suppressions',
505                           action='store_true',
506                           default=False,
507                           help='Ignore path-specific license whitelist.')
508  option_parser.add_option('--json', help='Path to JSON output file')
509  options, args = option_parser.parse_args()
510  return check_licenses(options, args)
511
512
513if '__main__' == __name__:
514  sys.exit(main())
515