Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 12 matching lines...) Expand all Loading... | |
| 23 import json | 23 import json |
| 24 import logging | 24 import logging |
| 25 import optparse | 25 import optparse |
| 26 import os | 26 import os |
| 27 import re | 27 import re |
| 28 import stat | 28 import stat |
| 29 import subprocess | 29 import subprocess |
| 30 import sys | 30 import sys |
| 31 import tempfile | 31 import tempfile |
| 32 | 32 |
| 33 import merge_isolate | |
| 33 import trace_inputs | 34 import trace_inputs |
| 34 import run_test_from_archive | 35 import run_test_from_archive |
| 35 | 36 |
| 36 | 37 |
| 37 def relpath(path, root): | 38 def relpath(path, root): |
| 38 """os.path.relpath() that keeps trailing slash.""" | 39 """os.path.relpath() that keeps trailing slash.""" |
| 39 out = os.path.relpath(path, root) | 40 out = os.path.relpath(path, root) |
| 40 if path.endswith(os.path.sep): | 41 if path.endswith(os.path.sep): |
| 41 out += os.path.sep | 42 out += os.path.sep |
| 42 return out | 43 return out |
| 43 | 44 |
| 44 | 45 |
| 46 def normpath(path): | |
| 47 """os.path.normpath() that keeps trailing slash.""" | |
| 48 out = os.path.normpath(path) | |
| 49 if path.endswith(('/', os.path.sep)): | |
| 50 out += os.path.sep | |
| 51 return out | |
| 52 | |
| 53 | |
| 45 def to_relative(path, root, relative): | 54 def to_relative(path, root, relative): |
| 46 """Converts any absolute path to a relative path, only if under root.""" | 55 """Converts any absolute path to a relative path, only if under root.""" |
| 47 if sys.platform == 'win32': | 56 if sys.platform == 'win32': |
| 48 path = path.lower() | 57 path = path.lower() |
| 49 root = root.lower() | 58 root = root.lower() |
| 50 relative = relative.lower() | 59 relative = relative.lower() |
| 51 if path.startswith(root): | 60 if path.startswith(root): |
| 52 logging.info('%s starts with %s' % (path, root)) | 61 logging.info('%s starts with %s' % (path, root)) |
| 53 path = os.path.relpath(path, relative) | 62 path = os.path.relpath(path, relative) |
| 54 else: | 63 else: |
| 55 logging.info('%s not under %s' % (path, root)) | 64 logging.info('%s not under %s' % (path, root)) |
| 56 return path | 65 return path |
| 57 | 66 |
| 58 | 67 |
| 59 def expand_directories(indir, infiles, blacklist): | 68 def expand_directories(indir, infiles, blacklist): |
| 60 """Expands the directories, applies the blacklist and verifies files exist.""" | 69 """Expands the directories, applies the blacklist and verifies files exist.""" |
| 61 logging.debug('expand_directories(%s, %s, %s)' % (indir, infiles, blacklist)) | 70 logging.debug('expand_directories(%s, %s, %s)' % (indir, infiles, blacklist)) |
| 62 outfiles = [] | 71 outfiles = [] |
| 63 for relfile in infiles: | 72 for relfile in infiles: |
| 64 if os.path.isabs(relfile): | 73 if os.path.isabs(relfile): |
| 65 raise run_test_from_archive.MappingError( | 74 raise run_test_from_archive.MappingError( |
| 66 'Can\'t map absolute path %s' % relfile) | 75 'Can\'t map absolute path %s' % relfile) |
| 67 infile = os.path.normpath(os.path.join(indir, relfile)) | 76 infile = normpath(os.path.join(indir, relfile)) |
| 68 if not infile.startswith(indir): | 77 if not infile.startswith(indir): |
| 69 raise run_test_from_archive.MappingError( | 78 raise run_test_from_archive.MappingError( |
| 70 'Can\'t map file %s outside %s' % (infile, indir)) | 79 'Can\'t map file %s outside %s' % (infile, indir)) |
| 71 | 80 |
| 72 if relfile.endswith(os.path.sep): | 81 if relfile.endswith(os.path.sep): |
| 73 if not os.path.isdir(infile): | 82 if not os.path.isdir(infile): |
| 74 raise run_test_from_archive.MappingError( | 83 raise run_test_from_archive.MappingError( |
| 75 'Input directory %s must have a trailing slash' % infile) | 84 '%s is not a directory' % infile) |
| 76 for dirpath, dirnames, filenames in os.walk(infile): | 85 for dirpath, dirnames, filenames in os.walk(infile): |
| 77 # Convert the absolute path to subdir + relative subdirectory. | 86 # Convert the absolute path to subdir + relative subdirectory. |
| 78 reldirpath = dirpath[len(indir)+1:] | 87 reldirpath = dirpath[len(indir)+1:] |
| 79 outfiles.extend(os.path.join(reldirpath, f) for f in filenames) | 88 files_to_add = (os.path.join(reldirpath, f) for f in filenames) |
| 89 outfiles.extend(f for f in files_to_add if not blacklist(f)) | |
| 80 for index, dirname in enumerate(dirnames): | 90 for index, dirname in enumerate(dirnames): |
| 81 # Do not process blacklisted directories. | 91 # Do not process blacklisted directories. |
| 82 if blacklist(os.path.join(reldirpath, dirname)): | 92 if blacklist(os.path.join(reldirpath, dirname)): |
| 83 del dirnames[index] | 93 del dirnames[index] |
| 84 else: | 94 else: |
| 95 # Always add individual files even if they were blacklisted. | |
| 96 if os.path.isdir(infile): | |
| 97 raise run_test_from_archive.MappingError( | |
| 98 'Input directory %s must have a trailing slash' % infile) | |
| 85 if not os.path.isfile(infile): | 99 if not os.path.isfile(infile): |
| 86 raise run_test_from_archive.MappingError( | 100 raise run_test_from_archive.MappingError( |
| 87 'Input file %s doesn\'t exist' % infile) | 101 'Input file %s doesn\'t exist' % infile) |
| 88 outfiles.append(relfile) | 102 outfiles.append(relfile) |
| 89 return outfiles | 103 return outfiles |
| 90 | 104 |
| 91 | 105 |
| 92 def process_inputs(indir, infiles, need_hash, read_only): | 106 def replace_variable(part, variables): |
| 107 m = re.match(r'<\(([A-Z_]+)\)', part) | |
| 108 if m: | |
| 109 return variables[m.group(1)] | |
| 110 return part | |
| 111 | |
| 112 | |
| 113 def eval_variables(item, variables): | |
| 114 return ''.join( | |
| 115 replace_variable(p, variables) for p in re.split(r'(<\([A-Z_]+\))', item)) | |
| 116 | |
| 117 | |
| 118 def load_isolate(content, variables, error): | |
| 119 """Loads the .isolate file. Returns the command, dependencies and read_only | |
| 120 flag. | |
| 121 """ | |
| 122 # Load the .isolate file, process its conditions, retrieve the command and | |
| 123 # dependencies. | |
| 124 configs = merge_isolate.load_gyp(merge_isolate.eval_content(content)) | |
| 125 flavor = trace_inputs.get_flavor() | |
| 126 config = configs.per_os.get(flavor) or configs.per_os.get(None) | |
| 127 if not config: | |
| 128 error('Failed to load configuration for \'%s\'' % flavor) | |
| 129 | |
| 130 # Convert the variables and merge tracked and untracked dependencies. | |
| 131 # isolate.py doesn't care about the trackability of the dependencies. | |
| 132 infiles = [ | |
| 133 eval_variables(f, variables) for f in config.tracked | |
| 134 ] + [ | |
| 135 eval_variables(f, variables) for f in config.untracked | |
| 136 ] | |
| 137 command = [eval_variables(i, variables) for i in config.command] | |
| 138 return command, infiles, config.read_only | |
| 139 | |
| 140 | |
| 141 def process_inputs(prevdict, indir, infiles, level, read_only): | |
| 93 """Returns a dictionary of input files, populated with the files' mode and | 142 """Returns a dictionary of input files, populated with the files' mode and |
| 94 hash. | 143 hash. |
| 95 | 144 |
| 145 |prevdict| is the previous dictionary. It is used to retrieve the cached sha-1 | |
| 146 to skip recalculating the hash. | |
| 147 | |
| 148 |level| determines the amount of information retrieved. | |
| 149 1 loads no information. 2 loads minimal stat() information. 3 calculates the | |
| 150 sha-1 of the file's content. | |
| 151 | |
| 96 The file mode is manipulated if read_only is True. In practice, we only save | 152 The file mode is manipulated if read_only is True. In practice, we only save |
| 97 one of 4 modes: 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r). | 153 one of 4 modes: 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r). |
| 98 """ | 154 """ |
| 99 outdict = {} | 155 outdict = {} |
| 100 for infile in infiles: | 156 for infile in infiles: |
| 101 filepath = os.path.join(indir, infile) | 157 filepath = os.path.join(indir, infile) |
| 102 filemode = stat.S_IMODE(os.stat(filepath).st_mode) | 158 outdict[infile] = {} |
| 103 # Remove write access for non-owner. | 159 if level >= 2: |
| 104 filemode &= ~(stat.S_IWGRP | stat.S_IWOTH) | 160 filestats = os.stat(filepath) |
| 105 if read_only: | 161 filemode = stat.S_IMODE(filestats.st_mode) |
| 106 filemode &= ~stat.S_IWUSR | 162 # Remove write access for non-owner. |
| 107 if filemode & stat.S_IXUSR: | 163 filemode &= ~(stat.S_IWGRP | stat.S_IWOTH) |
| 108 filemode |= (stat.S_IXGRP | stat.S_IXOTH) | 164 if read_only: |
| 109 else: | 165 filemode &= ~stat.S_IWUSR |
| 110 filemode &= ~(stat.S_IXGRP | stat.S_IXOTH) | 166 if filemode & stat.S_IXUSR: |
| 111 outdict[infile] = { | 167 filemode |= (stat.S_IXGRP | stat.S_IXOTH) |
| 112 'mode': filemode, | 168 else: |
| 113 } | 169 filemode &= ~(stat.S_IXGRP | stat.S_IXOTH) |
| 114 if need_hash: | 170 outdict[infile]['mode'] = filemode |
| 171 outdict[infile]['size'] = filestats.st_size | |
| 172 # Used the skip recalculating the hash. Use the most recent update time. | |
|
Roger Tawa OOO till Jul 10th
2012/04/11 15:15:03
the --> to ?
| |
| 173 outdict[infile]['timestamp'] = int(round( | |
| 174 max(filestats.st_mtime, filestats.st_ctime))) | |
| 175 # If the timestamp wasn't updated, carry on the sha-1. | |
| 176 if (prevdict.get(infile, {}).get('timestamp') == | |
| 177 outdict[infile]['timestamp'] and | |
| 178 'sha-1' in prevdict[infile]): | |
| 179 # Reuse the previous hash. | |
| 180 outdict[infile]['sha-1'] = prevdict[infile]['sha-1'] | |
| 181 | |
| 182 if level >= 3 and not outdict[infile].get('sha-1'): | |
| 115 h = hashlib.sha1() | 183 h = hashlib.sha1() |
| 116 with open(filepath, 'rb') as f: | 184 with open(filepath, 'rb') as f: |
| 117 h.update(f.read()) | 185 h.update(f.read()) |
| 118 outdict[infile]['sha-1'] = h.hexdigest() | 186 outdict[infile]['sha-1'] = h.hexdigest() |
| 119 return outdict | 187 return outdict |
| 120 | 188 |
| 121 | 189 |
| 122 def recreate_tree(outdir, indir, infiles, action): | 190 def recreate_tree(outdir, indir, infiles, action): |
| 123 """Creates a new tree with only the input files in it. | 191 """Creates a new tree with only the input files in it. |
| 124 | 192 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 145 | 213 |
| 146 for relfile in infiles: | 214 for relfile in infiles: |
| 147 infile = os.path.join(indir, relfile) | 215 infile = os.path.join(indir, relfile) |
| 148 outfile = os.path.join(outdir, relfile) | 216 outfile = os.path.join(outdir, relfile) |
| 149 outsubdir = os.path.dirname(outfile) | 217 outsubdir = os.path.dirname(outfile) |
| 150 if not os.path.isdir(outsubdir): | 218 if not os.path.isdir(outsubdir): |
| 151 os.makedirs(outsubdir) | 219 os.makedirs(outsubdir) |
| 152 run_test_from_archive.link_file(outfile, infile, action) | 220 run_test_from_archive.link_file(outfile, infile, action) |
| 153 | 221 |
| 154 | 222 |
| 155 def separate_inputs_command(args, root, files): | 223 def isolate( |
| 156 """Strips off the command line from the inputs. | 224 outdir, indir, infiles, mode, read_only, cmd, relative_cwd, resultfile): |
| 157 | |
| 158 gyp provides input paths relative to cwd. Convert them to be relative to root. | |
| 159 OptionParser kindly strips off '--' from sys.argv if it's provided and that's | |
| 160 the first non-arg value. Manually look up if it was present in sys.argv. | |
| 161 """ | |
| 162 cmd = [] | |
| 163 if '--' in args: | |
| 164 i = args.index('--') | |
| 165 cmd = args[i+1:] | |
| 166 args = args[:i] | |
| 167 elif '--' in sys.argv: | |
| 168 # optparse is messing with us. Fix it manually. | |
| 169 cmd = args | |
| 170 args = [] | |
| 171 if files: | |
| 172 args = [ | |
| 173 i.decode('utf-8') for i in open(files, 'rb').read().splitlines() if i | |
| 174 ] + args | |
| 175 cwd = os.getcwd() | |
| 176 return [relpath(os.path.join(cwd, arg), root) for arg in args], cmd | |
| 177 | |
| 178 | |
| 179 def isolate(outdir, resultfile, indir, infiles, mode, read_only, cmd, no_save): | |
| 180 """Main function to isolate a target with its dependencies. | 225 """Main function to isolate a target with its dependencies. |
| 181 | 226 |
| 182 Arguments: | 227 Arguments: |
| 183 - outdir: Output directory where the result is stored. Depends on |mode|. | 228 - outdir: Output directory where the result is stored. Depends on |mode|. |
| 184 - resultfile: File to save the json data. | |
| 185 - indir: Root directory to be used as the base directory for infiles. | 229 - indir: Root directory to be used as the base directory for infiles. |
| 186 - infiles: List of files, with relative path, to process. | 230 - infiles: List of files, with relative path, to process. |
| 187 - mode: Action to do. See file level docstring. | 231 - mode: Action to do. See file level docstring. |
| 188 - read_only: Makes the temporary directory read only. | 232 - read_only: Makes the temporary directory read only. |
| 189 - cmd: Command to execute. | 233 - cmd: Command to execute. |
| 190 - no_save: If True, do not touch resultfile. | 234 - relative_cwd: Directory relative to the base directory where to start the |
| 235 command from. In general, this path will be the path | |
| 236 containing the gyp file where the target was defined. This | |
| 237 relative directory may be created implicitely if a file from | |
| 238 this directory is needed to run the test. Otherwise it won't | |
| 239 be created and the process creation will fail. It's up to the | |
| 240 caller to create this directory manually before starting the | |
| 241 test. | |
| 242 - resultfile: Path where to read and write the metadata. | |
| 191 | 243 |
| 192 Some arguments are optional, dependending on |mode|. See the corresponding | 244 Some arguments are optional, dependending on |mode|. See the corresponding |
| 193 MODE<mode> function for the exact behavior. | 245 MODE<mode> function for the exact behavior. |
| 194 """ | 246 """ |
| 195 mode_fn = getattr(sys.modules[__name__], 'MODE' + mode) | 247 mode_fn = getattr(sys.modules[__name__], 'MODE' + mode) |
| 196 assert mode_fn | 248 assert mode_fn |
| 197 assert os.path.isabs(resultfile) | 249 |
| 250 # Load the previous results as an optimization. | |
| 251 prevdict = {} | |
| 252 if resultfile and os.path.isfile(resultfile): | |
| 253 resultfile = os.path.abspath(resultfile) | |
| 254 with open(resultfile, 'rb') as f: | |
| 255 prevdict = json.load(f) | |
| 256 else: | |
| 257 resultfile = os.path.abspath(resultfile) | |
| 258 # Works with native os.path.sep but stores as '/'. | |
| 259 if 'files' in prevdict and os.path.sep != '/': | |
| 260 prevdict['files'] = dict( | |
| 261 (k.replace('/', os.path.sep), v) | |
| 262 for k, v in prevdict['files'].iteritems()) | |
| 263 | |
| 198 | 264 |
| 199 infiles = expand_directories( | 265 infiles = expand_directories( |
| 200 indir, infiles, lambda x: re.match(r'.*\.(svn|pyc)$', x)) | 266 indir, infiles, lambda x: re.match(r'.*\.(svn|pyc)$', x)) |
| 201 | 267 |
| 202 # Note the relative current directory. | |
| 203 # In general, this path will be the path containing the gyp file where the | |
| 204 # target was defined. This relative directory may be created implicitely if a | |
| 205 # file from this directory is needed to run the test. Otherwise it won't be | |
| 206 # created and the process creation will fail. It's up to the caller to create | |
| 207 # this directory manually before starting the test. | |
| 208 cwd = os.getcwd() | |
| 209 relative_cwd = os.path.relpath(cwd, indir) | |
| 210 | |
| 211 # Workaround make behavior of passing absolute paths. | |
| 212 cmd = [to_relative(i, indir, cwd) for i in cmd] | |
| 213 | |
| 214 if not cmd: | |
| 215 # Note that it is exactly the reverse of relative_cwd. | |
| 216 cmd = [os.path.join(os.path.relpath(indir, cwd), infiles[0])] | |
| 217 if cmd[0].endswith('.py'): | |
| 218 cmd.insert(0, sys.executable) | |
| 219 | |
| 220 # Only hashtable mode really needs the sha-1. | 268 # Only hashtable mode really needs the sha-1. |
| 221 dictfiles = process_inputs(indir, infiles, mode == 'hashtable', read_only) | 269 level = { |
| 270 'check': 1, | |
| 271 'hashtable': 3, | |
| 272 'remap': 2, | |
| 273 'run': 2, | |
| 274 'trace': 2, | |
|
Roger Tawa OOO till Jul 10th
2012/04/11 15:15:03
shouldn't you declare some constants instead of ha
| |
| 275 } | |
| 276 dictfiles = process_inputs( | |
| 277 prevdict.get('files', {}), indir, infiles, level[mode], read_only) | |
| 222 | 278 |
| 223 result = mode_fn( | 279 result = mode_fn( |
| 224 outdir, indir, dictfiles, read_only, cmd, relative_cwd, resultfile) | 280 outdir, indir, dictfiles, read_only, cmd, relative_cwd, resultfile) |
| 281 out = { | |
| 282 'command': cmd, | |
| 283 'relative_cwd': relative_cwd, | |
| 284 'files': dictfiles, | |
| 285 # Makes the directories read-only in addition to the files. | |
| 286 'read_only': read_only, | |
| 287 } | |
| 225 | 288 |
| 226 if result == 0 and not no_save: | 289 # Works with native os.path.sep but stores as '/'. |
| 227 # Saves the resulting file. | 290 if os.path.sep != '/': |
| 228 out = { | 291 out['files'] = dict( |
| 229 'command': cmd, | 292 (k.replace(os.path.sep, '/'), v) for k, v in out['files'].iteritems()) |
| 230 'relative_cwd': relative_cwd, | 293 |
| 231 'files': dictfiles, | 294 f = None |
| 232 'read_only': read_only, | 295 try: |
| 233 } | 296 if resultfile: |
| 234 with open(resultfile, 'wb') as f: | 297 f = open(resultfile, 'wb') |
| 235 json.dump(out, f, indent=2, sort_keys=True) | 298 else: |
| 299 f = sys.stdout | |
| 300 json.dump(out, f, indent=2, sort_keys=True) | |
| 301 f.write('\n') | |
| 302 finally: | |
| 303 if resultfile and f: | |
| 304 f.close() | |
| 305 | |
| 306 total_bytes = sum(i.get('size', 0) for i in out['files'].itervalues()) | |
| 307 if total_bytes: | |
| 308 logging.debug('Total size: %d bytes' % total_bytes) | |
| 236 return result | 309 return result |
| 237 | 310 |
| 238 | 311 |
| 239 def MODEcheck( | 312 def MODEcheck( |
| 240 _outdir, _indir, _dictfiles, _read_only, _cmd, _relative_cwd, _resultfile): | 313 _outdir, _indir, _dictfiles, _read_only, _cmd, _relative_cwd, _resultfile): |
| 241 """No-op.""" | 314 """No-op.""" |
| 242 return 0 | 315 return 0 |
| 243 | 316 |
| 244 | 317 |
| 245 def MODEhashtable( | 318 def MODEhashtable( |
| 246 outdir, indir, dictfiles, _read_only, _cmd, _relative_cwd, resultfile): | 319 outdir, indir, dictfiles, _read_only, _cmd, _relative_cwd, resultfile): |
| 247 outdir = outdir or os.path.dirname(resultfile) | 320 outdir = outdir or os.path.join(os.path.dirname(resultfile), 'hashtable') |
| 321 if not os.path.isdir(outdir): | |
| 322 os.makedirs(outdir) | |
| 248 for relfile, properties in dictfiles.iteritems(): | 323 for relfile, properties in dictfiles.iteritems(): |
| 249 infile = os.path.join(indir, relfile) | 324 infile = os.path.join(indir, relfile) |
| 250 outfile = os.path.join(outdir, properties['sha-1']) | 325 outfile = os.path.join(outdir, properties['sha-1']) |
| 251 if os.path.isfile(outfile): | 326 if os.path.isfile(outfile): |
| 252 # Just do a quick check that the file size matches. | 327 # Just do a quick check that the file size matches. No need to stat() |
| 253 if os.stat(infile).st_size == os.stat(outfile).st_size: | 328 # again the input file, grab the value from the dict. |
| 329 out_size = os.stat(outfile).st_size | |
| 330 in_size = dictfiles.get(infile, {}).get('size') or os.stat(infile).st_size | |
| 331 if in_size == out_size: | |
| 254 continue | 332 continue |
| 255 # Otherwise, an exception will be raised. | 333 # Otherwise, an exception will be raised. |
| 256 run_test_from_archive.link_file( | 334 run_test_from_archive.link_file( |
| 257 outfile, infile, run_test_from_archive.HARDLINK) | 335 outfile, infile, run_test_from_archive.HARDLINK) |
| 258 return 0 | 336 return 0 |
| 259 | 337 |
| 260 | 338 |
| 261 def MODEremap( | 339 def MODEremap( |
| 262 outdir, indir, dictfiles, read_only, _cmd, _relative_cwd, _resultfile): | 340 outdir, indir, dictfiles, read_only, _cmd, _relative_cwd, _resultfile): |
| 263 if not outdir: | 341 if not outdir: |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 277 """Always uses a temporary directory.""" | 355 """Always uses a temporary directory.""" |
| 278 try: | 356 try: |
| 279 outdir = tempfile.mkdtemp(prefix='isolate') | 357 outdir = tempfile.mkdtemp(prefix='isolate') |
| 280 recreate_tree( | 358 recreate_tree( |
| 281 outdir, indir, dictfiles.keys(), run_test_from_archive.HARDLINK) | 359 outdir, indir, dictfiles.keys(), run_test_from_archive.HARDLINK) |
| 282 cwd = os.path.join(outdir, relative_cwd) | 360 cwd = os.path.join(outdir, relative_cwd) |
| 283 if not os.path.isdir(cwd): | 361 if not os.path.isdir(cwd): |
| 284 os.makedirs(cwd) | 362 os.makedirs(cwd) |
| 285 if read_only: | 363 if read_only: |
| 286 run_test_from_archive.make_writable(outdir, True) | 364 run_test_from_archive.make_writable(outdir, True) |
| 287 | 365 cmd = trace_inputs.fix_python_path(cmd) |
| 288 logging.info('Running %s, cwd=%s' % (cmd, cwd)) | 366 logging.info('Running %s, cwd=%s' % (cmd, cwd)) |
| 289 return subprocess.call(cmd, cwd=cwd) | 367 return subprocess.call(cmd, cwd=cwd) |
| 290 finally: | 368 finally: |
| 291 run_test_from_archive.rmtree(outdir) | 369 run_test_from_archive.rmtree(outdir) |
| 292 | 370 |
| 293 | 371 |
| 294 def MODEtrace( | 372 def MODEtrace( |
| 295 _outdir, indir, _dictfiles, _read_only, cmd, relative_cwd, resultfile): | 373 _outdir, indir, _dictfiles, _read_only, cmd, relative_cwd, resultfile): |
| 296 """Shortcut to use trace_inputs.py properly. | 374 """Shortcut to use trace_inputs.py properly. |
| 297 | 375 |
| 298 It constructs the equivalent of dictfiles. It is hardcoded to base the | 376 It constructs the equivalent of dictfiles. It is hardcoded to base the |
| 299 checkout at src/. | 377 checkout at src/. |
| 300 """ | 378 """ |
| 301 logging.info('Running %s, cwd=%s' % (cmd, os.path.join(indir, relative_cwd))) | 379 logging.info('Running %s, cwd=%s' % (cmd, os.path.join(indir, relative_cwd))) |
| 302 try: | 380 if resultfile: |
| 303 # Guesswork here. | 381 # Guesswork here. |
| 304 product_dir = os.path.relpath(os.path.dirname(resultfile), indir) | 382 product_dir = os.path.dirname(resultfile) |
| 305 except ValueError: | 383 if product_dir and indir: |
| 306 product_dir = '' | 384 product_dir = os.path.relpath(product_dir, indir) |
| 385 else: | |
| 386 product_dir = None | |
| 307 return trace_inputs.trace_inputs( | 387 return trace_inputs.trace_inputs( |
| 308 '%s.log' % resultfile, | 388 '%s.log' % resultfile, |
| 309 cmd, | 389 cmd, |
| 310 indir, | 390 indir, |
| 311 relative_cwd, | 391 relative_cwd, |
| 312 product_dir, | 392 product_dir, |
| 313 False) | 393 False) |
| 314 | 394 |
| 315 | 395 |
| 316 def get_valid_modes(): | 396 def get_valid_modes(): |
| 317 """Returns the modes that can be used.""" | 397 """Returns the modes that can be used.""" |
| 318 return sorted( | 398 return sorted( |
| 319 i[4:] for i in dir(sys.modules[__name__]) if i.startswith('MODE')) | 399 i[4:] for i in dir(sys.modules[__name__]) if i.startswith('MODE')) |
| 320 | 400 |
| 321 | 401 |
| 322 def main(): | 402 def main(): |
| 403 default_variables = ['OS=%s' % trace_inputs.get_flavor()] | |
| 404 if sys.platform in ('win32', 'cygwin'): | |
| 405 default_variables.append('EXECUTABLE_SUFFIX=.exe') | |
| 406 else: | |
| 407 default_variables.append('EXECUTABLE_SUFFIX=') | |
| 323 valid_modes = get_valid_modes() | 408 valid_modes = get_valid_modes() |
| 324 parser = optparse.OptionParser( | 409 parser = optparse.OptionParser( |
| 325 usage='%prog [options] [inputs] -- [command line]', | 410 usage='%prog [options] [.isolate file]', |
| 326 description=sys.modules[__name__].__doc__) | 411 description=sys.modules[__name__].__doc__) |
| 327 parser.allow_interspersed_args = False | |
| 328 parser.format_description = lambda *_: parser.description | 412 parser.format_description = lambda *_: parser.description |
| 329 parser.add_option( | 413 parser.add_option( |
| 330 '-v', '--verbose', action='count', default=0, help='Use multiple times') | 414 '-v', '--verbose', |
| 415 action='count', | |
| 416 default=2 if 'ISOLATE_DEBUG' in os.environ else 0, | |
| 417 help='Use multiple times') | |
| 331 parser.add_option( | 418 parser.add_option( |
| 332 '--mode', choices=valid_modes, | 419 '-m', '--mode', |
| 420 choices=valid_modes, | |
| 333 help='Determines the action to be taken: %s' % ', '.join(valid_modes)) | 421 help='Determines the action to be taken: %s' % ', '.join(valid_modes)) |
| 334 parser.add_option( | 422 parser.add_option( |
| 335 '--result', metavar='FILE', | 423 '-r', '--result', |
| 336 help='File containing the json information about inputs') | 424 metavar='FILE', |
| 425 help='Result file to store the json manifest') | |
| 337 parser.add_option( | 426 parser.add_option( |
| 338 '--root', metavar='DIR', help='Base directory to fetch files, required') | 427 '-V', '--variable', |
| 428 action='append', | |
| 429 default=default_variables, | |
| 430 dest='variables', | |
| 431 metavar='FOO=BAR', | |
| 432 help='Variables to process in the .isolate file, default: %default') | |
| 339 parser.add_option( | 433 parser.add_option( |
| 340 '--outdir', metavar='DIR', | 434 '-o', '--outdir', metavar='DIR', |
| 341 help='Directory used to recreate the tree or store the hash table. ' | 435 help='Directory used to recreate the tree or store the hash table. ' |
| 342 'For run and remap, uses a /tmp subdirectory. For the other modes, ' | 436 'If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will ' |
| 343 'defaults to the directory containing --result') | 437 'be used. Otherwise, for run and remap, uses a /tmp subdirectory. ' |
| 344 parser.add_option( | 438 'For the other modes, defaults to the directory containing --result') |
| 345 '--read-only', action='store_true', default=False, | |
| 346 help='Make the temporary tree read-only') | |
| 347 parser.add_option( | |
| 348 '--from-results', action='store_true', | |
| 349 help='Loads everything from the result file instead of generating it') | |
| 350 parser.add_option( | |
| 351 '--files', metavar='FILE', | |
| 352 help='File to be read containing input files') | |
| 353 | 439 |
| 354 options, args = parser.parse_args() | 440 options, args = parser.parse_args() |
| 355 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] | 441 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] |
| 356 logging.basicConfig( | 442 logging.basicConfig( |
| 357 level=level, | 443 level=level, |
| 358 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') | 444 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') |
| 359 | 445 |
| 360 if not options.mode: | 446 if not options.mode: |
| 361 parser.error('--mode is required') | 447 parser.error('--mode is required') |
| 448 if len(args) != 1: | |
| 449 parser.error('Use only one argument which should be a .isolate file') | |
| 450 input_file = os.path.abspath(args[0]) | |
| 362 | 451 |
| 363 if not options.result: | 452 # Extract the variables. |
| 364 parser.error('--result is required.') | 453 variables = dict(i.split('=', 1) for i in options.variables) |
| 365 if options.from_results: | 454 if not variables.get('DEPTH'): |
| 366 if not options.root: | 455 parser.error('--variable DEPTH=<base dir> is required') |
| 367 options.root = os.getcwd() | |
| 368 if args: | |
| 369 parser.error('Arguments cannot be used with --from-result') | |
| 370 if options.files: | |
| 371 parser.error('--files cannot be used with --from-result') | |
| 372 else: | |
| 373 if not options.root: | |
| 374 parser.error('--root is required.') | |
| 375 | 456 |
| 376 options.result = os.path.abspath(options.result) | 457 PATH_VARIABLES = ('DEPTH', 'PRODUCT_DIR') |
| 458 # Process path variables as a special case. First normalize it, verifies it | |
| 459 # exists, convert it to an absolute path, then calculate relative_dir, and | |
| 460 # finally convert it back to a relative value from relative_dir. | |
| 461 abs_variables = {} | |
| 462 for i in PATH_VARIABLES: | |
| 463 if i not in variables: | |
| 464 continue | |
| 465 abs_variables[i] = os.path.normpath(variables[i]) | |
| 466 if not os.path.isdir(abs_variables[i]): | |
| 467 parser.error('%s is not a directory' % abs_variables[i]) | |
| 468 abs_variables[i] = os.path.abspath(abs_variables[i]) | |
| 377 | 469 |
| 378 # Normalize the root input directory. | 470 # The relative directory is automatically determined by the relative path |
| 379 indir = os.path.normpath(options.root) | 471 # between DEPTH and the directory containing the .isolate file. |
| 380 if not os.path.isdir(indir): | 472 isolate_dir = os.path.dirname(os.path.abspath(input_file)) |
| 381 parser.error('%s is not a directory' % indir) | 473 relative_dir = os.path.relpath(isolate_dir, abs_variables['DEPTH']) |
| 474 logging.debug('relative_dir: %s' % relative_dir) | |
| 382 | 475 |
| 383 # Do not call abspath until it was verified the directory exists. | 476 # Directories are _relative_ to relative_dir. |
| 384 indir = os.path.abspath(indir) | 477 for i in PATH_VARIABLES: |
| 478 if i not in variables: | |
| 479 continue | |
| 480 variables[i] = os.path.relpath(abs_variables[i], isolate_dir) | |
| 385 | 481 |
| 386 logging.info('sys.argv: %s' % sys.argv) | 482 logging.debug( |
| 387 logging.info('cwd: %s' % os.getcwd()) | 483 'variables: %s' % ', '.join( |
| 388 logging.info('Args: %s' % args) | 484 '%s=%s' % (k, v) for k, v in variables.iteritems())) |
| 389 if not options.from_results: | |
| 390 infiles, cmd = separate_inputs_command(args, indir, options.files) | |
| 391 if not infiles: | |
| 392 parser.error('Need at least one input file to map') | |
| 393 else: | |
| 394 data = json.load(open(options.result)) | |
| 395 cmd = data['command'] | |
| 396 infiles = data['files'].keys() | |
| 397 os.chdir(data['relative_cwd']) | |
| 398 | 485 |
| 399 logging.info('infiles: %s' % infiles) | 486 # TODO(maruel): Case insensitive file systems. |
| 487 if not input_file.startswith(abs_variables['DEPTH']): | |
| 488 parser.error( | |
| 489 '%s must be under %s, as it is used as the relative start directory.' % | |
| 490 (args[0], abs_variables['DEPTH'])) | |
| 491 | |
| 492 command, infiles, read_only = load_isolate( | |
| 493 open(input_file, 'rb').read(), variables, parser.error) | |
| 494 logging.debug('command: %s' % command) | |
| 495 logging.debug('infiles: %s' % infiles) | |
| 496 logging.debug('read_only: %s' % read_only) | |
| 497 infiles = [normpath(os.path.join(relative_dir, f)) for f in infiles] | |
| 498 logging.debug('processed infiles: %s' % infiles) | |
| 400 | 499 |
| 401 try: | 500 try: |
| 402 return isolate( | 501 return isolate( |
| 403 options.outdir, | 502 options.outdir, |
| 404 options.result, | 503 abs_variables['DEPTH'], |
| 405 indir, | |
| 406 infiles, | 504 infiles, |
| 407 options.mode, | 505 options.mode, |
| 408 options.read_only, | 506 read_only, |
| 409 cmd, | 507 command, |
| 410 options.from_results) | 508 relative_dir, |
| 509 options.result) | |
| 411 except run_test_from_archive.MappingError, e: | 510 except run_test_from_archive.MappingError, e: |
| 412 print >> sys.stderr, str(e) | 511 print >> sys.stderr, str(e) |
| 413 return 1 | 512 return 1 |
| 414 | 513 |
| 415 | 514 |
| 416 if __name__ == '__main__': | 515 if __name__ == '__main__': |
| 417 sys.exit(main()) | 516 sys.exit(main()) |
| OLD | NEW |