Commit 271375b0 authored by maruel@chromium.org's avatar maruel@chromium.org

Move DEPS parsing into a single function. This is a move towards having each...

Move DEPS parsing into a single function. This is a move towards having each DEPS entry being a Dependency instance.

TEST=new revinfo unit tests

Review URL: http://codereview.chromium.org/2839008

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@50630 0039d316-1c4b-4281-b951-d872f2087c98
parent bbfc80f6
...@@ -166,6 +166,8 @@ class Dependency(GClientKeywords): ...@@ -166,6 +166,8 @@ class Dependency(GClientKeywords):
self.deps_hooks = [] self.deps_hooks = []
self.dependencies = [] self.dependencies = []
self.deps_file = deps_file or self.DEPS_FILE self.deps_file = deps_file or self.DEPS_FILE
self.deps_parsed = False
self.direct_reference = False
# Sanity checks # Sanity checks
if not self.name and self.parent: if not self.name and self.parent:
...@@ -179,49 +181,37 @@ class Dependency(GClientKeywords): ...@@ -179,49 +181,37 @@ class Dependency(GClientKeywords):
raise gclient_utils.Error('deps_file name must not be a path, just a ' raise gclient_utils.Error('deps_file name must not be a path, just a '
'filename. %s' % self.deps_file) 'filename. %s' % self.deps_file)
def _ParseSolutionDeps(self, solution_name, solution_deps_content, def ParseDepsFile(self, direct_reference):
custom_vars, parse_hooks): """Parses the DEPS file for this dependency."""
"""Parses the DEPS file for the specified solution. if direct_reference:
# Maybe it was referenced earlier by a From() keyword but it's now
Args: # directly referenced.
solution_name: The name of the solution to query. self.direct_reference = direct_reference
solution_deps_content: Content of the DEPS file for the solution self.deps_parsed = True
custom_vars: A dict of vars to override any vars defined in the DEPS file. filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
if not os.path.isfile(filepath):
Returns:
A dict mapping module names (as relative paths) to URLs or an empty
dict if the solution does not have a DEPS file.
"""
# Skip empty
if not solution_deps_content:
return {} return {}
# Eval the content deps_content = gclient_utils.FileRead(filepath)
# Eval the content.
# One thing is unintuitive, vars= {} must happen before Var() use.
local_scope = {} local_scope = {}
var = self.VarImpl(custom_vars, local_scope) var = self.VarImpl(self.custom_vars, local_scope)
global_scope = { global_scope = {
"File": self.FileImpl, 'File': self.FileImpl,
"From": self.FromImpl, 'From': self.FromImpl,
"Var": var.Lookup, 'Var': var.Lookup,
"deps_os": {}, 'deps_os': {},
} }
exec(solution_deps_content, global_scope, local_scope) exec(deps_content, global_scope, local_scope)
deps = local_scope.get("deps", {}) deps = local_scope.get('deps', {})
# load os specific dependencies if defined. these dependencies may # load os specific dependencies if defined. these dependencies may
# override or extend the values defined by the 'deps' member. # override or extend the values defined by the 'deps' member.
if "deps_os" in local_scope: if 'deps_os' in local_scope:
if self._options.deps_os is not None: for deps_os_key in self.enforced_os():
deps_to_include = self._options.deps_os.split(",") os_deps = local_scope['deps_os'].get(deps_os_key, {})
if "all" in deps_to_include: if len(self.enforced_os()) > 1:
deps_to_include = list(set(self.DEPS_OS_CHOICES.itervalues())) # Ignore any conflict when including deps for more than one
else:
deps_to_include = [self.DEPS_OS_CHOICES.get(sys.platform, "unix")]
deps_to_include = set(deps_to_include)
for deps_os_key in deps_to_include:
os_deps = local_scope["deps_os"].get(deps_os_key, {})
if len(deps_to_include) > 1:
# Ignore any overrides when including deps for more than one
# platform, so we collect the broadest set of dependencies available. # platform, so we collect the broadest set of dependencies available.
# We may end up with the wrong revision of something for our # We may end up with the wrong revision of something for our
# platform, but this is the best we can do. # platform, but this is the best we can do.
...@@ -229,36 +219,35 @@ class Dependency(GClientKeywords): ...@@ -229,36 +219,35 @@ class Dependency(GClientKeywords):
else: else:
deps.update(os_deps) deps.update(os_deps)
if 'hooks' in local_scope and parse_hooks: self.deps_hooks.extend(local_scope.get('hooks', []))
# TODO(maruel): Temporary Hack. Since this function is misplaced, find the
# right 'self' to add the hooks. # If a line is in custom_deps, but not in the solution, we want to append
for d in self.dependencies: # this line to the solution.
if d.name == solution_name: for d in self.custom_deps:
d.deps_hooks.extend(local_scope['hooks']) if d not in deps:
break deps[d] = self.custom_deps[d]
# If use_relative_paths is set in the DEPS file, regenerate # If use_relative_paths is set in the DEPS file, regenerate
# the dictionary using paths relative to the directory containing # the dictionary using paths relative to the directory containing
# the DEPS file. # the DEPS file.
if local_scope.get('use_relative_paths'): use_relative_paths = local_scope.get('use_relative_paths', False)
if use_relative_paths:
rel_deps = {} rel_deps = {}
for d, url in deps.items(): for d, url in deps.items():
# normpath is required to allow DEPS to use .. in their # normpath is required to allow DEPS to use .. in their
# dependency local path. # dependency local path.
rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
return rel_deps deps = rel_deps
else: # TODO(maruel): Add these dependencies into self.dependencies.
return deps return deps
def _ParseAllDeps(self, solution_urls, solution_deps_content): def _ParseAllDeps(self, solution_urls):
"""Parse the complete list of dependencies for the client. """Parse the complete list of dependencies for the client.
Args: Args:
solution_urls: A dict mapping module names (as relative paths) to URLs solution_urls: A dict mapping module names (as relative paths) to URLs
corresponding to the solutions specified by the client. This parameter corresponding to the solutions specified by the client. This parameter
is passed as an optimization. is passed as an optimization.
solution_deps_content: A dict mapping module names to the content
of their DEPS files
Returns: Returns:
A dict mapping module names (as relative paths) to URLs corresponding A dict mapping module names (as relative paths) to URLs corresponding
...@@ -269,11 +258,7 @@ class Dependency(GClientKeywords): ...@@ -269,11 +258,7 @@ class Dependency(GClientKeywords):
""" """
deps = {} deps = {}
for solution in self.dependencies: for solution in self.dependencies:
solution_deps = self._ParseSolutionDeps( solution_deps = solution.ParseDepsFile(True)
solution.name,
solution_deps_content[solution.name],
solution.custom_vars,
True)
# If a line is in custom_deps, but not in the solution, we want to append # If a line is in custom_deps, but not in the solution, we want to append
# this line to the solution. # this line to the solution.
...@@ -379,6 +364,12 @@ class Dependency(GClientKeywords): ...@@ -379,6 +364,12 @@ class Dependency(GClientKeywords):
if matching_file_list: if matching_file_list:
self._RunHookAction(hook_dict, matching_file_list) self._RunHookAction(hook_dict, matching_file_list)
def root_dir(self):
return self.parent.root_dir()
def enforced_os(self):
return self.parent.enforced_os()
class GClient(Dependency): class GClient(Dependency):
"""Main gclient checkout root where .gclient resides.""" """Main gclient checkout root where .gclient resides."""
...@@ -428,8 +419,15 @@ solutions = [ ...@@ -428,8 +419,15 @@ solutions = [
def __init__(self, root_dir, options): def __init__(self, root_dir, options):
Dependency.__init__(self, None, None, None) Dependency.__init__(self, None, None, None)
self._root_dir = root_dir
self._options = options self._options = options
if options.deps_os:
enforced_os = options.deps_os.split(',')
else:
enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
if 'all' in enforced_os:
enforced_os = self.DEPS_OS_CHOICES.itervalues()
self._enforced_os = list(set(enforced_os))
self._root_dir = root_dir
self.config_content = None self.config_content = None
def SetConfig(self, content): def SetConfig(self, content):
...@@ -567,7 +565,6 @@ solutions = [ ...@@ -567,7 +565,6 @@ solutions = [
run_scm = not (command == 'runhooks' and self._options.force) run_scm = not (command == 'runhooks' and self._options.force)
entries = {} entries = {}
entries_deps_content = {}
file_list = [] file_list = []
# Run on the base solutions first. # Run on the base solutions first.
for solution in self.dependencies: for solution in self.dependencies:
...@@ -582,18 +579,10 @@ solutions = [ ...@@ -582,18 +579,10 @@ solutions = [
scm.RunCommand(command, self._options, args, file_list) scm.RunCommand(command, self._options, args, file_list)
file_list = [os.path.join(name, f.strip()) for f in file_list] file_list = [os.path.join(name, f.strip()) for f in file_list]
self._options.revision = None self._options.revision = None
try:
deps_content = gclient_utils.FileRead(
os.path.join(self.root_dir(), name, solution.deps_file))
except IOError, e:
if e.errno != errno.ENOENT:
raise
deps_content = ""
entries_deps_content[name] = deps_content
# Process the dependencies next (sort alphanumerically to ensure that # Process the dependencies next (sort alphanumerically to ensure that
# containing directories get populated first and for readability) # containing directories get populated first and for readability)
deps = self._ParseAllDeps(entries, entries_deps_content) deps = self._ParseAllDeps(entries)
deps_to_process = deps.keys() deps_to_process = deps.keys()
deps_to_process.sort() deps_to_process.sort()
...@@ -625,16 +614,12 @@ solutions = [ ...@@ -625,16 +614,12 @@ solutions = [
# Second pass for inherited deps (via the From keyword) # Second pass for inherited deps (via the From keyword)
for d in deps_to_process: for d in deps_to_process:
if isinstance(deps[d], self.FromImpl): if isinstance(deps[d], self.FromImpl):
filename = os.path.join(self.root_dir(),
deps[d].module_name,
self.DEPS_FILE)
content = gclient_utils.FileRead(filename)
sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
False)
# Getting the URL from the sub_deps file can involve having to resolve # Getting the URL from the sub_deps file can involve having to resolve
# a File() or having to resolve a relative URL. To resolve relative # a File() or having to resolve a relative URL. To resolve relative
# URLs, we need to pass in the orignal sub deps URL. # URLs, we need to pass in the orignal sub deps URL.
sub_deps_base_url = deps[deps[d].module_name] sub_deps_base_url = deps[deps[d].module_name]
sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
).ParseDepsFile(False)
url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps) url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
entries[d] = url entries[d] = url
if run_scm: if run_scm:
...@@ -722,7 +707,6 @@ solutions = [ ...@@ -722,7 +707,6 @@ solutions = [
# Dictionary of { path : SCM url } to ensure no duplicate solutions # Dictionary of { path : SCM url } to ensure no duplicate solutions
solution_names = {} solution_names = {}
entries = {} entries = {}
entries_deps_content = {}
# Run on the base solutions first. # Run on the base solutions first.
for solution in self.dependencies: for solution in self.dependencies:
# Dictionary of { path : SCM url } to describe the gclient checkout # Dictionary of { path : SCM url } to describe the gclient checkout
...@@ -732,22 +716,10 @@ solutions = [ ...@@ -732,22 +716,10 @@ solutions = [
(url, rev) = GetURLAndRev(name, solution.url) (url, rev) = GetURLAndRev(name, solution.url)
entries[name] = "%s@%s" % (url, rev) entries[name] = "%s@%s" % (url, rev)
solution_names[name] = "%s@%s" % (url, rev) solution_names[name] = "%s@%s" % (url, rev)
deps_file = solution.deps_file
if '/' in deps_file or '\\' in deps_file:
raise gclient_utils.Error('deps_file name must not be a path, just a '
'filename.')
try:
deps_content = gclient_utils.FileRead(
os.path.join(self.root_dir(), name, deps_file))
except IOError, e:
if e.errno != errno.ENOENT:
raise
deps_content = ""
entries_deps_content[name] = deps_content
# Process the dependencies next (sort alphanumerically to ensure that # Process the dependencies next (sort alphanumerically to ensure that
# containing directories get populated first and for readability) # containing directories get populated first and for readability)
deps = self._ParseAllDeps(entries, entries_deps_content) deps = self._ParseAllDeps(entries)
deps_to_process = deps.keys() deps_to_process = deps.keys()
deps_to_process.sort() deps_to_process.sort()
...@@ -764,13 +736,11 @@ solutions = [ ...@@ -764,13 +736,11 @@ solutions = [
if deps_parent_url.find("@") < 0: if deps_parent_url.find("@") < 0:
raise gclient_utils.Error("From %s missing revisioned url" % raise gclient_utils.Error("From %s missing revisioned url" %
deps[d].module_name) deps[d].module_name)
content = gclient_utils.FileRead(os.path.join( sub_deps_base_url = deps[deps[d].module_name]
self.root_dir(), sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
deps[d].module_name, ).ParseDepsFile(False)
self.DEPS_FILE)) url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {}, (url, rev) = GetURLAndRev(d, url)
False)
(url, rev) = GetURLAndRev(d, sub_deps[d])
entries[d] = "%s@%s" % (url, rev) entries[d] = "%s@%s" % (url, rev)
# Build the snapshot configuration string # Build the snapshot configuration string
...@@ -799,6 +769,9 @@ solutions = [ ...@@ -799,6 +769,9 @@ solutions = [
def root_dir(self): def root_dir(self):
return self._root_dir return self._root_dir
def enforced_os(self):
return self._enforced_os
#### gclient commands. #### gclient commands.
......
...@@ -337,8 +337,8 @@ class FakeRepos(object): ...@@ -337,8 +337,8 @@ class FakeRepos(object):
# - deps_os # - deps_os
# - var # - var
# - hooks # - hooks
# TODO(maruel):
# - From # - From
# TODO(maruel):
# - File # - File
# - $matching_files # - $matching_files
# - use_relative_paths # - use_relative_paths
...@@ -360,8 +360,8 @@ deps_os = { ...@@ -360,8 +360,8 @@ deps_os = {
fs = file_system(2, """ fs = file_system(2, """
deps = { deps = {
'src/other': 'svn://%(host)s/svn/trunk/other', 'src/other': 'svn://%(host)s/svn/trunk/other',
'src/third_party/foo': '/trunk/third_party/foo@1', #'src/third_party/foo': '/trunk/third_party/foo@1',
#'src/third_party/foo': From('src/other', 'foo/bar'), 'src/third_party/foo': From('src/other', 'foo/bar'),
} }
# I think this is wrong to have the hooks run from the base of the gclient # I think this is wrong to have the hooks run from the base of the gclient
# checkout. It's maybe a bit too late to change that behavior. # checkout. It's maybe a bit too late to change that behavior.
...@@ -405,8 +405,8 @@ deps = { ...@@ -405,8 +405,8 @@ deps = {
# - deps_os # - deps_os
# - var # - var
# - hooks # - hooks
# TODO(maruel):
# - From # - From
# TODO(maruel):
# - File # - File
# - $matching_files # - $matching_files
# - use_relative_paths # - use_relative_paths
...@@ -467,8 +467,8 @@ deps = { ...@@ -467,8 +467,8 @@ deps = {
'DEPS': """ 'DEPS': """
deps = { deps = {
'src/repo2': 'git://%(host)s/git/repo_2@%(hash)s', 'src/repo2': 'git://%(host)s/git/repo_2@%(hash)s',
'src/repo2/repo_renamed': '/repo_3', #'src/repo2/repo_renamed': '/repo_3',
#'src/repo2/repo_renamed': From('src/repo2', 'foo/bar'), 'src/repo2/repo_renamed': From('src/repo2', 'foo/bar'),
} }
# I think this is wrong to have the hooks run from the base of the gclient # I think this is wrong to have the hooks run from the base of the gclient
# checkout. It's maybe a bit too late to change that behavior. # checkout. It's maybe a bit too late to change that behavior.
......
...@@ -283,6 +283,8 @@ class GClientSmokeSVN(GClientSmokeBase): ...@@ -283,6 +283,8 @@ class GClientSmokeSVN(GClientSmokeBase):
# matching. # matching.
results = self.gclient(['revert', '--deps', 'mac']) results = self.gclient(['revert', '--deps', 'mac'])
out = self.splitBlock(results[0]) out = self.splitBlock(results[0])
# src, src/other is missing, src/other, src/third_party/foo is missing,
# src/third_party/foo, 2 svn hooks.
self.assertEquals(7, len(out)) self.assertEquals(7, len(out))
self.checkString('', results[1]) self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
...@@ -375,7 +377,6 @@ class GClientSmokeSVN(GClientSmokeBase): ...@@ -375,7 +377,6 @@ class GClientSmokeSVN(GClientSmokeBase):
self.assertEquals([], out) self.assertEquals([], out)
def testRevInfo(self): def testRevInfo(self):
# TODO(maruel): Test multiple solutions.
self.gclient(['config', self.svn_base + 'trunk/src/']) self.gclient(['config', self.svn_base + 'trunk/src/'])
self.gclient(['sync', '--deps', 'mac']) self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac']) results = self.gclient(['revinfo', '--deps', 'mac'])
...@@ -586,6 +587,7 @@ class GClientSmokeBoth(GClientSmokeBase): ...@@ -586,6 +587,7 @@ class GClientSmokeBoth(GClientSmokeBase):
'{"name": "src-git",' '{"name": "src-git",'
'"url": "' + self.git_base + 'repo_1"}]']) '"url": "' + self.git_base + 'repo_1"}]'])
results = self.gclient(['sync', '--deps', 'mac']) results = self.gclient(['sync', '--deps', 'mac'])
# 3x svn checkout, 3x run hooks
self.checkBlock(results[0], self.checkBlock(results[0],
['running', 'running', 'running', 'running', 'running', ['running', 'running', 'running', 'running', 'running',
'running', 'running']) 'running', 'running'])
...@@ -633,6 +635,31 @@ class GClientSmokeBoth(GClientSmokeBase): ...@@ -633,6 +635,31 @@ class GClientSmokeBoth(GClientSmokeBase):
('trunk/third_party/foo@2', 'src/third_party/prout'))) ('trunk/third_party/foo@2', 'src/third_party/prout')))
self.assertTree(tree) self.assertTree(tree)
def testRevInfo(self):
if not self.enabled:
return
self.gclient(['config', '--spec',
'solutions=['
'{"name": "src",'
' "url": "' + self.svn_base + 'trunk/src/"},'
'{"name": "src-git",'
'"url": "' + self.git_base + 'repo_1"}]'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac'])
out = ('src: %(svn_base)s/src/@2;\n'
'src-git: %(git_base)srepo_1@%(hash1)s;\n'
'src/other: %(svn_base)s/other@2;\n'
'src/repo2: %(git_base)srepo_2@%(hash2)s;\n'
'src/repo2/repo_renamed: %(git_base)srepo_3@%(hash3)s;\n'
'src/third_party/foo: %(svn_base)s/third_party/foo@1\n') % {
'svn_base': self.svn_base + 'trunk',
'git_base': self.git_base,
'hash1': self.githash('repo_1', 2),
'hash2': self.githash('repo_2', 1),
'hash3': self.githash('repo_3', 2),
}
self.check((out, '', 0), results)
if __name__ == '__main__': if __name__ == '__main__':
if '-c' in sys.argv: if '-c' in sys.argv:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment