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