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

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

Issue 10019014: Convert isolate.py to exclusively use .isolate files. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Reduce copy pasted constants 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 12 matching lines...) Expand all
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
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
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())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698