Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Checks third-party licenses for the purposes of the Android WebView build. | 6 """Checks third-party licenses for the purposes of the Android WebView build. |
| 7 | 7 |
| 8 The Android tree includes a snapshot of Chromium in order to power the system | 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 | 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 | 10 with Android, and that we meet the requirements of those licenses. It can also |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 29 REPOSITORY_ROOT = os.path.abspath(os.path.join( | 29 REPOSITORY_ROOT = os.path.abspath(os.path.join( |
| 30 os.path.dirname(__file__), '..', '..')) | 30 os.path.dirname(__file__), '..', '..')) |
| 31 | 31 |
| 32 # Import third_party/PRESUBMIT.py via imp to avoid importing a random | 32 # Import third_party/PRESUBMIT.py via imp to avoid importing a random |
| 33 # PRESUBMIT.py from $PATH, also make sure we don't generate a .pyc file. | 33 # PRESUBMIT.py from $PATH, also make sure we don't generate a .pyc file. |
| 34 sys.dont_write_bytecode = True | 34 sys.dont_write_bytecode = True |
| 35 third_party = \ | 35 third_party = \ |
| 36 imp.load_source('PRESUBMIT', \ | 36 imp.load_source('PRESUBMIT', \ |
| 37 os.path.join(REPOSITORY_ROOT, 'third_party', 'PRESUBMIT.py')) | 37 os.path.join(REPOSITORY_ROOT, 'third_party', 'PRESUBMIT.py')) |
| 38 | 38 |
| 39 sys.path.append(os.path.join(REPOSITORY_ROOT, 'build/android/gyp/util')) | |
| 40 import build_utils | |
| 39 sys.path.append(os.path.join(REPOSITORY_ROOT, 'third_party')) | 41 sys.path.append(os.path.join(REPOSITORY_ROOT, 'third_party')) |
| 40 import jinja2 | 42 import jinja2 |
| 41 sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools')) | 43 sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools')) |
| 42 from copyright_scanner import copyright_scanner | 44 from copyright_scanner import copyright_scanner |
| 43 import licenses | 45 import licenses |
| 44 | 46 |
| 45 | 47 |
| 46 class InputApi(object): | 48 class InputApi(object): |
| 47 def __init__(self): | 49 def __init__(self): |
| 48 self.os_path = os.path | 50 self.os_path = os.path |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 163 third_party_dirs, whitelisted_files) | 165 third_party_dirs, whitelisted_files) |
| 164 | 166 |
| 165 problem_paths.extend(more_problem_paths) | 167 problem_paths.extend(more_problem_paths) |
| 166 | 168 |
| 167 return (licenses_check if all_licenses_valid else ScanResult.Errors, | 169 return (licenses_check if all_licenses_valid else ScanResult.Errors, |
| 168 problem_paths) | 170 problem_paths) |
| 169 | 171 |
| 170 | 172 |
| 171 class TemplateEntryGenerator(object): | 173 class TemplateEntryGenerator(object): |
| 172 def __init__(self): | 174 def __init__(self): |
| 173 self._generate_licenses_file_list_only = False | |
| 174 self._toc_index = 0 | 175 self._toc_index = 0 |
| 175 | 176 |
| 176 def SetGenerateLicensesFileListOnly(self, generate_licenses_file_list_only): | |
| 177 self._generate_licenses_file_list_only = generate_licenses_file_list_only | |
| 178 | |
| 179 def _ReadFileGuessEncoding(self, name): | 177 def _ReadFileGuessEncoding(self, name): |
| 180 if self._generate_licenses_file_list_only: | |
| 181 return '' | |
| 182 contents = '' | 178 contents = '' |
| 183 with open(name, 'rb') as input_file: | 179 with open(name, 'rb') as input_file: |
| 184 contents = input_file.read() | 180 contents = input_file.read() |
| 185 try: | 181 try: |
| 186 return contents.decode('utf8') | 182 return contents.decode('utf8') |
| 187 except UnicodeDecodeError: | 183 except UnicodeDecodeError: |
| 188 pass | 184 pass |
| 189 # If it's not UTF-8, it must be CP-1252. Fail otherwise. | 185 # If it's not UTF-8, it must be CP-1252. Fail otherwise. |
| 190 return contents.decode('cp1252') | 186 return contents.decode('cp1252') |
| 191 | 187 |
| 192 def MetadataToTemplateEntry(self, metadata): | 188 def MetadataToTemplateEntry(self, metadata): |
| 193 self._toc_index += 1 | 189 self._toc_index += 1 |
| 194 return { | 190 return { |
| 195 'name': metadata['Name'], | 191 'name': metadata['Name'], |
| 196 'url': metadata['URL'], | 192 'url': metadata['URL'], |
| 197 'license_file': metadata['License File'], | 193 'license_file': metadata['License File'], |
| 198 'license': self._ReadFileGuessEncoding(metadata['License File']), | 194 'license': self._ReadFileGuessEncoding(metadata['License File']), |
| 199 'toc_href': 'entry' + str(self._toc_index), | 195 'toc_href': 'entry' + str(self._toc_index), |
| 200 } | 196 } |
| 201 | 197 |
| 202 | 198 |
| 203 def GenerateNoticeFile(generate_licenses_file_list_only=False): | 199 def GenerateNoticeFile(): |
| 204 """Generates the contents of an Android NOTICE file for the third-party code. | 200 """Generates the contents of an Android NOTICE file for the third-party code. |
| 205 This is used by the snapshot tool. | 201 This is used by the snapshot tool. |
| 206 Returns: | 202 Returns: |
| 207 The contents of the NOTICE file. | 203 A tuple of (input paths, contents of the NOTICE file). |
| 208 """ | 204 """ |
| 209 | |
| 210 generator = TemplateEntryGenerator() | 205 generator = TemplateEntryGenerator() |
| 211 generator.SetGenerateLicensesFileListOnly(generate_licenses_file_list_only) | |
| 212 # Start from Chromium's LICENSE file | 206 # Start from Chromium's LICENSE file |
| 213 entries = [generator.MetadataToTemplateEntry({ | 207 entries = [generator.MetadataToTemplateEntry({ |
| 214 'Name': 'The Chromium Project', | 208 'Name': 'The Chromium Project', |
| 215 'URL': 'http://www.chromium.org', | 209 'URL': 'http://www.chromium.org', |
| 216 'License File': os.path.join(REPOSITORY_ROOT, 'LICENSE') }) | 210 'License File': os.path.join(REPOSITORY_ROOT, 'LICENSE') }) |
| 217 ] | 211 ] |
| 218 | 212 |
| 219 third_party_dirs = licenses.FindThirdPartyDirsWithFiles(REPOSITORY_ROOT) | 213 third_party_dirs = licenses.FindThirdPartyDirsWithFiles(REPOSITORY_ROOT) |
| 220 # We provide attribution for all third-party directories. | 214 # We provide attribution for all third-party directories. |
| 221 # TODO(mnaganov): Limit this to only code used by the WebView binary. | 215 # TODO(mnaganov): Limit this to only code used by the WebView binary. |
| 222 for directory in sorted(third_party_dirs): | 216 for directory in sorted(third_party_dirs): |
| 223 try: | 217 try: |
| 224 metadata = licenses.ParseDir(directory, REPOSITORY_ROOT, | 218 metadata = licenses.ParseDir(directory, REPOSITORY_ROOT, |
| 225 require_license_file=False) | 219 require_license_file=False) |
| 226 except licenses.LicenseError: | 220 except licenses.LicenseError: |
| 227 # Since this code is called during project files generation, | 221 # Since this code is called during project files generation, |
| 228 # we don't want to break the it. But we assume that release | 222 # we don't want to break the it. But we assume that release |
| 229 # WebView apks are built using checkouts that pass | 223 # WebView apks are built using checkouts that pass |
| 230 # 'webview_licenses.py scan' check, thus they don't contain | 224 # 'webview_licenses.py scan' check, thus they don't contain |
| 231 # projects with non-compatible licenses. | 225 # projects with non-compatible licenses. |
| 232 continue | 226 continue |
| 233 license_file = metadata['License File'] | 227 license_file = metadata['License File'] |
| 234 if license_file and license_file != licenses.NOT_SHIPPED: | 228 if license_file and license_file != licenses.NOT_SHIPPED: |
| 235 entries.append(generator.MetadataToTemplateEntry(metadata)) | 229 entries.append(generator.MetadataToTemplateEntry(metadata)) |
| 236 | 230 |
| 237 if generate_licenses_file_list_only: | 231 entries.sort(key=lambda entry: entry['name']) |
| 238 return [entry['license_file'] for entry in entries] | 232 |
| 239 else: | 233 license_file_list = sorted(set([entry['license_file'] for entry in entries])) |
| 240 env = jinja2.Environment( | 234 license_file_list = [os.path.relpath(p) for p in license_file_list] |
| 241 loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), | 235 env = jinja2.Environment( |
| 242 extensions=['jinja2.ext.autoescape']) | 236 loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), |
| 243 template = env.get_template('licenses_notice.tmpl') | 237 extensions=['jinja2.ext.autoescape']) |
| 244 return template.render({ 'entries': entries }).encode('utf8') | 238 template = env.get_template('licenses_notice.tmpl') |
| 239 notice_file_contents = template.render({'entries': entries}).encode('utf8') | |
| 240 return (license_file_list, notice_file_contents) | |
| 245 | 241 |
| 246 | 242 |
| 247 def main(): | 243 def main(): |
| 248 class FormatterWithNewLines(optparse.IndentedHelpFormatter): | 244 class FormatterWithNewLines(optparse.IndentedHelpFormatter): |
| 249 def format_description(self, description): | 245 def format_description(self, description): |
| 250 paras = description.split('\n') | 246 paras = description.split('\n') |
| 251 formatted_paras = [textwrap.fill(para, self.width) for para in paras] | 247 formatted_paras = [textwrap.fill(para, self.width) for para in paras] |
| 252 return '\n'.join(formatted_paras) + '\n' | 248 return '\n'.join(formatted_paras) + '\n' |
| 253 | 249 |
| 254 parser = optparse.OptionParser(formatter=FormatterWithNewLines(), | 250 parser = optparse.OptionParser(formatter=FormatterWithNewLines(), |
| 255 usage='%prog [options]') | 251 usage='%prog [options]') |
| 256 parser.add_option('--json', help='Path to JSON output file') | 252 parser.add_option('--json', help='Path to JSON output file') |
| 253 build_utils.AddDepfileOption(parser) | |
| 257 parser.description = (__doc__ + | 254 parser.description = (__doc__ + |
| 258 '\nCommands:\n' | 255 '\nCommands:\n' |
| 259 ' scan Check licenses.\n' | 256 ' scan Check licenses.\n' |
| 260 ' notice_deps Generate the list of dependencies for ' | |
| 261 'Android NOTICE file.\n' | 257 'Android NOTICE file.\n' |
| 262 ' notice [file] Generate Android NOTICE file on ' | 258 ' notice [file] Generate Android NOTICE file on ' |
| 263 'stdout or into |file|.\n' | 259 'stdout or into |file|.\n' |
| 264 ' display_copyrights Display autorship on the files' | 260 ' display_copyrights Display autorship on the files' |
| 265 ' using names provided via stdin.\n') | 261 ' using names provided via stdin.\n') |
| 266 (options, args) = parser.parse_args() | 262 options, args = parser.parse_args() |
| 267 if len(args) < 1: | 263 if len(args) < 1: |
| 268 parser.print_help() | 264 parser.print_help() |
| 269 return ScanResult.Errors | 265 return ScanResult.Errors |
| 270 | 266 |
| 271 if args[0] == 'scan': | 267 if args[0] == 'scan': |
| 272 scan_result, problem_paths = _Scan() | 268 scan_result, problem_paths = _Scan() |
| 273 if scan_result == ScanResult.Ok: | 269 if scan_result == ScanResult.Ok: |
| 274 print 'OK!' | 270 print 'OK!' |
| 275 if options.json: | 271 if options.json: |
| 276 with open(options.json, 'w') as f: | 272 with open(options.json, 'w') as f: |
| 277 json.dump(problem_paths, f) | 273 json.dump(problem_paths, f) |
| 278 return scan_result | 274 return scan_result |
| 279 elif args[0] == 'notice_deps': | |
| 280 # 'set' is used to eliminate duplicate references to the same license file. | |
| 281 print ' '.join( | |
| 282 sorted(set(GenerateNoticeFile(generate_licenses_file_list_only=True)))) | |
| 283 return ScanResult.Ok | |
| 284 elif args[0] == 'gn_notice_deps': | |
| 285 # generate list for gn. | |
| 286 # 'set' is used to eliminate duplicate references to the same license file. | |
| 287 gn_file_list = ['"' + f + '"' for f in | |
| 288 sorted(set(GenerateNoticeFile(generate_licenses_file_list_only=True)))] | |
| 289 print '[%s] ' % ','.join(gn_file_list) | |
| 290 return ScanResult.Ok | |
| 291 elif args[0] == 'notice': | 275 elif args[0] == 'notice': |
| 292 notice_file_contents = GenerateNoticeFile() | 276 license_file_list, notice_file_contents = GenerateNoticeFile() |
| 293 if len(args) == 1: | 277 if len(args) == 1: |
| 294 print notice_file_contents | 278 print notice_file_contents |
| 295 else: | 279 else: |
| 296 with open(args[1], 'w') as output_file: | 280 with open(args[1], 'w') as output_file: |
| 297 output_file.write(notice_file_contents) | 281 output_file.write(notice_file_contents) |
| 282 if options.depfile: | |
| 283 assert args[1] | |
| 284 # Add in build.ninja so that the target will be considered dirty whenever | |
| 285 # gn gen is run. Otherwise, it will fail to notice new files being added. | |
| 286 # This is still no perfect, as it will fail if no build files are changed, | |
| 287 # but a new README.chromium / LICENSE is added. This shouldn't happen in | |
| 288 # practice however. | |
| 289 build_utils.WriteDepfile(options.depfile, args[1], | |
| 290 license_file_list + ['build.ninja']) | |
| 291 | |
|
Dirk Pranke
2017/02/10 00:15:59
I don't understand this block. Why do you want to
agrieve
2017/02/10 00:32:35
Before this change, it was scanning the filesystem
| |
| 298 return ScanResult.Ok | 292 return ScanResult.Ok |
| 299 elif args[0] == 'display_copyrights': | 293 elif args[0] == 'display_copyrights': |
| 300 files = sys.stdin.read().splitlines() | 294 files = sys.stdin.read().splitlines() |
| 301 for f, c in \ | 295 for f, c in \ |
| 302 zip(files, copyright_scanner.FindCopyrights(InputApi(), '.', files)): | 296 zip(files, copyright_scanner.FindCopyrights(InputApi(), '.', files)): |
| 303 print f, '\t', ' / '.join(sorted(c)) | 297 print f, '\t', ' / '.join(sorted(c)) |
| 304 return ScanResult.Ok | 298 return ScanResult.Ok |
| 305 parser.print_help() | 299 parser.print_help() |
| 306 return ScanResult.Errors | 300 return ScanResult.Errors |
| 307 | 301 |
| 308 if __name__ == '__main__': | 302 if __name__ == '__main__': |
| 309 sys.exit(main()) | 303 sys.exit(main()) |
| OLD | NEW |