Chromium Code Reviews| 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 # Ignore these tools. | |
|
Nico
2012/08/09 17:29:04
nit: If you do append() instead of `list = list +
Steve Block
2012/08/09 20:03:19
Done.
| |
| 66 directory_list.append('android_webview/tools/') | |
| 67 # This is a build intermediate directory. | |
| 68 directory_list.append('chrome/app/theme/google_chrome/') | |
| 69 # This is a test output directory. | |
| 70 directory_list.append('data/page_cycler/') | |
| 71 # 'Copyright' appears in strings. | |
| 72 directory_list.append('chrome/app/resources/') | |
| 73 | |
| 74 # Exclude files under listed directories and some known offenders. | |
| 75 offending_files = [] | |
| 76 for x in files: | |
| 77 x = os.path.normpath(x) | |
| 78 is_in_listed_directory = False | |
| 79 for y in directory_list: | |
| 80 if x.startswith(y): | |
| 81 is_in_listed_directory = True | |
| 82 break | |
| 83 if not is_in_listed_directory: | |
| 84 offending_files.append(x) | |
| 85 | |
| 86 all_files_valid = True | |
| 87 unknown = set(offending_files) - set(whitelisted_files) | |
| 88 if unknown: | |
| 89 print 'The following files contain a third-party license but are not in ' \ | |
| 90 'a listed third-party directory and are not whitelisted. You must ' \ | |
| 91 'add the following files to the whitelist.\n%s' % \ | |
| 92 '\n'.join(sorted(unknown)) | |
| 93 all_files_valid = False | |
| 94 | |
| 95 stale = set(whitelisted_files) - set(offending_files) | |
| 96 if stale: | |
| 97 print 'The following files are whitelisted unnecessarily. You must ' \ | |
| 98 ' remove the following files from the whitelist.\n%s' % \ | |
| 99 '\n'.join(sorted(stale)) | |
| 100 all_files_valid = False | |
| 101 | |
| 102 return all_files_valid | |
| 103 | |
| 104 | |
| 105 def _ReadFile(path): | |
| 106 """Reads a file from disk. | |
| 107 Args: | |
| 108 path: The path of the file to read, relative to the root of the repository. | |
| 109 Returns: | |
| 110 The contents of the file as a string. | |
| 111 """ | |
| 112 | |
| 113 return open(os.path.join(REPOSITORY_ROOT, path), 'rb').read() | |
| 114 | |
| 115 | |
| 116 def _FindThirdPartyDirs(): | |
| 117 """Gets the list of third-party directories. | |
| 118 Returns: | |
| 119 The list of third-party directories. | |
| 120 """ | |
| 121 | |
| 122 prune_paths = [ | |
| 123 # Placeholder directory, no third-party code. | |
| 124 os.path.join('third_party', 'adobe'), | |
| 125 # Apache 2.0 license. See | |
| 126 # https://code.google.com/p/chromium/issues/detail?id=140478. | |
| 127 os.path.join('third_party', 'bidichecker'), | |
| 128 ] | |
| 129 return licenses.FindThirdPartyDirs(prune_paths) | |
| 130 | |
| 131 | |
| 132 def _Scan(): | |
| 133 """Checks that license meta-data is present for all third-party code. | |
| 134 Returns: | |
| 135 Whether the check succeeded. | |
| 136 """ | |
| 137 | |
| 138 third_party_dirs = _FindThirdPartyDirs() | |
| 139 | |
| 140 # First, check designated third-party directories using src/tools/licenses.py. | |
| 141 all_licenses_valid = True | |
| 142 for path in sorted(third_party_dirs): | |
| 143 try: | |
| 144 licenses.ParseDir(path) | |
| 145 except licenses.LicenseError, e: | |
| 146 print 'Got LicenseError "%s" while scanning %s' % (e, path) | |
| 147 all_licenses_valid = False | |
| 148 | |
| 149 # Second, check for non-standard license text. | |
| 150 files_data = _ReadFile(os.path.join('android_webview', 'tools', | |
| 151 'third_party_files_whitelist.txt')) | |
| 152 whitelisted_files = [] | |
| 153 for line in files_data.splitlines(): | |
| 154 match = re.match(r'([^#\s]+)', line) | |
| 155 if match: | |
| 156 whitelisted_files.append(match.group(1)) | |
| 157 return _CheckLicenseHeaders(third_party_dirs, whitelisted_files) \ | |
| 158 and all_licenses_valid | |
| 159 | |
| 160 | |
| 161 def _GenerateNoticeFile(print_warnings): | |
| 162 """Generates the contents of an Android NOTICE file for the third-party code. | |
| 163 Args: | |
| 164 print_warnings: Whether to print warnings. | |
| 165 Returns: | |
| 166 The contents of the NOTICE file. | |
| 167 """ | |
| 168 | |
| 169 third_party_dirs = _FindThirdPartyDirs() | |
| 170 | |
| 171 # Don't forget Chromium's LICENSE file | |
| 172 content = [_ReadFile('LICENSE')] | |
| 173 | |
| 174 # We provide attribution for all third-party directories. | |
| 175 # TODO(steveblock): Limit this to only code used by the WebView binary. | |
| 176 for directory in third_party_dirs: | |
| 177 license_file = licenses.ParseDir(directory)['License File'] | |
| 178 if license_file != licenses.NOT_SHIPPED: | |
| 179 content.append(_ReadFile(license_file)) | |
| 180 | |
| 181 return '\n'.join(content) | |
| 182 | |
| 183 | |
| 184 def main(): | |
| 185 class FormatterWithNewLines(optparse.IndentedHelpFormatter): | |
| 186 def format_description(self, description): | |
| 187 paras = description.split('\n') | |
| 188 formatted_paras = [textwrap.fill(para, self.width) for para in paras] | |
| 189 return '\n'.join(formatted_paras) + '\n' | |
| 190 | |
| 191 parser = optparse.OptionParser(formatter=FormatterWithNewLines(), | |
| 192 usage='%prog [options]') | |
| 193 parser.description = (__doc__ + | |
| 194 '\nCommands:\n' \ | |
| 195 ' scan Check licenses.\n' \ | |
| 196 ' notice Generate Android NOTICE file on stdout') | |
| 197 (options, args) = parser.parse_args() | |
| 198 if len(args) != 1: | |
| 199 parser.print_help() | |
| 200 return 1 | |
| 201 | |
| 202 if os.getcwd() != REPOSITORY_ROOT: | |
| 203 print "This tool can only be run from the repository root." | |
| 204 return 1 | |
| 205 | |
| 206 if args[0] == 'scan': | |
| 207 if _Scan(): | |
| 208 print 'OK!' | |
| 209 return 0 | |
| 210 else: | |
| 211 return 1 | |
| 212 elif args[0] == 'notice': | |
| 213 print _GenerateNoticeFile(print_warnings=False) | |
| 214 return 0 | |
| 215 | |
| 216 parser.print_help() | |
| 217 return 1 | |
| 218 | |
| 219 if __name__ == '__main__': | |
| 220 sys.exit(main()) | |
| OLD | NEW |