import re
import sys
-from utils import short_expression_finder
# Files that should be 0-length when mapped.
KEY_TOUCHED = 'isolate_dependency_touched'
raise IsolateError(
'Variable "%s" was not found in %s.\nDid you forget to specify '
'--path-variable?' % (m.group(1), variables))
- return variables[m.group(1)]
+ return str(variables[m.group(1)])
return part
for p in re.split(r'(<\(' + VALID_VARIABLE + '\))', item))
-def split_touched(files):
- """Splits files that are touched vs files that are read."""
- tracked = []
- touched = []
- for f in files:
- if f.size:
- tracked.append(f)
- else:
- touched.append(f)
- return tracked, touched
-
-
def pretty_print(variables, stdout):
"""Outputs a .isolate file from the decoded variables.
"""
# Order the dictionary keys by these keys in priority.
ORDER = (
- 'variables', 'condition', 'command', 'read_only',
+ 'variables', 'condition', 'command', 'files', 'read_only',
KEY_TRACKED, KEY_UNTRACKED)
def sorting_key(x):
KEY_TRACKED,
KEY_UNTRACKED,
'command',
+ 'files',
'read_only',
]
assert isinstance(variables, dict), variables
verify_variables(variables)
-def remove_weak_dependencies(values, key, item, item_configs):
- """Removes any configs from this key if the item is already under a
- strong key.
- """
- if key == KEY_TOUCHED:
- item_configs = set(item_configs)
- for stronger_key in (KEY_TRACKED, KEY_UNTRACKED):
- try:
- item_configs -= values[stronger_key][item]
- except KeyError:
- pass
-
- return item_configs
-
-
-def remove_repeated_dependencies(folders, key, item, item_configs):
- """Removes any configs from this key if the item is in a folder that is
- already included."""
-
- if key in (KEY_UNTRACKED, KEY_TRACKED, KEY_TOUCHED):
- item_configs = set(item_configs)
- for (folder, configs) in folders.iteritems():
- if folder != item and item.startswith(folder):
- item_configs -= configs
-
- return item_configs
-
-
def get_folders(values_dict):
"""Returns a dict of all the folders in the given value_dict."""
return dict(
)
-def invert_map(variables):
- """Converts {config: {deptype: list(depvals)}} to
- {deptype: {depval: set(configs)}}.
- """
- KEYS = (
- KEY_TOUCHED,
- KEY_TRACKED,
- KEY_UNTRACKED,
- 'command',
- 'read_only',
- )
- out = dict((key, {}) for key in KEYS)
- for config, values in variables.iteritems():
- for key in KEYS:
- if key == 'command':
- items = [tuple(values[key])] if key in values else []
- elif key == 'read_only':
- items = [values[key]] if key in values else []
- else:
- assert key in (KEY_TOUCHED, KEY_TRACKED, KEY_UNTRACKED)
- items = values.get(key, [])
- for item in items:
- out[key].setdefault(item, set()).add(config)
- return out
-
-
-def reduce_inputs(values):
- """Reduces the output of invert_map() to the strictest minimum list.
-
- Looks at each individual file and directory, maps where they are used and
- reconstructs the inverse dictionary.
-
- Returns the minimized dictionary.
- """
- KEYS = (
- KEY_TOUCHED,
- KEY_TRACKED,
- KEY_UNTRACKED,
- 'command',
- 'read_only',
- )
-
- # Folders can only live in KEY_UNTRACKED.
- folders = get_folders(values.get(KEY_UNTRACKED, {}))
-
- out = dict((key, {}) for key in KEYS)
- for key in KEYS:
- for item, item_configs in values.get(key, {}).iteritems():
- item_configs = remove_weak_dependencies(values, key, item, item_configs)
- item_configs = remove_repeated_dependencies(
- folders, key, item, item_configs)
- if item_configs:
- out[key][item] = item_configs
- return out
-
-
-def convert_map_to_isolate_dict(values, config_variables):
- """Regenerates back a .isolate configuration dict from files and dirs
- mappings generated from reduce_inputs().
- """
- # Gather a list of configurations for set inversion later.
- all_mentioned_configs = set()
- for configs_by_item in values.itervalues():
- for configs in configs_by_item.itervalues():
- all_mentioned_configs.update(configs)
-
- # Invert the mapping to make it dict first.
- conditions = {}
- for key in values:
- for item, configs in values[key].iteritems():
- then = conditions.setdefault(frozenset(configs), {})
- variables = then.setdefault('variables', {})
-
- if key == 'read_only':
- if not isinstance(item, int):
- raise IsolateError(
- 'Unexpected entry type %r for key %s' % (item, key))
- variables[key] = item
- elif key == 'command':
- if not isinstance(item, tuple):
- raise IsolateError(
- 'Unexpected entry type %r for key %s' % (item, key))
- if key in variables:
- raise IsolateError('Unexpected duplicate key %s' % key)
- if not item:
- raise IsolateError('Expected non empty entry in %s' % key)
- variables[key] = list(item)
- elif key in (KEY_TOUCHED, KEY_TRACKED, KEY_UNTRACKED):
- if not isinstance(item, basestring):
- raise IsolateError('Unexpected entry type %r' % item)
- if not item:
- raise IsolateError('Expected non empty entry in %s' % key)
- # The list of items (files or dirs). Append the new item and keep
- # the list sorted.
- l = variables.setdefault(key, [])
- l.append(item)
- l.sort()
- else:
- raise IsolateError('Unexpected key %s' % key)
-
- if all_mentioned_configs:
- # Change [(1, 2), (3, 4)] to [set(1, 3), set(2, 4)]
- config_values = map(set, zip(*all_mentioned_configs))
- for i in config_values:
- i.discard(None)
- sef = short_expression_finder.ShortExpressionFinder(
- zip(config_variables, config_values))
- conditions = sorted([sef.get_expr(c), v] for c, v in conditions.iteritems())
- else:
- conditions = []
- out = {'conditions': conditions}
- for c in conditions:
- if c[0] == '':
- # Extract the global.
- out.update(c[1])
- conditions.remove(c)
- break
- return out
-
-
class ConfigSettings(object):
"""Represents the dependency variables for a single build configuration.
The structure is immutable.
- .touch, .tracked and .untracked are the list of dependencies. The items in
- these lists use '/' as a path separator.
.command and .isolate_dir describe how to run the command. .isolate_dir uses
the OS' native path separator. It must be an absolute path, it's the path
where to start the command from.
+ .files is the list of dependencies. The items use '/' as a path separator.
.read_only describe how to map the files.
"""
def __init__(self, values, isolate_dir):
else:
# Otherwise, the path must be absolute.
assert os.path.isabs(isolate_dir), isolate_dir
- self.touched = sorted(values.get(KEY_TOUCHED, []))
- self.tracked = sorted(values.get(KEY_TRACKED, []))
- self.untracked = sorted(values.get(KEY_UNTRACKED, []))
+
+ self.files = sorted(
+ values.get('files', []) +
+ values.get(KEY_TOUCHED, []) +
+ values.get(KEY_TRACKED, []) +
+ values.get(KEY_UNTRACKED, []))
self.command = values.get('command', [])[:]
self.isolate_dir = isolate_dir
self.read_only = values.get('read_only')
use_rhs = bool(not self.command and rhs.command)
else:
# If self doesn't define any file, use rhs.
- use_rhs = not bool(self.touched or self.tracked or self.untracked)
+ use_rhs = not bool(self.files)
if use_rhs:
# Rebase files in rhs.
l_rel_cwd, r_rel_cwd = r_rel_cwd, l_rel_cwd
return sorted(l + map(rebase_item, r))
var = {
- KEY_TOUCHED: map_both(self.touched, rhs.touched),
- KEY_TRACKED: map_both(self.tracked, rhs.tracked),
- KEY_UNTRACKED: map_both(self.untracked, rhs.untracked),
'command': self.command or rhs.command,
+ 'files': map_both(self.files, rhs.files),
'read_only': rhs.read_only if self.read_only is None else self.read_only,
}
return ConfigSettings(var, l_rel_cwd)
out = {}
if self.command:
out['command'] = self.command
- if self.touched:
- out[KEY_TOUCHED] = self.touched
- if self.tracked:
- out[KEY_TRACKED] = self.tracked
- if self.untracked:
- out[KEY_UNTRACKED] = self.untracked
+ if self.files:
+ out['files'] = self.files
if self.read_only is not None:
out['read_only'] = self.read_only
# TODO(maruel): Probably better to not output it if command is None?
def __str__(self):
"""Returns a short representation useful for debugging."""
- files = ''.join(
- '\n ' + f for f in (self.touched + self.tracked + self.untracked))
+ files = ''.join('\n ' + f for f in self.files)
return 'ConfigSettings(%s, %s, %s, %s)' % (
self.command,
self.isolate_dir,
"""
return dict((k, v.flatten()) for k, v in self._by_config.iteritems())
- def make_isolate_file(self):
- """Returns a dictionary suitable for writing to a .isolate file.
- """
- dependencies_by_config = self.flatten()
- configs_by_dependency = reduce_inputs(invert_map(dependencies_by_config))
- return convert_map_to_isolate_dict(configs_by_dependency,
- self.config_variables)
-
def __str__(self):
return 'Configs(%s,%s)' % (
self._config_variables,
'command': [
...
],
- 'isolate_dependency_tracked': [
- ...
- ],
- 'isolate_dependency_untracked': [
+ 'files': [
...
],
'read_only': 0,
filtered for the specific OS.
Returns:
- tuple of command, dependencies, touched, read_only flag, isolate_dir.
+ tuple of command, dependencies, read_only flag, isolate_dir.
The dependencies are fixed to use os.path.sep.
"""
# Load the .isolate file, process its conditions, retrieve the command and
# A configuration is to be created with all the combinations of free
# variables.
config = isolate.get_config(config_name)
- # Merge tracked and untracked variables, isolate.py doesn't care about the
- # trackability of the variables, only the build tool does.
- dependencies = sorted(
- f.replace('/', os.path.sep) for f in config.tracked + config.untracked
- )
- touched = sorted(f.replace('/', os.path.sep) for f in config.touched)
- return (
- config.command, dependencies, touched, config.read_only,
- config.isolate_dir)
+ dependencies = [f.replace('/', os.path.sep) for f in config.files]
+ return config.command, dependencies, config.read_only, config.isolate_dir