OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/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 """Checks third-party licenses for the purposes of the Android WebView build. |
| 7 |
| 8 The Android tree includes a snapshot of Chromium in order to power the system |
| 9 WebView. This tool checks that all code uses open-source licenses compatible |
| 10 with Android, and that we meet the requirements of those licenses. It can also |
| 11 be used to generate an Android NOTICE file for the third-party code. |
| 12 |
| 13 It makes use of src/tools/licenses.py and the README.chromium files on which |
| 14 it depends. It also makes use of a data file, third_party_files_whitelist.txt, |
| 15 which whitelists indicidual files which contain third-party code but which |
| 16 aren't in a third-party directory with a README.chromium file. |
| 17 """ |
| 18 |
| 19 import optparse |
| 20 import os |
| 21 import re |
| 22 import subprocess |
| 23 import sys |
| 24 import textwrap |
| 25 |
| 26 |
| 27 REPOSITORY_ROOT = os.path.abspath(os.path.join( |
| 28 os.path.dirname(__file__), '..', '..')) |
| 29 |
| 30 sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools')) |
| 31 import licenses |
| 32 |
| 33 |
| 34 def _CheckLicenseHeaders(directory_list, whitelisted_files): |
| 35 """Checks that all files which are not in a listed third-party directory, |
| 36 and which do not use the standard Chromium license, are whitelisted. |
| 37 Args: |
| 38 directory_list: The list of directories. |
| 39 whitelisted_files: The whitelist of files. |
| 40 Returns: |
| 41 True if all files with non-standard license headers are whitelisted and the |
| 42 whitelist contains no stale entries, otherwise false. |
| 43 """ |
| 44 |
| 45 # Matches one of ... |
| 46 # - '[Cc]opyright', but not when followed by |
| 47 # ' 20[0-9][0-9] The Chromium Authors.', with optional (c) and date range |
| 48 # - '([Cc]) (19|20)[0-9][0-9]', but not when preceeded by the word copyright, |
| 49 # as this is handled above |
| 50 regex = '[Cc]opyright(?!( \(c\))? 20[0-9][0-9](-20[0-9][0-9])? ' \ |
| 51 'The Chromium Authors\. All rights reserved\.)' \ |
| 52 '|' \ |
| 53 '(?<!(pyright |opyright))\([Cc]\) (19|20)[0-9][0-9]' |
| 54 |
| 55 args = ['grep', |
| 56 '-rPlI', |
| 57 '--exclude-dir', 'third_party', |
| 58 '--exclude-dir', 'out', |
| 59 '--exclude-dir', '.git', |
| 60 regex, |
| 61 '.'] |
| 62 p = subprocess.Popen(args=args, cwd=REPOSITORY_ROOT, stdout=subprocess.PIPE) |
| 63 files = p.communicate()[0].splitlines() |
| 64 |
| 65 directory_list = directory_list[:] |
| 66 # Ignore these tools. |
| 67 directory_list.append('android_webview/tools/') |
| 68 # This is a build intermediate directory. |
| 69 directory_list.append('chrome/app/theme/google_chrome/') |
| 70 # This is a test output directory. |
| 71 directory_list.append('data/page_cycler/') |
| 72 # 'Copyright' appears in strings. |
| 73 directory_list.append('chrome/app/resources/') |
| 74 |
| 75 # Exclude files under listed directories and some known offenders. |
| 76 offending_files = [] |
| 77 for x in files: |
| 78 x = os.path.normpath(x) |
| 79 is_in_listed_directory = False |
| 80 for y in directory_list: |
| 81 if x.startswith(y): |
| 82 is_in_listed_directory = True |
| 83 break |
| 84 if not is_in_listed_directory: |
| 85 offending_files.append(x) |
| 86 |
| 87 all_files_valid = True |
| 88 unknown = set(offending_files) - set(whitelisted_files) |
| 89 if unknown: |
| 90 print 'The following files contain a third-party license but are not in ' \ |
| 91 'a listed third-party directory and are not whitelisted. You must ' \ |
| 92 'add the following files to the whitelist.\n%s' % \ |
| 93 '\n'.join(sorted(unknown)) |
| 94 all_files_valid = False |
| 95 |
| 96 stale = set(whitelisted_files) - set(offending_files) |
| 97 if stale: |
| 98 print 'The following files are whitelisted unnecessarily. You must ' \ |
| 99 ' remove the following files from the whitelist.\n%s' % \ |
| 100 '\n'.join(sorted(stale)) |
| 101 all_files_valid = False |
| 102 |
| 103 return all_files_valid |
| 104 |
| 105 |
| 106 def _ReadFile(path): |
| 107 """Reads a file from disk. |
| 108 Args: |
| 109 path: The path of the file to read, relative to the root of the repository. |
| 110 Returns: |
| 111 The contents of the file as a string. |
| 112 """ |
| 113 |
| 114 return open(os.path.join(REPOSITORY_ROOT, path), 'rb').read() |
| 115 |
| 116 |
| 117 def _FindThirdPartyDirs(): |
| 118 """Gets the list of third-party directories. |
| 119 Returns: |
| 120 The list of third-party directories. |
| 121 """ |
| 122 |
| 123 prune_paths = [ |
| 124 # Placeholder directory, no third-party code. |
| 125 os.path.join('third_party', 'adobe'), |
| 126 # Apache 2.0 license. See |
| 127 # https://code.google.com/p/chromium/issues/detail?id=140478. |
| 128 os.path.join('third_party', 'bidichecker'), |
| 129 ] |
| 130 return licenses.FindThirdPartyDirs(prune_paths) |
| 131 |
| 132 |
| 133 def _Scan(): |
| 134 """Checks that license meta-data is present for all third-party code. |
| 135 Returns: |
| 136 Whether the check succeeded. |
| 137 """ |
| 138 |
| 139 third_party_dirs = _FindThirdPartyDirs() |
| 140 |
| 141 # First, check designated third-party directories using src/tools/licenses.py. |
| 142 all_licenses_valid = True |
| 143 for path in sorted(third_party_dirs): |
| 144 try: |
| 145 licenses.ParseDir(path) |
| 146 except licenses.LicenseError, e: |
| 147 print 'Got LicenseError "%s" while scanning %s' % (e, path) |
| 148 all_licenses_valid = False |
| 149 |
| 150 # Second, check for non-standard license text. |
| 151 files_data = _ReadFile(os.path.join('android_webview', 'tools', |
| 152 'third_party_files_whitelist.txt')) |
| 153 whitelisted_files = [] |
| 154 for line in files_data.splitlines(): |
| 155 match = re.match(r'([^#\s]+)', line) |
| 156 if match: |
| 157 whitelisted_files.append(match.group(1)) |
| 158 return _CheckLicenseHeaders(third_party_dirs, whitelisted_files) \ |
| 159 and all_licenses_valid |
| 160 |
| 161 |
| 162 def _GenerateNoticeFile(print_warnings): |
| 163 """Generates the contents of an Android NOTICE file for the third-party code. |
| 164 Args: |
| 165 print_warnings: Whether to print warnings. |
| 166 Returns: |
| 167 The contents of the NOTICE file. |
| 168 """ |
| 169 |
| 170 third_party_dirs = _FindThirdPartyDirs() |
| 171 |
| 172 # Don't forget Chromium's LICENSE file |
| 173 content = [_ReadFile('LICENSE')] |
| 174 |
| 175 # We provide attribution for all third-party directories. |
| 176 # TODO(steveblock): Limit this to only code used by the WebView binary. |
| 177 for directory in third_party_dirs: |
| 178 license_file = licenses.ParseDir(directory)['License File'] |
| 179 if license_file != licenses.NOT_SHIPPED: |
| 180 content.append(_ReadFile(license_file)) |
| 181 |
| 182 return '\n'.join(content) |
| 183 |
| 184 |
| 185 def main(): |
| 186 class FormatterWithNewLines(optparse.IndentedHelpFormatter): |
| 187 def format_description(self, description): |
| 188 paras = description.split('\n') |
| 189 formatted_paras = [textwrap.fill(para, self.width) for para in paras] |
| 190 return '\n'.join(formatted_paras) + '\n' |
| 191 |
| 192 parser = optparse.OptionParser(formatter=FormatterWithNewLines(), |
| 193 usage='%prog [options]') |
| 194 parser.description = (__doc__ + |
| 195 '\nCommands:\n' \ |
| 196 ' scan Check licenses.\n' \ |
| 197 ' notice Generate Android NOTICE file on stdout') |
| 198 (options, args) = parser.parse_args() |
| 199 if len(args) != 1: |
| 200 parser.print_help() |
| 201 return 1 |
| 202 |
| 203 if os.getcwd() != REPOSITORY_ROOT: |
| 204 print "This tool can only be run from the repository root." |
| 205 return 1 |
| 206 |
| 207 if args[0] == 'scan': |
| 208 if _Scan(): |
| 209 print 'OK!' |
| 210 return 0 |
| 211 else: |
| 212 return 1 |
| 213 elif args[0] == 'notice': |
| 214 print _GenerateNoticeFile(print_warnings=False) |
| 215 return 0 |
| 216 |
| 217 parser.print_help() |
| 218 return 1 |
| 219 |
| 220 if __name__ == '__main__': |
| 221 sys.exit(main()) |
OLD | NEW |