Index: tools/isolate/isolate.py |
diff --git a/tools/isolate/isolate.py b/tools/isolate/isolate.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..be4f076efa3a3b3ea32eeb11708f4d30faeb366e |
--- /dev/null |
+++ b/tools/isolate/isolate.py |
@@ -0,0 +1,167 @@ |
+#!/usr/bin/env python |
+# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Does one of the following depending on the --mode argument: |
+ check verify all the inputs exist, touches the file specified with |
+ --result and exits. |
+ run recreates a tree with all the inputs files and run the executable |
+ in it. |
+ remap stores all the inputs files in a directory without running the |
+ executable. |
+""" |
+ |
+import logging |
+import optparse |
+import os |
+import re |
+import shutil |
+import subprocess |
+import sys |
+import tempfile |
+import time |
+ |
+import tree_creator |
+ |
+ |
+def touch(filename): |
+ """Implements the equivalent of the 'touch' command.""" |
+ if not os.path.exists(filename): |
+ open(filename, 'a').close() |
+ os.utime(filename, None) |
+ |
+ |
+def rmtree(root): |
+ """Wrapper around shutil.rmtree() to retry automatically on Windows.""" |
+ if sys.platform == 'win32': |
+ for i in range(3): |
+ try: |
+ shutil.rmtree(root) |
+ break |
+ except WindowsError: # pylint: disable=E0602 |
+ delay = (i+1)*2 |
+ print >> sys.stderr, ( |
+ 'The test has subprocess outliving it. Sleep %d seconds.' % delay) |
+ time.sleep(delay) |
+ else: |
+ shutil.rmtree(root) |
+ |
+ |
+def isolate(outdir, root, resultfile, mode, read_only, args): |
+ """Main function to isolate a target with its dependencies.""" |
+ cmd = [] |
+ if '--' in args: |
+ # Strip off the command line from the inputs. |
+ i = args.index('--') |
+ cmd = args[i+1:] |
+ args = args[:i] |
+ |
+ # gyp provides paths relative to cwd. Convert them to be relative to |
+ # root. |
+ cwd = os.getcwd() |
+ |
+ def make_relpath(i): |
+ """Makes an input file a relative path but keeps any trailing slash.""" |
+ out = os.path.relpath(os.path.join(cwd, i), root) |
+ if i.endswith('/'): |
+ out += '/' |
+ return out |
+ |
+ infiles = [make_relpath(i) for i in args] |
+ |
+ if not infiles: |
+ raise ValueError('Need at least one input file to map') |
+ |
+ # Other modes ignore the command. |
+ if mode == 'run' and not cmd: |
+ print >> sys.stderr, 'Using first input %s as executable' % infiles[0] |
+ cmd = [infiles[0]] |
+ |
+ tempdir = None |
+ try: |
+ if not outdir and mode != 'check': |
+ tempdir = tempfile.mkdtemp(prefix='isolate') |
+ outdir = tempdir |
+ elif outdir: |
+ outdir = os.path.abspath(outdir) |
+ |
+ tree_creator.recreate_tree( |
+ outdir, |
+ os.path.abspath(root), |
+ infiles, |
+ tree_creator.DRY_RUN if mode == 'check' else tree_creator.HARDLINK, |
+ lambda x: re.match(r'.*\.(svn|pyc)$', x)) |
+ |
+ if mode != 'check' and read_only: |
+ tree_creator.make_writable(outdir, True) |
+ |
+ if mode in ('check', 'remap'): |
+ result = 0 |
+ else: |
+ # TODO(maruel): Remove me. Layering violation. Used by |
+ # base/base_paths_linux.cc |
+ env = os.environ.copy() |
+ env['CR_SOURCE_ROOT'] = outdir.encode() |
+ # Rebase the command to the right path. |
+ cmd[0] = os.path.join(outdir, cmd[0]) |
+ logging.info('Running %s' % cmd) |
+ result = subprocess.call(cmd, cwd=outdir, env=env) |
+ |
+ if not result and resultfile: |
+ # Signal the build tool that the test succeeded. |
+ touch(resultfile) |
+ return result |
+ finally: |
+ if tempdir and mode == 'isolate': |
+ if read_only: |
+ tree_creator.make_writable(tempdir, False) |
+ rmtree(tempdir) |
+ |
+ |
+def main(): |
+ parser = optparse.OptionParser( |
+ usage='%prog [options] [inputs] -- [command line]', |
+ description=sys.modules[__name__].__doc__) |
+ parser.allow_interspersed_args = False |
+ parser.format_description = lambda *_: parser.description |
+ parser.add_option( |
+ '-v', '--verbose', action='count', default=0, help='Use multiple times') |
+ parser.add_option( |
+ '--mode', choices=['remap', 'check', 'run'], |
+ help='Determines the action to be taken') |
+ parser.add_option( |
+ '--result', metavar='X', |
+ help='File to be touched when the command succeeds') |
+ parser.add_option('--root', help='Base directory to fetch files, required') |
+ parser.add_option( |
+ '--outdir', metavar='X', |
+ help='Directory used to recreate the tree. Defaults to a /tmp ' |
+ 'subdirectory') |
+ parser.add_option( |
+ '--read-only', action='store_true', |
+ help='Make the temporary tree read-only') |
+ |
+ options, args = parser.parse_args() |
+ level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] |
+ logging.basicConfig( |
+ level=level, |
+ format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') |
+ |
+ if not options.root: |
+ parser.error('--root is required.') |
+ |
+ try: |
+ return isolate( |
+ options.outdir, |
+ options.root, |
+ options.result, |
+ options.mode, |
+ options.read_only, |
+ args) |
+ except ValueError, e: |
+ parser.error(str(e)) |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |