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