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