gclient: add use_relative_hooks
When a recursive dependency has use_relative_paths it also makes sense
to have the hooks working directory by the dependency's directory.
Otherwise if a hook uses one of the relative dependencies it is impossible
to know which path prefix to use.
However we cannot change the behavior of hooks with use_relative_paths
because it would break existing projects that use_relative_paths but
hardcoded the prefix for hooks. Instead we add a second boolean,
use_relative_hooks that triggers the behavior.
Adds tests for the new behavior and a test for existing interactio
between hooks and recursedeps.
BUG=chromium:875245
Change-Id: Ie4c526baa425ff887b3be54e0feca7c597ededec
Reviewed-on: https://chromium-review.googlesource.com/1213327
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Edward Lesmes <ehmaldonado@chromium.org>
diff --git a/gclient.py b/gclient.py
index f65af57..1637190 100755
--- a/gclient.py
+++ b/gclient.py
@@ -151,7 +151,7 @@
"""Descriptor of command ran before/after sync or on demand."""
def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
- variables=None, verbose=False):
+ variables=None, verbose=False, cwd_base=None):
"""Constructor.
Arguments:
@@ -169,9 +169,11 @@
self._condition = condition
self._variables = variables
self._verbose = verbose
+ self._cwd_base = cwd_base
@staticmethod
- def from_dict(d, variables=None, verbose=False, conditions=None):
+ def from_dict(d, variables=None, verbose=False, conditions=None,
+ cwd_base=None):
"""Creates a Hook instance from a dict like in the DEPS file."""
# Merge any local and inherited conditions.
gclient_eval.UpdateCondition(d, 'and', conditions)
@@ -183,7 +185,8 @@
d.get('condition'),
variables=variables,
# Always print the header if not printing to a TTY.
- verbose=verbose or not setup_color.IS_TTY)
+ verbose=verbose or not setup_color.IS_TTY,
+ cwd_base=cwd_base)
@property
def action(self):
@@ -201,6 +204,13 @@
def condition(self):
return self._condition
+ @property
+ def effective_cwd(self):
+ cwd = self._cwd_base
+ if self._cwd:
+ cwd = os.path.join(cwd, self._cwd)
+ return cwd
+
def matches(self, file_list):
"""Returns true if the pattern matches any of files in the list."""
if not self._pattern:
@@ -208,7 +218,7 @@
pattern = re.compile(self._pattern)
return bool([f for f in file_list if pattern.search(f)])
- def run(self, root):
+ def run(self):
"""Executes the hook's command (provided the condition is met)."""
if (self._condition and
not gclient_eval.EvaluateCondition(self._condition, self._variables)):
@@ -224,13 +234,10 @@
elif cmd[0] == 'vpython' and _detect_host_os() == 'win':
cmd[0] += '.bat'
- cwd = root
- if self._cwd:
- cwd = os.path.join(cwd, self._cwd)
try:
start_time = time.time()
gclient_utils.CheckCallAndFilterAndHeader(
- cmd, cwd=cwd, always=self._verbose)
+ cmd, cwd=self.effective_cwd, always=self._verbose)
except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
# Use a discrete exit status code of 2 to indicate that a hook action
# failed. Users of this script may wish to treat hook action failures
@@ -755,6 +762,18 @@
deps_to_add = self._deps_to_objects(
self._postprocess_deps(deps, rel_prefix), use_relative_paths)
+ # compute which working directory should be used for hooks
+ use_relative_hooks = local_scope.get('use_relative_hooks', False)
+ hooks_cwd = self.root.root_dir
+ if use_relative_hooks:
+ if not use_relative_paths:
+ raise gclient_utils.Error(
+ 'ParseDepsFile(%s): use_relative_hooks must be used with '
+ 'use_relative_paths' % self.name)
+ hooks_cwd = os.path.join(hooks_cwd, self.name)
+ logging.warning('Updating hook base working directory to %s.',
+ hooks_cwd)
+
# override named sets of hooks by the custom hooks
hooks_to_run = []
hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
@@ -770,11 +789,12 @@
if self.should_recurse:
self._pre_deps_hooks = [
Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
- conditions=self.condition)
+ conditions=self.condition, cwd_base=hooks_cwd)
for hook in local_scope.get('pre_deps_hooks', [])
]
- self.add_dependencies_and_close(deps_to_add, hooks_to_run)
+ self.add_dependencies_and_close(deps_to_add, hooks_to_run,
+ hooks_cwd=hooks_cwd)
logging.info('ParseDepsFile(%s) done' % self.name)
def _get_option(self, attr, default):
@@ -783,15 +803,18 @@
obj = obj.parent
return getattr(obj._options, attr, default)
- def add_dependencies_and_close(self, deps_to_add, hooks):
+ def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
"""Adds the dependencies, hooks and mark the parsing as done."""
+ if hooks_cwd == None:
+ hooks_cwd = self.root.root_dir
+
for dep in deps_to_add:
if dep.verify_validity():
self.add_dependency(dep)
self._mark_as_parsed([
Hook.from_dict(
h, variables=self.get_vars(), verbose=self.root._options.verbose,
- conditions=self.condition)
+ conditions=self.condition, cwd_base=hooks_cwd)
for h in hooks
])
@@ -1021,7 +1044,7 @@
for hook in hooks:
if progress:
progress.update(extra=hook.name or '')
- hook.run(self.root.root_dir)
+ hook.run()
if progress:
progress.end()
@@ -1034,7 +1057,7 @@
assert not s.processed
self._pre_deps_hooks_ran = True
for hook in self.pre_deps_hooks:
- hook.run(self.root.root_dir)
+ hook.run()
def GetCipdRoot(self):
if self.root is self:
@@ -2257,9 +2280,10 @@
s.append(' "pattern": "%s",' % hook.pattern)
if hook.condition is not None:
s.append(' "condition": %r,' % hook.condition)
+ # Flattened hooks need to be written relative to the root gclient dir
+ cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
s.extend(
- # Hooks run in the parent directory of their dep.
- [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
+ [' "cwd": "%s",' % cwd] +
[' "action": ['] +
[' "%s",' % arg for arg in hook.action] +
[' ]', ' },', '']
@@ -2286,9 +2310,10 @@
s.append(' "pattern": "%s",' % hook.pattern)
if hook.condition is not None:
s.append(' "condition": %r,' % hook.condition)
+ # Flattened hooks need to be written relative to the root gclient dir
+ cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
s.extend(
- # Hooks run in the parent directory of their dep.
- [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
+ [' "cwd": "%s",' % cwd] +
[' "action": ['] +
[' "%s",' % arg for arg in hook.action] +
[' ]', ' },', '']