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 | 1f8ba35 | 2014-11-04 15:55:03 -0500 | [diff] [blame] | 16 | __version__ = '0.4.3' |
Marc-Antoine Ruel | cfb6085 | 2014-07-02 15:22:00 -0400 | [diff] [blame] | 17 | |
Marc-Antoine Ruel | 9dfdcc2 | 2014-01-08 14:14:18 -0500 | [diff] [blame] | 18 | import datetime |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 19 | import itertools |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 20 | import logging |
| 21 | import optparse |
| 22 | import os |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 23 | import re |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 24 | import subprocess |
| 25 | import sys |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 26 | |
Vadim Shtayura | e34e13a | 2014-02-02 11:23:26 -0800 | [diff] [blame] | 27 | import auth |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 28 | import isolate_format |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 29 | import isolated_format |
maruel@chromium.org | fb78d43 | 2013-08-28 21:22:40 +0000 | [diff] [blame] | 30 | import isolateserver |
maruel@chromium.org | b8375c2 | 2012-10-05 18:10:01 +0000 | [diff] [blame] | 31 | import run_isolated |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 32 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 33 | from third_party import colorama |
| 34 | from third_party.depot_tools import fix_encoding |
| 35 | from third_party.depot_tools import subcommand |
| 36 | |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 37 | from utils import file_path |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 38 | from utils import tools |
| 39 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 40 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 41 | # Exit code of 'archive' and 'batcharchive' if the command fails due to an error |
| 42 | # in *.isolate file (format error, or some referenced files are missing, etc.) |
| 43 | EXIT_CODE_ISOLATE_ERROR = 1 |
| 44 | |
| 45 | |
| 46 | # Exit code of 'archive' and 'batcharchive' if the command fails due to |
| 47 | # a network or server issue. It is an infrastructure failure. |
| 48 | EXIT_CODE_UPLOAD_ERROR = 101 |
| 49 | |
| 50 | |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 51 | # Supported version of *.isolated.gen.json files consumed by CMDbatcharchive. |
| 52 | ISOLATED_GEN_JSON_VERSION = 1 |
| 53 | |
| 54 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 55 | class ExecutionError(Exception): |
| 56 | """A generic error occurred.""" |
| 57 | def __str__(self): |
| 58 | return self.args[0] |
| 59 | |
| 60 | |
| 61 | ### Path handling code. |
| 62 | |
| 63 | |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 64 | def recreate_tree(outdir, indir, infiles, action, as_hash): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 65 | """Creates a new tree with only the input files in it. |
| 66 | |
| 67 | Arguments: |
| 68 | outdir: Output directory to create the files in. |
| 69 | indir: Root directory the infiles are based in. |
| 70 | infiles: dict of files to map from |indir| to |outdir|. |
Marc-Antoine Ruel | e4ad07e | 2014-10-15 20:22:29 -0400 | [diff] [blame] | 71 | action: One of accepted action of file_path.link_file(). |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 72 | as_hash: Output filename is the hash instead of relfile. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 73 | """ |
| 74 | logging.info( |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 75 | 'recreate_tree(outdir=%s, indir=%s, files=%d, action=%s, as_hash=%s)' % |
| 76 | (outdir, indir, len(infiles), action, as_hash)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 77 | |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 78 | 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] | 79 | if not os.path.isdir(outdir): |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 80 | logging.info('Creating %s' % outdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 81 | os.makedirs(outdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 82 | |
| 83 | for relfile, metadata in infiles.iteritems(): |
| 84 | infile = os.path.join(indir, relfile) |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 85 | if as_hash: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 86 | # Do the hashtable specific checks. |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 87 | if 'l' in metadata: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 88 | # Skip links when storing a hashtable. |
| 89 | continue |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 90 | outfile = os.path.join(outdir, metadata['h']) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 91 | if os.path.isfile(outfile): |
| 92 | # Just do a quick check that the file size matches. No need to stat() |
| 93 | # again the input file, grab the value from the dict. |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 94 | if not 's' in metadata: |
Marc-Antoine Ruel | 1e7658c | 2014-08-28 19:46:39 -0400 | [diff] [blame] | 95 | raise isolated_format.MappingError( |
maruel@chromium.org | 861a5e7 | 2012-10-09 14:49:42 +0000 | [diff] [blame] | 96 | 'Misconfigured item %s: %s' % (relfile, metadata)) |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 97 | if metadata['s'] == os.stat(outfile).st_size: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 98 | continue |
| 99 | else: |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 100 | logging.warn('Overwritting %s' % metadata['h']) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 101 | os.remove(outfile) |
| 102 | else: |
| 103 | outfile = os.path.join(outdir, relfile) |
| 104 | outsubdir = os.path.dirname(outfile) |
| 105 | if not os.path.isdir(outsubdir): |
| 106 | os.makedirs(outsubdir) |
| 107 | |
maruel@chromium.org | e5c1713 | 2012-11-21 18:18:46 +0000 | [diff] [blame] | 108 | if 'l' in metadata: |
| 109 | pointed = metadata['l'] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 110 | logging.debug('Symlink: %s -> %s' % (outfile, pointed)) |
maruel@chromium.org | f43e68b | 2012-10-15 20:23:10 +0000 | [diff] [blame] | 111 | # symlink doesn't exist on Windows. |
| 112 | os.symlink(pointed, outfile) # pylint: disable=E1101 |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 113 | else: |
Marc-Antoine Ruel | e4ad07e | 2014-10-15 20:22:29 -0400 | [diff] [blame] | 114 | file_path.link_file(outfile, infile, action) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 115 | |
| 116 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 117 | ### Variable stuff. |
| 118 | |
| 119 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 120 | def _normalize_path_variable(cwd, relative_base_dir, key, value): |
| 121 | """Normalizes a path variable into a relative directory. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 122 | """ |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 123 | # Variables could contain / or \ on windows. Always normalize to |
| 124 | # os.path.sep. |
| 125 | x = os.path.join(cwd, value.strip().replace('/', os.path.sep)) |
| 126 | normalized = file_path.get_native_path_case(os.path.normpath(x)) |
| 127 | if not os.path.isdir(normalized): |
| 128 | raise ExecutionError('%s=%s is not a directory' % (key, normalized)) |
| 129 | |
| 130 | # All variables are relative to the .isolate file. |
| 131 | normalized = os.path.relpath(normalized, relative_base_dir) |
| 132 | logging.debug( |
| 133 | 'Translated variable %s from %s to %s', key, value, normalized) |
| 134 | return normalized |
| 135 | |
| 136 | |
| 137 | def normalize_path_variables(cwd, path_variables, relative_base_dir): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 138 | """Processes path variables as a special case and returns a copy of the dict. |
| 139 | |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 140 | For each 'path' variable: first normalizes it based on |cwd|, verifies it |
| 141 | exists then sets it as relative to relative_base_dir. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 142 | """ |
| 143 | logging.info( |
| 144 | 'normalize_path_variables(%s, %s, %s)', cwd, path_variables, |
| 145 | relative_base_dir) |
Marc-Antoine Ruel | 9cc42c3 | 2013-12-11 09:35:55 -0500 | [diff] [blame] | 146 | assert isinstance(cwd, unicode), cwd |
| 147 | assert isinstance(relative_base_dir, unicode), relative_base_dir |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 148 | relative_base_dir = file_path.get_native_path_case(relative_base_dir) |
| 149 | return dict( |
| 150 | (k, _normalize_path_variable(cwd, relative_base_dir, k, v)) |
| 151 | for k, v in path_variables.iteritems()) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 152 | |
| 153 | |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 154 | ### Internal state files. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 155 | |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 156 | |
| 157 | def isolatedfile_to_state(filename): |
| 158 | """For a '.isolate' file, returns the path to the saved '.state' file.""" |
| 159 | return filename + '.state' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 160 | |
| 161 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 162 | def chromium_save_isolated(isolated, data, path_variables, algo): |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 163 | """Writes one or many .isolated files. |
| 164 | |
| 165 | This slightly increases the cold cache cost but greatly reduce the warm cache |
| 166 | cost by splitting low-churn files off the master .isolated file. It also |
| 167 | reduces overall isolateserver memcache consumption. |
| 168 | """ |
| 169 | slaves = [] |
| 170 | |
| 171 | def extract_into_included_isolated(prefix): |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 172 | new_slave = { |
| 173 | 'algo': data['algo'], |
| 174 | 'files': {}, |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 175 | 'version': data['version'], |
| 176 | } |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 177 | for f in data['files'].keys(): |
| 178 | if f.startswith(prefix): |
| 179 | new_slave['files'][f] = data['files'].pop(f) |
| 180 | if new_slave['files']: |
| 181 | slaves.append(new_slave) |
| 182 | |
| 183 | # Split test/data/ in its own .isolated file. |
| 184 | extract_into_included_isolated(os.path.join('test', 'data', '')) |
| 185 | |
| 186 | # Split everything out of PRODUCT_DIR in its own .isolated file. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 187 | if path_variables.get('PRODUCT_DIR'): |
| 188 | extract_into_included_isolated(path_variables['PRODUCT_DIR']) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 189 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 190 | files = [] |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 191 | for index, f in enumerate(slaves): |
| 192 | slavepath = isolated[:-len('.isolated')] + '.%d.isolated' % index |
Marc-Antoine Ruel | de01180 | 2013-11-12 15:19:47 -0500 | [diff] [blame] | 193 | tools.write_json(slavepath, f, True) |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 194 | data.setdefault('includes', []).append( |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 195 | isolated_format.hash_file(slavepath, algo)) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 196 | files.append(os.path.basename(slavepath)) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 197 | |
Marc-Antoine Ruel | 52436aa | 2014-08-28 21:57:57 -0400 | [diff] [blame] | 198 | files.extend(isolated_format.save_isolated(isolated, data)) |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 199 | return files |
| 200 | |
| 201 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 202 | class Flattenable(object): |
| 203 | """Represents data that can be represented as a json file.""" |
| 204 | MEMBERS = () |
| 205 | |
| 206 | def flatten(self): |
| 207 | """Returns a json-serializable version of itself. |
| 208 | |
| 209 | Skips None entries. |
| 210 | """ |
| 211 | items = ((member, getattr(self, member)) for member in self.MEMBERS) |
| 212 | return dict((member, value) for member, value in items if value is not None) |
| 213 | |
| 214 | @classmethod |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 215 | def load(cls, data, *args, **kwargs): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 216 | """Loads a flattened version.""" |
| 217 | data = data.copy() |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 218 | out = cls(*args, **kwargs) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 219 | for member in out.MEMBERS: |
| 220 | if member in data: |
| 221 | # Access to a protected member XXX of a client class |
| 222 | # pylint: disable=W0212 |
| 223 | out._load_member(member, data.pop(member)) |
| 224 | if data: |
| 225 | raise ValueError( |
| 226 | 'Found unexpected entry %s while constructing an object %s' % |
| 227 | (data, cls.__name__), data, cls.__name__) |
| 228 | return out |
| 229 | |
| 230 | def _load_member(self, member, value): |
| 231 | """Loads a member into self.""" |
| 232 | setattr(self, member, value) |
| 233 | |
| 234 | @classmethod |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 235 | def load_file(cls, filename, *args, **kwargs): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 236 | """Loads the data from a file or return an empty instance.""" |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 237 | try: |
Marc-Antoine Ruel | de01180 | 2013-11-12 15:19:47 -0500 | [diff] [blame] | 238 | out = cls.load(tools.read_json(filename), *args, **kwargs) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 239 | logging.debug('Loaded %s(%s)', cls.__name__, filename) |
maruel@chromium.org | e9403ab | 2013-09-20 18:03:49 +0000 | [diff] [blame] | 240 | except (IOError, ValueError) as e: |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 241 | # On failure, loads the default instance. |
| 242 | out = cls(*args, **kwargs) |
maruel@chromium.org | e9403ab | 2013-09-20 18:03:49 +0000 | [diff] [blame] | 243 | logging.warn('Failed to load %s: %s', filename, e) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 244 | return out |
| 245 | |
| 246 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 247 | class SavedState(Flattenable): |
| 248 | """Describes the content of a .state file. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 249 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 250 | This file caches the items calculated by this script and is used to increase |
| 251 | the performance of the script. This file is not loaded by run_isolated.py. |
| 252 | This file can always be safely removed. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 253 | |
| 254 | It is important to note that the 'files' dict keys are using native OS path |
| 255 | separator instead of '/' used in .isolate file. |
| 256 | """ |
| 257 | MEMBERS = ( |
Marc-Antoine Ruel | 0519946 | 2014-03-13 15:40:48 -0400 | [diff] [blame] | 258 | # Value of sys.platform so that the file is rejected if loaded from a |
| 259 | # different OS. While this should never happen in practice, users are ... |
| 260 | # "creative". |
| 261 | 'OS', |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 262 | # Algorithm used to generate the hash. The only supported value is at the |
| 263 | # time of writting 'sha-1'. |
| 264 | 'algo', |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 265 | # List of included .isolated files. Used to support/remember 'slave' |
| 266 | # .isolated files. Relative path to isolated_basedir. |
| 267 | 'child_isolated_files', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 268 | # Cache of the processed command. This value is saved because .isolated |
| 269 | # files are never loaded by isolate.py so it's the only way to load the |
| 270 | # command safely. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 271 | 'command', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 272 | # GYP variables that are used to generate conditions. The most frequent |
| 273 | # example is 'OS'. |
| 274 | 'config_variables', |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 275 | # GYP variables that will be replaced in 'command' and paths but will not be |
| 276 | # considered a relative directory. |
| 277 | 'extra_variables', |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 278 | # 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] | 279 | 'files', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 280 | # Path of the original .isolate file. Relative path to isolated_basedir. |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 281 | 'isolate_file', |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 282 | # GYP variables used to generate the .isolated files paths based on path |
| 283 | # variables. Frequent examples are DEPTH and PRODUCT_DIR. |
| 284 | 'path_variables', |
Marc-Antoine Ruel | 33d442a | 2014-10-03 14:41:51 -0400 | [diff] [blame] | 285 | # If the generated directory tree should be read-only. Defaults to 1. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 286 | 'read_only', |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 287 | # Relative cwd to use to start the command. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 288 | 'relative_cwd', |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 289 | # Root directory the files are mapped from. |
| 290 | 'root_dir', |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 291 | # Version of the saved state file format. Any breaking change must update |
| 292 | # the value. |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 293 | 'version', |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 294 | ) |
| 295 | |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 296 | # Bump this version whenever the saved state changes. It is also keyed on the |
| 297 | # .isolated file version so any change in the generator will invalidate .state |
| 298 | # files. |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 299 | EXPECTED_VERSION = isolated_format.ISOLATED_FILE_VERSION + '.2' |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 300 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 301 | def __init__(self, isolated_basedir): |
| 302 | """Creates an empty SavedState. |
| 303 | |
Marc-Antoine Ruel | 8472efa | 2014-03-18 14:32:50 -0400 | [diff] [blame] | 304 | Arguments: |
| 305 | isolated_basedir: the directory where the .isolated and .isolated.state |
| 306 | files are saved. |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 307 | """ |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 308 | super(SavedState, self).__init__() |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 309 | assert os.path.isabs(isolated_basedir), isolated_basedir |
| 310 | assert os.path.isdir(isolated_basedir), isolated_basedir |
| 311 | self.isolated_basedir = isolated_basedir |
| 312 | |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 313 | # The default algorithm used. |
Marc-Antoine Ruel | 0519946 | 2014-03-13 15:40:48 -0400 | [diff] [blame] | 314 | self.OS = sys.platform |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 315 | self.algo = isolated_format.SUPPORTED_ALGOS['sha-1'] |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 316 | self.child_isolated_files = [] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 317 | self.command = [] |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 318 | self.config_variables = {} |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 319 | self.extra_variables = {} |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 320 | self.files = {} |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 321 | self.isolate_file = None |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 322 | self.path_variables = {} |
Marc-Antoine Ruel | 33d442a | 2014-10-03 14:41:51 -0400 | [diff] [blame] | 323 | # Defaults to 1 when compiling to .isolated. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 324 | self.read_only = None |
| 325 | self.relative_cwd = None |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 326 | self.root_dir = None |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 327 | self.version = self.EXPECTED_VERSION |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 328 | |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 329 | def update_config(self, config_variables): |
| 330 | """Updates the saved state with only config variables.""" |
| 331 | self.config_variables.update(config_variables) |
| 332 | |
| 333 | def update(self, isolate_file, path_variables, extra_variables): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 334 | """Updates the saved state with new data to keep GYP variables and internal |
| 335 | reference to the original .isolate file. |
| 336 | """ |
maruel@chromium.org | e99c151 | 2013-04-09 20:24:11 +0000 | [diff] [blame] | 337 | assert os.path.isabs(isolate_file) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 338 | # Convert back to a relative path. On Windows, if the isolate and |
| 339 | # isolated files are on different drives, isolate_file will stay an absolute |
| 340 | # path. |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 341 | isolate_file = file_path.safe_relpath(isolate_file, self.isolated_basedir) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 342 | |
| 343 | # The same .isolate file should always be used to generate the .isolated and |
| 344 | # .isolated.state. |
| 345 | assert isolate_file == self.isolate_file or not self.isolate_file, ( |
| 346 | isolate_file, self.isolate_file) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 347 | self.extra_variables.update(extra_variables) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 348 | self.isolate_file = isolate_file |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 349 | self.path_variables.update(path_variables) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 350 | |
Marc-Antoine Ruel | 6b1084e | 2014-09-30 15:14:58 -0400 | [diff] [blame] | 351 | def update_isolated(self, command, infiles, read_only, relative_cwd): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 352 | """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] | 353 | |
maruel@chromium.org | 7b844a6 | 2013-09-17 13:04:59 +0000 | [diff] [blame] | 354 | 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] | 355 | not calculated here. |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 356 | """ |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 357 | self.command = command |
| 358 | # Add new files. |
| 359 | for f in infiles: |
| 360 | self.files.setdefault(f, {}) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 361 | # Prune extraneous files that are not a dependency anymore. |
Marc-Antoine Ruel | 6b1084e | 2014-09-30 15:14:58 -0400 | [diff] [blame] | 362 | for f in set(self.files).difference(set(infiles)): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 363 | del self.files[f] |
| 364 | if read_only is not None: |
| 365 | self.read_only = read_only |
| 366 | self.relative_cwd = relative_cwd |
| 367 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 368 | def to_isolated(self): |
| 369 | """Creates a .isolated dictionary out of the saved state. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 370 | |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 371 | https://code.google.com/p/swarming/wiki/IsolatedDesign |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 372 | """ |
| 373 | def strip(data): |
| 374 | """Returns a 'files' entry with only the whitelisted keys.""" |
| 375 | return dict((k, data[k]) for k in ('h', 'l', 'm', 's') if k in data) |
| 376 | |
| 377 | out = { |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 378 | 'algo': isolated_format.SUPPORTED_ALGOS_REVERSE[self.algo], |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 379 | 'files': dict( |
| 380 | (filepath, strip(data)) for filepath, data in self.files.iteritems()), |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 381 | # The version of the .state file is different than the one of the |
| 382 | # .isolated file. |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 383 | 'version': isolated_format.ISOLATED_FILE_VERSION, |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 384 | } |
| 385 | if self.command: |
| 386 | out['command'] = self.command |
Marc-Antoine Ruel | 33d442a | 2014-10-03 14:41:51 -0400 | [diff] [blame] | 387 | out['read_only'] = self.read_only if self.read_only is not None else 1 |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 388 | if self.relative_cwd: |
| 389 | out['relative_cwd'] = self.relative_cwd |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 390 | return out |
| 391 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 392 | @property |
| 393 | def isolate_filepath(self): |
| 394 | """Returns the absolute path of self.isolate_file.""" |
| 395 | return os.path.normpath( |
| 396 | os.path.join(self.isolated_basedir, self.isolate_file)) |
| 397 | |
| 398 | # Arguments number differs from overridden method |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 399 | @classmethod |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 400 | def load(cls, data, isolated_basedir): # pylint: disable=W0221 |
| 401 | """Special case loading to disallow different OS. |
| 402 | |
| 403 | It is not possible to load a .isolated.state files from a different OS, this |
| 404 | file is saved in OS-specific format. |
| 405 | """ |
| 406 | out = super(SavedState, cls).load(data, isolated_basedir) |
Marc-Antoine Ruel | 0519946 | 2014-03-13 15:40:48 -0400 | [diff] [blame] | 407 | if data.get('OS') != sys.platform: |
Marc-Antoine Ruel | 1e7658c | 2014-08-28 19:46:39 -0400 | [diff] [blame] | 408 | raise isolated_format.IsolatedError('Unexpected OS %s', data.get('OS')) |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 409 | |
| 410 | # Converts human readable form back into the proper class type. |
Marc-Antoine Ruel | 0519946 | 2014-03-13 15:40:48 -0400 | [diff] [blame] | 411 | algo = data.get('algo') |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 412 | if not algo in isolated_format.SUPPORTED_ALGOS: |
Marc-Antoine Ruel | 1e7658c | 2014-08-28 19:46:39 -0400 | [diff] [blame] | 413 | raise isolated_format.IsolatedError('Unknown algo \'%s\'' % out.algo) |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 414 | out.algo = isolated_format.SUPPORTED_ALGOS[algo] |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 415 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 416 | # Refuse the load non-exact version, even minor difference. This is unlike |
| 417 | # isolateserver.load_isolated(). This is because .isolated.state could have |
| 418 | # changed significantly even in minor version difference. |
Marc-Antoine Ruel | 226ab80 | 2014-03-29 16:22:36 -0400 | [diff] [blame] | 419 | if out.version != cls.EXPECTED_VERSION: |
Marc-Antoine Ruel | 1e7658c | 2014-08-28 19:46:39 -0400 | [diff] [blame] | 420 | raise isolated_format.IsolatedError( |
maruel@chromium.org | 999a1fd | 2013-09-20 17:41:07 +0000 | [diff] [blame] | 421 | 'Unsupported version \'%s\'' % out.version) |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 422 | |
Marc-Antoine Ruel | 16ebc2e | 2014-02-13 15:39:15 -0500 | [diff] [blame] | 423 | # The .isolate file must be valid. If it is not present anymore, zap the |
| 424 | # value as if it was not noted, so .isolate_file can safely be overriden |
| 425 | # later. |
| 426 | if out.isolate_file and not os.path.isfile(out.isolate_filepath): |
| 427 | out.isolate_file = None |
| 428 | if out.isolate_file: |
| 429 | # It could be absolute on Windows if the drive containing the .isolate and |
| 430 | # the drive containing the .isolated files differ, .e.g .isolate is on |
| 431 | # C:\\ and .isolated is on D:\\ . |
| 432 | assert not os.path.isabs(out.isolate_file) or sys.platform == 'win32' |
| 433 | assert os.path.isfile(out.isolate_filepath), out.isolate_filepath |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 434 | return out |
| 435 | |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 436 | def flatten(self): |
| 437 | """Makes sure 'algo' is in human readable form.""" |
| 438 | out = super(SavedState, self).flatten() |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 439 | out['algo'] = isolated_format.SUPPORTED_ALGOS_REVERSE[out['algo']] |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 440 | return out |
| 441 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 442 | def __str__(self): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 443 | def dict_to_str(d): |
| 444 | return ''.join('\n %s=%s' % (k, d[k]) for k in sorted(d)) |
| 445 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 446 | out = '%s(\n' % self.__class__.__name__ |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 447 | out += ' command: %s\n' % self.command |
| 448 | out += ' files: %d\n' % len(self.files) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 449 | out += ' isolate_file: %s\n' % self.isolate_file |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 450 | out += ' read_only: %s\n' % self.read_only |
maruel@chromium.org | 9e9ceaa | 2013-04-05 15:42:42 +0000 | [diff] [blame] | 451 | out += ' relative_cwd: %s\n' % self.relative_cwd |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 452 | out += ' child_isolated_files: %s\n' % self.child_isolated_files |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 453 | out += ' path_variables: %s\n' % dict_to_str(self.path_variables) |
| 454 | out += ' config_variables: %s\n' % dict_to_str(self.config_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 455 | out += ' extra_variables: %s\n' % dict_to_str(self.extra_variables) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 456 | return out |
| 457 | |
| 458 | |
| 459 | class CompleteState(object): |
| 460 | """Contains all the state to run the task at hand.""" |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 461 | def __init__(self, isolated_filepath, saved_state): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 462 | super(CompleteState, self).__init__() |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 463 | assert isolated_filepath is None or os.path.isabs(isolated_filepath) |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 464 | self.isolated_filepath = isolated_filepath |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 465 | # Contains the data to ease developer's use-case but that is not strictly |
| 466 | # necessary. |
| 467 | self.saved_state = saved_state |
| 468 | |
| 469 | @classmethod |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 470 | def load_files(cls, isolated_filepath): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 471 | """Loads state from disk.""" |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 472 | assert os.path.isabs(isolated_filepath), isolated_filepath |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 473 | isolated_basedir = os.path.dirname(isolated_filepath) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 474 | return cls( |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 475 | isolated_filepath, |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 476 | SavedState.load_file( |
| 477 | isolatedfile_to_state(isolated_filepath), isolated_basedir)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 478 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 479 | def load_isolate( |
| 480 | self, cwd, isolate_file, path_variables, config_variables, |
Marc-Antoine Ruel | 1f8ba35 | 2014-11-04 15:55:03 -0500 | [diff] [blame] | 481 | extra_variables, blacklist, ignore_broken_items): |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 482 | """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] | 483 | .isolate file. |
| 484 | |
| 485 | Processes the loaded data, deduce root_dir, relative_cwd. |
| 486 | """ |
| 487 | # Make sure to not depend on os.getcwd(). |
| 488 | assert os.path.isabs(isolate_file), isolate_file |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 489 | isolate_file = file_path.get_native_path_case(isolate_file) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 490 | logging.info( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 491 | 'CompleteState.load_isolate(%s, %s, %s, %s, %s, %s)', |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 492 | cwd, isolate_file, path_variables, config_variables, extra_variables, |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 493 | ignore_broken_items) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 494 | |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 495 | # Config variables are not affected by the paths and must be used to |
| 496 | # retrieve the paths, so update them first. |
| 497 | self.saved_state.update_config(config_variables) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 498 | |
| 499 | with open(isolate_file, 'r') as f: |
| 500 | # At that point, variables are not replaced yet in command and infiles. |
| 501 | # infiles may contain directory entries and is in posix style. |
Marc-Antoine Ruel | 6b1084e | 2014-09-30 15:14:58 -0400 | [diff] [blame] | 502 | command, infiles, read_only, isolate_cmd_dir = ( |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 503 | isolate_format.load_isolate_for_config( |
| 504 | os.path.dirname(isolate_file), f.read(), |
| 505 | self.saved_state.config_variables)) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 506 | |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 507 | # Processes the variables with the new found relative root. Note that 'cwd' |
| 508 | # is used when path variables are used. |
| 509 | path_variables = normalize_path_variables( |
| 510 | cwd, path_variables, isolate_cmd_dir) |
| 511 | # Update the rest of the saved state. |
| 512 | self.saved_state.update(isolate_file, path_variables, extra_variables) |
| 513 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 514 | total_variables = self.saved_state.path_variables.copy() |
| 515 | total_variables.update(self.saved_state.config_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 516 | total_variables.update(self.saved_state.extra_variables) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 517 | command = [ |
| 518 | isolate_format.eval_variables(i, total_variables) for i in command |
| 519 | ] |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 520 | |
| 521 | total_variables = self.saved_state.path_variables.copy() |
| 522 | total_variables.update(self.saved_state.extra_variables) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 523 | infiles = [ |
| 524 | isolate_format.eval_variables(f, total_variables) for f in infiles |
| 525 | ] |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 526 | # 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] | 527 | # form '../../foo/bar'. Note that path variables must be taken in account |
| 528 | # too, add them as if they were input files. |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 529 | self.saved_state.root_dir = isolate_format.determine_root_dir( |
Marc-Antoine Ruel | 6b1084e | 2014-09-30 15:14:58 -0400 | [diff] [blame] | 530 | isolate_cmd_dir, infiles + self.saved_state.path_variables.values()) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 531 | # The relative directory is automatically determined by the relative path |
| 532 | # between root_dir and the directory containing the .isolate file, |
| 533 | # isolate_base_dir. |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 534 | relative_cwd = os.path.relpath(isolate_cmd_dir, self.saved_state.root_dir) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 535 | # 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] | 536 | # inside it. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 537 | for k, v in self.saved_state.path_variables.iteritems(): |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 538 | dest = os.path.join(isolate_cmd_dir, relative_cwd, v) |
| 539 | if not file_path.path_starts_with(self.saved_state.root_dir, dest): |
Marc-Antoine Ruel | 1e7658c | 2014-08-28 19:46:39 -0400 | [diff] [blame] | 540 | raise isolated_format.MappingError( |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 541 | 'Path variable %s=%r points outside the inferred root directory ' |
| 542 | '%s; %s' |
| 543 | % (k, v, self.saved_state.root_dir, dest)) |
| 544 | # Normalize the files based to self.saved_state.root_dir. It is important to |
| 545 | # keep the trailing os.path.sep at that step. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 546 | infiles = [ |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 547 | file_path.relpath( |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 548 | file_path.normpath(os.path.join(isolate_cmd_dir, f)), |
| 549 | self.saved_state.root_dir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 550 | for f in infiles |
| 551 | ] |
Marc-Antoine Ruel | 0519946 | 2014-03-13 15:40:48 -0400 | [diff] [blame] | 552 | follow_symlinks = sys.platform != 'win32' |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 553 | # Expand the directories by listing each file inside. Up to now, trailing |
Marc-Antoine Ruel | 6b1084e | 2014-09-30 15:14:58 -0400 | [diff] [blame] | 554 | # os.path.sep must be kept. |
Marc-Antoine Ruel | 9225779 | 2014-08-28 20:51:08 -0400 | [diff] [blame] | 555 | infiles = isolated_format.expand_directories_and_symlinks( |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 556 | self.saved_state.root_dir, |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 557 | infiles, |
Marc-Antoine Ruel | 1f8ba35 | 2014-11-04 15:55:03 -0500 | [diff] [blame] | 558 | tools.gen_blacklist(blacklist), |
csharp@chromium.org | 84d2e2e | 2013-03-27 13:38:42 +0000 | [diff] [blame] | 559 | follow_symlinks, |
csharp@chromium.org | 0185680 | 2012-11-12 17:48:13 +0000 | [diff] [blame] | 560 | ignore_broken_items) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 561 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 562 | # Finally, update the new data to be able to generate the foo.isolated file, |
| 563 | # the file that is used by run_isolated.py. |
Marc-Antoine Ruel | 6b1084e | 2014-09-30 15:14:58 -0400 | [diff] [blame] | 564 | self.saved_state.update_isolated(command, infiles, read_only, relative_cwd) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 565 | logging.debug(self) |
| 566 | |
Marc-Antoine Ruel | 9225779 | 2014-08-28 20:51:08 -0400 | [diff] [blame] | 567 | def files_to_metadata(self, subdir): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 568 | """Updates self.saved_state.files with the files' mode and hash. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 569 | |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 570 | If |subdir| is specified, filters to a subdirectory. The resulting .isolated |
| 571 | file is tainted. |
| 572 | |
Marc-Antoine Ruel | 9225779 | 2014-08-28 20:51:08 -0400 | [diff] [blame] | 573 | See isolated_format.file_to_metadata() for more information. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 574 | """ |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 575 | for infile in sorted(self.saved_state.files): |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 576 | if subdir and not infile.startswith(subdir): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 577 | self.saved_state.files.pop(infile) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 578 | else: |
| 579 | filepath = os.path.join(self.root_dir, infile) |
Marc-Antoine Ruel | 9225779 | 2014-08-28 20:51:08 -0400 | [diff] [blame] | 580 | self.saved_state.files[infile] = isolated_format.file_to_metadata( |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 581 | filepath, |
| 582 | self.saved_state.files[infile], |
maruel@chromium.org | baa108d | 2013-03-28 13:24:51 +0000 | [diff] [blame] | 583 | self.saved_state.read_only, |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 584 | self.saved_state.algo) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 585 | |
| 586 | def save_files(self): |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 587 | """Saves self.saved_state and creates a .isolated file.""" |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 588 | logging.debug('Dumping to %s' % self.isolated_filepath) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 589 | self.saved_state.child_isolated_files = chromium_save_isolated( |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 590 | self.isolated_filepath, |
| 591 | self.saved_state.to_isolated(), |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 592 | self.saved_state.path_variables, |
maruel@chromium.org | 385d73d | 2013-09-19 18:33:21 +0000 | [diff] [blame] | 593 | self.saved_state.algo) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 594 | total_bytes = sum( |
| 595 | 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] | 596 | if total_bytes: |
maruel@chromium.org | d3a1776 | 2012-12-13 14:17:15 +0000 | [diff] [blame] | 597 | # TODO(maruel): Stats are missing the .isolated files. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 598 | logging.debug('Total size: %d bytes' % total_bytes) |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 599 | saved_state_file = isolatedfile_to_state(self.isolated_filepath) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 600 | logging.debug('Dumping to %s' % saved_state_file) |
Marc-Antoine Ruel | de01180 | 2013-11-12 15:19:47 -0500 | [diff] [blame] | 601 | tools.write_json(saved_state_file, self.saved_state.flatten(), True) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 602 | |
| 603 | @property |
| 604 | def root_dir(self): |
Marc-Antoine Ruel | edf2895 | 2014-03-31 19:55:47 -0400 | [diff] [blame] | 605 | return self.saved_state.root_dir |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 606 | |
| 607 | def __str__(self): |
| 608 | def indent(data, indent_length): |
| 609 | """Indents text.""" |
| 610 | spacing = ' ' * indent_length |
| 611 | return ''.join(spacing + l for l in str(data).splitlines(True)) |
| 612 | |
| 613 | out = '%s(\n' % self.__class__.__name__ |
| 614 | out += ' root_dir: %s\n' % self.root_dir |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 615 | out += ' saved_state: %s)' % indent(self.saved_state, 2) |
| 616 | return out |
| 617 | |
| 618 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 619 | def load_complete_state(options, cwd, subdir, skip_update): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 620 | """Loads a CompleteState. |
| 621 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 622 | This includes data from .isolate and .isolated.state files. Never reads the |
| 623 | .isolated file. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 624 | |
| 625 | Arguments: |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 626 | options: Options instance generated with process_isolate_options. For either |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 627 | options.isolate and options.isolated, if the value is set, it is an |
| 628 | absolute path. |
| 629 | cwd: base directory to be used when loading the .isolate file. |
| 630 | subdir: optional argument to only process file in the subdirectory, relative |
| 631 | to CompleteState.root_dir. |
| 632 | skip_update: Skip trying to load the .isolate file and processing the |
| 633 | dependencies. It is useful when not needed, like when tracing. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 634 | """ |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 635 | assert not options.isolate or os.path.isabs(options.isolate) |
| 636 | assert not options.isolated or os.path.isabs(options.isolated) |
maruel@chromium.org | 561d4b2 | 2013-09-26 21:08:08 +0000 | [diff] [blame] | 637 | cwd = file_path.get_native_path_case(unicode(cwd)) |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 638 | if options.isolated: |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 639 | # 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] | 640 | # Note: this call doesn't load the .isolate file. |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 641 | complete_state = CompleteState.load_files(options.isolated) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 642 | else: |
| 643 | # Constructs a dummy object that cannot be saved. Useful for temporary |
Marc-Antoine Ruel | 8472efa | 2014-03-18 14:32:50 -0400 | [diff] [blame] | 644 | # commands like 'run'. There is no directory containing a .isolated file so |
| 645 | # specify the current working directory as a valid directory. |
| 646 | complete_state = CompleteState(None, SavedState(os.getcwd())) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 647 | |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 648 | if not options.isolate: |
| 649 | if not complete_state.saved_state.isolate_file: |
| 650 | if not skip_update: |
| 651 | raise ExecutionError('A .isolate file is required.') |
| 652 | isolate = None |
| 653 | else: |
| 654 | isolate = complete_state.saved_state.isolate_filepath |
| 655 | else: |
| 656 | isolate = options.isolate |
| 657 | if complete_state.saved_state.isolate_file: |
Marc-Antoine Ruel | 3798993 | 2013-11-19 16:28:08 -0500 | [diff] [blame] | 658 | rel_isolate = file_path.safe_relpath( |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 659 | options.isolate, complete_state.saved_state.isolated_basedir) |
| 660 | if rel_isolate != complete_state.saved_state.isolate_file: |
Marc-Antoine Ruel | 8472efa | 2014-03-18 14:32:50 -0400 | [diff] [blame] | 661 | # This happens if the .isolate file was moved for example. In this case, |
| 662 | # discard the saved state. |
| 663 | logging.warning( |
| 664 | '--isolated %s != %s as saved in %s. Discarding saved state', |
| 665 | rel_isolate, |
| 666 | complete_state.saved_state.isolate_file, |
| 667 | isolatedfile_to_state(options.isolated)) |
| 668 | complete_state = CompleteState( |
| 669 | options.isolated, |
| 670 | SavedState(complete_state.saved_state.isolated_basedir)) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 671 | |
| 672 | if not skip_update: |
| 673 | # Then load the .isolate and expands directories. |
| 674 | complete_state.load_isolate( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 675 | cwd, isolate, options.path_variables, options.config_variables, |
Marc-Antoine Ruel | 1f8ba35 | 2014-11-04 15:55:03 -0500 | [diff] [blame] | 676 | options.extra_variables, options.blacklist, options.ignore_broken_items) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 677 | |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 678 | # Regenerate complete_state.saved_state.files. |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 679 | if subdir: |
maruel@chromium.org | 306e0e7 | 2012-11-02 18:22:03 +0000 | [diff] [blame] | 680 | subdir = unicode(subdir) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 681 | # This is tricky here. If it is a path, take it from the root_dir. If |
| 682 | # it is a variable, it must be keyed from the directory containing the |
| 683 | # .isolate file. So translate all variables first. |
| 684 | translated_path_variables = dict( |
| 685 | (k, |
| 686 | os.path.normpath(os.path.join(complete_state.saved_state.relative_cwd, |
| 687 | v))) |
| 688 | for k, v in complete_state.saved_state.path_variables.iteritems()) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 689 | subdir = isolate_format.eval_variables(subdir, translated_path_variables) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 690 | subdir = subdir.replace('/', os.path.sep) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 691 | |
| 692 | if not skip_update: |
Marc-Antoine Ruel | 9225779 | 2014-08-28 20:51:08 -0400 | [diff] [blame] | 693 | complete_state.files_to_metadata(subdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 694 | return complete_state |
| 695 | |
| 696 | |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 697 | def create_isolate_tree(outdir, root_dir, files, relative_cwd, read_only): |
| 698 | """Creates a isolated tree usable for test execution. |
| 699 | |
| 700 | Returns the current working directory where the isolated command should be |
| 701 | started in. |
| 702 | """ |
Marc-Antoine Ruel | 361bfda | 2014-01-15 15:26:39 -0500 | [diff] [blame] | 703 | # Forcibly copy when the tree has to be read only. Otherwise the inode is |
| 704 | # modified, and this cause real problems because the user's source tree |
| 705 | # becomes read only. On the other hand, the cost of doing file copy is huge. |
| 706 | if read_only not in (0, None): |
Marc-Antoine Ruel | e4ad07e | 2014-10-15 20:22:29 -0400 | [diff] [blame] | 707 | action = file_path.COPY |
Marc-Antoine Ruel | 361bfda | 2014-01-15 15:26:39 -0500 | [diff] [blame] | 708 | else: |
Marc-Antoine Ruel | e4ad07e | 2014-10-15 20:22:29 -0400 | [diff] [blame] | 709 | action = file_path.HARDLINK_WITH_FALLBACK |
Marc-Antoine Ruel | 361bfda | 2014-01-15 15:26:39 -0500 | [diff] [blame] | 710 | |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 711 | recreate_tree( |
| 712 | outdir=outdir, |
| 713 | indir=root_dir, |
| 714 | infiles=files, |
Marc-Antoine Ruel | 361bfda | 2014-01-15 15:26:39 -0500 | [diff] [blame] | 715 | action=action, |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 716 | as_hash=False) |
| 717 | cwd = os.path.normpath(os.path.join(outdir, relative_cwd)) |
| 718 | if not os.path.isdir(cwd): |
| 719 | # It can happen when no files are mapped from the directory containing the |
| 720 | # .isolate file. But the directory must exist to be the current working |
| 721 | # directory. |
| 722 | os.makedirs(cwd) |
| 723 | run_isolated.change_tree_read_only(outdir, read_only) |
| 724 | return cwd |
| 725 | |
| 726 | |
Vadim Shtayura | c28b74f | 2014-10-06 20:00:08 -0700 | [diff] [blame] | 727 | @tools.profile |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 728 | def prepare_for_archival(options, cwd): |
| 729 | """Loads the isolated file and create 'infiles' for archival.""" |
| 730 | complete_state = load_complete_state( |
| 731 | options, cwd, options.subdir, False) |
| 732 | # Make sure that complete_state isn't modified until save_files() is |
| 733 | # called, because any changes made to it here will propagate to the files |
| 734 | # created (which is probably not intended). |
| 735 | complete_state.save_files() |
| 736 | |
| 737 | infiles = complete_state.saved_state.files |
| 738 | # Add all the .isolated files. |
| 739 | isolated_hash = [] |
| 740 | isolated_files = [ |
| 741 | options.isolated, |
| 742 | ] + complete_state.saved_state.child_isolated_files |
| 743 | for item in isolated_files: |
| 744 | item_path = os.path.join( |
| 745 | os.path.dirname(complete_state.isolated_filepath), item) |
Marc-Antoine Ruel | 8bee66d | 2014-08-28 19:02:07 -0400 | [diff] [blame] | 746 | # Do not use isolated_format.hash_file() here because the file is |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 747 | # likely smallish (under 500kb) and its file size is needed. |
| 748 | with open(item_path, 'rb') as f: |
| 749 | content = f.read() |
| 750 | isolated_hash.append( |
| 751 | complete_state.saved_state.algo(content).hexdigest()) |
| 752 | isolated_metadata = { |
| 753 | 'h': isolated_hash[-1], |
| 754 | 's': len(content), |
| 755 | 'priority': '0' |
| 756 | } |
| 757 | infiles[item_path] = isolated_metadata |
| 758 | return complete_state, infiles, isolated_hash |
| 759 | |
| 760 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 761 | def isolate_and_archive(trees, isolate_server, namespace): |
| 762 | """Isolates and uploads a bunch of isolated trees. |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 763 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 764 | Args: |
| 765 | trees: list of pairs (Options, working directory) that describe what tree |
| 766 | to isolate. Options are processed by 'process_isolate_options'. |
| 767 | isolate_server: URL of Isolate Server to upload to. |
| 768 | namespace: namespace to upload to. |
| 769 | |
| 770 | Returns a dict {target name -> isolate hash or None}, where target name is |
| 771 | a name of *.isolated file without an extension (e.g. 'base_unittests'). |
| 772 | |
| 773 | Have multiple failure modes: |
| 774 | * If the upload fails due to server or network error returns None. |
| 775 | * If some *.isolate file is incorrect (but rest of them are fine and were |
| 776 | successfully uploaded), returns a dict where the value of the entry |
| 777 | corresponding to invalid *.isolate file is None. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 778 | """ |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 779 | if not trees: |
| 780 | return {} |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 781 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 782 | # Helper generator to avoid materializing the full (huge) list of files until |
| 783 | # the very end (in upload_tree). |
| 784 | def emit_files(root_dir, files): |
| 785 | for path, meta in files.iteritems(): |
| 786 | yield (os.path.join(root_dir, path), meta) |
| 787 | |
| 788 | # Process all *.isolate files, it involves parsing, file system traversal and |
| 789 | # hashing. The result is a list of generators that produce files to upload |
| 790 | # and the mapping {target name -> hash of *.isolated file} to return from |
| 791 | # this function. |
| 792 | files_generators = [] |
| 793 | isolated_hashes = {} |
| 794 | with tools.Profiler('Isolate'): |
| 795 | for opts, cwd in trees: |
| 796 | target_name = os.path.splitext(os.path.basename(opts.isolated))[0] |
| 797 | try: |
| 798 | complete_state, files, isolated_hash = prepare_for_archival(opts, cwd) |
| 799 | files_generators.append(emit_files(complete_state.root_dir, files)) |
| 800 | isolated_hashes[target_name] = isolated_hash[0] |
| 801 | print('%s %s' % (isolated_hash[0], target_name)) |
| 802 | except Exception: |
| 803 | logging.exception('Exception when isolating %s', target_name) |
| 804 | isolated_hashes[target_name] = None |
| 805 | |
| 806 | # All bad? Nothing to upload. |
| 807 | if all(v is None for v in isolated_hashes.itervalues()): |
| 808 | return isolated_hashes |
| 809 | |
| 810 | # Now upload all necessary files at once. |
| 811 | with tools.Profiler('Upload'): |
| 812 | try: |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 813 | isolateserver.upload_tree( |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 814 | base_url=isolate_server, |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 815 | infiles=itertools.chain(*files_generators), |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 816 | namespace=namespace) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 817 | except Exception: |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 818 | logging.exception('Exception while uploading files') |
| 819 | return None |
| 820 | |
| 821 | return isolated_hashes |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 822 | |
| 823 | |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 824 | def parse_archive_command_line(args, cwd): |
| 825 | """Given list of arguments for 'archive' command returns parsed options. |
| 826 | |
| 827 | Used by CMDbatcharchive to parse options passed via JSON. See also CMDarchive. |
| 828 | """ |
| 829 | parser = optparse.OptionParser() |
| 830 | add_isolate_options(parser) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 831 | add_subdir_option(parser) |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 832 | options, args = parser.parse_args(args) |
| 833 | if args: |
| 834 | parser.error('Unsupported argument: %s' % args) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 835 | process_isolate_options(parser, options, cwd) |
| 836 | return options |
| 837 | |
| 838 | |
| 839 | ### Commands. |
| 840 | |
| 841 | |
| 842 | def CMDarchive(parser, args): |
| 843 | """Creates a .isolated file and uploads the tree to an isolate server. |
| 844 | |
| 845 | All the files listed in the .isolated file are put in the isolate server |
| 846 | cache via isolateserver.py. |
| 847 | """ |
| 848 | add_isolate_options(parser) |
| 849 | add_subdir_option(parser) |
Marc-Antoine Ruel | f7d737d | 2014-12-10 15:36:29 -0500 | [diff] [blame^] | 850 | isolateserver.add_isolate_server_options(parser) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 851 | auth.add_auth_options(parser) |
| 852 | options, args = parser.parse_args(args) |
Marc-Antoine Ruel | f7d737d | 2014-12-10 15:36:29 -0500 | [diff] [blame^] | 853 | if args: |
| 854 | parser.error('Unsupported argument: %s' % args) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 855 | process_isolate_options(parser, options) |
| 856 | auth.process_auth_options(parser, options) |
| 857 | isolateserver.process_isolate_server_options(parser, options) |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 858 | result = isolate_and_archive( |
| 859 | [(options, os.getcwd())], options.isolate_server, options.namespace) |
| 860 | if result is None: |
| 861 | return EXIT_CODE_UPLOAD_ERROR |
| 862 | assert len(result) == 1, result |
| 863 | if result.values()[0] is None: |
| 864 | return EXIT_CODE_ISOLATE_ERROR |
| 865 | return 0 |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 866 | |
| 867 | |
| 868 | @subcommand.usage('-- GEN_JSON_1 GEN_JSON_2 ...') |
| 869 | def CMDbatcharchive(parser, args): |
| 870 | """Archives multiple isolated trees at once. |
| 871 | |
| 872 | Using single command instead of multiple sequential invocations allows to cut |
| 873 | redundant work when isolated trees share common files (e.g. file hashes are |
| 874 | checked only once, their presence on the server is checked only once, and |
| 875 | so on). |
| 876 | |
| 877 | Takes a list of paths to *.isolated.gen.json files that describe what trees to |
| 878 | isolate. Format of files is: |
| 879 | { |
Andrew Wang | 8364855 | 2014-11-14 13:26:49 -0800 | [diff] [blame] | 880 | "version": 1, |
| 881 | "dir": <absolute path to a directory all other paths are relative to>, |
| 882 | "args": [list of command line arguments for single 'archive' command] |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 883 | } |
| 884 | """ |
Marc-Antoine Ruel | f7d737d | 2014-12-10 15:36:29 -0500 | [diff] [blame^] | 885 | isolateserver.add_isolate_server_options(parser) |
Marc-Antoine Ruel | 1f8ba35 | 2014-11-04 15:55:03 -0500 | [diff] [blame] | 886 | isolateserver.add_archive_options(parser) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 887 | auth.add_auth_options(parser) |
Vadim Shtayura | f4e9ccb | 2014-10-01 21:24:53 -0700 | [diff] [blame] | 888 | parser.add_option( |
| 889 | '--dump-json', |
| 890 | metavar='FILE', |
| 891 | help='Write isolated hashes of archived trees to this file as JSON') |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 892 | options, args = parser.parse_args(args) |
| 893 | auth.process_auth_options(parser, options) |
| 894 | isolateserver.process_isolate_server_options(parser, options) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 895 | |
| 896 | # Validate all incoming options, prepare what needs to be archived as a list |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 897 | # of tuples (archival options, working directory). |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 898 | work_units = [] |
| 899 | for gen_json_path in args: |
| 900 | # Validate JSON format of a *.isolated.gen.json file. |
| 901 | data = tools.read_json(gen_json_path) |
| 902 | if data.get('version') != ISOLATED_GEN_JSON_VERSION: |
| 903 | parser.error('Invalid version in %s' % gen_json_path) |
| 904 | cwd = data.get('dir') |
| 905 | if not isinstance(cwd, unicode) or not os.path.isdir(cwd): |
| 906 | parser.error('Invalid dir in %s' % gen_json_path) |
| 907 | args = data.get('args') |
| 908 | if (not isinstance(args, list) or |
| 909 | not all(isinstance(x, unicode) for x in args)): |
| 910 | parser.error('Invalid args in %s' % gen_json_path) |
| 911 | # Convert command line (embedded in JSON) to Options object. |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 912 | work_units.append((parse_archive_command_line(args, cwd), cwd)) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 913 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 914 | # Perform the archival, all at once. |
| 915 | isolated_hashes = isolate_and_archive( |
| 916 | work_units, options.isolate_server, options.namespace) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 917 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 918 | # TODO(vadimsh): isolate_and_archive returns None on upload failure, there's |
| 919 | # no way currently to figure out what *.isolated file from a batch were |
| 920 | # successfully uploaded, so consider them all failed (and emit empty dict |
| 921 | # as JSON result). |
Vadim Shtayura | f4e9ccb | 2014-10-01 21:24:53 -0700 | [diff] [blame] | 922 | if options.dump_json: |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 923 | tools.write_json(options.dump_json, isolated_hashes or {}, False) |
Vadim Shtayura | f4e9ccb | 2014-10-01 21:24:53 -0700 | [diff] [blame] | 924 | |
Vadim Shtayura | ea38c57 | 2014-10-06 16:57:16 -0700 | [diff] [blame] | 925 | if isolated_hashes is None: |
| 926 | return EXIT_CODE_UPLOAD_ERROR |
| 927 | |
| 928 | # isolated_hashes[x] is None if 'x.isolate' contains a error. |
| 929 | if not all(isolated_hashes.itervalues()): |
| 930 | return EXIT_CODE_ISOLATE_ERROR |
| 931 | |
| 932 | return 0 |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 933 | |
| 934 | |
| 935 | def CMDcheck(parser, args): |
| 936 | """Checks that all the inputs are present and generates .isolated.""" |
| 937 | add_isolate_options(parser) |
| 938 | add_subdir_option(parser) |
| 939 | options, args = parser.parse_args(args) |
| 940 | if args: |
| 941 | parser.error('Unsupported argument: %s' % args) |
| 942 | process_isolate_options(parser, options) |
maruel@chromium.org | 2f952d8 | 2013-09-13 01:53:17 +0000 | [diff] [blame] | 943 | |
| 944 | complete_state = load_complete_state( |
| 945 | options, os.getcwd(), options.subdir, False) |
| 946 | |
| 947 | # Nothing is done specifically. Just store the result and state. |
| 948 | complete_state.save_files() |
| 949 | return 0 |
| 950 | |
| 951 | |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 952 | def CMDremap(parser, args): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 953 | """Creates a directory with all the dependencies mapped into it. |
| 954 | |
| 955 | Useful to test manually why a test is failing. The target executable is not |
| 956 | run. |
| 957 | """ |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 958 | add_isolate_options(parser) |
Marc-Antoine Ruel | e236b5c | 2014-09-08 18:40:40 -0400 | [diff] [blame] | 959 | add_outdir_options(parser) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 960 | add_skip_refresh_option(parser) |
maruel@chromium.org | 9268f04 | 2012-10-17 17:36:41 +0000 | [diff] [blame] | 961 | options, args = parser.parse_args(args) |
| 962 | if args: |
| 963 | parser.error('Unsupported argument: %s' % args) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 964 | cwd = os.getcwd() |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 965 | process_isolate_options(parser, options, cwd, require_isolated=False) |
Marc-Antoine Ruel | e236b5c | 2014-09-08 18:40:40 -0400 | [diff] [blame] | 966 | process_outdir_options(parser, options, cwd) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 967 | complete_state = load_complete_state(options, cwd, None, options.skip_refresh) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 968 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 969 | if not os.path.isdir(options.outdir): |
| 970 | os.makedirs(options.outdir) |
| 971 | print('Remapping into %s' % options.outdir) |
| 972 | if os.listdir(options.outdir): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 973 | raise ExecutionError('Can\'t remap in a non-empty directory') |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 974 | |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 975 | create_isolate_tree( |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 976 | options.outdir, complete_state.root_dir, complete_state.saved_state.files, |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 977 | complete_state.saved_state.relative_cwd, |
| 978 | complete_state.saved_state.read_only) |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 979 | if complete_state.isolated_filepath: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 980 | complete_state.save_files() |
| 981 | return 0 |
| 982 | |
| 983 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 984 | @subcommand.usage('-- [extra arguments]') |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 985 | def CMDrun(parser, args): |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 986 | """Runs the test executable in an isolated (temporary) directory. |
| 987 | |
| 988 | All the dependencies are mapped into the temporary directory and the |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 989 | directory is cleaned up after the target exits. |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 990 | |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 991 | Argument processing stops at -- and these arguments are appended to the |
| 992 | command line of the target to run. For example, use: |
| 993 | isolate.py run --isolated foo.isolated -- --gtest_filter=Foo.Bar |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 994 | """ |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 995 | add_isolate_options(parser) |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 996 | add_skip_refresh_option(parser) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 997 | options, args = parser.parse_args(args) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 998 | process_isolate_options(parser, options, require_isolated=False) |
maruel@chromium.org | 8abec8b | 2013-04-16 19:34:20 +0000 | [diff] [blame] | 999 | complete_state = load_complete_state( |
| 1000 | options, os.getcwd(), None, options.skip_refresh) |
maruel@chromium.org | f75b0cb | 2012-12-11 21:39:00 +0000 | [diff] [blame] | 1001 | cmd = complete_state.saved_state.command + args |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1002 | if not cmd: |
maruel@chromium.org | 2902988 | 2013-08-30 12:15:40 +0000 | [diff] [blame] | 1003 | raise ExecutionError('No command to run.') |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1004 | cmd = tools.fix_python_path(cmd) |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1005 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 1006 | outdir = run_isolated.make_temp_dir( |
| 1007 | 'isolate-%s' % datetime.date.today(), |
| 1008 | os.path.dirname(complete_state.root_dir)) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1009 | try: |
Marc-Antoine Ruel | 7124e39 | 2014-01-09 11:49:21 -0500 | [diff] [blame] | 1010 | # TODO(maruel): Use run_isolated.run_tha_test(). |
Marc-Antoine Ruel | 1e9ad0c | 2014-01-14 15:20:33 -0500 | [diff] [blame] | 1011 | cwd = create_isolate_tree( |
| 1012 | outdir, complete_state.root_dir, complete_state.saved_state.files, |
| 1013 | complete_state.saved_state.relative_cwd, |
| 1014 | complete_state.saved_state.read_only) |
John Abd-El-Malek | 3f99868 | 2014-09-17 17:48:09 -0700 | [diff] [blame] | 1015 | file_path.ensure_command_has_abs_path(cmd, cwd) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1016 | logging.info('Running %s, cwd=%s' % (cmd, cwd)) |
Marc-Antoine Ruel | 926dccd | 2014-09-17 13:40:24 -0400 | [diff] [blame] | 1017 | try: |
| 1018 | result = subprocess.call(cmd, cwd=cwd) |
| 1019 | except OSError: |
| 1020 | sys.stderr.write( |
| 1021 | 'Failed to executed the command; executable is missing, maybe you\n' |
| 1022 | 'forgot to map it in the .isolate file?\n %s\n in %s\n' % |
| 1023 | (' '.join(cmd), cwd)) |
| 1024 | result = 1 |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1025 | finally: |
Marc-Antoine Ruel | e4ad07e | 2014-10-15 20:22:29 -0400 | [diff] [blame] | 1026 | file_path.rmtree(outdir) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1027 | |
maruel@chromium.org | 4b57f69 | 2012-10-05 20:33:09 +0000 | [diff] [blame] | 1028 | if complete_state.isolated_filepath: |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1029 | complete_state.save_files() |
| 1030 | return result |
| 1031 | |
| 1032 | |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1033 | def _process_variable_arg(option, opt, _value, parser): |
| 1034 | """Called by OptionParser to process a --<foo>-variable argument.""" |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1035 | if not parser.rargs: |
| 1036 | raise optparse.OptionValueError( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1037 | 'Please use %s FOO=BAR or %s FOO BAR' % (opt, opt)) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1038 | k = parser.rargs.pop(0) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1039 | variables = getattr(parser.values, option.dest) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1040 | if '=' in k: |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1041 | k, v = k.split('=', 1) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1042 | else: |
| 1043 | if not parser.rargs: |
| 1044 | raise optparse.OptionValueError( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1045 | 'Please use %s FOO=BAR or %s FOO BAR' % (opt, opt)) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1046 | v = parser.rargs.pop(0) |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1047 | if not re.match('^' + isolate_format.VALID_VARIABLE + '$', k): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1048 | raise optparse.OptionValueError( |
Marc-Antoine Ruel | a5a3622 | 2014-01-09 10:35:45 -0500 | [diff] [blame] | 1049 | 'Variable \'%s\' doesn\'t respect format \'%s\'' % |
| 1050 | (k, isolate_format.VALID_VARIABLE)) |
Marc-Antoine Ruel | 9cc42c3 | 2013-12-11 09:35:55 -0500 | [diff] [blame] | 1051 | variables.append((k, v.decode('utf-8'))) |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1052 | |
| 1053 | |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1054 | def add_variable_option(parser): |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1055 | """Adds --isolated and --<foo>-variable to an OptionParser.""" |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1056 | parser.add_option( |
| 1057 | '-s', '--isolated', |
| 1058 | metavar='FILE', |
| 1059 | help='.isolated file to generate or read') |
| 1060 | # Keep for compatibility. TODO(maruel): Remove once not used anymore. |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1061 | parser.add_option( |
| 1062 | '-r', '--result', |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1063 | dest='isolated', |
| 1064 | help=optparse.SUPPRESS_HELP) |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1065 | is_win = sys.platform in ('win32', 'cygwin') |
| 1066 | # There is really 3 kind of variables: |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1067 | # - path variables, like DEPTH or PRODUCT_DIR that should be |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1068 | # replaced opportunistically when tracing tests. |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1069 | # - extraneous things like EXECUTABE_SUFFIX. |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1070 | # - configuration variables that are to be used in deducing the matrix to |
| 1071 | # reduce. |
| 1072 | # - unrelated variables that are used as command flags for example. |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1073 | parser.add_option( |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1074 | '--config-variable', |
maruel@chromium.org | 712454d | 2013-04-04 17:52:34 +0000 | [diff] [blame] | 1075 | action='callback', |
| 1076 | callback=_process_variable_arg, |
Marc-Antoine Ruel | 0519946 | 2014-03-13 15:40:48 -0400 | [diff] [blame] | 1077 | default=[], |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1078 | dest='config_variables', |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1079 | metavar='FOO BAR', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1080 | help='Config variables are used to determine which conditions should be ' |
| 1081 | 'matched when loading a .isolate file, default: %default. ' |
| 1082 | 'All 3 kinds of variables are persistent accross calls, they are ' |
| 1083 | 'saved inside <.isolated>.state') |
| 1084 | parser.add_option( |
| 1085 | '--path-variable', |
| 1086 | action='callback', |
| 1087 | callback=_process_variable_arg, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1088 | default=[], |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1089 | dest='path_variables', |
| 1090 | metavar='FOO BAR', |
| 1091 | help='Path variables are used to replace file paths when loading a ' |
| 1092 | '.isolate file, default: %default') |
| 1093 | parser.add_option( |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1094 | '--extra-variable', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1095 | action='callback', |
| 1096 | callback=_process_variable_arg, |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1097 | default=[('EXECUTABLE_SUFFIX', '.exe' if is_win else '')], |
| 1098 | dest='extra_variables', |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1099 | metavar='FOO BAR', |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1100 | help='Extraneous variables are replaced on the \'command\' entry and on ' |
| 1101 | 'paths in the .isolate file but are not considered relative paths.') |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1102 | |
| 1103 | |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 1104 | def add_isolate_options(parser): |
| 1105 | """Adds --isolate, --isolated, --out and --<foo>-variable options.""" |
Marc-Antoine Ruel | 1f8ba35 | 2014-11-04 15:55:03 -0500 | [diff] [blame] | 1106 | isolateserver.add_archive_options(parser) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 1107 | group = optparse.OptionGroup(parser, 'Common options') |
| 1108 | group.add_option( |
| 1109 | '-i', '--isolate', |
| 1110 | metavar='FILE', |
| 1111 | help='.isolate file to load the dependency data from') |
| 1112 | add_variable_option(group) |
| 1113 | group.add_option( |
| 1114 | '--ignore_broken_items', action='store_true', |
| 1115 | default=bool(os.environ.get('ISOLATE_IGNORE_BROKEN_ITEMS')), |
| 1116 | help='Indicates that invalid entries in the isolated file to be ' |
| 1117 | 'only be logged and not stop processing. Defaults to True if ' |
| 1118 | 'env var ISOLATE_IGNORE_BROKEN_ITEMS is set') |
| 1119 | parser.add_option_group(group) |
| 1120 | |
| 1121 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 1122 | def add_subdir_option(parser): |
| 1123 | parser.add_option( |
| 1124 | '--subdir', |
| 1125 | help='Filters to a subdirectory. Its behavior changes depending if it ' |
| 1126 | 'is a relative path as a string or as a path variable. Path ' |
| 1127 | 'variables are always keyed from the directory containing the ' |
| 1128 | '.isolate file. Anything else is keyed on the root directory.') |
| 1129 | |
| 1130 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 1131 | def add_skip_refresh_option(parser): |
| 1132 | parser.add_option( |
| 1133 | '--skip-refresh', action='store_true', |
| 1134 | help='Skip reading .isolate file and do not refresh the hash of ' |
| 1135 | 'dependencies') |
| 1136 | |
Marc-Antoine Ruel | f9538ee | 2014-01-30 10:43:54 -0500 | [diff] [blame] | 1137 | |
Marc-Antoine Ruel | e236b5c | 2014-09-08 18:40:40 -0400 | [diff] [blame] | 1138 | def add_outdir_options(parser): |
| 1139 | """Adds --outdir, which is orthogonal to --isolate-server. |
| 1140 | |
| 1141 | Note: On upload, separate commands are used between 'archive' and 'hashtable'. |
| 1142 | On 'download', the same command can download from either an isolate server or |
| 1143 | a file system. |
| 1144 | """ |
| 1145 | parser.add_option( |
| 1146 | '-o', '--outdir', metavar='DIR', |
| 1147 | help='Directory used to recreate the tree.') |
| 1148 | |
| 1149 | |
| 1150 | def process_outdir_options(parser, options, cwd): |
| 1151 | if not options.outdir: |
| 1152 | parser.error('--outdir is required.') |
| 1153 | if file_path.is_url(options.outdir): |
| 1154 | parser.error('Can\'t use an URL for --outdir.') |
| 1155 | options.outdir = unicode(options.outdir).replace('/', os.path.sep) |
| 1156 | # outdir doesn't need native path case since tracing is never done from there. |
| 1157 | options.outdir = os.path.abspath( |
| 1158 | os.path.normpath(os.path.join(cwd, options.outdir))) |
| 1159 | # In theory, we'd create the directory outdir right away. Defer doing it in |
| 1160 | # case there's errors in the command line. |
| 1161 | |
| 1162 | |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 1163 | def process_isolate_options(parser, options, cwd=None, require_isolated=True): |
| 1164 | """Handles options added with 'add_isolate_options'. |
| 1165 | |
| 1166 | Mutates |options| in place, by normalizing path to isolate file, values of |
| 1167 | variables, etc. |
| 1168 | """ |
| 1169 | cwd = file_path.get_native_path_case(unicode(cwd or os.getcwd())) |
| 1170 | |
| 1171 | # Parse --isolated option. |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1172 | if options.isolated: |
maruel@chromium.org | 61a9b3b | 2012-12-12 17:18:52 +0000 | [diff] [blame] | 1173 | options.isolated = os.path.normpath( |
| 1174 | os.path.join(cwd, options.isolated.replace('/', os.path.sep))) |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1175 | if require_isolated and not options.isolated: |
maruel@chromium.org | 75c05b4 | 2013-07-25 15:51:48 +0000 | [diff] [blame] | 1176 | parser.error('--isolated is required.') |
maruel@chromium.org | 0cd0b18 | 2012-10-22 13:34:15 +0000 | [diff] [blame] | 1177 | if options.isolated and not options.isolated.endswith('.isolated'): |
| 1178 | parser.error('--isolated value must end with \'.isolated\'') |
maruel@chromium.org | 715e3fb | 2013-03-19 15:44:06 +0000 | [diff] [blame] | 1179 | |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 1180 | # Processes all the --<foo>-variable flags. |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 1181 | def try_make_int(s): |
maruel@chromium.org | e83215b | 2013-02-21 14:16:59 +0000 | [diff] [blame] | 1182 | """Converts a value to int if possible, converts to unicode otherwise.""" |
benrg@chromium.org | 609b798 | 2013-02-07 16:44:46 +0000 | [diff] [blame] | 1183 | try: |
| 1184 | return int(s) |
| 1185 | except ValueError: |
maruel@chromium.org | e83215b | 2013-02-21 14:16:59 +0000 | [diff] [blame] | 1186 | return s.decode('utf-8') |
Marc-Antoine Ruel | 1c1edd6 | 2013-12-06 09:13:13 -0500 | [diff] [blame] | 1187 | options.config_variables = dict( |
| 1188 | (k, try_make_int(v)) for k, v in options.config_variables) |
| 1189 | options.path_variables = dict(options.path_variables) |
Marc-Antoine Ruel | f3589b1 | 2013-12-06 14:26:16 -0500 | [diff] [blame] | 1190 | options.extra_variables = dict(options.extra_variables) |
maruel@chromium.org | b253fb8 | 2012-10-16 21:44:48 +0000 | [diff] [blame] | 1191 | |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 1192 | # Normalize the path in --isolate. |
| 1193 | if options.isolate: |
| 1194 | # TODO(maruel): Work with non-ASCII. |
| 1195 | # The path must be in native path case for tracing purposes. |
| 1196 | options.isolate = unicode(options.isolate).replace('/', os.path.sep) |
| 1197 | options.isolate = os.path.normpath(os.path.join(cwd, options.isolate)) |
| 1198 | options.isolate = file_path.get_native_path_case(options.isolate) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1199 | |
| 1200 | |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1201 | def main(argv): |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1202 | dispatcher = subcommand.CommandDispatcher(__name__) |
Vadim Shtayura | fddb143 | 2014-09-30 18:32:41 -0700 | [diff] [blame] | 1203 | parser = tools.OptionParserWithLogging( |
| 1204 | version=__version__, verbose=int(os.environ.get('ISOLATE_DEBUG', 0))) |
| 1205 | return dispatcher.execute(parser, argv) |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1206 | |
| 1207 | |
| 1208 | if __name__ == '__main__': |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1209 | fix_encoding.fix_encoding() |
vadimsh@chromium.org | a432647 | 2013-08-24 02:05:41 +0000 | [diff] [blame] | 1210 | tools.disable_buffering() |
maruel@chromium.org | e532251 | 2013-08-19 20:17:57 +0000 | [diff] [blame] | 1211 | colorama.init() |
maruel@chromium.org | 8fb47fe | 2012-10-03 20:13:15 +0000 | [diff] [blame] | 1212 | sys.exit(main(sys.argv[1:])) |