Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(263)

Side by Side Diff: android_webview/tools/webview_licenses.py

Issue 2670833004: Use a depfile to know when to regenerate notice files (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « android_webview/BUILD.gn ('k') | components/resources/BUILD.gn » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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())
OLDNEW
« no previous file with comments | « android_webview/BUILD.gn ('k') | components/resources/BUILD.gn » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698