• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    # http://crbug.com/334668
301    # MIT license.
302    'tools/swarming_client/third_party/httplib2': [
303        'UNKNOWN',
304    ],
305
306    # http://crbug.com/334668
307    # Apache v2.0.
308    'tools/swarming_client/third_party/oauth2client': [
309        'UNKNOWN',
310    ],
311
312    # https://github.com/kennethreitz/requests/issues/1610
313    'tools/swarming_client/third_party/requests': [
314        'UNKNOWN',
315    ],
316
317    'third_party/swig/Lib/linkruntime.c': [  # http://crbug.com/98585
318        'UNKNOWN',
319    ],
320    'third_party/talloc': [
321        'GPL (v3 or later)',
322        'UNKNOWN',  # http://crbug.com/98588
323    ],
324    'third_party/tcmalloc': [
325        'UNKNOWN',  # http://crbug.com/98589
326    ],
327    'third_party/tlslite': [
328        'UNKNOWN',
329    ],
330    'third_party/webdriver': [  # http://crbug.com/98590
331        'UNKNOWN',
332    ],
333
334    # https://github.com/html5lib/html5lib-python/issues/125
335    # https://github.com/KhronosGroup/WebGL/issues/435
336    'third_party/webgl/src': [
337        'UNKNOWN',
338    ],
339
340    'third_party/webrtc': [  # http://crbug.com/98592
341        'UNKNOWN',
342    ],
343    'third_party/xdg-utils': [  # http://crbug.com/98593
344        'UNKNOWN',
345    ],
346    'third_party/yasm/source': [  # http://crbug.com/98594
347        'UNKNOWN',
348    ],
349    'third_party/zlib/contrib/minizip': [
350        'UNKNOWN',
351    ],
352    'third_party/zlib/trees.h': [
353        'UNKNOWN',
354    ],
355    'tools/emacs': [  # http://crbug.com/98595
356        'UNKNOWN',
357    ],
358    'tools/gyp/test': [
359        'UNKNOWN',
360    ],
361    'tools/python/google/__init__.py': [
362        'UNKNOWN',
363    ],
364    'tools/stats_viewer/Properties/AssemblyInfo.cs': [
365        'UNKNOWN',
366    ],
367    'tools/symsrc/pefile.py': [
368        'UNKNOWN',
369    ],
370    'tools/telemetry/third_party/pyserial': [
371        # https://sourceforge.net/p/pyserial/feature-requests/35/
372        'UNKNOWN',
373    ],
374    'v8/test/cctest': [  # http://crbug.com/98597
375        'UNKNOWN',
376    ],
377}
378
379
380def check_licenses(options, args):
381  # Figure out which directory we have to check.
382  if len(args) == 0:
383    # No directory to check specified, use the repository root.
384    start_dir = options.base_directory
385  elif len(args) == 1:
386    # Directory specified. Start here. It's supposed to be relative to the
387    # base directory.
388    start_dir = os.path.abspath(os.path.join(options.base_directory, args[0]))
389  else:
390    # More than one argument, we don't handle this.
391    PrintUsage()
392    return 1
393
394  print "Using base directory:", options.base_directory
395  print "Checking:", start_dir
396  print
397
398  licensecheck_path = os.path.abspath(os.path.join(options.base_directory,
399                                                   'third_party',
400                                                   'devscripts',
401                                                   'licensecheck.pl'))
402
403  licensecheck = subprocess.Popen([licensecheck_path,
404                                   '-l', '100',
405                                   '-r', start_dir],
406                                  stdout=subprocess.PIPE,
407                                  stderr=subprocess.PIPE)
408  stdout, stderr = licensecheck.communicate()
409  if options.verbose:
410    print '----------- licensecheck stdout -----------'
411    print stdout
412    print '--------- end licensecheck stdout ---------'
413  if licensecheck.returncode != 0 or stderr:
414    print '----------- licensecheck stderr -----------'
415    print stderr
416    print '--------- end licensecheck stderr ---------'
417    print "\nFAILED\n"
418    return 1
419
420  used_suppressions = set()
421  errors = []
422
423  for line in stdout.splitlines():
424    filename, license = line.split(':', 1)
425    filename = os.path.relpath(filename.strip(), options.base_directory)
426
427    # All files in the build output directory are generated one way or another.
428    # There's no need to check them.
429    if filename.startswith('out/'):
430      continue
431
432    # For now we're just interested in the license.
433    license = license.replace('*No copyright*', '').strip()
434
435    # Skip generated files.
436    if 'GENERATED FILE' in license:
437      continue
438
439    if license in WHITELISTED_LICENSES:
440      continue
441
442    if not options.ignore_suppressions:
443      matched_prefixes = [
444          prefix for prefix in PATH_SPECIFIC_WHITELISTED_LICENSES
445          if filename.startswith(prefix) and
446          license in PATH_SPECIFIC_WHITELISTED_LICENSES[prefix]]
447      if matched_prefixes:
448        used_suppressions.update(set(matched_prefixes))
449        continue
450
451    errors.append({'filename': filename, 'license': license})
452
453  if options.json:
454    with open(options.json, 'w') as f:
455      json.dump(errors, f)
456
457  if errors:
458    for error in errors:
459      print "'%s' has non-whitelisted license '%s'" % (
460          error['filename'], error['license'])
461    print "\nFAILED\n"
462    print "Please read",
463    print "http://www.chromium.org/developers/adding-3rd-party-libraries"
464    print "for more info how to handle the failure."
465    print
466    print "Please respect OWNERS of checklicenses.py. Changes violating"
467    print "this requirement may be reverted."
468
469    # Do not print unused suppressions so that above message is clearly
470    # visible and gets proper attention. Too much unrelated output
471    # would be distracting and make the important points easier to miss.
472
473    return 1
474
475  print "\nSUCCESS\n"
476
477  if not len(args):
478    unused_suppressions = set(
479        PATH_SPECIFIC_WHITELISTED_LICENSES.iterkeys()).difference(
480            used_suppressions)
481    if unused_suppressions:
482      print "\nNOTE: unused suppressions detected:\n"
483      print '\n'.join(unused_suppressions)
484
485  return 0
486
487
488def main():
489  default_root = os.path.abspath(
490      os.path.join(os.path.dirname(__file__), '..', '..'))
491  option_parser = optparse.OptionParser()
492  option_parser.add_option('--root', default=default_root,
493                           dest='base_directory',
494                           help='Specifies the repository root. This defaults '
495                           'to "../.." relative to the script file, which '
496                           'will normally be the repository root.')
497  option_parser.add_option('-v', '--verbose', action='store_true',
498                           default=False, help='Print debug logging')
499  option_parser.add_option('--ignore-suppressions',
500                           action='store_true',
501                           default=False,
502                           help='Ignore path-specific license whitelist.')
503  option_parser.add_option('--json', help='Path to JSON output file')
504  options, args = option_parser.parse_args()
505  return check_licenses(options, args)
506
507
508if '__main__' == __name__:
509  sys.exit(main())
510