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