#!/usr/bin/env python3 # Copyright (c) 2013 The Chromium Authors. All rights reserved. # # SPDX-License-Identifier: BSD-3-Clause # """Makes sure that all files contain proper licensing information.""" import optparse import os.path import subprocess import sys def PrintUsage(): print("""Usage: python checklicenses.py [--root ] [tocheck] --root Specifies the repository root. This defaults to ".." relative to the script file. This will be correct given the normal location of the script in "/tools". --ignore-suppressions Ignores path-specific allowed license. Useful when trying to remove a suppression/allowed entry. --list-allowed Print a list of allowed licenses and exit. tocheck Specifies the directory, relative to root, to check. This defaults to "." so it checks everything. Examples: python checklicenses.py python checklicenses.py --root ~/chromium/src third_party""") ALLOWED_LICENSES = [ 'BSD (1 clause)', 'BSD (2 clause)', 'BSD (2 clause) GPL (v2 or later)', 'BSD (3 clause)', 'BSD (4 clause (University of California-Specific))', 'GPL (v2 or later)', 'ISC', 'ISC GPL (v2 or later)', 'LGPL (v2 or later)', 'LGPL (v2.1 or later)', 'MIT/X11 (BSD like)', 'Public domain', 'Public domain GPL (v2 or later)', 'Public domain MIT/X11 (BSD like)', 'zlib/libpng', 'zlib/libpng GPL (v2 or later)', ] PATH_SPECIFIC_ALLOWED_LICENSES = { # Some of the libpcap include files (including pcap.h) have the # 4-clause BSD license with an advertising clause for the Computer # Systems Engineering Group at Lawrence Berkeley Laboratory. # We have always distributed packages including the headers, # so if this is a problem it's one even if these files aren't # copied into our repository. 'libpcap/pcap': [ 'BSD (4 clause)', ], 'wsutil/strnatcmp.c': [ 'Zlib', ], 'wsutil/strnatcmp.h': [ 'Zlib', ], 'resources/protocols/dtds': [ 'UNKNOWN', ], 'resources/protocols/diameter/dictionary.dtd': [ 'UNKNOWN', ], 'resources/protocols/wimaxasncp/dictionary.dtd': [ 'UNKNOWN', ], 'doc/': [ 'UNKNOWN', ], 'doc/custom_layer_chm.xsl': [ 'UNKNOWN', ], 'doc/custom_layer_single_html.xsl': [ 'UNKNOWN', ], 'fix': [ 'UNKNOWN', ], 'wsutil/g711.c': [ 'UNKNOWN', ], 'packaging/macosx': [ 'UNKNOWN', ], 'epan/except.c': [ 'UNKNOWN', ], 'epan/except.h': [ 'UNKNOWN', ], # Generated header files by lex/lemon/whatever 'epan/dtd_grammar.h': [ 'UNKNOWN', ], 'epan/dfilter/grammar.h': [ 'UNKNOWN', ], 'epan/dfilter/grammar.c': [ 'UNKNOWN', ], 'epan/dissectors/packet-ieee80211-radiotap-iter.': [ # Using ISC license only 'ISC GPL (v2)' ], # Mentions BSD-3-clause twice due to embedding of code: 'epan/dissectors/packet-communityid.c': [ 'BSD (3 clause) BSD (3 clause)', ], 'plugins/mate/mate_grammar.h': [ 'UNKNOWN', ], 'vcs_version.h': [ 'UNKNOWN', ], # Special IDL license that appears to be compatible as far as I (not a # lawyer) can tell. See # https://lists.wireshark.org/archives/wireshark-dev/201310/msg00234.html 'epan/dissectors/pidl/idl_types.h': [ 'UNKNOWN', ], # The following tools are under incompatible licenses (mostly GPLv3 or # GPLv3+), but this is OK since they are not actually linked into Wireshark 'tools/pidl': [ 'UNKNOWN', ], 'tools/lemon': [ 'UNKNOWN', ], 'tools/licensecheck.pl': [ 'GPL (v2)' ], '.gitlab/': [ 'UNKNOWN', ], 'wsutil/dtoa.c': [ 'dtoa', ], 'wsutil/dtoa.h': [ 'dtoa', ], 'wsutil/safe-math.h': [ # Public domain (CC0) 'UNKNOWN', ], } def check_licenses(options, args): if options.list_allowed: print('\n'.join(ALLOWED_LICENSES)) sys.exit(0) # Figure out which directory we have to check. if len(args) == 0: # No directory to check specified, use the repository root. start_dir = options.base_directory elif len(args) == 1: # Directory specified. Start here. It's supposed to be relative to the # base directory. start_dir = os.path.abspath(os.path.join(options.base_directory, args[0])) else: # More than one argument, we don't handle this. PrintUsage() return 1 print("Using base directory: %s" % options.base_directory) print("Checking: %s" % start_dir) print("") licensecheck_path = os.path.abspath(os.path.join(options.base_directory, 'tools', 'licensecheck.pl')) licensecheck = subprocess.Popen([licensecheck_path, '-l', '160', '-r', start_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = licensecheck.communicate() stdout = stdout.decode('utf-8') stderr = stderr.decode('utf-8') if options.verbose: print('----------- licensecheck stdout -----------') print(stdout) print('--------- end licensecheck stdout ---------') if licensecheck.returncode != 0 or stderr: print('----------- licensecheck stderr -----------') print(stderr) print('--------- end licensecheck stderr ---------') print("\nFAILED\n") return 1 success = True exit_status = 0 for line in stdout.splitlines(): filename, license = line.split(':', 1) filename = os.path.relpath(filename.strip(), options.base_directory) # All files in the build output directory are generated one way or another. # There's no need to check them. if os.path.dirname(filename).startswith('build'): continue # For now we're just interested in the license. license = license.replace('*No copyright*', '').strip() # Skip generated files. if 'GENERATED FILE' in license: continue # Support files which provide a choice between licenses. if any(item in ALLOWED_LICENSES for item in license.split(';')): continue if not options.ignore_suppressions: found_path_specific = False for prefix in PATH_SPECIFIC_ALLOWED_LICENSES: if (filename.startswith(prefix) and license in PATH_SPECIFIC_ALLOWED_LICENSES[prefix]): found_path_specific = True break if found_path_specific: continue reason = "License '%s' for '%s' is not allowed." % (license, filename) success = False print(reason) exit_status = 1 if success: print("\nSUCCESS\n") return 0 else: print("\nFAILED\n") return exit_status def main(): default_root = os.path.abspath( os.path.join(os.path.dirname(__file__), '..')) option_parser = optparse.OptionParser() option_parser.add_option('--root', default=default_root, dest='base_directory', help='Specifies the repository root. This defaults ' 'to "../.." relative to the script file, which ' 'will normally be the repository root.') option_parser.add_option('-v', '--verbose', action='store_true', default=False, help='Print debug logging') option_parser.add_option('--list-allowed', action='store_true', default=False, help='Print a list of allowed licenses and exit.') option_parser.add_option('--ignore-suppressions', action='store_true', default=False, help='Ignore path-specific allowed license.') options, args = option_parser.parse_args() return check_licenses(options, args) if '__main__' == __name__: sys.exit(main())