diff options
Diffstat (limited to 'tools/checklicenses.py')
-rwxr-xr-x | tools/checklicenses.py | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/tools/checklicenses.py b/tools/checklicenses.py new file mode 100755 index 00000000..192fecbe --- /dev/null +++ b/tools/checklicenses.py @@ -0,0 +1,262 @@ +#!/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 <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 "<root>/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)', + 'GPL (v2 or later)', + 'GPL (v3 or later) (with Bison parser exception)', + '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 = { + 'caputils/airpcap.h': [ + 'BSD-3-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', + ], + 'docbook/custom_layer_chm.xsl': [ + 'UNKNOWN', + ], + 'docbook/custom_layer_single_html.xsl': [ + 'UNKNOWN', + ], + 'docbook/ws.css' : [ + '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://www.wireshark.org/lists/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/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', '150', + '-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()) |