OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env 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. |
| 5 |
| 6 """Does one of the following depending on the --mode argument: |
| 7 check verify all the inputs exist, touches the file specified with |
| 8 --result and exits. |
| 9 run recreates a tree with all the inputs files and run the executable |
| 10 in it. |
| 11 remap stores all the inputs files in a directory without running the |
| 12 executable. |
| 13 """ |
| 14 |
| 15 import logging |
| 16 import optparse |
| 17 import os |
| 18 import re |
| 19 import shutil |
| 20 import subprocess |
| 21 import sys |
| 22 import tempfile |
| 23 import time |
| 24 |
| 25 import tree_creator |
| 26 |
| 27 |
| 28 def touch(filename): |
| 29 """Implements the equivalent of the 'touch' command.""" |
| 30 if not os.path.exists(filename): |
| 31 open(filename, 'a').close() |
| 32 os.utime(filename, None) |
| 33 |
| 34 |
| 35 def rmtree(root): |
| 36 """Wrapper around shutil.rmtree() to retry automatically on Windows.""" |
| 37 if sys.platform == 'win32': |
| 38 for i in range(3): |
| 39 try: |
| 40 shutil.rmtree(root) |
| 41 break |
| 42 except WindowsError: # pylint: disable=E0602 |
| 43 delay = (i+1)*2 |
| 44 print >> sys.stderr, ( |
| 45 'The test has subprocess outliving it. Sleep %d seconds.' % delay) |
| 46 time.sleep(delay) |
| 47 else: |
| 48 shutil.rmtree(root) |
| 49 |
| 50 |
| 51 def isolate(outdir, root, resultfile, mode, read_only, args): |
| 52 """Main function to isolate a target with its dependencies.""" |
| 53 cmd = [] |
| 54 if '--' in args: |
| 55 # Strip off the command line from the inputs. |
| 56 i = args.index('--') |
| 57 cmd = args[i+1:] |
| 58 args = args[:i] |
| 59 |
| 60 # gyp provides paths relative to cwd. Convert them to be relative to |
| 61 # root. |
| 62 cwd = os.getcwd() |
| 63 |
| 64 def make_relpath(i): |
| 65 """Makes an input file a relative path but keeps any trailing slash.""" |
| 66 out = os.path.relpath(os.path.join(cwd, i), root) |
| 67 if i.endswith('/'): |
| 68 out += '/' |
| 69 return out |
| 70 |
| 71 infiles = [make_relpath(i) for i in args] |
| 72 |
| 73 if not infiles: |
| 74 raise ValueError('Need at least one input file to map') |
| 75 |
| 76 # Other modes ignore the command. |
| 77 if mode == 'run' and not cmd: |
| 78 print >> sys.stderr, 'Using first input %s as executable' % infiles[0] |
| 79 cmd = [infiles[0]] |
| 80 |
| 81 tempdir = None |
| 82 try: |
| 83 if not outdir and mode != 'check': |
| 84 tempdir = tempfile.mkdtemp(prefix='isolate') |
| 85 outdir = tempdir |
| 86 elif outdir: |
| 87 outdir = os.path.abspath(outdir) |
| 88 |
| 89 tree_creator.recreate_tree( |
| 90 outdir, |
| 91 os.path.abspath(root), |
| 92 infiles, |
| 93 tree_creator.DRY_RUN if mode == 'check' else tree_creator.HARDLINK, |
| 94 lambda x: re.match(r'.*\.(svn|pyc)$', x)) |
| 95 |
| 96 if mode != 'check' and read_only: |
| 97 tree_creator.make_writable(outdir, True) |
| 98 |
| 99 if mode in ('check', 'remap'): |
| 100 result = 0 |
| 101 else: |
| 102 # TODO(maruel): Remove me. Layering violation. Used by |
| 103 # base/base_paths_linux.cc |
| 104 env = os.environ.copy() |
| 105 env['CR_SOURCE_ROOT'] = outdir.encode() |
| 106 # Rebase the command to the right path. |
| 107 cmd[0] = os.path.join(outdir, cmd[0]) |
| 108 logging.info('Running %s' % cmd) |
| 109 result = subprocess.call(cmd, cwd=outdir, env=env) |
| 110 |
| 111 if not result and resultfile: |
| 112 # Signal the build tool that the test succeeded. |
| 113 touch(resultfile) |
| 114 return result |
| 115 finally: |
| 116 if tempdir and mode == 'isolate': |
| 117 if read_only: |
| 118 tree_creator.make_writable(tempdir, False) |
| 119 rmtree(tempdir) |
| 120 |
| 121 |
| 122 def main(): |
| 123 parser = optparse.OptionParser( |
| 124 usage='%prog [options] [inputs] -- [command line]', |
| 125 description=sys.modules[__name__].__doc__) |
| 126 parser.allow_interspersed_args = False |
| 127 parser.format_description = lambda *_: parser.description |
| 128 parser.add_option( |
| 129 '-v', '--verbose', action='count', default=0, help='Use multiple times') |
| 130 parser.add_option( |
| 131 '--mode', choices=['remap', 'check', 'run'], |
| 132 help='Determines the action to be taken') |
| 133 parser.add_option( |
| 134 '--result', metavar='X', |
| 135 help='File to be touched when the command succeeds') |
| 136 parser.add_option('--root', help='Base directory to fetch files, required') |
| 137 parser.add_option( |
| 138 '--outdir', metavar='X', |
| 139 help='Directory used to recreate the tree. Defaults to a /tmp ' |
| 140 'subdirectory') |
| 141 parser.add_option( |
| 142 '--read-only', action='store_true', |
| 143 help='Make the temporary tree read-only') |
| 144 |
| 145 options, args = parser.parse_args() |
| 146 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] |
| 147 logging.basicConfig( |
| 148 level=level, |
| 149 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') |
| 150 |
| 151 if not options.root: |
| 152 parser.error('--root is required.') |
| 153 |
| 154 try: |
| 155 return isolate( |
| 156 options.outdir, |
| 157 options.root, |
| 158 options.result, |
| 159 options.mode, |
| 160 options.read_only, |
| 161 args) |
| 162 except ValueError, e: |
| 163 parser.error(str(e)) |
| 164 |
| 165 |
| 166 if __name__ == '__main__': |
| 167 sys.exit(main()) |
OLD | NEW |