[SyncFS] Build indexes from FileTracker entries on disk.
[chromium-blink-merge.git] / android_webview / tools / webview_licenses.py
blob63a26e21548a7bb6a409ea1c75c159c1108cd790
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.
6 """Checks third-party licenses for the purposes of the Android WebView build.
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.
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 """
19 import glob
20 import imp
21 import optparse
22 import os
23 import re
24 import subprocess
25 import sys
26 import textwrap
29 REPOSITORY_ROOT = os.path.abspath(os.path.join(
30 os.path.dirname(__file__), '..', '..'))
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.
34 sys.dont_write_bytecode = True
35 third_party = \
36 imp.load_source('PRESUBMIT', \
37 os.path.join(REPOSITORY_ROOT, 'third_party', 'PRESUBMIT.py'))
39 sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools'))
40 import licenses
42 import known_issues
44 class InputApi(object):
45 def __init__(self):
46 self.re = re
48 def GetIncompatibleDirectories():
49 """Gets a list of third-party directories which use licenses incompatible
50 with Android. This is used by the snapshot tool.
51 Returns:
52 A list of directories.
53 """
55 result = []
56 for directory in _FindThirdPartyDirs():
57 if directory in known_issues.KNOWN_ISSUES:
58 result.append(directory)
59 continue
60 try:
61 metadata = licenses.ParseDir(directory, REPOSITORY_ROOT,
62 require_license_file=False)
63 except licenses.LicenseError as e:
64 print 'Got LicenseError while scanning ' + directory
65 raise
66 if metadata.get('License Android Compatible', 'no').upper() == 'YES':
67 continue
68 license = re.split(' [Ll]icenses?$', metadata['License'])[0]
69 if not third_party.LicenseIsCompatibleWithAndroid(InputApi(), license):
70 result.append(directory)
71 return result
73 def GetUnknownIncompatibleDirectories():
74 """Gets a list of third-party directories which use licenses incompatible
75 with Android which are not present in the known_issues.py file.
76 This is used by the AOSP bot.
77 Returns:
78 A list of directories.
79 """
80 incompatible_directories = frozenset(GetIncompatibleDirectories())
81 known_incompatible = []
82 for path, exclude_list in known_issues.KNOWN_INCOMPATIBLE.iteritems():
83 for exclude in exclude_list:
84 if glob.has_magic(exclude):
85 exclude_dirname = os.path.dirname(exclude)
86 if glob.has_magic(exclude_dirname):
87 print ('Exclude path %s contains an unexpected glob expression,' \
88 ' skipping.' % exclude)
89 exclude = exclude_dirname
90 known_incompatible.append(os.path.normpath(os.path.join(path, exclude)))
91 known_incompatible = frozenset(known_incompatible)
92 return incompatible_directories.difference(known_incompatible)
95 class ScanResult(object):
96 Ok, Warnings, Errors = range(3)
98 def _CheckLicenseHeaders(excluded_dirs_list, whitelisted_files):
99 """Checks that all files which are not in a listed third-party directory,
100 and which do not use the standard Chromium license, are whitelisted.
101 Args:
102 excluded_dirs_list: The list of directories to exclude from scanning.
103 whitelisted_files: The whitelist of files.
104 Returns:
105 ScanResult.Ok if all files with non-standard license headers are whitelisted
106 and the whitelist contains no stale entries;
107 ScanResult.Warnings if there are stale entries;
108 ScanResult.Errors if new non-whitelisted entries found.
111 excluded_dirs_list = [d for d in excluded_dirs_list if not 'third_party' in d]
112 # Using a common pattern for third-partyies makes the ignore regexp shorter
113 excluded_dirs_list.append('third_party')
114 # VCS dirs
115 excluded_dirs_list.append('.git')
116 excluded_dirs_list.append('.svn')
117 # Build output
118 excluded_dirs_list.append('out/Debug')
119 excluded_dirs_list.append('out/Release')
120 # 'Copyright' appears in license agreements
121 excluded_dirs_list.append('chrome/app/resources')
122 # Quickoffice js files from internal src used on buildbots. crbug.com/350472.
123 excluded_dirs_list.append('chrome/browser/resources/chromeos/quickoffice')
124 # This is a test output directory
125 excluded_dirs_list.append('chrome/tools/test/reference_build')
126 # blink style copy right headers.
127 excluded_dirs_list.append('content/shell/renderer/test_runner')
128 # blink style copy right headers.
129 excluded_dirs_list.append('content/shell/tools/plugin')
130 # This is tests directory, doesn't exist in the snapshot
131 excluded_dirs_list.append('content/test/data')
132 # This is a tests directory that doesn't exist in the shipped product.
133 excluded_dirs_list.append('gin/test')
134 # This is a test output directory
135 excluded_dirs_list.append('data/dom_perf')
136 # This is a tests directory that doesn't exist in the shipped product.
137 excluded_dirs_list.append('tools/perf/page_sets')
138 excluded_dirs_list.append('tools/perf/page_sets/tough_animation_cases')
139 # Histogram tools, doesn't exist in the snapshot
140 excluded_dirs_list.append('tools/histograms')
141 # Swarming tools, doesn't exist in the snapshot
142 excluded_dirs_list.append('tools/swarming_client')
143 # Arm sysroot tools, doesn't exist in the snapshot
144 excluded_dirs_list.append('arm-sysroot')
145 # Data is not part of open source chromium, but are included on some bots.
146 excluded_dirs_list.append('data')
147 # This is not part of open source chromium, but are included on some bots.
148 excluded_dirs_list.append('skia/tools/clusterfuzz-data')
150 args = ['android_webview/tools/find_copyrights.pl',
152 ] + excluded_dirs_list
153 p = subprocess.Popen(args=args, cwd=REPOSITORY_ROOT, stdout=subprocess.PIPE)
154 lines = p.communicate()[0].splitlines()
156 offending_files = []
157 allowed_copyrights = '^(?:\*No copyright\*' \
158 '|20[0-9][0-9](?:-20[0-9][0-9])? The Chromium Authors\. ' \
159 'All rights reserved.*)$'
160 allowed_copyrights_re = re.compile(allowed_copyrights)
161 for l in lines:
162 entries = l.split('\t')
163 if entries[1] == "GENERATED FILE":
164 continue
165 copyrights = entries[1].split(' / ')
166 for c in copyrights:
167 if c and not allowed_copyrights_re.match(c):
168 offending_files.append(os.path.normpath(entries[0]))
169 break
171 unknown = set(offending_files) - set(whitelisted_files)
172 if unknown:
173 print 'The following files contain a third-party license but are not in ' \
174 'a listed third-party directory and are not whitelisted. You must ' \
175 'add the following files to the whitelist.\n%s' % \
176 '\n'.join(sorted(unknown))
178 stale = set(whitelisted_files) - set(offending_files)
179 if stale:
180 print 'The following files are whitelisted unnecessarily. You must ' \
181 'remove the following files from the whitelist.\n%s' % \
182 '\n'.join(sorted(stale))
183 missing = [f for f in whitelisted_files if not os.path.exists(f)]
184 if missing:
185 print 'The following files are whitelisted, but do not exist.\n%s' % \
186 '\n'.join(sorted(missing))
188 if unknown:
189 return ScanResult.Errors
190 elif stale or missing:
191 return ScanResult.Warnings
192 else:
193 return ScanResult.Ok
196 def _ReadFile(path):
197 """Reads a file from disk.
198 Args:
199 path: The path of the file to read, relative to the root of the repository.
200 Returns:
201 The contents of the file as a string.
204 return open(os.path.join(REPOSITORY_ROOT, path), 'rb').read()
207 def _FindThirdPartyDirs():
208 """Gets the list of third-party directories.
209 Returns:
210 The list of third-party directories.
213 # Please don't add here paths that have problems with license files,
214 # as they will end up included in Android WebView snapshot.
215 # Instead, add them into known_issues.py.
216 prune_paths = [
217 # Temporary until we figure out how not to check out quickoffice on the
218 # Android license check bot. Tracked in crbug.com/350472.
219 os.path.join('chrome', 'browser', 'resources', 'chromeos', 'quickoffice'),
220 # Placeholder directory, no third-party code.
221 os.path.join('third_party', 'adobe'),
222 # Apache 2.0 license. See
223 # https://code.google.com/p/chromium/issues/detail?id=140478.
224 os.path.join('third_party', 'bidichecker'),
225 # Isn't checked out on clients
226 os.path.join('third_party', 'gles2_conform'),
227 # The llvm-build doesn't exist for non-clang builder
228 os.path.join('third_party', 'llvm-build'),
229 # Binaries doesn't apply to android
230 os.path.join('third_party', 'widevine'),
231 # third_party directories in this tree aren't actually third party, but
232 # provide a way to shadow experimental buildfiles into those directories.
233 os.path.join('build', 'secondary'),
234 # Not shipped, Chromium code
235 os.path.join('tools', 'swarming_client'),
237 third_party_dirs = licenses.FindThirdPartyDirs(prune_paths, REPOSITORY_ROOT)
238 return licenses.FilterDirsWithFiles(third_party_dirs, REPOSITORY_ROOT)
241 def _Scan():
242 """Checks that license meta-data is present for all third-party code and
243 that all non third-party code doesn't contain external copyrighted code.
244 Returns:
245 ScanResult.Ok if everything is in order;
246 ScanResult.Warnings if there are non-fatal problems (e.g. stale whitelist
247 entries)
248 ScanResult.Errors otherwise.
251 third_party_dirs = _FindThirdPartyDirs()
253 # First, check designated third-party directories using src/tools/licenses.py.
254 all_licenses_valid = True
255 for path in sorted(third_party_dirs):
256 try:
257 licenses.ParseDir(path, REPOSITORY_ROOT)
258 except licenses.LicenseError, e:
259 if not (path in known_issues.KNOWN_ISSUES):
260 print 'Got LicenseError "%s" while scanning %s' % (e, path)
261 all_licenses_valid = False
263 # Second, check for non-standard license text.
264 files_data = _ReadFile(os.path.join('android_webview', 'tools',
265 'third_party_files_whitelist.txt'))
266 whitelisted_files = []
267 for line in files_data.splitlines():
268 match = re.match(r'([^#\s]+)', line)
269 if match:
270 whitelisted_files.append(match.group(1))
271 licenses_check = _CheckLicenseHeaders(third_party_dirs, whitelisted_files)
273 return licenses_check if all_licenses_valid else ScanResult.Errors
276 def GenerateNoticeFile():
277 """Generates the contents of an Android NOTICE file for the third-party code.
278 This is used by the snapshot tool.
279 Returns:
280 The contents of the NOTICE file.
283 third_party_dirs = _FindThirdPartyDirs()
285 # Don't forget Chromium's LICENSE file
286 content = [_ReadFile('LICENSE')]
288 # We provide attribution for all third-party directories.
289 # TODO(steveblock): Limit this to only code used by the WebView binary.
290 for directory in sorted(third_party_dirs):
291 metadata = licenses.ParseDir(directory, REPOSITORY_ROOT,
292 require_license_file=False)
293 license_file = metadata['License File']
294 if license_file and license_file != licenses.NOT_SHIPPED:
295 content.append(_ReadFile(license_file))
297 return '\n'.join(content)
300 def _ProcessIncompatibleResult(incompatible_directories):
301 if incompatible_directories:
302 print ("Incompatibly licensed directories found:\n" +
303 "\n".join(sorted(incompatible_directories)))
304 return ScanResult.Errors
305 return ScanResult.Ok
307 def main():
308 class FormatterWithNewLines(optparse.IndentedHelpFormatter):
309 def format_description(self, description):
310 paras = description.split('\n')
311 formatted_paras = [textwrap.fill(para, self.width) for para in paras]
312 return '\n'.join(formatted_paras) + '\n'
314 parser = optparse.OptionParser(formatter=FormatterWithNewLines(),
315 usage='%prog [options]')
316 parser.description = (__doc__ +
317 '\nCommands:\n' \
318 ' scan Check licenses.\n' \
319 ' notice Generate Android NOTICE file on stdout.\n' \
320 ' incompatible_directories Scan for incompatibly'
321 ' licensed directories.\n'
322 ' all_incompatible_directories Scan for incompatibly'
323 ' licensed directories (even those in'
324 ' known_issues.py).\n')
325 (_, args) = parser.parse_args()
326 if len(args) != 1:
327 parser.print_help()
328 return ScanResult.Errors
330 if args[0] == 'scan':
331 scan_result = _Scan()
332 if scan_result == ScanResult.Ok:
333 print 'OK!'
334 return scan_result
335 elif args[0] == 'notice':
336 print GenerateNoticeFile()
337 return ScanResult.Ok
338 elif args[0] == 'incompatible_directories':
339 return _ProcessIncompatibleResult(GetUnknownIncompatibleDirectories())
340 elif args[0] == 'all_incompatible_directories':
341 return _ProcessIncompatibleResult(GetIncompatibleDirectories())
342 parser.print_help()
343 return ScanResult.Errors
345 if __name__ == '__main__':
346 sys.exit(main())