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

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

Powered by Google App Engine
This is Rietveld 408576698