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