Home | History | Annotate | Download | only in checklicenses
      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 
      9 import json
     10 import optparse
     11 import os.path
     12 import subprocess
     13 import sys
     14 
     15 
     16 def 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 
     28 Examples:
     29   python checklicenses.py
     30   python checklicenses.py --root ~/chromium/src third_party"""
     31 
     32 
     33 WHITELISTED_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 
    104 PATH_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 
    380 def 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 
    488 def 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 
    508 if '__main__' == __name__:
    509   sys.exit(main())
    510