1#!/usr/bin/env python
2# Copyright (c) 2015 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
15import logging
16
17def PrintUsage():
18  print '''Usage: python checklicenses.py [--root <root>] [tocheck]
19  --root   Specifies the repository root. This defaults to '../..' relative
20           to the script file. This will be correct given the normal location
21           of the script in '<root>/tools/checklicenses'.
22
23  tocheck  Specifies the directory, relative to root, to check. This defaults
24           to '.' so it checks everything.
25
26Examples:
27  python checklicenses.py
28  python checklicenses.py --root ~/chromium/src third_party'''
29
30
31WHITELISTED_LICENSES = [
32    'Apache (v2.0)',
33    'BSD (3 clause)',
34    'BSD-like',
35    'MIT/X11 (BSD like)',
36    'zlib/libpng',
37]
38
39
40PATH_SPECIFIC_WHITELISTED_LICENSES = {
41    'tracing/third_party/devscripts': [
42        'GPL (v2 or later)',
43    ],
44}
45
46
47def check_licenses(base_directory, target_directory=None):
48  # Figure out which directory we have to check.
49  if not target_directory:
50    # No directory to check specified, use the repository root.
51    start_dir = base_directory
52  else:
53    # Directory specified. Start here. It's supposed to be relative to the
54    # base directory.
55    start_dir = os.path.abspath(os.path.join(base_directory, target_directory))
56
57  logging.info('Using base directory: %s' % base_directory)
58  logging.info('Checking: %s' % start_dir)
59  logging.info('')
60
61  licensecheck_path = os.path.abspath(os.path.join(base_directory,
62                                                   'tracing',
63                                                   'third_party',
64                                                   'devscripts',
65                                                   'licensecheck.pl'))
66
67  licensecheck = subprocess.Popen([licensecheck_path,
68                                   '-l', '100',
69                                   '-r', start_dir],
70                                  stdout=subprocess.PIPE,
71                                  stderr=subprocess.PIPE)
72  stdout, stderr = licensecheck.communicate()
73  logging.info('----------- licensecheck stdout -----------')
74  logging.info(stdout)
75  logging.info('--------- end licensecheck stdout ---------')
76  if licensecheck.returncode != 0 or stderr:
77    print '----------- licensecheck stderr -----------'
78    print stderr
79    print '--------- end licensecheck stderr ---------'
80    return 1
81
82  used_suppressions = set()
83  errors = []
84
85  for line in stdout.splitlines():
86    filename, license = line.split(':', 1)
87    filename = os.path.relpath(filename.strip(), base_directory)
88
89    # All files in the build output directory are generated one way or another.
90    # There's no need to check them.
91    if filename.startswith('out/'):
92      continue
93
94    # For now we're just interested in the license.
95    license = license.replace('*No copyright*', '').strip()
96
97    # Skip generated files.
98    if 'GENERATED FILE' in license:
99      continue
100
101    if license in WHITELISTED_LICENSES:
102      continue
103
104    matched_prefixes = [
105        prefix for prefix in PATH_SPECIFIC_WHITELISTED_LICENSES
106        if filename.startswith(prefix) and
107        license in PATH_SPECIFIC_WHITELISTED_LICENSES[prefix]]
108    if matched_prefixes:
109      used_suppressions.update(set(matched_prefixes))
110      continue
111
112    errors.append({'filename': filename, 'license': license})
113
114  if errors:
115    for error in errors:
116      print "'%s' has non-whitelisted license '%s'" % (
117          error['filename'], error['license'])
118    print '\nFAILED\n'
119    print 'Please read',
120    print 'http://www.chromium.org/developers/adding-3rd-party-libraries'
121    print 'for more info how to handle the failure.'
122    print
123    print 'Please respect OWNERS of checklicenses.py. Changes violating'
124    print 'this requirement may be reverted.'
125
126    # Do not print unused suppressions so that above message is clearly
127    # visible and gets proper attention. Too much unrelated output
128    # would be distracting and make the important points easier to miss.
129
130    return 1
131
132
133  return 0
134
135
136def main():
137  default_root = os.path.abspath(
138      os.path.join(os.path.dirname(__file__), '..'))
139  option_parser = optparse.OptionParser()
140  option_parser.add_option('--root', default=default_root,
141                           dest='base_directory',
142                           help='Specifies the repository root. This defaults '
143                           "to '..' relative to the script file, which "
144                           'will normally be the repository root.')
145  options, args = option_parser.parse_args()
146
147  target_directory = None
148  if len(args) == 1:
149    target_directory = args[0]
150  elif len(args) > 1:
151    PrintUsage()
152    return 1
153  results = check_licenses(options.base_directory, target_directory)
154  if not results:
155    print 'SUCCESS'
156  return results
157
158
159if '__main__' == __name__:
160  sys.exit(main())
161