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

Side by Side Diff: tools/isolate/isolate.py

Issue 10080013: Makes the code simpler by automatically deducing the root directory to use. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Now completely working on windows Created 8 years, 8 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 | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 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 """Does one of the following depending on the --mode argument: 6 """Does one of the following depending on the --mode argument:
7 check Verifies all the inputs exist, touches the file specified with 7 check Verifies all the inputs exist, touches the file specified with
8 --result and exits. 8 --result and exits.
9 hashtable Puts a manifest file and hard links each of the inputs into the 9 hashtable Puts a manifest file and hard links each of the inputs into the
10 output directory. 10 output directory.
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
222 222
223 for relfile in infiles: 223 for relfile in infiles:
224 infile = os.path.join(indir, relfile) 224 infile = os.path.join(indir, relfile)
225 outfile = os.path.join(outdir, relfile) 225 outfile = os.path.join(outdir, relfile)
226 outsubdir = os.path.dirname(outfile) 226 outsubdir = os.path.dirname(outfile)
227 if not os.path.isdir(outsubdir): 227 if not os.path.isdir(outsubdir):
228 os.makedirs(outsubdir) 228 os.makedirs(outsubdir)
229 run_test_from_archive.link_file(outfile, infile, action) 229 run_test_from_archive.link_file(outfile, infile, action)
230 230
231 231
232 def isolate( 232 def load_results(resultfile):
233 outdir, indir, infiles, mode, read_only, cmd, relative_cwd, resultfile): 233 """Loads the previous results as an optimization."""
234 """Main function to isolate a target with its dependencies. 234 data = {}
235
236 Arguments:
237 - outdir: Output directory where the result is stored. Depends on |mode|.
238 - indir: Root directory to be used as the base directory for infiles.
239 - infiles: List of files, with relative path, to process.
240 - mode: Action to do. See file level docstring.
241 - read_only: Makes the temporary directory read only.
242 - cmd: Command to execute.
243 - relative_cwd: Directory relative to the base directory where to start the
244 command from. In general, this path will be the path
245 containing the gyp file where the target was defined. This
246 relative directory may be created implicitely if a file from
247 this directory is needed to run the test. Otherwise it won't
248 be created and the process creation will fail. It's up to the
249 caller to create this directory manually before starting the
250 test.
251 - resultfile: Path where to read and write the metadata.
252
253 Some arguments are optional, dependending on |mode|. See the corresponding
254 MODE<mode> function for the exact behavior.
255 """
256 mode_fn = getattr(sys.modules[__name__], 'MODE' + mode)
257 assert mode_fn
258
259 # Load the previous results as an optimization.
260 prevdict = {}
261 if resultfile and os.path.isfile(resultfile): 235 if resultfile and os.path.isfile(resultfile):
262 resultfile = os.path.abspath(resultfile) 236 resultfile = os.path.abspath(resultfile)
263 with open(resultfile, 'rb') as f: 237 with open(resultfile, 'r') as f:
264 prevdict = json.load(f) 238 data = json.load(f)
265 else: 239 else:
266 resultfile = os.path.abspath(resultfile) 240 resultfile = os.path.abspath(resultfile)
241
267 # Works with native os.path.sep but stores as '/'. 242 # Works with native os.path.sep but stores as '/'.
268 if 'files' in prevdict and os.path.sep != '/': 243 if 'files' in data and os.path.sep != '/':
269 prevdict['files'] = dict( 244 data['files'] = dict(
270 (k.replace('/', os.path.sep), v) 245 (k.replace('/', os.path.sep), v)
271 for k, v in prevdict['files'].iteritems()) 246 for k, v in data['files'].iteritems())
247 return data
272 248
273 249
274 infiles = expand_directories( 250 def save_results(resultfile, data):
275 indir, infiles, lambda x: re.match(r'.*\.(svn|pyc)$', x)) 251 data = data.copy()
276
277 # Only hashtable mode really needs the sha-1.
278 level = {
279 'check': NO_INFO,
280 'hashtable': WITH_HASH,
281 'remap': STATS_ONLY,
282 'run': STATS_ONLY,
283 'trace': STATS_ONLY,
284 }
285 dictfiles = process_inputs(
286 prevdict.get('files', {}), indir, infiles, level[mode], read_only)
287
288 result = mode_fn(
289 outdir, indir, dictfiles, read_only, cmd, relative_cwd, resultfile)
290 out = {
291 'command': cmd,
292 'relative_cwd': relative_cwd,
293 'files': dictfiles,
294 # Makes the directories read-only in addition to the files.
295 'read_only': read_only,
296 }
297 252
298 # Works with native os.path.sep but stores as '/'. 253 # Works with native os.path.sep but stores as '/'.
299 if os.path.sep != '/': 254 if os.path.sep != '/':
300 out['files'] = dict( 255 data['files'] = dict(
301 (k.replace(os.path.sep, '/'), v) for k, v in out['files'].iteritems()) 256 (k.replace(os.path.sep, '/'), v) for k, v in data['files'].iteritems())
302 257
303 f = None 258 f = None
304 try: 259 try:
305 if resultfile: 260 if resultfile:
306 f = open(resultfile, 'wb') 261 f = open(resultfile, 'wb')
307 else: 262 else:
308 f = sys.stdout 263 f = sys.stdout
309 json.dump(out, f, indent=2, sort_keys=True) 264 json.dump(data, f, indent=2, sort_keys=True)
310 f.write('\n') 265 f.write('\n')
311 finally: 266 finally:
312 if resultfile and f: 267 if resultfile and f:
313 f.close() 268 f.close()
314 269
315 total_bytes = sum(i.get('size', 0) for i in out['files'].itervalues()) 270 total_bytes = sum(i.get('size', 0) for i in data['files'].itervalues())
316 if total_bytes: 271 if total_bytes:
317 logging.debug('Total size: %d bytes' % total_bytes) 272 logging.debug('Total size: %d bytes' % total_bytes)
318 return result
319 273
320 274
321 def MODEcheck( 275 def isolate(outdir, mode, indir, infiles, data):
322 _outdir, _indir, _dictfiles, _read_only, _cmd, _relative_cwd, _resultfile): 276 """Main function to isolate a target with its dependencies.
277
278 Arguments:
279 - outdir: Output directory where the result is stored. Depends on |mode|.
280 - indir: Root directory to be used as the base directory for infiles.
281 - infiles: List of files, with relative path, to process.
282 - mode: Action to do. See file level docstring.
283 - data: Contains all the command specific meta-data.
284
285 Some arguments are optional, dependending on |mode|. See the corresponding
286 MODE<mode> function for the exact behavior.
287 """
288 modes = {
289 'check': MODEcheck,
290 'hashtable': MODEhashtable,
291 'remap': MODEremap,
292 'run': MODErun,
293 'trace': MODEtrace,
294 }
295 mode_fn = modes[mode]
296
297 infiles = expand_directories(
298 indir, infiles, lambda x: re.match(r'.*\.(git|svn|pyc)$', x))
299
300 # Only hashtable mode really needs the sha-1.
301 level = {
302 'check': NO_INFO,
303 'hashtable': WITH_HASH,
304 'remap': STATS_ONLY,
305 'run': STATS_ONLY,
306 'trace': STATS_ONLY,
307 }
308 # Regenerate data['files'] from infiles.
309 data['files'] = process_inputs(
310 data.get('files', {}), indir, infiles, level[mode], data.get('read_only'))
311
312 result = mode_fn(outdir, indir, data)
313 return result, data
314
315
316 def MODEcheck(_outdir, _indir, _data):
323 """No-op.""" 317 """No-op."""
324 return 0 318 return 0
325 319
326 320
327 def MODEhashtable( 321 def MODEhashtable(outdir, indir, data):
328 outdir, indir, dictfiles, _read_only, _cmd, _relative_cwd, resultfile): 322 outdir = (
329 outdir = outdir or os.path.join(os.path.dirname(resultfile), 'hashtable') 323 outdir or os.path.join(os.path.dirname(data['resultdir']), 'hashtable'))
330 if not os.path.isdir(outdir): 324 if not os.path.isdir(outdir):
331 os.makedirs(outdir) 325 os.makedirs(outdir)
332 for relfile, properties in dictfiles.iteritems(): 326 for relfile, properties in data['files'].iteritems():
333 infile = os.path.join(indir, relfile) 327 infile = os.path.join(indir, relfile)
334 outfile = os.path.join(outdir, properties['sha-1']) 328 outfile = os.path.join(outdir, properties['sha-1'])
335 if os.path.isfile(outfile): 329 if os.path.isfile(outfile):
336 # Just do a quick check that the file size matches. No need to stat() 330 # Just do a quick check that the file size matches. No need to stat()
337 # again the input file, grab the value from the dict. 331 # again the input file, grab the value from the dict.
338 out_size = os.stat(outfile).st_size 332 out_size = os.stat(outfile).st_size
339 in_size = dictfiles.get(infile, {}).get('size') or os.stat(infile).st_size 333 in_size = (
334 data.get('files', {}).get(infile, {}).get('size') or
335 os.stat(infile).st_size)
340 if in_size == out_size: 336 if in_size == out_size:
341 continue 337 continue
342 # Otherwise, an exception will be raised. 338 # Otherwise, an exception will be raised.
343 run_test_from_archive.link_file( 339 run_test_from_archive.link_file(
344 outfile, infile, run_test_from_archive.HARDLINK) 340 outfile, infile, run_test_from_archive.HARDLINK)
345 return 0 341 return 0
346 342
347 343
348 def MODEremap( 344 def MODEremap(outdir, indir, data):
349 outdir, indir, dictfiles, read_only, _cmd, _relative_cwd, _resultfile):
350 if not outdir: 345 if not outdir:
351 outdir = tempfile.mkdtemp(prefix='isolate') 346 outdir = tempfile.mkdtemp(prefix='isolate')
352 else: 347 else:
353 if not os.path.isdir(outdir): 348 if not os.path.isdir(outdir):
354 os.makedirs(outdir) 349 os.makedirs(outdir)
355 print 'Remapping into %s' % outdir 350 print 'Remapping into %s' % outdir
356 if len(os.listdir(outdir)): 351 if len(os.listdir(outdir)):
357 print 'Can\'t remap in a non-empty directory' 352 print 'Can\'t remap in a non-empty directory'
358 return 1 353 return 1
359 recreate_tree(outdir, indir, dictfiles.keys(), run_test_from_archive.HARDLINK) 354 recreate_tree(
360 if read_only: 355 outdir, indir, data['files'].keys(), run_test_from_archive.HARDLINK)
356 if data['read_only']:
361 run_test_from_archive.make_writable(outdir, True) 357 run_test_from_archive.make_writable(outdir, True)
362 return 0 358 return 0
363 359
364 360
365 def MODErun( 361 def MODErun(_outdir, indir, data):
366 _outdir, indir, dictfiles, read_only, cmd, relative_cwd, _resultfile):
367 """Always uses a temporary directory.""" 362 """Always uses a temporary directory."""
368 try: 363 try:
369 outdir = tempfile.mkdtemp(prefix='isolate') 364 outdir = tempfile.mkdtemp(prefix='isolate')
370 recreate_tree( 365 recreate_tree(
371 outdir, indir, dictfiles.keys(), run_test_from_archive.HARDLINK) 366 outdir, indir, data['files'].keys(), run_test_from_archive.HARDLINK)
372 cwd = os.path.join(outdir, relative_cwd) 367 cwd = os.path.join(outdir, data['relative_cwd'])
373 if not os.path.isdir(cwd): 368 if not os.path.isdir(cwd):
374 os.makedirs(cwd) 369 os.makedirs(cwd)
375 if read_only: 370 if data['read_only']:
376 run_test_from_archive.make_writable(outdir, True) 371 run_test_from_archive.make_writable(outdir, True)
377 if not cmd: 372 if not data['command']:
378 print 'No command to run' 373 print 'No command to run'
379 return 1 374 return 1
380 cmd = trace_inputs.fix_python_path(cmd) 375 cmd = trace_inputs.fix_python_path(data['command'])
381 logging.info('Running %s, cwd=%s' % (cmd, cwd)) 376 logging.info('Running %s, cwd=%s' % (cmd, cwd))
382 return subprocess.call(cmd, cwd=cwd) 377 return subprocess.call(cmd, cwd=cwd)
383 finally: 378 finally:
384 run_test_from_archive.rmtree(outdir) 379 run_test_from_archive.rmtree(outdir)
385 380
386 381
387 def MODEtrace( 382 def MODEtrace(_outdir, indir, data):
388 _outdir, indir, _dictfiles, _read_only, cmd, relative_cwd, resultfile):
389 """Shortcut to use trace_inputs.py properly. 383 """Shortcut to use trace_inputs.py properly.
390 384
391 It constructs the equivalent of dictfiles. It is hardcoded to base the 385 It constructs the equivalent of dictfiles. It is hardcoded to base the
392 checkout at src/. 386 checkout at src/.
393 """ 387 """
394 logging.info('Running %s, cwd=%s' % (cmd, os.path.join(indir, relative_cwd))) 388 logging.info(
395 if resultfile: 389 'Running %s, cwd=%s' % (
396 # Guesswork here. 390 data['command'], os.path.join(indir, data['relative_cwd'])))
397 product_dir = os.path.dirname(resultfile) 391 product_dir = None
398 if product_dir and indir: 392 if data['resultdir'] and indir:
399 product_dir = os.path.relpath(product_dir, indir) 393 # Defaults to none if both are the same directory.
400 else: 394 product_dir = os.path.relpath(data['resultdir'], indir) or None
401 product_dir = None 395 if not data['command']:
402 if not cmd:
403 print 'No command to run' 396 print 'No command to run'
404 return 1 397 return 1
405 return trace_inputs.trace_inputs( 398 return trace_inputs.trace_inputs(
406 '%s.log' % resultfile, 399 data['resultfile'],
407 cmd, 400 data['command'],
408 indir, 401 indir,
409 relative_cwd, 402 data['relative_cwd'],
410 product_dir, 403 product_dir,
411 False) 404 False)
412 405
413 406
414 def get_valid_modes(): 407 def get_valid_modes():
415 """Returns the modes that can be used.""" 408 """Returns the modes that can be used."""
416 return sorted( 409 return sorted(
417 i[4:] for i in dir(sys.modules[__name__]) if i.startswith('MODE')) 410 i[4:] for i in dir(sys.modules[__name__]) if i.startswith('MODE'))
418 411
419 412
413 def process_options(variables, resultfile, input_file, error):
414 """Processes the options and loads the input file. Returns the processed
415 values.
416 """
417 input_file = os.path.abspath(input_file)
418 isolate_dir = os.path.dirname(input_file)
419 resultfile = os.path.abspath(resultfile)
420 logging.info(
421 'process_options(%s, %s, %s, ...)' % (variables, resultfile, input_file))
422
423 # Process path variables as a special case. First normalize it, verifies it
424 # exists, convert it to an absolute path, then set it as relative to
425 # isolate_dir.
426 for i in ('DEPTH', 'PRODUCT_DIR'):
427 if i not in variables:
428 continue
429 variable = os.path.normpath(variables[i])
430 if not os.path.isdir(variable):
431 error('%s=%s is not a directory' % (i, variable))
432 variable = os.path.abspath(variable)
433 # All variables are relative to the input file.
434 variables[i] = os.path.relpath(variable, isolate_dir)
435
436 command, infiles, read_only = load_isolate(
437 open(input_file, 'r').read(), variables, error)
438
439 # The trick used to determine the root directory is to look at "how far" back
440 # up it is looking up.
441 # TODO(maruel): Stop the msbuild generator from generating a mix of / and \\.
442 root_dir = isolate_dir.replace(os.path.sep, '/')
443 for i in infiles:
444 i = i.replace(os.path.sep, '/')
445 x = isolate_dir.replace(os.path.sep, '/')
446 while i.startswith('../'):
447 i = i[3:]
448 assert not i.startswith('/')
449 x = posixpath.dirname(x)
450 if root_dir.startswith(x):
451 root_dir = x
452 root_dir = root_dir.replace('/', os.path.sep)
453 # The relative directory is automatically determined by the relative path
454 # between root_dir and the directory containing the .isolate file.
455 relative_dir = os.path.relpath(isolate_dir, root_dir)
456 logging.debug('relative_dir: %s' % relative_dir)
457
458 logging.debug(
459 'variables: %s' % ', '.join(
460 '%s=%s' % (k, v) for k, v in variables.iteritems()))
461
462 data = load_results(resultfile)
463
464 command, infiles, read_only = load_isolate(
465 open(input_file, 'r').read(), variables, error)
466
467 # Update data with the up to date information:
468 data['command'] = command
469 data['read_only'] = read_only
470 data['relative_cwd'] = relative_dir
471 data['resultfile'] = resultfile
472 data['resultdir'] = os.path.dirname(resultfile)
473
474 # Keep the old variables.
475 data.setdefault('variables', {}).update(variables)
476
477 logging.debug('command: %s' % command)
478 logging.debug('infiles: %s' % infiles)
479 logging.debug('read_only: %s' % read_only)
480 infiles = [normpath(os.path.join(relative_dir, f)) for f in infiles]
481 logging.debug('processed infiles: %s' % infiles)
482 return root_dir, infiles, data
483
484
420 def main(): 485 def main():
421 default_variables = [('OS', trace_inputs.get_flavor())] 486 default_variables = [('OS', trace_inputs.get_flavor())]
422 if sys.platform in ('win32', 'cygwin'): 487 if sys.platform in ('win32', 'cygwin'):
423 default_variables.append(('EXECUTABLE_SUFFIX', '.exe')) 488 default_variables.append(('EXECUTABLE_SUFFIX', '.exe'))
424 else: 489 else:
425 default_variables.append(('EXECUTABLE_SUFFIX', '')) 490 default_variables.append(('EXECUTABLE_SUFFIX', ''))
426 valid_modes = get_valid_modes() 491 valid_modes = get_valid_modes()
427 parser = optparse.OptionParser( 492 parser = optparse.OptionParser(
428 usage='%prog [options] [.isolate file]', 493 usage='%prog [options] [.isolate file]',
429 description=sys.modules[__name__].__doc__) 494 description=sys.modules[__name__].__doc__)
(...skipping 28 matching lines...) Expand all
458 523
459 options, args = parser.parse_args() 524 options, args = parser.parse_args()
460 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] 525 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)]
461 logging.basicConfig( 526 logging.basicConfig(
462 level=level, 527 level=level,
463 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') 528 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s')
464 529
465 if not options.mode: 530 if not options.mode:
466 parser.error('--mode is required') 531 parser.error('--mode is required')
467 if len(args) != 1: 532 if len(args) != 1:
533 logging.debug('%s' % sys.argv)
468 parser.error('Use only one argument which should be a .isolate file') 534 parser.error('Use only one argument which should be a .isolate file')
469 535
470 input_file = os.path.abspath(args[0]) 536 root_dir, infiles, data = process_options(
471 isolate_dir = os.path.dirname(input_file) 537 dict(options.variables), options.result, args[0], parser.error)
472
473 # Extract the variables.
474 variables = dict(options.variables)
475 # Process path variables as a special case. First normalize it, verifies it
476 # exists, convert it to an absolute path, then set it as relative to
477 # isolate_dir.
478 for i in ('PRODUCT_DIR',):
479 if i not in variables:
480 continue
481 variable = os.path.normpath(variables[i])
482 if not os.path.isdir(variable):
483 parser.error('%s=%s is not a directory' % (i, variable))
484 variable = os.path.abspath(variable)
485 # All variables are relative to the input file.
486 variables[i] = os.path.relpath(variable, isolate_dir)
487
488 command, infiles, read_only = load_isolate(
489 open(input_file, 'r').read(), variables, parser.error)
490
491 # The trick used to determine the root directory is to look at "how far" back
492 # up it is looking up.
493 root_dir = isolate_dir.replace(os.path.sep, '/')
494 for i in infiles:
495 i = i.replace(os.path.sep, '/')
496 x = isolate_dir.replace(os.path.sep, '/')
497 while i.startswith('../'):
498 i = i[3:]
499 assert not i.startswith('/')
500 x = posixpath.dirname(x)
501 if root_dir.startswith(x):
502 root_dir = x
503 root_dir = root_dir.replace('/', os.path.sep)
504 # The relative directory is automatically determined by the relative path
505 # between root_dir and the directory containing the .isolate file.
506 relative_dir = os.path.relpath(isolate_dir, root_dir)
507 logging.debug('relative_dir: %s' % relative_dir)
508
509 logging.debug(
510 'variables: %s' % ', '.join(
511 '%s=%s' % (k, v) for k, v in variables.iteritems()))
512 logging.debug('command: %s' % command)
513 logging.debug('infiles: %s' % infiles)
514 logging.debug('read_only: %s' % read_only)
515 infiles = [normpath(os.path.join(relative_dir, f)) for f in infiles]
516 logging.debug('processed infiles: %s' % infiles)
517 538
518 try: 539 try:
519 return isolate( 540 resultcode, data = isolate(
520 options.outdir, 541 options.outdir,
542 options.mode,
521 root_dir, 543 root_dir,
522 infiles, 544 infiles,
523 options.mode, 545 data)
524 read_only,
525 command,
526 relative_dir,
527 options.result)
528 except run_test_from_archive.MappingError, e: 546 except run_test_from_archive.MappingError, e:
529 print >> sys.stderr, str(e) 547 print >> sys.stderr, str(e)
530 return 1 548 return 1
549 save_results(options.result, data)
550 return resultcode
531 551
532 552
533 if __name__ == '__main__': 553 if __name__ == '__main__':
534 sys.exit(main()) 554 sys.exit(main())
OLDNEW
« no previous file with comments | « third_party/cacheinvalidation/cacheinvalidation.gyp ('k') | tools/isolate/isolate_smoke_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698