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