bisect-kit: support non-default DEPS file name
In gclient's recursedeps entries, DEPS filename could be configurable,
not always "DEPS". Recently one such entry was added to src-internal.
BUG=None
TEST=bisect_cr_localbuild_internal with branch after 3579
Change-Id: I5b850399b6dd7f0405ef6fee4abd5276cdac070f
Reviewed-on: https://chromium-review.googlesource.com/1297557
Commit-Ready: Kuang-che Wu <kcwu@chromium.org>
Tested-by: Kuang-che Wu <kcwu@chromium.org>
Reviewed-by: Chi-Ngai Wan <cnwan@google.com>
diff --git a/bisect_kit/gclient_util.py b/bisect_kit/gclient_util.py
index a84c68b..4a00b90 100644
--- a/bisect_kit/gclient_util.py
+++ b/bisect_kit/gclient_util.py
@@ -222,17 +222,17 @@
# TODO(kcwu): refactor git_util.get_history_recursively() to reuse this class.
- def __init__(self, parent_deps, name, start_time, end_time):
+ def __init__(self, parent_deps, entry, start_time, end_time):
"""TimeSeriesTree constructor.
Args:
parent_deps: parent DEPS of the given period. None if this is tree root.
- name: project name
+ entry: project entry
start_time: start time
end_time: end time
"""
self.parent_deps = parent_deps
- self.name = name
+ self.entry = entry
self.snapshots = {}
self.start_time = start_time
self.end_time = end_time
@@ -242,28 +242,29 @@
self.alive_children = {}
# All historical children (TimeSeriesTree object) between start_time and
- # end_time. It's possible that children with the same name appear more than
+ # end_time. It's possible that children with the same entry appear more than
# once in this list because they are removed and added back to the DEPS
# file.
self.subtrees = []
- def subtree_eq(self, deps_a, deps_b, child_name):
+ def subtree_eq(self, deps_a, deps_b, child_entry):
"""Compares subtree of two Deps.
Args:
deps_a: Deps object
deps_b: Deps object
- child_name: the subtree to compare
+ child_entry: the subtree to compare
Returns:
True if the said subtree of these two Deps equal
"""
# Need to compare variables because they may influence subtree parsing
# behavior
- return (deps_a.entries[child_name] == deps_b.entries[child_name] and
+ path = child_entry[0]
+ return (deps_a.entries[path] == deps_b.entries[path] and
deps_a.variables == deps_b.variables)
- def add_snapshot(self, timestamp, deps, children_names):
+ def add_snapshot(self, timestamp, deps, children_entries):
"""Adds parsed DEPS result and children.
For example, if a given DEPS file has N revisions between start_time and
@@ -273,42 +274,42 @@
Args:
timestamp: timestamp of `deps`
deps: Deps object
- children_names: list of names of deps' children
+ children_entries: list of names of deps' children
"""
assert timestamp not in self.snapshots
self.snapshots[timestamp] = deps
- for child_name in set(self.alive_children.keys() + children_names):
- # `child_name` is added at `timestamp`
- if child_name not in self.alive_children:
- self.alive_children[child_name] = timestamp, deps
+ for child_entry in set(self.alive_children.keys() + children_entries):
+ # `child_entry` is added at `timestamp`
+ if child_entry not in self.alive_children:
+ self.alive_children[child_entry] = timestamp, deps
- # `child_name` is removed at `timestamp`
- elif child_name not in children_names:
+ # `child_entry` is removed at `timestamp`
+ elif child_entry not in children_entries:
self.subtrees.append(
- TimeSeriesTree(self.alive_children[child_name][1], child_name,
- self.alive_children[child_name][0], timestamp))
- del self.alive_children[child_name]
+ TimeSeriesTree(self.alive_children[child_entry][1], child_entry,
+ self.alive_children[child_entry][0], timestamp))
+ del self.alive_children[child_entry]
- # `child_name` is alive before and after `timestamp`
+ # `child_entry` is alive before and after `timestamp`
else:
- last_deps = self.alive_children[child_name][1]
- if not self.subtree_eq(last_deps, deps, child_name):
+ last_deps = self.alive_children[child_entry][1]
+ if not self.subtree_eq(last_deps, deps, child_entry):
self.subtrees.append(
- TimeSeriesTree(last_deps, child_name,
- self.alive_children[child_name][0], timestamp))
- self.alive_children[child_name] = timestamp, deps
+ TimeSeriesTree(last_deps, child_entry,
+ self.alive_children[child_entry][0], timestamp))
+ self.alive_children[child_entry] = timestamp, deps
def no_more_snapshot(self, deps):
"""Indicates all snapshots are added.
add_snapshot() should not be invoked after no_more_snapshot().
"""
- for child_name, (timestamp, deps) in self.alive_children.items():
+ for child_entry, (timestamp, deps) in self.alive_children.items():
if timestamp == self.end_time:
continue
self.subtrees.append(
- TimeSeriesTree(deps, child_name, timestamp, self.end_time))
+ TimeSeriesTree(deps, child_entry, timestamp, self.end_time))
self.alive_children = None
def events(self):
@@ -329,11 +330,11 @@
last_deps = None
for timestamp, deps in self.snapshots.items():
- result.append((timestamp, self.name, deps, False))
+ result.append((timestamp, self.entry, deps, False))
last_deps = deps
assert last_deps
- result.append((self.end_time, self.name, last_deps, True))
+ result.append((self.end_time, self.entry, last_deps, True))
for subtree in self.subtrees:
for event in subtree.events():
@@ -359,12 +360,12 @@
# i.e. modification, so use counter to track.
end_counter = collections.Counter()
- for timestamp, name, deps, end in events:
- forest[name] = deps
+ for timestamp, entry, deps, end in events:
+ forest[entry] = deps
if end:
- end_counter[name] += 1
+ end_counter[entry] += 1
else:
- end_counter[name] -= 1
+ end_counter[entry] -= 1
# Merge Deps at time `timestamp` into single path_specs.
path_specs = {}
@@ -375,10 +376,10 @@
yield timestamp, path_specs
# Remove deps which are removed at this timestamp.
- for name, count in end_counter.items():
- assert -1 <= count <= 1, (timestamp, name)
+ for entry, count in end_counter.items():
+ assert -1 <= count <= 1, (timestamp, entry)
if count == 1:
- del forest[name]
+ del forest[entry]
class DepsParser(object):
@@ -441,13 +442,19 @@
deps.entries[path] = dep
recursedeps = []
- for path in local_scope.get('recursedeps', []):
- assert isinstance(path, str)
+ for recurse_entry in local_scope.get('recursedeps', []):
+ # Normalize entries.
+ if isinstance(recurse_entry, tuple):
+ path, deps_file = recurse_entry
+ else:
+ assert isinstance(path, str)
+ path, deps_file = recurse_entry, 'DEPS'
+
if local_scope.get('use_relative_paths', False):
path = os.path.join(parent_path, path)
path = path.format(**deps.variables)
if path in deps.entries:
- recursedeps.append(path)
+ recursedeps.append((path, deps_file))
deps.recursedeps = recursedeps
return deps
@@ -515,7 +522,7 @@
tstree.no_more_snapshot(deps)
for subtree in tstree.subtrees:
- path = subtree.name
+ path, deps_file = subtree.entry
path_spec = subtree.parent_deps.entries[path].as_path_spec()
self.construct_deps_tree(
subtree,
@@ -524,7 +531,8 @@
subtree.start_time,
subtree.end_time,
parent_vars=subtree.parent_deps.variables,
- parent_path=path)
+ parent_path=path,
+ deps_file=deps_file)
def enumerate_path_specs(self, start_time, end_time, path):
tstree = TimeSeriesTree(None, path, start_time, end_time)
diff --git a/bisect_kit/gclient_util_test.py b/bisect_kit/gclient_util_test.py
new file mode 100644
index 0000000..36fd969
--- /dev/null
+++ b/bisect_kit/gclient_util_test.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright 2018 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Test gclient_util module."""
+
+from __future__ import print_function
+import unittest
+import textwrap
+
+from bisect_kit import gclient_util
+
+
+class TestDepsParser(unittest.TestCase):
+ """Tests gclient_util.DepsParser."""
+
+ def test_parse_single_deps(self):
+ deps_content = textwrap.dedent('''\
+ vars = {
+ 'chromium_git': 'https://chromium.googlesource.com',
+ 'buildtools_revision': 'refs/heads/master',
+ 'checkout_foo': False,
+ 'checkout_bar': True,
+ }
+
+ deps = {
+ 'src/buildtools':
+ Var('chromium_git') + '/chromium/buildtools.git' + '@' +
+ Var('buildtools_revision'),
+ 'src/foo': {
+ 'url': Var('chromium_git') + '/chromium/foo.git' + '@' +
+ 'refs/heads/master',
+ 'condition': 'checkout_foo',
+ },
+ 'src/bar': {
+ 'url': Var('chromium_git') + '/chromium/bar.git' + '@' +
+ 'refs/heads/master',
+ 'condition': 'checkout_bar',
+ },
+ }
+
+ recursedeps = [
+ 'src/buildtools',
+ 'src/foo',
+ 'src/bar',
+ ]
+ ''')
+
+ parser = gclient_util.DepsParser('/dummy', None)
+ deps = parser.parse_single_deps(deps_content)
+
+ buildtools = deps.entries['src/buildtools']
+ self.assertEqual(buildtools.dep_type, 'git')
+ self.assertEqual(
+ buildtools.url,
+ 'https://chromium.googlesource.com/chromium/buildtools.git'
+ '@refs/heads/master')
+
+ self.assertIn(('src/buildtools', 'DEPS'), deps.recursedeps)
+ self.assertNotIn(('src/foo', 'DEPS'), deps.recursedeps)
+ self.assertIn(('src/bar', 'DEPS'), deps.recursedeps)
+
+ def test_parse_dep_type(self):
+ deps_content = textwrap.dedent('''\
+ deps = {
+ 'src/foo': {
+ 'packages': [
+ {
+ 'package': 'foo',
+ 'version': 'version:1.0',
+ }
+ ],
+ 'dep_type': 'cipd',
+ 'condition': 'False',
+ },
+ }
+ ''')
+
+ parser = gclient_util.DepsParser('/dummy', None)
+ deps = parser.parse_single_deps(deps_content)
+ # We don't support cipd yet. This test just make sure parsing is not
+ # broken.
+ self.assertEqual(len(deps.entries), 0)
+
+ def test_parse_recursedeps(self):
+ deps_content = textwrap.dedent('''\
+ deps = {
+ 'src/foo': 'http://example.com/foo.git@refs/heads/master',
+ 'src/bar': 'http://example.com/bar.git@refs/heads/master',
+ }
+
+ recursedeps = [
+ 'src/foo',
+ ('src/bar', 'DEPS.bar'),
+ ]
+ ''')
+
+ parser = gclient_util.DepsParser('/dummy', None)
+ deps = parser.parse_single_deps(deps_content)
+ self.assertIn(('src/foo', 'DEPS'), deps.recursedeps)
+ self.assertIn(('src/bar', 'DEPS.bar'), deps.recursedeps)
+
+
+if __name__ == '__main__':
+ unittest.main()