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 listed. | |
|
Nico
2012/08/09 15:02:29
s/are listed/are whitelisted/?
Steve Block
2012/08/09 17:24:59
Done.
| |
| 37 Args: | |
| 38 directory_list: The list of directories. | |
| 39 whitelisted_files: The list of files. | |
| 40 Returns: | |
| 41 True if all files with non-standard license headers are listed and the | |
| 42 file list contains no stale entries, otherwise false. | |
| 43 """ | |
| 44 | |
| 45 # Matches one of ... | |
| 46 # - '[Cc]opyright', but not when followed by | |
| 47 # ' (c) 20[0-9][0-9] The Chromium Authors.', with optional 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[01][0189])? ' \ | |
|
Nico
2012/08/09 15:02:29
Why not 20[0-9][0-9] for the second year in a rang
Steve Block
2012/08/09 17:24:59
Done.
| |
| 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 '.'] | |
|
Nico
2012/08/09 15:02:29
Do you set / check the cwd anywhere?
Steve Block
2012/08/09 17:24:59
I set the cwd on the line below. But it turns out
| |
| 62 p = subprocess.Popen(args=args, cwd=REPOSITORY_ROOT, stdout=subprocess.PIPE) | |
| 63 files = p.communicate()[0].splitlines() | |
| 64 | |
| 65 # Exclude files under listed directories and some known offendors. | |
|
Nico
2012/08/09 15:02:29
"offenders" I think
Steve Block
2012/08/09 17:24:59
Done.
| |
| 66 offending_files = [] | |
| 67 for x in files: | |
| 68 x = os.path.normpath(x) | |
| 69 is_in_listed_directory = False | |
| 70 for y in directory_list: | |
| 71 if x.startswith(y): | |
| 72 is_in_listed_directory = True | |
| 73 break | |
| 74 if (not is_in_listed_directory | |
| 75 # Ignore these tools. | |
| 76 and not x.startswith('android_webview/tools/') | |
| 77 # This is a build intermediate directory. | |
| 78 and not x.startswith('chrome/app/theme/google_chrome/') | |
| 79 # This is a test output directory. | |
| 80 and not x.startswith('data/page_cycler/') | |
| 81 # 'Copyright' appears in strings. | |
| 82 and not x.startswith('chrome/app/resources/')): | |
|
Nico
2012/08/09 15:02:29
nit: Could you instead say
directory_list = dir
Steve Block
2012/08/09 17:24:59
Done.
| |
| 83 offending_files += [x] | |
|
Nico
2012/08/09 15:02:29
.append
Steve Block
2012/08/09 17:24:59
Done.
| |
| 84 | |
| 85 result = True | |
|
Nico
2012/08/09 15:02:29
s/result/all_files_valid/
Steve Block
2012/08/09 17:24:59
Done.
| |
| 86 unknown = set(offending_files) - set(whitelisted_files) | |
| 87 if unknown: | |
| 88 print 'The following files contain a third-party license but are not in ' \ | |
| 89 'a listed third-party directory and are not whitelisted. You must ' \ | |
| 90 'add the following files to the whitelist.\n%s' % \ | |
| 91 '\n'.join(sorted(unknown)) | |
| 92 result = False | |
| 93 | |
| 94 stale = set(whitelisted_files) - set(offending_files) | |
| 95 if stale: | |
| 96 print 'The following files are whitelisted unnecessarily. You must ' \ | |
| 97 ' remove the following files from the whitelist list.\n%s' % \ | |
|
Nico
2012/08/09 15:02:29
s/whitelist list/whitelist/
Steve Block
2012/08/09 17:24:59
Done.
| |
| 98 '\n'.join(sorted(stale)) | |
| 99 result = False | |
| 100 | |
| 101 return result | |
| 102 | |
| 103 | |
| 104 def _ReadFile(path): | |
| 105 """Reads a file from disk. | |
| 106 Args: | |
| 107 path: The path of the file to read, relative to the root of the repository. | |
| 108 Returns: | |
| 109 The contents of the file as a string. | |
| 110 """ | |
| 111 | |
| 112 with file(os.path.join(REPOSITORY_ROOT, path), 'r') as f: | |
| 113 lines = f.read() | |
|
Nico
2012/08/09 15:02:29
fyi: if you write
return open(...path...).read(
Steve Block
2012/08/09 17:24:59
Done.
| |
| 114 return lines | |
| 115 | |
| 116 | |
| 117 def _Scan(): | |
| 118 """Checks that license meta-data is present for all third-party code. | |
| 119 Returns: | |
| 120 Whether the check succeeded. | |
| 121 """ | |
| 122 | |
| 123 third_party_dirs = _FindThirdPartyDirs() | |
| 124 | |
| 125 # First, check designated third-party directories using src/tools/licenses.py. | |
| 126 result = True | |
|
Nico
2012/08/09 15:02:29
s/result/all_licenses_valid/
Steve Block
2012/08/09 17:24:59
Done.
| |
| 127 for path in sorted(third_party_dirs): | |
| 128 try: | |
| 129 licenses.ParseDir(path) | |
| 130 except licenses.LicenseError, e: | |
| 131 print 'Got LicenseError "%s" while scanning %s' % (e, path) | |
| 132 result = False | |
| 133 | |
| 134 # Second, check for non-standard license text. | |
| 135 files_data = _ReadFile(os.path.join('android_webview', 'tools', | |
| 136 'third_party_files_whitelist.txt')) | |
| 137 whitelisted_files = [] | |
| 138 for line in files_data.splitlines(): | |
| 139 match = re.match(r'([^#\s]*)', line) | |
| 140 if match and not len(match.group(1)) == 0: | |
|
Nico
2012/08/09 15:02:29
if you say + instead of *, you don't need the seco
Steve Block
2012/08/09 17:24:59
Done.
| |
| 141 whitelisted_files += [match.group(1)] | |
|
Nico
2012/08/09 15:02:29
append
Steve Block
2012/08/09 17:24:59
Done.
| |
| 142 return _CheckLicenseHeaders(third_party_dirs, whitelisted_files) and result | |
| 143 | |
| 144 | |
| 145 def _FindThirdPartyDirs(): | |
|
Nico
2012/08/09 15:02:29
Move above _Scan
Steve Block
2012/08/09 17:24:59
Done.
| |
| 146 """Gets the list of third-party directories. | |
| 147 Returns: | |
| 148 The list of third-party directories. | |
| 149 """ | |
| 150 | |
| 151 prune_paths = [ | |
| 152 # Placeholder directory, no third-party code. | |
| 153 os.path.join('third_party', 'adobe'), | |
| 154 # Apache 2.0 license. See | |
| 155 # https://code.google.com/p/chromium/issues/detail?id=140478. | |
| 156 os.path.join('third_party', 'bidichecker'), | |
| 157 ] | |
| 158 return licenses.FindThirdPartyDirs(prune_paths) | |
| 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 content += [_ReadFile(licenses.ParseDir(directory)['License File'])] | |
| 178 | |
| 179 return '\n'.join(content) | |
| 180 | |
| 181 | |
| 182 def main(): | |
| 183 class IndentedHelpFormatterWithNL(optparse.IndentedHelpFormatter): | |
| 184 def format_description(self, description): | |
| 185 if not description: return "" | |
| 186 desc_width = self.width - self.current_indent | |
| 187 indent = " "*self.current_indent | |
| 188 bits = description.split('\n') | |
| 189 formatted_bits = [ | |
| 190 textwrap.fill(bit, | |
| 191 desc_width, | |
| 192 initial_indent=indent, | |
| 193 subsequent_indent=indent) | |
| 194 for bit in bits] | |
| 195 result = '\n'.join(formatted_bits) + '\n' | |
| 196 return result | |
|
Nico
2012/08/09 15:02:29
Do you need this? If so, adjust coding style (it k
Steve Block
2012/08/09 17:24:59
I found the snippet on a forum. I meant to clean i
| |
| 197 | |
| 198 parser = optparse.OptionParser(formatter=IndentedHelpFormatterWithNL(), | |
| 199 usage='%prog [options]') | |
| 200 parser.description = (__doc__ + | |
| 201 '\nCommands:\n' \ | |
| 202 ' scan Check licenses.\n' \ | |
| 203 ' notice Generate Android NOTICE file on stdout') | |
| 204 (options, args) = parser.parse_args() | |
| 205 if len(args) != 1: | |
| 206 parser.print_help() | |
| 207 return 1 | |
| 208 | |
| 209 if args[0] == 'scan': | |
| 210 if _Scan(): | |
| 211 print 'OK!' | |
| 212 return 0 | |
| 213 else: | |
| 214 return 1 | |
| 215 elif args[0] == 'notice': | |
| 216 print _GenerateNoticeFile(print_warnings=False) | |
| 217 return 0 | |
| 218 | |
| 219 parser.print_help() | |
| 220 return 1 | |
| 221 | |
| 222 if __name__ == '__main__': | |
| 223 sys.exit(main()) | |
| OLD | NEW |