maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
Marc-Antoine Ruel | 8add124 | 2013-11-05 17:28:27 -0500 | [diff] [blame] | 2 | # Copyright 2012 The Swarming Authors. All rights reserved. |
Marc-Antoine Ruel | e98b112 | 2013-11-05 20:27:57 -0500 | [diff] [blame] | 3 | # Use of this source code is governed under the Apache License, Version 2.0 that |
| 4 | # can be found in the LICENSE file. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 5 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 6 | """Front end tool to operate on .isolate files. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 7 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 8 | This includes creating, merging or compiling them to generate a .isolated file. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 9 | |
| 10 | See more information at |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 11 | https://code.google.com/p/swarming/wiki/IsolateDesign |
| 12 | https://code.google.com/p/swarming/wiki/IsolateUserGuide |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 13 | """ |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 14 | # Run ./isolate.py --help for more detailed information. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 15 | |
Marc-Antoine Ruel | 9dfdcc2 | 2014-01-08 14:14:18 -0500 | [diff] [blame] | 16 | import datetime |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 17 | import logging |
| 18 | import optparse |
| 19 | import os |
| 20 | import posixpath |
| 21 | import re |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 22 | import subprocess |
| 23 | import sys |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 24 | |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 25 | import isolate_format |
maruel@chromium.org | fb78d43 | 2013-08-28 21:22:40 +0000 | [diff] [blame] | 26 | import isolateserver |
maruel@chromium.org | b8375c2 | 2012-10-05 18:10:01 +0000 | [diff] [blame] | 27 | import run_isolated |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 28 | import trace_inputs |
| 29 | |
| 30 | # Import here directly so isolate is easier to use as a library. |
maruel@chromium.org | b8375c2 | 2012-10-05 18:10:01 +0000 | [diff] [blame] | 31 | from run_isolated import get_flavor |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 32 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 33 | from third_party import colorama |
| 34 | from third_party.depot_tools import fix_encoding |
| 35 | from third_party.depot_tools import subcommand |
| 36 | |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 37 | from utils import file_path |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 38 | from utils import tools |
| 39 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 40 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 41 | __version__ = '0.3' |
maruel@chromium.org | 3d67199 | 2013-08-20 00:38:27 +0000 | [diff] [blame] | 42 | |
| 43 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 44 | class ExecutionError(Exception): |
| 45 | """A generic error occurred.""" |
| 46 | def __str__(self): |
| 47 | return self.args[0] |
| 48 | |
| 49 | |
| 50 | ### Path handling code. |
| 51 | |
| 52 | |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 53 | def expand_directories_and_symlinks(indir, infiles, blacklist, |
csharp@chromium.org | 84d2e2e | 2013-03-27 13:38:42 +0000 | [diff] [blame] | 54 | follow_symlinks, ignore_broken_items): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 55 | """Expands the directories and the symlinks, applies the blacklist and |
| 56 | verifies files exist. |
| 57 | |
| 58 | Files are specified in os native path separator. |
| 59 | """ |
| 60 | outfiles = [] |
| 61 | for relfile in infiles: |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 62 | try: |
Marc-Antoine Ruel | fcc3cd8 | 2013-11-19 16:31:38 -0500 | [diff] [blame] | 63 | outfiles.extend( |
| 64 | isolateserver.expand_directory_and_symlink( |
| 65 | indir, relfile, blacklist, follow_symlinks)) |
maruel@chromium.org | 9958e4a | 2013-09-17 00:01:48 +0000 | [diff] [blame] | 66 | except isolateserver.MappingError as e: |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 67 | if ignore_broken_items: |
| 68 | logging.info('warning: %s', e) |
| 69 | else: |
| 70 | raise |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 71 | return outfiles |
| 72 | |
| 73 | |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 74 | def recreate_tree(outdir, indir, infiles, action, as_hash): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 75 | """Creates a new tree with only the input files in it. |
| 76 | |
| 77 | Arguments: |
| 78 | outdir: Output directory to create the files in. |
| 79 | indir: Root directory the infiles are based in. |
| 80 | infiles: dict of files to map from |indir| to |outdir|. |
maruel@chromium.org | ba6489b | 2013-07-11 20:23:33 +0000 | [diff] [blame] | 81 | action: One of accepted action of run_isolated.link_file(). |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 82 | as_hash: Output filename is the hash instead of relfile. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 83 | """ |
| 84 | logging.info( |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 85 | 'recreate_tree(outdir=%s, indir=%s, files=%d, action=%s, as_hash=%s)' % |
| 86 | (outdir, indir, len(infiles), action, as_hash)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 87 | |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 88 | assert os.path.isabs(outdir) and outdir == os.path.normpath(outdir), outdir |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 89 | if not os.path.isdir(outdir): |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 90 | logging.info('Creating %s' % outdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 91 | os.makedirs(outdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 92 | |
| 93 | for relfile, metadata in infiles.iteritems(): |
| 94 | infile = os.path.join(indir, relfile) |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 95 | if as_hash: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 96 | # Do the hashtable specific checks. |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 97 | if 'l' in metadata: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 98 | # Skip links when storing a hashtable. |
| 99 | continue |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 100 | outfile = os.path.join(outdir, metadata['h']) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 101 | if os.path.isfile(outfile): |
| 102 | # Just do a quick check that the file size matches. No need to stat() |
| 103 | # again the input file, grab the value from the dict. |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 104 | if not 's' in metadata: |
maruel@chromium.org | 9958e4a | 2013-09-17 00:01:48 +0000 | [diff] [blame] | 105 | raise isolateserver.MappingError( |
maruel@chromium.org | 861a5e7 | 2012-10-09 14:49:42 +0000 | [diff] [blame] | 106 | 'Misconfigured item %s: %s' % (relfile, metadata)) |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 107 | if metadata['s'] == os.stat(outfile).st_size: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 108 | continue |
| 109 | else: |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 110 | logging.warn('Overwritting %s' % metadata['h']) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 111 | os.remove(outfile) |
| 112 | else: |
| 113 | outfile = os.path.join(outdir, relfile) |
| 114 | outsubdir = os.path.dirname(outfile) |
| 115 | if not os.path.isdir(outsubdir): |
| 116 | os.makedirs(outsubdir) |
| 117 | |
| 118 | # TODO(csharp): Fix crbug.com/150823 and enable the touched logic again. |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 119 | # if metadata.get('T') == True: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 120 | # open(outfile, 'ab').close() |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 121 | if 'l' in metadata: |
| 122 | pointed = metadata['l'] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 123 | logging.debug('Symlink: %s -> %s' % (outfile, pointed)) |
maruel@chromium.org | f43e68b | 2012-10-15 20:23:10 +0000 | [diff] [blame] | 124 | # symlink doesn't exist on Windows. |
| 125 | os.symlink(pointed, outfile) # pylint: disable=E1101 |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 126 | else: |
maruel@chromium.org | b8375c2 | 2012-10-05 18:10:01 +0000 | [diff] [blame] | 127 | run_isolated.link_file(outfile, infile, action) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 128 | |
| 129 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 130 | ### Variable stuff. |
| 131 | |
| 132 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 133 | def _normalize_path_variable(cwd, relative_base_dir, key, value): |
| 134 | """Normalizes a path variable into a relative directory. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 135 | """ |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 136 | # Variables could contain / or \ on windows. Always normalize to |
| 137 | # os.path.sep. |
| 138 | x = os.path.join(cwd, value.strip().replace('/', os.path.sep)) |
| 139 | normalized = file_path.get_native_path_case(os.path.normpath(x)) |
| 140 | if not os.path.isdir(normalized): |
| 141 | raise ExecutionError('%s=%s is not a directory' % (key, normalized)) |
| 142 | |
| 143 | # All variables are relative to the .isolate file. |
| 144 | normalized = os.path.relpath(normalized, relative_base_dir) |
| 145 | logging.debug( |
| 146 | 'Translated variable %s from %s to %s', key, value, normalized) |
| 147 | return normalized |
| 148 | |
| 149 | |
| 150 | def normalize_path_variables(cwd, path_variables, relative_base_dir): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 151 | """Processes path variables as a special case and returns a copy of the dict. |
| 152 | |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 153 | For each 'path' variable: first normalizes it based on |cwd|, verifies it |
| 154 | exists then sets it as relative to relative_base_dir. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 155 | """ |
| 156 | logging.info( |
| 157 | 'normalize_path_variables(%s, %s, %s)', cwd, path_variables, |
| 158 | relative_base_dir) |
Marc-Antoine Ruel | 9cc42c3 | 2013-12-11 09:35:55 -0500 | [diff] [blame] | 159 | assert isinstance(cwd, unicode), cwd |
| 160 | assert isinstance(relative_base_dir, unicode), relative_base_dir |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 161 | relative_base_dir = file_path.get_native_path_case(relative_base_dir) |
| 162 | return dict( |
| 163 | (k, _normalize_path_variable(cwd, relative_base_dir, k, v)) |
| 164 | for k, v in path_variables.iteritems()) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 165 | |
| 166 | |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 167 | ### Internal state files. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 168 | |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 169 | |
| 170 | def isolatedfile_to_state(filename): |
| 171 | """For a '.isolate' file, returns the path to the saved '.state' file.""" |
| 172 | return filename + '.state' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 173 | |
| 174 | |
| 175 | def classify_files(root_dir, tracked, untracked): |
| 176 | """Converts the list of files into a .isolate 'variables' dictionary. |
| 177 | |
| 178 | Arguments: |
| 179 | - tracked: list of files names to generate a dictionary out of that should |
| 180 | probably be tracked. |
| 181 | - untracked: list of files names that must not be tracked. |
| 182 | """ |
| 183 | # These directories are not guaranteed to be always present on every builder. |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 184 | CHROMIUM_OPTIONAL_DIRECTORIES = ( |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 185 | 'test/data/plugin', |
| 186 | 'third_party/WebKit/LayoutTests', |
| 187 | ) |
| 188 | |
| 189 | new_tracked = [] |
| 190 | new_untracked = list(untracked) |
| 191 | |
| 192 | def should_be_tracked(filepath): |
| 193 | """Returns True if it is a file without whitespace in a non-optional |
| 194 | directory that has no symlink in its path. |
| 195 | """ |
| 196 | if filepath.endswith('/'): |
| 197 | return False |
| 198 | if ' ' in filepath: |
| 199 | return False |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 200 | if any(i in filepath for i in CHROMIUM_OPTIONAL_DIRECTORIES): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 201 | return False |
| 202 | # Look if any element in the path is a symlink. |
| 203 | split = filepath.split('/') |
| 204 | for i in range(len(split)): |
| 205 | if os.path.islink(os.path.join(root_dir, '/'.join(split[:i+1]))): |
| 206 | return False |
| 207 | return True |
| 208 | |
| 209 | for filepath in sorted(tracked): |
| 210 | if should_be_tracked(filepath): |
| 211 | new_tracked.append(filepath) |
| 212 | else: |
| 213 | # Anything else. |
| 214 | new_untracked.append(filepath) |
| 215 | |
| 216 | variables = {} |
| 217 | if new_tracked: |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 218 | variables[isolate_format.KEY_TRACKED] = sorted(new_tracked) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 219 | if new_untracked: |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 220 | variables[isolate_format.KEY_UNTRACKED] = sorted(new_untracked) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 221 | return variables |
| 222 | |
| 223 | |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 224 | def chromium_fix(f, variables): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 225 | """Fixes an isolate dependency with Chromium-specific fixes.""" |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 226 | # Skip log in PRODUCT_DIR. Note that these are applied on '/' style path |
| 227 | # separator. |
| 228 | LOG_FILE = re.compile(r'^\<\(PRODUCT_DIR\)\/[^\/]+\.log$') |
maruel@chromium.org | a5c1c90 | 2012-11-15 18:47:53 +0000 | [diff] [blame] | 229 | # Ignored items. |
| 230 | IGNORED_ITEMS = ( |
maruel@chromium.org | d37462e | 2012-11-16 14:58:58 +0000 | [diff] [blame] | 231 | # http://crbug.com/160539, on Windows, it's in chrome/. |
maruel@chromium.org | a5c1c90 | 2012-11-15 18:47:53 +0000 | [diff] [blame] | 232 | 'Media Cache/', |
maruel@chromium.org | d37462e | 2012-11-16 14:58:58 +0000 | [diff] [blame] | 233 | 'chrome/Media Cache/', |
maruel@chromium.org | a5c1c90 | 2012-11-15 18:47:53 +0000 | [diff] [blame] | 234 | # 'First Run' is not created by the compile, but by the test itself. |
| 235 | '<(PRODUCT_DIR)/First Run') |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 236 | |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 237 | # Blacklist logs and other unimportant files. |
| 238 | if LOG_FILE.match(f) or f in IGNORED_ITEMS: |
| 239 | logging.debug('Ignoring %s', f) |
| 240 | return None |
| 241 | |
maruel@chromium.org | 7650e42 | 2012-11-16 21:56:42 +0000 | [diff] [blame] | 242 | EXECUTABLE = re.compile( |
| 243 | r'^(\<\(PRODUCT_DIR\)\/[^\/\.]+)' + |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 244 | re.escape(variables.get('EXECUTABLE_SUFFIX', '')) + |
maruel@chromium.org | 7650e42 | 2012-11-16 21:56:42 +0000 | [diff] [blame] | 245 | r'$') |
| 246 | match = EXECUTABLE.match(f) |
| 247 | if match: |
| 248 | return match.group(1) + '<(EXECUTABLE_SUFFIX)' |
| 249 | |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 250 | if sys.platform == 'darwin': |
| 251 | # On OSX, the name of the output is dependent on gyp define, it can be |
| 252 | # 'Google Chrome.app' or 'Chromium.app', same for 'XXX |
| 253 | # Framework.framework'. Furthermore, they are versioned with a gyp |
| 254 | # variable. To lower the complexity of the .isolate file, remove all the |
| 255 | # individual entries that show up under any of the 4 entries and replace |
| 256 | # them with the directory itself. Overall, this results in a bit more |
| 257 | # files than strictly necessary. |
| 258 | OSX_BUNDLES = ( |
| 259 | '<(PRODUCT_DIR)/Chromium Framework.framework/', |
| 260 | '<(PRODUCT_DIR)/Chromium.app/', |
| 261 | '<(PRODUCT_DIR)/Google Chrome Framework.framework/', |
| 262 | '<(PRODUCT_DIR)/Google Chrome.app/', |
| 263 | ) |
| 264 | for prefix in OSX_BUNDLES: |
| 265 | if f.startswith(prefix): |
| 266 | # Note this result in duplicate values, so the a set() must be used to |
| 267 | # remove duplicates. |
| 268 | return prefix |
| 269 | return f |
| 270 | |
| 271 | |
| 272 | def generate_simplified( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 273 | tracked, untracked, touched, root_dir, path_variables, extra_variables, |
| 274 | relative_cwd, trace_blacklist): |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 275 | """Generates a clean and complete .isolate 'variables' dictionary. |
| 276 | |
| 277 | Cleans up and extracts only files from within root_dir then processes |
| 278 | variables and relative_cwd. |
| 279 | """ |
| 280 | root_dir = os.path.realpath(root_dir) |
| 281 | logging.info( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 282 | 'generate_simplified(%d files, %s, %s, %s, %s)' % |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 283 | (len(tracked) + len(untracked) + len(touched), |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 284 | root_dir, path_variables, extra_variables, relative_cwd)) |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 285 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 286 | # Preparation work. |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 287 | relative_cwd = file_path.cleanup_path(relative_cwd) |
maruel@chromium.org | ec91af1 | 2012-10-18 20:45:57 +0000 | [diff] [blame] | 288 | assert not os.path.isabs(relative_cwd), relative_cwd |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 289 | |
| 290 | # Normalizes to posix path. .isolate files are using posix paths on all OSes |
| 291 | # for coherency. |
maruel@chromium.org | 136b05a | 2012-11-20 18:49:44 +0000 | [diff] [blame] | 292 | path_variables = dict( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 293 | (k, v.replace(os.path.sep, '/')) for k, v in path_variables.iteritems()) |
| 294 | # Contains normalized path_variables plus extra_variables. |
| 295 | total_variables = path_variables.copy() |
| 296 | total_variables.update(extra_variables) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 297 | |
| 298 | # Actual work: Process the files. |
| 299 | # TODO(maruel): if all the files in a directory are in part tracked and in |
| 300 | # part untracked, the directory will not be extracted. Tracked files should be |
| 301 | # 'promoted' to be untracked as needed. |
| 302 | tracked = trace_inputs.extract_directories( |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 303 | root_dir, tracked, trace_blacklist) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 304 | untracked = trace_inputs.extract_directories( |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 305 | root_dir, untracked, trace_blacklist) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 306 | # touched is not compressed, otherwise it would result in files to be archived |
| 307 | # that we don't need. |
| 308 | |
maruel@chromium.org | ec91af1 | 2012-10-18 20:45:57 +0000 | [diff] [blame] | 309 | root_dir_posix = root_dir.replace(os.path.sep, '/') |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 310 | def fix(f): |
| 311 | """Bases the file on the most restrictive variable.""" |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 312 | # Important, GYP stores the files with / and not \. |
| 313 | f = f.replace(os.path.sep, '/') |
maruel@chromium.org | ec91af1 | 2012-10-18 20:45:57 +0000 | [diff] [blame] | 314 | logging.debug('fix(%s)' % f) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 315 | # If it's not already a variable. |
| 316 | if not f.startswith('<'): |
| 317 | # relative_cwd is usually the directory containing the gyp file. It may be |
| 318 | # empty if the whole directory containing the gyp file is needed. |
maruel@chromium.org | 8b056ba | 2012-10-16 14:04:49 +0000 | [diff] [blame] | 319 | # Use absolute paths in case cwd_dir is outside of root_dir. |
maruel@chromium.org | ec91af1 | 2012-10-18 20:45:57 +0000 | [diff] [blame] | 320 | # Convert the whole thing to / since it's isolate's speak. |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 321 | f = file_path.posix_relpath( |
maruel@chromium.org | ec91af1 | 2012-10-18 20:45:57 +0000 | [diff] [blame] | 322 | posixpath.join(root_dir_posix, f), |
| 323 | posixpath.join(root_dir_posix, relative_cwd)) or './' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 324 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 325 | # Use the longest value first. |
| 326 | for key, value in sorted( |
| 327 | path_variables.iteritems(), key=lambda x: -len(x[1])): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 328 | if f.startswith(value): |
| 329 | f = '<(%s)%s' % (key, f[len(value):]) |
| 330 | logging.debug('Converted to %s' % f) |
| 331 | break |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 332 | return f |
| 333 | |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 334 | def fix_all(items): |
| 335 | """Reduces the items to convert variables, removes unneeded items, apply |
| 336 | chromium-specific fixes and only return unique items. |
| 337 | """ |
| 338 | variables_converted = (fix(f.path) for f in items) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 339 | chromium_fixed = ( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 340 | chromium_fix(f, total_variables) for f in variables_converted) |
maruel@chromium.org | 2bbc9cd | 2012-11-15 19:35:32 +0000 | [diff] [blame] | 341 | return set(f for f in chromium_fixed if f) |
| 342 | |
| 343 | tracked = fix_all(tracked) |
| 344 | untracked = fix_all(untracked) |
| 345 | touched = fix_all(touched) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 346 | out = classify_files(root_dir, tracked, untracked) |
| 347 | if touched: |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 348 | out[isolate_format.KEY_TOUCHED] = sorted(touched) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 349 | return out |
| 350 | |
| 351 | |
| 352 | def generate_isolate( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 353 | tracked, untracked, touched, root_dir, path_variables, config_variables, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 354 | extra_variables, relative_cwd, trace_blacklist): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 355 | """Generates a clean and complete .isolate file.""" |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 356 | dependencies = generate_simplified( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 357 | tracked, untracked, touched, root_dir, path_variables, extra_variables, |
| 358 | relative_cwd, trace_blacklist) |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 359 | config_variable_names, config_values = zip( |
| 360 | *sorted(config_variables.iteritems())) |
Marc-Antoine Ruel | 67d3c0a | 2014-01-10 09:12:39 -0500 | [diff] [blame] | 361 | out = isolate_format.Configs(None, config_variable_names) |
Marc-Antoine Ruel | 3ae9e6e | 2014-01-13 15:42:16 -0500 | [diff] [blame] | 362 | # TODO(maruel): Create a public interface in Configs to add a ConfigSettings. |
| 363 | # pylint: disable=W0212 |
| 364 | out._by_config[config_values] = isolate_format.ConfigSettings(dependencies) |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 365 | return out.make_isolate_file() |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 366 | |
| 367 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 368 | def chromium_save_isolated(isolated, data, path_variables, algo): |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 369 | """Writes one or many .isolated files. |
| 370 | |
| 371 | This slightly increases the cold cache cost but greatly reduce the warm cache |
| 372 | cost by splitting low-churn files off the master .isolated file. It also |
| 373 | reduces overall isolateserver memcache consumption. |
| 374 | """ |
| 375 | slaves = [] |
| 376 | |
| 377 | def extract_into_included_isolated(prefix): |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 378 | new_slave = { |
| 379 | 'algo': data['algo'], |
| 380 | 'files': {}, |
| 381 | 'os': data['os'], |
| 382 | 'version': data['version'], |
| 383 | } |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 384 | for f in data['files'].keys(): |
| 385 | if f.startswith(prefix): |
| 386 | new_slave['files'][f] = data['files'].pop(f) |
| 387 | if new_slave['files']: |
| 388 | slaves.append(new_slave) |
| 389 | |
| 390 | # Split test/data/ in its own .isolated file. |
| 391 | extract_into_included_isolated(os.path.join('test', 'data', '')) |
| 392 | |
| 393 | # Split everything out of PRODUCT_DIR in its own .isolated file. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 394 | if path_variables.get('PRODUCT_DIR'): |
| 395 | extract_into_included_isolated(path_variables['PRODUCT_DIR']) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 396 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 397 | files = [] |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 398 | for index, f in enumerate(slaves): |
| 399 | slavepath = isolated[:-len('.isolated')] + '.%d.isolated' % index |
Marc-Antoine Ruel | de01180 | 2013-11-12 15:19:47 -0500 | [diff] [blame] | 400 | tools.write_json(slavepath, f, True) |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 401 | data.setdefault('includes', []).append( |
| 402 | isolateserver.hash_file(slavepath, algo)) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 403 | files.append(os.path.basename(slavepath)) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 404 | |
Marc-Antoine Ruel | fcc3cd8 | 2013-11-19 16:31:38 -0500 | [diff] [blame] | 405 | files.extend(isolateserver.save_isolated(isolated, data)) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 406 | return files |
| 407 | |
| 408 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 409 | class Flattenable(object): |
| 410 | """Represents data that can be represented as a json file.""" |
| 411 | MEMBERS = () |
| 412 | |
| 413 | def flatten(self): |
| 414 | """Returns a json-serializable version of itself. |
| 415 | |
| 416 | Skips None entries. |
| 417 | """ |
| 418 | items = ((member, getattr(self, member)) for member in self.MEMBERS) |
| 419 | return dict((member, value) for member, value in items if value is not None) |
| 420 | |
| 421 | @classmethod |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 422 | def load(cls, data, *args, **kwargs): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 423 | """Loads a flattened version.""" |
| 424 | data = data.copy() |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 425 | out = cls(*args, **kwargs) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 426 | for member in out.MEMBERS: |
| 427 | if member in data: |
| 428 | # Access to a protected member XXX of a client class |
| 429 | # pylint: disable=W0212 |
| 430 | out._load_member(member, data.pop(member)) |
| 431 | if data: |
| 432 | raise ValueError( |
| 433 | 'Found unexpected entry %s while constructing an object %s' % |
| 434 | (data, cls.__name__), data, cls.__name__) |
| 435 | return out |
| 436 | |
| 437 | def _load_member(self, member, value): |
| 438 | """Loads a member into self.""" |
| 439 | setattr(self, member, value) |
| 440 | |
| 441 | @classmethod |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 442 | def load_file(cls, filename, *args, **kwargs): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 443 | """Loads the data from a file or return an empty instance.""" |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 444 | try: |
Marc-Antoine Ruel | de01180 | 2013-11-12 15:19:47 -0500 | [diff] [blame] | 445 | out = cls.load(tools.read_json(filename), *args, **kwargs) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 446 | logging.debug('Loaded %s(%s)', cls.__name__, filename) |
maruel@chromium.org | e9403ab | 2013-09-20 18:03:49 +0000 | [diff] [blame] | 447 | except (IOError, ValueError) as e: |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 448 | # On failure, loads the default instance. |
| 449 | out = cls(*args, **kwargs) |
maruel@chromium.org | e9403ab | 2013-09-20 18:03:49 +0000 | [diff] [blame] | 450 | logging.warn('Failed to load %s: %s', filename, e) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 451 | return out |
| 452 | |
| 453 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 454 | class SavedState(Flattenable): |
| 455 | """Describes the content of a .state file. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 456 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 457 | This file caches the items calculated by this script and is used to increase |
| 458 | the performance of the script. This file is not loaded by run_isolated.py. |
| 459 | This file can always be safely removed. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 460 | |
| 461 | It is important to note that the 'files' dict keys are using native OS path |
| 462 | separator instead of '/' used in .isolate file. |
| 463 | """ |
| 464 | MEMBERS = ( |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 465 | # Algorithm used to generate the hash. The only supported value is at the |
| 466 | # time of writting 'sha-1'. |
| 467 | 'algo', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 468 | # Cache of the processed command. This value is saved because .isolated |
| 469 | # files are never loaded by isolate.py so it's the only way to load the |
| 470 | # command safely. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 471 | 'command', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 472 | # GYP variables that are used to generate conditions. The most frequent |
| 473 | # example is 'OS'. |
| 474 | 'config_variables', |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 475 | # GYP variables that will be replaced in 'command' and paths but will not be |
| 476 | # considered a relative directory. |
| 477 | 'extra_variables', |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 478 | # Cache of the files found so the next run can skip hash calculation. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 479 | 'files', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 480 | # Path of the original .isolate file. Relative path to isolated_basedir. |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 481 | 'isolate_file', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 482 | # List of included .isolated files. Used to support/remember 'slave' |
| 483 | # .isolated files. Relative path to isolated_basedir. |
| 484 | 'child_isolated_files', |
| 485 | # If the generated directory tree should be read-only. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 486 | 'read_only', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 487 | # Relative cwd to use to start the command. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 488 | 'relative_cwd', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 489 | # GYP variables used to generate the .isolated files paths based on path |
| 490 | # variables. Frequent examples are DEPTH and PRODUCT_DIR. |
| 491 | 'path_variables', |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 492 | # Version of the file format in format 'major.minor'. Any non-breaking |
| 493 | # change must update minor. Any breaking change must update major. |
| 494 | 'version', |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 495 | ) |
| 496 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 497 | def __init__(self, isolated_basedir): |
| 498 | """Creates an empty SavedState. |
| 499 | |
| 500 | |isolated_basedir| is the directory where the .isolated and .isolated.state |
| 501 | files are saved. |
| 502 | """ |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 503 | super(SavedState, self).__init__() |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 504 | assert os.path.isabs(isolated_basedir), isolated_basedir |
| 505 | assert os.path.isdir(isolated_basedir), isolated_basedir |
| 506 | self.isolated_basedir = isolated_basedir |
| 507 | |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 508 | # The default algorithm used. |
| 509 | self.algo = isolateserver.SUPPORTED_ALGOS['sha-1'] |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 510 | self.child_isolated_files = [] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 511 | self.command = [] |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 512 | self.config_variables = {} |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 513 | self.extra_variables = {} |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 514 | self.files = {} |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 515 | self.isolate_file = None |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 516 | self.path_variables = {} |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 517 | self.read_only = None |
| 518 | self.relative_cwd = None |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 519 | self.version = isolateserver.ISOLATED_FILE_VERSION |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 520 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 521 | def update( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 522 | self, isolate_file, path_variables, config_variables, extra_variables): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 523 | """Updates the saved state with new data to keep GYP variables and internal |
| 524 | reference to the original .isolate file. |
| 525 | """ |
maruel@chromium.org | e99c151 | 2013-04-09 20:24:11 +0000 | [diff] [blame] | 526 | assert os.path.isabs(isolate_file) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 527 | # Convert back to a relative path. On Windows, if the isolate and |
| 528 | # isolated files are on different drives, isolate_file will stay an absolute |
| 529 | # path. |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 530 | isolate_file = file_path.safe_relpath(isolate_file, self.isolated_basedir) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 531 | |
| 532 | # The same .isolate file should always be used to generate the .isolated and |
| 533 | # .isolated.state. |
| 534 | assert isolate_file == self.isolate_file or not self.isolate_file, ( |
| 535 | isolate_file, self.isolate_file) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 536 | self.config_variables.update(config_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 537 | self.extra_variables.update(extra_variables) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 538 | self.isolate_file = isolate_file |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 539 | self.path_variables.update(path_variables) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 540 | |
| 541 | def update_isolated(self, command, infiles, touched, read_only, relative_cwd): |
| 542 | """Updates the saved state with data necessary to generate a .isolated file. |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 543 | |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 544 | The new files in |infiles| are added to self.files dict but their hash is |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 545 | not calculated here. |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 546 | """ |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 547 | self.command = command |
| 548 | # Add new files. |
| 549 | for f in infiles: |
| 550 | self.files.setdefault(f, {}) |
| 551 | for f in touched: |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 552 | self.files.setdefault(f, {})['T'] = True |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 553 | # Prune extraneous files that are not a dependency anymore. |
| 554 | for f in set(self.files).difference(set(infiles).union(touched)): |
| 555 | del self.files[f] |
| 556 | if read_only is not None: |
| 557 | self.read_only = read_only |
| 558 | self.relative_cwd = relative_cwd |
| 559 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 560 | def to_isolated(self): |
| 561 | """Creates a .isolated dictionary out of the saved state. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 562 | |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 563 | https://code.google.com/p/swarming/wiki/IsolatedDesign |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 564 | """ |
| 565 | def strip(data): |
| 566 | """Returns a 'files' entry with only the whitelisted keys.""" |
| 567 | return dict((k, data[k]) for k in ('h', 'l', 'm', 's') if k in data) |
| 568 | |
| 569 | out = { |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 570 | 'algo': isolateserver.SUPPORTED_ALGOS_REVERSE[self.algo], |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 571 | 'files': dict( |
| 572 | (filepath, strip(data)) for filepath, data in self.files.iteritems()), |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 573 | 'version': self.version, |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 574 | } |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 575 | if self.config_variables.get('OS'): |
| 576 | out['os'] = self.config_variables['OS'] |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 577 | if self.command: |
| 578 | out['command'] = self.command |
| 579 | if self.read_only is not None: |
| 580 | out['read_only'] = self.read_only |
| 581 | if self.relative_cwd: |
| 582 | out['relative_cwd'] = self.relative_cwd |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 583 | return out |
| 584 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 585 | @property |
| 586 | def isolate_filepath(self): |
| 587 | """Returns the absolute path of self.isolate_file.""" |
| 588 | return os.path.normpath( |
| 589 | os.path.join(self.isolated_basedir, self.isolate_file)) |
| 590 | |
| 591 | # Arguments number differs from overridden method |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 592 | @classmethod |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 593 | def load(cls, data, isolated_basedir): # pylint: disable=W0221 |
| 594 | """Special case loading to disallow different OS. |
| 595 | |
| 596 | It is not possible to load a .isolated.state files from a different OS, this |
| 597 | file is saved in OS-specific format. |
| 598 | """ |
| 599 | out = super(SavedState, cls).load(data, isolated_basedir) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 600 | if data.get('os'): |
| 601 | out.config_variables['OS'] = data['os'] |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 602 | |
| 603 | # Converts human readable form back into the proper class type. |
| 604 | algo = data.get('algo', 'sha-1') |
| 605 | if not algo in isolateserver.SUPPORTED_ALGOS: |
maruel@chromium.org | 999a1fd | 2013-09-20 17:41:07 +0000 | [diff] [blame] | 606 | raise isolateserver.ConfigError('Unknown algo \'%s\'' % out.algo) |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 607 | out.algo = isolateserver.SUPPORTED_ALGOS[algo] |
| 608 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 609 | # Refuse the load non-exact version, even minor difference. This is unlike |
| 610 | # isolateserver.load_isolated(). This is because .isolated.state could have |
| 611 | # changed significantly even in minor version difference. |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 612 | if not re.match(r'^(\d+)\.(\d+)$', out.version): |
maruel@chromium.org | 999a1fd | 2013-09-20 17:41:07 +0000 | [diff] [blame] | 613 | raise isolateserver.ConfigError('Unknown version \'%s\'' % out.version) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 614 | if out.version != isolateserver.ISOLATED_FILE_VERSION: |
maruel@chromium.org | 999a1fd | 2013-09-20 17:41:07 +0000 | [diff] [blame] | 615 | raise isolateserver.ConfigError( |
| 616 | 'Unsupported version \'%s\'' % out.version) |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 617 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 618 | # The .isolate file must be valid. It could be absolute on Windows if the |
| 619 | # drive containing the .isolate and the drive containing the .isolated files |
| 620 | # differ. |
| 621 | assert not os.path.isabs(out.isolate_file) or sys.platform == 'win32' |
| 622 | assert os.path.isfile(out.isolate_filepath), out.isolate_filepath |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 623 | return out |
| 624 | |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 625 | def flatten(self): |
| 626 | """Makes sure 'algo' is in human readable form.""" |
| 627 | out = super(SavedState, self).flatten() |
| 628 | out['algo'] = isolateserver.SUPPORTED_ALGOS_REVERSE[out['algo']] |
| 629 | return out |
| 630 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 631 | def __str__(self): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 632 | def dict_to_str(d): |
| 633 | return ''.join('\n %s=%s' % (k, d[k]) for k in sorted(d)) |
| 634 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 635 | out = '%s(\n' % self.__class__.__name__ |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 636 | out += ' command: %s\n' % self.command |
| 637 | out += ' files: %d\n' % len(self.files) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 638 | out += ' isolate_file: %s\n' % self.isolate_file |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 639 | out += ' read_only: %s\n' % self.read_only |
maruel@chromium.org | 9e9ceaa | 2013-04-05 15:42:42 +0000 | [diff] [blame] | 640 | out += ' relative_cwd: %s\n' % self.relative_cwd |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 641 | out += ' child_isolated_files: %s\n' % self.child_isolated_files |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 642 | out += ' path_variables: %s\n' % dict_to_str(self.path_variables) |
| 643 | out += ' config_variables: %s\n' % dict_to_str(self.config_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 644 | out += ' extra_variables: %s\n' % dict_to_str(self.extra_variables) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 645 | return out |
| 646 | |
| 647 | |
| 648 | class CompleteState(object): |
| 649 | """Contains all the state to run the task at hand.""" |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 650 | def __init__(self, isolated_filepath, saved_state): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 651 | super(CompleteState, self).__init__() |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 652 | assert isolated_filepath is None or os.path.isabs(isolated_filepath) |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 653 | self.isolated_filepath = isolated_filepath |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 654 | # Contains the data to ease developer's use-case but that is not strictly |
| 655 | # necessary. |
| 656 | self.saved_state = saved_state |
| 657 | |
| 658 | @classmethod |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 659 | def load_files(cls, isolated_filepath): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 660 | """Loads state from disk.""" |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 661 | assert os.path.isabs(isolated_filepath), isolated_filepath |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 662 | isolated_basedir = os.path.dirname(isolated_filepath) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 663 | return cls( |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 664 | isolated_filepath, |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 665 | SavedState.load_file( |
| 666 | isolatedfile_to_state(isolated_filepath), isolated_basedir)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 667 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 668 | def load_isolate( |
| 669 | self, cwd, isolate_file, path_variables, config_variables, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 670 | extra_variables, ignore_broken_items): |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 671 | """Updates self.isolated and self.saved_state with information loaded from a |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 672 | .isolate file. |
| 673 | |
| 674 | Processes the loaded data, deduce root_dir, relative_cwd. |
| 675 | """ |
| 676 | # Make sure to not depend on os.getcwd(). |
| 677 | assert os.path.isabs(isolate_file), isolate_file |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 678 | isolate_file = file_path.get_native_path_case(isolate_file) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 679 | logging.info( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 680 | 'CompleteState.load_isolate(%s, %s, %s, %s, %s, %s)', |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 681 | cwd, isolate_file, path_variables, config_variables, extra_variables, |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 682 | ignore_broken_items) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 683 | relative_base_dir = os.path.dirname(isolate_file) |
| 684 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 685 | # Processes the variables. |
| 686 | path_variables = normalize_path_variables( |
| 687 | cwd, path_variables, relative_base_dir) |
| 688 | # Update the saved state. |
| 689 | self.saved_state.update( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 690 | isolate_file, path_variables, config_variables, extra_variables) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 691 | path_variables = self.saved_state.path_variables |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 692 | |
| 693 | with open(isolate_file, 'r') as f: |
| 694 | # At that point, variables are not replaced yet in command and infiles. |
| 695 | # infiles may contain directory entries and is in posix style. |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 696 | command, infiles, touched, read_only = ( |
| 697 | isolate_format.load_isolate_for_config( |
| 698 | os.path.dirname(isolate_file), f.read(), |
| 699 | self.saved_state.config_variables)) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 700 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 701 | total_variables = self.saved_state.path_variables.copy() |
| 702 | total_variables.update(self.saved_state.config_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 703 | total_variables.update(self.saved_state.extra_variables) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 704 | command = [ |
| 705 | isolate_format.eval_variables(i, total_variables) for i in command |
| 706 | ] |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 707 | |
| 708 | total_variables = self.saved_state.path_variables.copy() |
| 709 | total_variables.update(self.saved_state.extra_variables) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 710 | infiles = [ |
| 711 | isolate_format.eval_variables(f, total_variables) for f in infiles |
| 712 | ] |
| 713 | touched = [ |
| 714 | isolate_format.eval_variables(f, total_variables) for f in touched |
| 715 | ] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 716 | # root_dir is automatically determined by the deepest root accessed with the |
maruel@chromium.org | 75584e2 | 2013-06-20 01:40:24 +0000 | [diff] [blame] | 717 | # form '../../foo/bar'. Note that path variables must be taken in account |
| 718 | # too, add them as if they were input files. |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 719 | root_dir = isolate_format.determine_root_dir( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 720 | relative_base_dir, infiles + touched + |
| 721 | self.saved_state.path_variables.values()) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 722 | # The relative directory is automatically determined by the relative path |
| 723 | # between root_dir and the directory containing the .isolate file, |
| 724 | # isolate_base_dir. |
| 725 | relative_cwd = os.path.relpath(relative_base_dir, root_dir) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 726 | # Now that we know where the root is, check that the path_variables point |
benrg@chromium.org | 9ae7286 | 2013-02-11 05:05:51 +0000 | [diff] [blame] | 727 | # inside it. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 728 | for k, v in self.saved_state.path_variables.iteritems(): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 729 | if not file_path.path_starts_with( |
| 730 | root_dir, os.path.join(relative_base_dir, v)): |
| 731 | raise isolateserver.MappingError( |
| 732 | 'Path variable %s=%r points outside the inferred root directory %s' |
| 733 | % (k, v, root_dir)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 734 | # Normalize the files based to root_dir. It is important to keep the |
| 735 | # trailing os.path.sep at that step. |
| 736 | infiles = [ |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 737 | file_path.relpath( |
| 738 | file_path.normpath(os.path.join(relative_base_dir, f)), root_dir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 739 | for f in infiles |
| 740 | ] |
| 741 | touched = [ |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 742 | file_path.relpath( |
| 743 | file_path.normpath(os.path.join(relative_base_dir, f)), root_dir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 744 | for f in touched |
| 745 | ] |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 746 | follow_symlinks = config_variables['OS'] != 'win' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 747 | # Expand the directories by listing each file inside. Up to now, trailing |
| 748 | # os.path.sep must be kept. Do not expand 'touched'. |
| 749 | infiles = expand_directories_and_symlinks( |
| 750 | root_dir, |
| 751 | infiles, |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 752 | lambda x: re.match(r'.*\.(git|svn|pyc)$', x), |
csharp@chromium.org | 84d2e2e | 2013-03-27 13:38:42 +0000 | [diff] [blame] | 753 | follow_symlinks, |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 754 | ignore_broken_items) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 755 | |
csharp@chromium.org | bc7c5d1 | 2013-03-21 16:39:15 +0000 | [diff] [blame] | 756 | # If we ignore broken items then remove any missing touched items. |
| 757 | if ignore_broken_items: |
| 758 | original_touched_count = len(touched) |
| 759 | touched = [touch for touch in touched if os.path.exists(touch)] |
| 760 | |
| 761 | if len(touched) != original_touched_count: |
maruel@chromium.org | 1d3a913 | 2013-07-18 20:06:15 +0000 | [diff] [blame] | 762 | logging.info('Removed %d invalid touched entries', |
csharp@chromium.org | bc7c5d1 | 2013-03-21 16:39:15 +0000 | [diff] [blame] | 763 | len(touched) - original_touched_count) |
| 764 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 765 | # Finally, update the new data to be able to generate the foo.isolated file, |
| 766 | # the file that is used by run_isolated.py. |
| 767 | self.saved_state.update_isolated( |
| 768 | command, infiles, touched, read_only, relative_cwd) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 769 | logging.debug(self) |
| 770 | |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 771 | def process_inputs(self, subdir): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 772 | """Updates self.saved_state.files with the files' mode and hash. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 773 | |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 774 | If |subdir| is specified, filters to a subdirectory. The resulting .isolated |
| 775 | file is tainted. |
| 776 | |
Marc-Antoine Ruel | fcc3cd8 | 2013-11-19 16:31:38 -0500 | [diff] [blame] | 777 | See isolateserver.process_input() for more information. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 778 | """ |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 779 | for infile in sorted(self.saved_state.files): |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 780 | if subdir and not infile.startswith(subdir): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 781 | self.saved_state.files.pop(infile) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 782 | else: |
| 783 | filepath = os.path.join(self.root_dir, infile) |
Marc-Antoine Ruel | fcc3cd8 | 2013-11-19 16:31:38 -0500 | [diff] [blame] | 784 | self.saved_state.files[infile] = isolateserver.process_input( |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 785 | filepath, |
| 786 | self.saved_state.files[infile], |
maruel@chromium.org | baa108d | 2013-03-28 13:24:51 +0000 | [diff] [blame] | 787 | self.saved_state.read_only, |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 788 | self.saved_state.config_variables['OS'], |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 789 | self.saved_state.algo) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 790 | |
| 791 | def save_files(self): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 792 | """Saves self.saved_state and creates a .isolated file.""" |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 793 | logging.debug('Dumping to %s' % self.isolated_filepath) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 794 | self.saved_state.child_isolated_files = chromium_save_isolated( |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 795 | self.isolated_filepath, |
| 796 | self.saved_state.to_isolated(), |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 797 | self.saved_state.path_variables, |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 798 | self.saved_state.algo) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 799 | total_bytes = sum( |
| 800 | i.get('s', 0) for i in self.saved_state.files.itervalues()) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 801 | if total_bytes: |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 802 | # TODO(maruel): Stats are missing the .isolated files. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 803 | logging.debug('Total size: %d bytes' % total_bytes) |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 804 | saved_state_file = isolatedfile_to_state(self.isolated_filepath) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 805 | logging.debug('Dumping to %s' % saved_state_file) |
Marc-Antoine Ruel | de01180 | 2013-11-12 15:19:47 -0500 | [diff] [blame] | 806 | tools.write_json(saved_state_file, self.saved_state.flatten(), True) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 807 | |
| 808 | @property |
| 809 | def root_dir(self): |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 810 | """Returns the absolute path of the root_dir to reference the .isolate file |
| 811 | via relative_cwd. |
| 812 | |
| 813 | So that join(root_dir, relative_cwd, basename(isolate_file)) is equivalent |
| 814 | to isolate_filepath. |
| 815 | """ |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 816 | if not self.saved_state.isolate_file: |
| 817 | raise ExecutionError('Please specify --isolate') |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 818 | isolate_dir = os.path.dirname(self.saved_state.isolate_filepath) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 819 | # Special case '.'. |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 820 | if self.saved_state.relative_cwd == '.': |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 821 | root_dir = isolate_dir |
| 822 | else: |
maruel@chromium.org | 87557b9 | 2013-10-16 18:04:11 +0000 | [diff] [blame] | 823 | if not isolate_dir.endswith(self.saved_state.relative_cwd): |
| 824 | raise ExecutionError( |
| 825 | ('Make sure the .isolate file is in the directory that will be ' |
| 826 | 'used as the relative directory. It is currently in %s and should ' |
| 827 | 'be in %s') % (isolate_dir, self.saved_state.relative_cwd)) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 828 | # Walk back back to the root directory. |
| 829 | root_dir = isolate_dir[:-(len(self.saved_state.relative_cwd) + 1)] |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 830 | return file_path.get_native_path_case(root_dir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 831 | |
| 832 | @property |
| 833 | def resultdir(self): |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 834 | """Returns the absolute path containing the .isolated file. |
| 835 | |
| 836 | It is usually equivalent to the variable PRODUCT_DIR. Uses the .isolated |
| 837 | path as the value. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 838 | """ |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 839 | return os.path.dirname(self.isolated_filepath) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 840 | |
| 841 | def __str__(self): |
| 842 | def indent(data, indent_length): |
| 843 | """Indents text.""" |
| 844 | spacing = ' ' * indent_length |
| 845 | return ''.join(spacing + l for l in str(data).splitlines(True)) |
| 846 | |
| 847 | out = '%s(\n' % self.__class__.__name__ |
| 848 | out += ' root_dir: %s\n' % self.root_dir |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 849 | out += ' saved_state: %s)' % indent(self.saved_state, 2) |
| 850 | return out |
| 851 | |
| 852 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 853 | def load_complete_state(options, cwd, subdir, skip_update): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 854 | """Loads a CompleteState. |
| 855 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 856 | This includes data from .isolate and .isolated.state files. Never reads the |
| 857 | .isolated file. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 858 | |
| 859 | Arguments: |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 860 | options: Options instance generated with OptionParserIsolate. For either |
| 861 | options.isolate and options.isolated, if the value is set, it is an |
| 862 | absolute path. |
| 863 | cwd: base directory to be used when loading the .isolate file. |
| 864 | subdir: optional argument to only process file in the subdirectory, relative |
| 865 | to CompleteState.root_dir. |
| 866 | skip_update: Skip trying to load the .isolate file and processing the |
| 867 | dependencies. It is useful when not needed, like when tracing. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 868 | """ |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 869 | assert not options.isolate or os.path.isabs(options.isolate) |
| 870 | assert not options.isolated or os.path.isabs(options.isolated) |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 871 | cwd = file_path.get_native_path_case(unicode(cwd)) |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 872 | if options.isolated: |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 873 | # Load the previous state if it was present. Namely, "foo.isolated.state". |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 874 | # Note: this call doesn't load the .isolate file. |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 875 | complete_state = CompleteState.load_files(options.isolated) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 876 | else: |
| 877 | # Constructs a dummy object that cannot be saved. Useful for temporary |
| 878 | # commands like 'run'. |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 879 | complete_state = CompleteState(None, SavedState()) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 880 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 881 | if not options.isolate: |
| 882 | if not complete_state.saved_state.isolate_file: |
| 883 | if not skip_update: |
| 884 | raise ExecutionError('A .isolate file is required.') |
| 885 | isolate = None |
| 886 | else: |
| 887 | isolate = complete_state.saved_state.isolate_filepath |
| 888 | else: |
| 889 | isolate = options.isolate |
| 890 | if complete_state.saved_state.isolate_file: |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 891 | rel_isolate = file_path.safe_relpath( |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 892 | options.isolate, complete_state.saved_state.isolated_basedir) |
| 893 | if rel_isolate != complete_state.saved_state.isolate_file: |
| 894 | raise ExecutionError( |
| 895 | '%s and %s do not match.' % ( |
| 896 | options.isolate, complete_state.saved_state.isolate_file)) |
| 897 | |
| 898 | if not skip_update: |
| 899 | # Then load the .isolate and expands directories. |
| 900 | complete_state.load_isolate( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 901 | cwd, isolate, options.path_variables, options.config_variables, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 902 | options.extra_variables, options.ignore_broken_items) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 903 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 904 | # Regenerate complete_state.saved_state.files. |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 905 | if subdir: |
maruel@chromium.org | 306e0e7 | 2012-11-02 18:22:03 +0000 | [diff] [blame] | 906 | subdir = unicode(subdir) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 907 | # This is tricky here. If it is a path, take it from the root_dir. If |
| 908 | # it is a variable, it must be keyed from the directory containing the |
| 909 | # .isolate file. So translate all variables first. |
| 910 | translated_path_variables = dict( |
| 911 | (k, |
| 912 | os.path.normpath(os.path.join(complete_state.saved_state.relative_cwd, |
| 913 | v))) |
| 914 | for k, v in complete_state.saved_state.path_variables.iteritems()) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 915 | subdir = isolate_format.eval_variables(subdir, translated_path_variables) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 916 | subdir = subdir.replace('/', os.path.sep) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 917 | |
| 918 | if not skip_update: |
| 919 | complete_state.process_inputs(subdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 920 | return complete_state |
| 921 | |
| 922 | |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 923 | def read_trace_as_isolate_dict(complete_state, trace_blacklist): |
maruel@chromium.org | e27c65f | 2012-10-22 13:56:53 +0000 | [diff] [blame] | 924 | """Reads a trace and returns the .isolate dictionary. |
| 925 | |
| 926 | Returns exceptions during the log parsing so it can be re-raised. |
| 927 | """ |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 928 | api = trace_inputs.get_api() |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 929 | logfile = complete_state.isolated_filepath + '.log' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 930 | if not os.path.isfile(logfile): |
| 931 | raise ExecutionError( |
| 932 | 'No log file \'%s\' to read, did you forget to \'trace\'?' % logfile) |
| 933 | try: |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 934 | data = api.parse_log(logfile, trace_blacklist, None) |
maruel@chromium.org | e27c65f | 2012-10-22 13:56:53 +0000 | [diff] [blame] | 935 | exceptions = [i['exception'] for i in data if 'exception' in i] |
| 936 | results = (i['results'] for i in data if 'results' in i) |
| 937 | results_stripped = (i.strip_root(complete_state.root_dir) for i in results) |
| 938 | files = set(sum((result.existent for result in results_stripped), [])) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 939 | tracked, touched = isolate_format.split_touched(files) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 940 | value = generate_isolate( |
| 941 | tracked, |
| 942 | [], |
| 943 | touched, |
| 944 | complete_state.root_dir, |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 945 | complete_state.saved_state.path_variables, |
| 946 | complete_state.saved_state.config_variables, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 947 | complete_state.saved_state.extra_variables, |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 948 | complete_state.saved_state.relative_cwd, |
| 949 | trace_blacklist) |
maruel@chromium.org | e27c65f | 2012-10-22 13:56:53 +0000 | [diff] [blame] | 950 | return value, exceptions |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 951 | except trace_inputs.TracingFailure, e: |
| 952 | raise ExecutionError( |
| 953 | 'Reading traces failed for: %s\n%s' % |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 954 | (' '.join(complete_state.saved_state.command), str(e))) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 955 | |
| 956 | |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 957 | def merge(complete_state, trace_blacklist): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 958 | """Reads a trace and merges it back into the source .isolate file.""" |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 959 | value, exceptions = read_trace_as_isolate_dict( |
| 960 | complete_state, trace_blacklist) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 961 | |
| 962 | # Now take that data and union it into the original .isolate file. |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 963 | with open(complete_state.saved_state.isolate_filepath, 'r') as f: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 964 | prev_content = f.read() |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 965 | isolate_dir = os.path.dirname(complete_state.saved_state.isolate_filepath) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 966 | prev_config = isolate_format.load_isolate_as_config( |
maruel@chromium.org | 8007b8f | 2012-12-14 15:45:18 +0000 | [diff] [blame] | 967 | isolate_dir, |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 968 | isolate_format.eval_content(prev_content), |
| 969 | isolate_format.extract_comment(prev_content)) |
| 970 | new_config = isolate_format.load_isolate_as_config(isolate_dir, value, '') |
| 971 | config = isolate_format.union(prev_config, new_config) |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 972 | data = config.make_isolate_file() |
maruel@chromium.org | ec91af1 | 2012-10-18 20:45:57 +0000 | [diff] [blame] | 973 | print('Updating %s' % complete_state.saved_state.isolate_file) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 974 | with open(complete_state.saved_state.isolate_filepath, 'wb') as f: |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 975 | isolate_format.print_all(config.file_comment, data, f) |
maruel@chromium.org | e27c65f | 2012-10-22 13:56:53 +0000 | [diff] [blame] | 976 | if exceptions: |
| 977 | # It got an exception, raise the first one. |
| 978 | raise \ |
| 979 | exceptions[0][0], \ |
| 980 | exceptions[0][1], \ |
| 981 | exceptions[0][2] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 982 | |
| 983 | |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 984 | def create_isolate_tree(outdir, root_dir, files, relative_cwd, read_only): |
| 985 | """Creates a isolated tree usable for test execution. |
| 986 | |
| 987 | Returns the current working directory where the isolated command should be |
| 988 | started in. |
| 989 | """ |
Marc-Antoine Ruel | 361bfda | 2014-01-15 15:26:39 -0500 | [diff] [blame] | 990 | # Forcibly copy when the tree has to be read only. Otherwise the inode is |
| 991 | # modified, and this cause real problems because the user's source tree |
| 992 | # becomes read only. On the other hand, the cost of doing file copy is huge. |
| 993 | if read_only not in (0, None): |
| 994 | action = run_isolated.COPY |
| 995 | else: |
| 996 | action = run_isolated.HARDLINK_WITH_FALLBACK |
| 997 | |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 998 | recreate_tree( |
| 999 | outdir=outdir, |
| 1000 | indir=root_dir, |
| 1001 | infiles=files, |
Marc-Antoine Ruel | 361bfda | 2014-01-15 15:26:39 -0500 | [diff] [blame] | 1002 | action=action, |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1003 | as_hash=False) |
| 1004 | cwd = os.path.normpath(os.path.join(outdir, relative_cwd)) |
| 1005 | if not os.path.isdir(cwd): |
| 1006 | # It can happen when no files are mapped from the directory containing the |
| 1007 | # .isolate file. But the directory must exist to be the current working |
| 1008 | # directory. |
| 1009 | os.makedirs(cwd) |
| 1010 | run_isolated.change_tree_read_only(outdir, read_only) |
| 1011 | return cwd |
| 1012 | |
| 1013 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1014 | def prepare_for_archival(options, cwd): |
| 1015 | """Loads the isolated file and create 'infiles' for archival.""" |
| 1016 | complete_state = load_complete_state( |
| 1017 | options, cwd, options.subdir, False) |
| 1018 | # Make sure that complete_state isn't modified until save_files() is |
| 1019 | # called, because any changes made to it here will propagate to the files |
| 1020 | # created (which is probably not intended). |
| 1021 | complete_state.save_files() |
| 1022 | |
| 1023 | infiles = complete_state.saved_state.files |
| 1024 | # Add all the .isolated files. |
| 1025 | isolated_hash = [] |
| 1026 | isolated_files = [ |
| 1027 | options.isolated, |
| 1028 | ] + complete_state.saved_state.child_isolated_files |
| 1029 | for item in isolated_files: |
| 1030 | item_path = os.path.join( |
| 1031 | os.path.dirname(complete_state.isolated_filepath), item) |
| 1032 | # Do not use isolateserver.hash_file() here because the file is |
| 1033 | # likely smallish (under 500kb) and its file size is needed. |
| 1034 | with open(item_path, 'rb') as f: |
| 1035 | content = f.read() |
| 1036 | isolated_hash.append( |
| 1037 | complete_state.saved_state.algo(content).hexdigest()) |
| 1038 | isolated_metadata = { |
| 1039 | 'h': isolated_hash[-1], |
| 1040 | 's': len(content), |
| 1041 | 'priority': '0' |
| 1042 | } |
| 1043 | infiles[item_path] = isolated_metadata |
| 1044 | return complete_state, infiles, isolated_hash |
| 1045 | |
| 1046 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1047 | ### Commands. |
| 1048 | |
| 1049 | |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 1050 | def CMDarchive(parser, args): |
| 1051 | """Creates a .isolated file and uploads the tree to an isolate server. |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1052 | |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 1053 | All the files listed in the .isolated file are put in the isolate server |
| 1054 | cache via isolateserver.py. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1055 | """ |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1056 | add_subdir_option(parser) |
| 1057 | parser.add_option( |
| 1058 | '-I', '--isolate-server', metavar='URL', |
| 1059 | default=os.environ.get('ISOLATE_SERVER', ''), |
| 1060 | help='URL of the isolate server') |
| 1061 | parser.add_option( |
| 1062 | '--namespace', metavar='NAME', default='default-gzip', |
| 1063 | help='Namespace to use on the isolate server') |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 1064 | options, args = parser.parse_args(args) |
| 1065 | if args: |
| 1066 | parser.error('Unsupported argument: %s' % args) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1067 | if not options.isolate_server: |
| 1068 | parser.error('--isolate-server is required.') |
| 1069 | cwd = os.getcwd() |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1070 | with tools.Profiler('GenerateHashtable'): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1071 | success = False |
| 1072 | try: |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1073 | complete_state, infiles, isolated_hash = prepare_for_archival( |
| 1074 | options, cwd) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 1075 | logging.info('Creating content addressed object store with %d item', |
| 1076 | len(infiles)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1077 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1078 | isolateserver.upload_tree( |
| 1079 | base_url=options.isolate_server, |
| 1080 | indir=complete_state.root_dir, |
| 1081 | infiles=infiles, |
| 1082 | namespace=options.namespace) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1083 | success = True |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1084 | print('%s %s' % (isolated_hash[0], os.path.basename(options.isolated))) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1085 | finally: |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 1086 | # If the command failed, delete the .isolated file if it exists. This is |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1087 | # important so no stale swarm job is executed. |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1088 | if not success and os.path.isfile(options.isolated): |
| 1089 | os.remove(options.isolated) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1090 | return int(not success) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1091 | |
| 1092 | |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 1093 | def CMDcheck(parser, args): |
| 1094 | """Checks that all the inputs are present and generates .isolated.""" |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1095 | add_subdir_option(parser) |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 1096 | options, args = parser.parse_args(args) |
| 1097 | if args: |
| 1098 | parser.error('Unsupported argument: %s' % args) |
| 1099 | |
| 1100 | complete_state = load_complete_state( |
| 1101 | options, os.getcwd(), options.subdir, False) |
| 1102 | |
| 1103 | # Nothing is done specifically. Just store the result and state. |
| 1104 | complete_state.save_files() |
| 1105 | return 0 |
| 1106 | |
| 1107 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1108 | def CMDhashtable(parser, args): |
| 1109 | """Creates a .isolated file and stores the contains in a directory. |
| 1110 | |
| 1111 | All the files listed in the .isolated file are put in the directory with their |
| 1112 | sha-1 as their file name. When using an NFS/CIFS server, the files can then be |
| 1113 | shared accross slaves without an isolate server. |
| 1114 | """ |
| 1115 | add_subdir_option(parser) |
| 1116 | add_outdir_option(parser) |
| 1117 | add_skip_refresh_option(parser) |
| 1118 | options, args = parser.parse_args(args) |
| 1119 | if args: |
| 1120 | parser.error('Unsupported argument: %s' % args) |
| 1121 | cwd = os.getcwd() |
| 1122 | process_outdir(parser, options, cwd) |
| 1123 | |
| 1124 | success = False |
| 1125 | try: |
| 1126 | complete_state, infiles, isolated_hash = prepare_for_archival(options, cwd) |
| 1127 | logging.info('Creating content addressed object store with %d item', |
| 1128 | len(infiles)) |
| 1129 | if not os.path.isdir(options.outdir): |
| 1130 | os.makedirs(options.outdir) |
| 1131 | |
| 1132 | # TODO(maruel): Make the files read-only? |
| 1133 | recreate_tree( |
| 1134 | outdir=options.outdir, |
| 1135 | indir=complete_state.root_dir, |
| 1136 | infiles=infiles, |
| 1137 | action=run_isolated.HARDLINK_WITH_FALLBACK, |
| 1138 | as_hash=True) |
| 1139 | success = True |
| 1140 | print('%s %s' % (isolated_hash[0], os.path.basename(options.isolated))) |
| 1141 | finally: |
| 1142 | # If the command failed, delete the .isolated file if it exists. This is |
| 1143 | # important so no stale swarm job is executed. |
| 1144 | if not success and os.path.isfile(options.isolated): |
| 1145 | os.remove(options.isolated) |
| 1146 | return int(not success) |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 1147 | |
| 1148 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1149 | def CMDmerge(parser, args): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1150 | """Reads and merges the data from the trace back into the original .isolate. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1151 | """ |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1152 | parser.require_isolated = False |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1153 | add_trace_option(parser) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 1154 | options, args = parser.parse_args(args) |
| 1155 | if args: |
| 1156 | parser.error('Unsupported argument: %s' % args) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1157 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1158 | complete_state = load_complete_state(options, os.getcwd(), None, False) |
Marc-Antoine Ruel | ac54cb4 | 2013-11-18 14:05:35 -0500 | [diff] [blame] | 1159 | blacklist = tools.gen_blacklist(options.trace_blacklist) |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1160 | merge(complete_state, blacklist) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1161 | return 0 |
| 1162 | |
| 1163 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1164 | def CMDread(parser, args): |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1165 | """Reads the trace file generated with command 'trace'.""" |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1166 | parser.require_isolated = False |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1167 | add_trace_option(parser) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1168 | add_skip_refresh_option(parser) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1169 | parser.add_option( |
| 1170 | '-m', '--merge', action='store_true', |
| 1171 | help='merge the results back in the .isolate file instead of printing') |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 1172 | options, args = parser.parse_args(args) |
| 1173 | if args: |
| 1174 | parser.error('Unsupported argument: %s' % args) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1175 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1176 | complete_state = load_complete_state( |
| 1177 | options, os.getcwd(), None, options.skip_refresh) |
Marc-Antoine Ruel | ac54cb4 | 2013-11-18 14:05:35 -0500 | [diff] [blame] | 1178 | blacklist = tools.gen_blacklist(options.trace_blacklist) |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1179 | value, exceptions = read_trace_as_isolate_dict(complete_state, blacklist) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1180 | if options.merge: |
| 1181 | merge(complete_state, blacklist) |
| 1182 | else: |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1183 | isolate_format.pretty_print(value, sys.stdout) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1184 | |
maruel@chromium.org | e27c65f | 2012-10-22 13:56:53 +0000 | [diff] [blame] | 1185 | if exceptions: |
| 1186 | # It got an exception, raise the first one. |
| 1187 | raise \ |
| 1188 | exceptions[0][0], \ |
| 1189 | exceptions[0][1], \ |
| 1190 | exceptions[0][2] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1191 | return 0 |
| 1192 | |
| 1193 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1194 | def CMDremap(parser, args): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1195 | """Creates a directory with all the dependencies mapped into it. |
| 1196 | |
| 1197 | Useful to test manually why a test is failing. The target executable is not |
| 1198 | run. |
| 1199 | """ |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1200 | parser.require_isolated = False |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1201 | add_outdir_option(parser) |
| 1202 | add_skip_refresh_option(parser) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 1203 | options, args = parser.parse_args(args) |
| 1204 | if args: |
| 1205 | parser.error('Unsupported argument: %s' % args) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1206 | cwd = os.getcwd() |
| 1207 | process_outdir(parser, options, cwd) |
| 1208 | complete_state = load_complete_state(options, cwd, None, options.skip_refresh) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1209 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1210 | if not os.path.isdir(options.outdir): |
| 1211 | os.makedirs(options.outdir) |
| 1212 | print('Remapping into %s' % options.outdir) |
| 1213 | if os.listdir(options.outdir): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1214 | raise ExecutionError('Can\'t remap in a non-empty directory') |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1215 | |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1216 | create_isolate_tree( |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1217 | options.outdir, complete_state.root_dir, complete_state.saved_state.files, |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1218 | complete_state.saved_state.relative_cwd, |
| 1219 | complete_state.saved_state.read_only) |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 1220 | if complete_state.isolated_filepath: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1221 | complete_state.save_files() |
| 1222 | return 0 |
| 1223 | |
| 1224 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1225 | def CMDrewrite(parser, args): |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1226 | """Rewrites a .isolate file into the canonical format.""" |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1227 | parser.require_isolated = False |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1228 | options, args = parser.parse_args(args) |
| 1229 | if args: |
| 1230 | parser.error('Unsupported argument: %s' % args) |
| 1231 | |
| 1232 | if options.isolated: |
| 1233 | # Load the previous state if it was present. Namely, "foo.isolated.state". |
| 1234 | complete_state = CompleteState.load_files(options.isolated) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1235 | isolate = options.isolate or complete_state.saved_state.isolate_filepath |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1236 | else: |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1237 | isolate = options.isolate |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1238 | if not isolate: |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1239 | parser.error('--isolate is required.') |
| 1240 | |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1241 | with open(isolate, 'r') as f: |
| 1242 | content = f.read() |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1243 | config = isolate_format.load_isolate_as_config( |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1244 | os.path.dirname(os.path.abspath(isolate)), |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1245 | isolate_format.eval_content(content), |
| 1246 | isolate_format.extract_comment(content)) |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 1247 | data = config.make_isolate_file() |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1248 | print('Updating %s' % isolate) |
| 1249 | with open(isolate, 'wb') as f: |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1250 | isolate_format.print_all(config.file_comment, data, f) |
maruel@chromium.org | 9f7f6d4 | 2013-02-04 18:31:17 +0000 | [diff] [blame] | 1251 | return 0 |
| 1252 | |
| 1253 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1254 | @subcommand.usage('-- [extra arguments]') |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1255 | def CMDrun(parser, args): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1256 | """Runs the test executable in an isolated (temporary) directory. |
| 1257 | |
| 1258 | All the dependencies are mapped into the temporary directory and the |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1259 | directory is cleaned up after the target exits. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1260 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1261 | Argument processing stops at -- and these arguments are appended to the |
| 1262 | command line of the target to run. For example, use: |
| 1263 | isolate.py run --isolated foo.isolated -- --gtest_filter=Foo.Bar |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1264 | """ |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1265 | parser.require_isolated = False |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1266 | add_skip_refresh_option(parser) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1267 | options, args = parser.parse_args(args) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1268 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1269 | complete_state = load_complete_state( |
| 1270 | options, os.getcwd(), None, options.skip_refresh) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 1271 | cmd = complete_state.saved_state.command + args |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1272 | if not cmd: |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1273 | raise ExecutionError('No command to run.') |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1274 | cmd = tools.fix_python_path(cmd) |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1275 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1276 | outdir = run_isolated.make_temp_dir( |
| 1277 | 'isolate-%s' % datetime.date.today(), |
| 1278 | os.path.dirname(complete_state.root_dir)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1279 | try: |
Marc-Antoine Ruel | 7124e39 | 2014-01-09 11:49:21 -0500 | [diff] [blame] | 1280 | # TODO(maruel): Use run_isolated.run_tha_test(). |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1281 | cwd = create_isolate_tree( |
| 1282 | outdir, complete_state.root_dir, complete_state.saved_state.files, |
| 1283 | complete_state.saved_state.relative_cwd, |
| 1284 | complete_state.saved_state.read_only) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1285 | logging.info('Running %s, cwd=%s' % (cmd, cwd)) |
| 1286 | result = subprocess.call(cmd, cwd=cwd) |
| 1287 | finally: |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1288 | run_isolated.rmtree(outdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1289 | |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 1290 | if complete_state.isolated_filepath: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1291 | complete_state.save_files() |
| 1292 | return result |
| 1293 | |
| 1294 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1295 | @subcommand.usage('-- [extra arguments]') |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1296 | def CMDtrace(parser, args): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1297 | """Traces the target using trace_inputs.py. |
| 1298 | |
| 1299 | It runs the executable without remapping it, and traces all the files it and |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1300 | its child processes access. Then the 'merge' command can be used to generate |
| 1301 | an updated .isolate file out of it or the 'read' command to print it out to |
| 1302 | stdout. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1303 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1304 | Argument processing stops at -- and these arguments are appended to the |
| 1305 | command line of the target to run. For example, use: |
| 1306 | isolate.py trace --isolated foo.isolated -- --gtest_filter=Foo.Bar |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1307 | """ |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1308 | add_trace_option(parser) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1309 | parser.add_option( |
| 1310 | '-m', '--merge', action='store_true', |
| 1311 | help='After tracing, merge the results back in the .isolate file') |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1312 | add_skip_refresh_option(parser) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1313 | options, args = parser.parse_args(args) |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1314 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 1315 | complete_state = load_complete_state( |
| 1316 | options, os.getcwd(), None, options.skip_refresh) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 1317 | cmd = complete_state.saved_state.command + args |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1318 | if not cmd: |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1319 | raise ExecutionError('No command to run.') |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1320 | cmd = tools.fix_python_path(cmd) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1321 | cwd = os.path.normpath(os.path.join( |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 1322 | unicode(complete_state.root_dir), |
| 1323 | complete_state.saved_state.relative_cwd)) |
maruel@chromium.org | 808f6af | 2012-10-11 14:08:08 +0000 | [diff] [blame] | 1324 | cmd[0] = os.path.normpath(os.path.join(cwd, cmd[0])) |
| 1325 | if not os.path.isfile(cmd[0]): |
| 1326 | raise ExecutionError( |
| 1327 | 'Tracing failed for: %s\nIt doesn\'t exit' % ' '.join(cmd)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1328 | logging.info('Running %s, cwd=%s' % (cmd, cwd)) |
| 1329 | api = trace_inputs.get_api() |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 1330 | logfile = complete_state.isolated_filepath + '.log' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1331 | api.clean_trace(logfile) |
maruel@chromium.org | b932214 | 2013-01-22 18:49:46 +0000 | [diff] [blame] | 1332 | out = None |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1333 | try: |
| 1334 | with api.get_tracer(logfile) as tracer: |
maruel@chromium.org | b932214 | 2013-01-22 18:49:46 +0000 | [diff] [blame] | 1335 | result, out = tracer.trace( |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1336 | cmd, |
| 1337 | cwd, |
| 1338 | 'default', |
| 1339 | True) |
| 1340 | except trace_inputs.TracingFailure, e: |
| 1341 | raise ExecutionError('Tracing failed for: %s\n%s' % (' '.join(cmd), str(e))) |
| 1342 | |
csharp@chromium.org | 5ab1ca9 | 2012-10-25 13:37:14 +0000 | [diff] [blame] | 1343 | if result: |
maruel@chromium.org | b932214 | 2013-01-22 18:49:46 +0000 | [diff] [blame] | 1344 | logging.error( |
| 1345 | 'Tracer exited with %d, which means the tests probably failed so the ' |
| 1346 | 'trace is probably incomplete.', result) |
| 1347 | logging.info(out) |
csharp@chromium.org | 5ab1ca9 | 2012-10-25 13:37:14 +0000 | [diff] [blame] | 1348 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1349 | complete_state.save_files() |
| 1350 | |
| 1351 | if options.merge: |
Marc-Antoine Ruel | ac54cb4 | 2013-11-18 14:05:35 -0500 | [diff] [blame] | 1352 | blacklist = tools.gen_blacklist(options.trace_blacklist) |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1353 | merge(complete_state, blacklist) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1354 | |
| 1355 | return result |
| 1356 | |
| 1357 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1358 | def _process_variable_arg(option, opt, _value, parser): |
| 1359 | """Called by OptionParser to process a --<foo>-variable argument.""" |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1360 | if not parser.rargs: |
| 1361 | raise optparse.OptionValueError( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1362 | 'Please use %s FOO=BAR or %s FOO BAR' % (opt, opt)) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1363 | k = parser.rargs.pop(0) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1364 | variables = getattr(parser.values, option.dest) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1365 | if '=' in k: |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1366 | k, v = k.split('=', 1) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1367 | else: |
| 1368 | if not parser.rargs: |
| 1369 | raise optparse.OptionValueError( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1370 | 'Please use %s FOO=BAR or %s FOO BAR' % (opt, opt)) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1371 | v = parser.rargs.pop(0) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1372 | if not re.match('^' + isolate_format.VALID_VARIABLE + '$', k): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1373 | raise optparse.OptionValueError( |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1374 | 'Variable \'%s\' doesn\'t respect format \'%s\'' % |
| 1375 | (k, isolate_format.VALID_VARIABLE)) |
Marc-Antoine Ruel | 9cc42c3 | 2013-12-11 09:35:55 -0500 | [diff] [blame] | 1376 | variables.append((k, v.decode('utf-8'))) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1377 | |
| 1378 | |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1379 | def add_variable_option(parser): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1380 | """Adds --isolated and --<foo>-variable to an OptionParser.""" |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1381 | parser.add_option( |
| 1382 | '-s', '--isolated', |
| 1383 | metavar='FILE', |
| 1384 | help='.isolated file to generate or read') |
| 1385 | # Keep for compatibility. TODO(maruel): Remove once not used anymore. |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1386 | parser.add_option( |
| 1387 | '-r', '--result', |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1388 | dest='isolated', |
| 1389 | help=optparse.SUPPRESS_HELP) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1390 | is_win = sys.platform in ('win32', 'cygwin') |
| 1391 | # There is really 3 kind of variables: |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1392 | # - path variables, like DEPTH or PRODUCT_DIR that should be |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1393 | # replaced opportunistically when tracing tests. |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1394 | # - extraneous things like EXECUTABE_SUFFIX. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1395 | # - configuration variables that are to be used in deducing the matrix to |
| 1396 | # reduce. |
| 1397 | # - unrelated variables that are used as command flags for example. |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1398 | parser.add_option( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1399 | '--config-variable', |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1400 | action='callback', |
| 1401 | callback=_process_variable_arg, |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1402 | default=[('OS', get_flavor())], |
| 1403 | dest='config_variables', |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1404 | metavar='FOO BAR', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1405 | help='Config variables are used to determine which conditions should be ' |
| 1406 | 'matched when loading a .isolate file, default: %default. ' |
| 1407 | 'All 3 kinds of variables are persistent accross calls, they are ' |
| 1408 | 'saved inside <.isolated>.state') |
| 1409 | parser.add_option( |
| 1410 | '--path-variable', |
| 1411 | action='callback', |
| 1412 | callback=_process_variable_arg, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1413 | default=[], |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1414 | dest='path_variables', |
| 1415 | metavar='FOO BAR', |
| 1416 | help='Path variables are used to replace file paths when loading a ' |
| 1417 | '.isolate file, default: %default') |
| 1418 | parser.add_option( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1419 | '--extra-variable', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1420 | action='callback', |
| 1421 | callback=_process_variable_arg, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1422 | default=[('EXECUTABLE_SUFFIX', '.exe' if is_win else '')], |
| 1423 | dest='extra_variables', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1424 | metavar='FOO BAR', |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1425 | help='Extraneous variables are replaced on the \'command\' entry and on ' |
| 1426 | 'paths in the .isolate file but are not considered relative paths.') |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1427 | |
| 1428 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1429 | def add_subdir_option(parser): |
| 1430 | parser.add_option( |
| 1431 | '--subdir', |
| 1432 | help='Filters to a subdirectory. Its behavior changes depending if it ' |
| 1433 | 'is a relative path as a string or as a path variable. Path ' |
| 1434 | 'variables are always keyed from the directory containing the ' |
| 1435 | '.isolate file. Anything else is keyed on the root directory.') |
| 1436 | |
| 1437 | |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1438 | def add_trace_option(parser): |
| 1439 | """Adds --trace-blacklist to the parser.""" |
| 1440 | parser.add_option( |
| 1441 | '--trace-blacklist', |
Marc-Antoine Ruel | ac54cb4 | 2013-11-18 14:05:35 -0500 | [diff] [blame] | 1442 | action='append', default=list(isolateserver.DEFAULT_BLACKLIST), |
maruel@chromium.org | 3683afe | 2013-07-27 00:09:27 +0000 | [diff] [blame] | 1443 | help='List of regexp to use as blacklist filter for files to consider ' |
| 1444 | 'important, not to be confused with --blacklist which blacklists ' |
| 1445 | 'test case.') |
| 1446 | |
| 1447 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame^] | 1448 | def add_outdir_option(parser): |
| 1449 | parser.add_option( |
| 1450 | '-o', '--outdir', metavar='DIR', |
| 1451 | help='Directory used to recreate the tree.') |
| 1452 | |
| 1453 | |
| 1454 | def add_skip_refresh_option(parser): |
| 1455 | parser.add_option( |
| 1456 | '--skip-refresh', action='store_true', |
| 1457 | help='Skip reading .isolate file and do not refresh the hash of ' |
| 1458 | 'dependencies') |
| 1459 | |
| 1460 | def process_outdir(parser, options, cwd): |
| 1461 | if not options.outdir: |
| 1462 | parser.error('--outdir is required.') |
| 1463 | if file_path.is_url(options.outdir): |
| 1464 | parser.error('Can\'t use an URL for --outdir with mode remap.') |
| 1465 | options.outdir = unicode(options.outdir).replace('/', os.path.sep) |
| 1466 | # outdir doesn't need native path case since tracing is never done from there. |
| 1467 | options.outdir = os.path.abspath( |
| 1468 | os.path.normpath(os.path.join(cwd, options.outdir))) |
| 1469 | # In theory, we'd create the directory outdir right away. Defer doing it in |
| 1470 | # case there's errors in the command line. |
| 1471 | |
| 1472 | |
maruel@chromium.org | 715e3fb | 2013-03-19 15:44:06 +0000 | [diff] [blame] | 1473 | def parse_isolated_option(parser, options, cwd, require_isolated): |
| 1474 | """Processes --isolated.""" |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1475 | if options.isolated: |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 1476 | options.isolated = os.path.normpath( |
| 1477 | os.path.join(cwd, options.isolated.replace('/', os.path.sep))) |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1478 | if require_isolated and not options.isolated: |
maruel@chromium.org | 75c05b4 | 2013-07-25 15:51:48 +0000 | [diff] [blame] | 1479 | parser.error('--isolated is required.') |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1480 | if options.isolated and not options.isolated.endswith('.isolated'): |
| 1481 | parser.error('--isolated value must end with \'.isolated\'') |
maruel@chromium.org | 715e3fb | 2013-03-19 15:44:06 +0000 | [diff] [blame] | 1482 | |
| 1483 | |
| 1484 | def parse_variable_option(options): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1485 | """Processes all the --<foo>-variable flags.""" |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 1486 | # TODO(benrg): Maybe we should use a copy of gyp's NameValueListToDict here, |
| 1487 | # but it wouldn't be backward compatible. |
| 1488 | def try_make_int(s): |
maruel@chromium.org | e83215b | 2013-02-21 14:16:59 +0000 | [diff] [blame] | 1489 | """Converts a value to int if possible, converts to unicode otherwise.""" |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 1490 | try: |
| 1491 | return int(s) |
| 1492 | except ValueError: |
maruel@chromium.org | e83215b | 2013-02-21 14:16:59 +0000 | [diff] [blame] | 1493 | return s.decode('utf-8') |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1494 | options.config_variables = dict( |
| 1495 | (k, try_make_int(v)) for k, v in options.config_variables) |
| 1496 | options.path_variables = dict(options.path_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1497 | options.extra_variables = dict(options.extra_variables) |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1498 | |
| 1499 | |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1500 | class OptionParserIsolate(tools.OptionParserWithLogging): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1501 | """Adds automatic --isolate, --isolated, --out and --<foo>-variable handling. |
| 1502 | """ |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1503 | # Set it to False if it is not required, e.g. it can be passed on but do not |
| 1504 | # fail if not given. |
| 1505 | require_isolated = True |
| 1506 | |
| 1507 | def __init__(self, **kwargs): |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1508 | tools.OptionParserWithLogging.__init__( |
maruel@chromium.org | 5527690 | 2012-10-05 20:56:19 +0000 | [diff] [blame] | 1509 | self, |
| 1510 | verbose=int(os.environ.get('ISOLATE_DEBUG', 0)), |
| 1511 | **kwargs) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1512 | group = optparse.OptionGroup(self, "Common options") |
| 1513 | group.add_option( |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1514 | '-i', '--isolate', |
| 1515 | metavar='FILE', |
| 1516 | help='.isolate file to load the dependency data from') |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1517 | add_variable_option(group) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1518 | group.add_option( |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 1519 | '--ignore_broken_items', action='store_true', |
maruel@chromium.org | f347c3a | 2012-12-11 19:03:28 +0000 | [diff] [blame] | 1520 | default=bool(os.environ.get('ISOLATE_IGNORE_BROKEN_ITEMS')), |
| 1521 | help='Indicates that invalid entries in the isolated file to be ' |
| 1522 | 'only be logged and not stop processing. Defaults to True if ' |
| 1523 | 'env var ISOLATE_IGNORE_BROKEN_ITEMS is set') |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1524 | self.add_option_group(group) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1525 | |
| 1526 | def parse_args(self, *args, **kwargs): |
| 1527 | """Makes sure the paths make sense. |
| 1528 | |
| 1529 | On Windows, / and \ are often mixed together in a path. |
| 1530 | """ |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1531 | options, args = tools.OptionParserWithLogging.parse_args( |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1532 | self, *args, **kwargs) |
| 1533 | if not self.allow_interspersed_args and args: |
| 1534 | self.error('Unsupported argument: %s' % args) |
| 1535 | |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 1536 | cwd = file_path.get_native_path_case(unicode(os.getcwd())) |
maruel@chromium.org | 715e3fb | 2013-03-19 15:44:06 +0000 | [diff] [blame] | 1537 | parse_isolated_option(self, options, cwd, self.require_isolated) |
| 1538 | parse_variable_option(options) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1539 | |
| 1540 | if options.isolate: |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 1541 | # TODO(maruel): Work with non-ASCII. |
| 1542 | # The path must be in native path case for tracing purposes. |
| 1543 | options.isolate = unicode(options.isolate).replace('/', os.path.sep) |
| 1544 | options.isolate = os.path.normpath(os.path.join(cwd, options.isolate)) |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 1545 | options.isolate = file_path.get_native_path_case(options.isolate) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1546 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1547 | return options, args |
| 1548 | |
| 1549 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1550 | def main(argv): |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1551 | dispatcher = subcommand.CommandDispatcher(__name__) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1552 | try: |
maruel@chromium.org | 3d67199 | 2013-08-20 00:38:27 +0000 | [diff] [blame] | 1553 | return dispatcher.execute(OptionParserIsolate(version=__version__), argv) |
vadimsh@chromium.org | d908a54 | 2013-10-30 01:36:17 +0000 | [diff] [blame] | 1554 | except Exception as e: |
| 1555 | tools.report_error(e) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1556 | return 1 |
| 1557 | |
| 1558 | |
| 1559 | if __name__ == '__main__': |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1560 | fix_encoding.fix_encoding() |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1561 | tools.disable_buffering() |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1562 | colorama.init() |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1563 | sys.exit(main(sys.argv[1:])) |