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