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