|
|
@ -49,8 +49,9 @@ Hooks
|
|
|
|
]
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
__version__ = "0.5"
|
|
|
|
__version__ = "0.4.1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import errno
|
|
|
|
import logging
|
|
|
|
import logging
|
|
|
|
import optparse
|
|
|
|
import optparse
|
|
|
|
import os
|
|
|
|
import os
|
|
|
@ -96,6 +97,22 @@ class GClientKeywords(object):
|
|
|
|
return 'From(%s, %s)' % (repr(self.module_name),
|
|
|
|
return 'From(%s, %s)' % (repr(self.module_name),
|
|
|
|
repr(self.sub_target_name))
|
|
|
|
repr(self.sub_target_name))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
|
|
|
|
|
|
|
|
"""Resolve the URL for this From entry."""
|
|
|
|
|
|
|
|
sub_deps_target_name = target_name
|
|
|
|
|
|
|
|
if self.sub_target_name:
|
|
|
|
|
|
|
|
sub_deps_target_name = self.sub_target_name
|
|
|
|
|
|
|
|
url = sub_deps[sub_deps_target_name]
|
|
|
|
|
|
|
|
if url.startswith('/'):
|
|
|
|
|
|
|
|
# If it's a relative URL, we need to resolve the URL relative to the
|
|
|
|
|
|
|
|
# sub deps base URL.
|
|
|
|
|
|
|
|
if not isinstance(sub_deps_base_url, basestring):
|
|
|
|
|
|
|
|
sub_deps_base_url = sub_deps_base_url.GetPath()
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
|
|
|
|
|
|
|
|
None)
|
|
|
|
|
|
|
|
url = scm.FullUrlForRelativeUrl(url)
|
|
|
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
|
|
class FileImpl(object):
|
|
|
|
class FileImpl(object):
|
|
|
|
"""Used to implement the File('') syntax which lets you sync a single file
|
|
|
|
"""Used to implement the File('') syntax which lets you sync a single file
|
|
|
|
from a SVN repo."""
|
|
|
|
from a SVN repo."""
|
|
|
@ -143,7 +160,6 @@ class Dependency(GClientKeywords):
|
|
|
|
self.parent = parent
|
|
|
|
self.parent = parent
|
|
|
|
self.name = name
|
|
|
|
self.name = name
|
|
|
|
self.url = url
|
|
|
|
self.url = url
|
|
|
|
self.parsed_url = None
|
|
|
|
|
|
|
|
# These 2 are only set in .gclient and not in DEPS files.
|
|
|
|
# These 2 are only set in .gclient and not in DEPS files.
|
|
|
|
self.safesync_url = safesync_url
|
|
|
|
self.safesync_url = safesync_url
|
|
|
|
self.custom_vars = custom_vars or {}
|
|
|
|
self.custom_vars = custom_vars or {}
|
|
|
@ -151,18 +167,12 @@ 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
|
|
|
|
# A cache of the files affected by the current operation, necessary for
|
|
|
|
|
|
|
|
# hooks.
|
|
|
|
|
|
|
|
self.file_list = []
|
|
|
|
|
|
|
|
self.deps_parsed = False
|
|
|
|
self.deps_parsed = False
|
|
|
|
self.direct_reference = False
|
|
|
|
self.direct_reference = False
|
|
|
|
|
|
|
|
|
|
|
|
# Sanity checks
|
|
|
|
# Sanity checks
|
|
|
|
if not self.name and self.parent:
|
|
|
|
if not self.name and self.parent:
|
|
|
|
raise gclient_utils.Error('Dependency without name')
|
|
|
|
raise gclient_utils.Error('Dependency without name')
|
|
|
|
if self.name in [d.name for d in self.tree(False)]:
|
|
|
|
|
|
|
|
raise gclient_utils.Error('Dependency %s specified more than once' %
|
|
|
|
|
|
|
|
self.name)
|
|
|
|
|
|
|
|
if not isinstance(self.url,
|
|
|
|
if not isinstance(self.url,
|
|
|
|
(basestring, self.FromImpl, self.FileImpl, None.__class__)):
|
|
|
|
(basestring, self.FromImpl, self.FileImpl, None.__class__)):
|
|
|
|
raise gclient_utils.Error('dependency url must be either a string, None, '
|
|
|
|
raise gclient_utils.Error('dependency url must be either a string, None, '
|
|
|
@ -172,65 +182,16 @@ 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 LateOverride(self, url):
|
|
|
|
|
|
|
|
overriden_url = self.get_custom_deps(self.name, url)
|
|
|
|
|
|
|
|
if overriden_url != url:
|
|
|
|
|
|
|
|
self.parsed_url = overriden_url
|
|
|
|
|
|
|
|
logging.debug('%s, %s was overriden to %s' % (self.name, url,
|
|
|
|
|
|
|
|
self.parsed_url))
|
|
|
|
|
|
|
|
elif isinstance(url, self.FromImpl):
|
|
|
|
|
|
|
|
ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
|
|
|
|
|
|
|
|
if not len(ref) == 1:
|
|
|
|
|
|
|
|
raise Exception('Failed to find one reference to %s. %s' % (
|
|
|
|
|
|
|
|
url.module_name, ref))
|
|
|
|
|
|
|
|
ref = ref[0]
|
|
|
|
|
|
|
|
sub_target = url.sub_target_name or url
|
|
|
|
|
|
|
|
# Make sure the referenced dependency DEPS file is loaded and file the
|
|
|
|
|
|
|
|
# inner referenced dependency.
|
|
|
|
|
|
|
|
ref.ParseDepsFile(False)
|
|
|
|
|
|
|
|
found_dep = None
|
|
|
|
|
|
|
|
for d in ref.dependencies:
|
|
|
|
|
|
|
|
if d.name == sub_target:
|
|
|
|
|
|
|
|
found_dep = d
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
if not found_dep:
|
|
|
|
|
|
|
|
raise Exception('Couldn\'t find %s in %s, referenced by %s' % (
|
|
|
|
|
|
|
|
sub_target, ref.name, self.name))
|
|
|
|
|
|
|
|
# Call LateOverride() again.
|
|
|
|
|
|
|
|
self.parsed_url = found_dep.LateOverride(found_dep.url)
|
|
|
|
|
|
|
|
logging.debug('%s, %s to %s' % (self.name, url, self.parsed_url))
|
|
|
|
|
|
|
|
elif isinstance(url, basestring):
|
|
|
|
|
|
|
|
parsed_url = urlparse.urlparse(url)
|
|
|
|
|
|
|
|
if not parsed_url[0]:
|
|
|
|
|
|
|
|
# A relative url. Fetch the real base.
|
|
|
|
|
|
|
|
path = parsed_url[2]
|
|
|
|
|
|
|
|
if not path.startswith('/'):
|
|
|
|
|
|
|
|
raise gclient_utils.Error(
|
|
|
|
|
|
|
|
'relative DEPS entry \'%s\' must begin with a slash' % url)
|
|
|
|
|
|
|
|
# Create a scm just to query the full url.
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(self.parent.parsed_url, self.root_dir(),
|
|
|
|
|
|
|
|
None)
|
|
|
|
|
|
|
|
self.parsed_url = scm.FullUrlForRelativeUrl(url)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self.parsed_url = url
|
|
|
|
|
|
|
|
logging.debug('%s, %s -> %s' % (self.name, url, self.parsed_url))
|
|
|
|
|
|
|
|
elif isinstance(url, self.FileImpl):
|
|
|
|
|
|
|
|
self.parsed_url = url
|
|
|
|
|
|
|
|
logging.debug('%s, %s -> %s (File)' % (self.name, url, self.parsed_url))
|
|
|
|
|
|
|
|
return self.parsed_url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ParseDepsFile(self, direct_reference):
|
|
|
|
def ParseDepsFile(self, direct_reference):
|
|
|
|
"""Parses the DEPS file for this dependency."""
|
|
|
|
"""Parses the DEPS file for this dependency."""
|
|
|
|
if direct_reference:
|
|
|
|
if direct_reference:
|
|
|
|
# Maybe it was referenced earlier by a From() keyword but it's now
|
|
|
|
# Maybe it was referenced earlier by a From() keyword but it's now
|
|
|
|
# directly referenced.
|
|
|
|
# directly referenced.
|
|
|
|
self.direct_reference = direct_reference
|
|
|
|
self.direct_reference = direct_reference
|
|
|
|
if self.deps_parsed:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
self.deps_parsed = True
|
|
|
|
self.deps_parsed = True
|
|
|
|
filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
|
|
|
|
filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
|
|
|
|
if not os.path.isfile(filepath):
|
|
|
|
if not os.path.isfile(filepath):
|
|
|
|
return
|
|
|
|
return {}
|
|
|
|
deps_content = gclient_utils.FileRead(filepath)
|
|
|
|
deps_content = gclient_utils.FileRead(filepath)
|
|
|
|
|
|
|
|
|
|
|
|
# Eval the content.
|
|
|
|
# Eval the content.
|
|
|
@ -281,104 +242,110 @@ class Dependency(GClientKeywords):
|
|
|
|
# dependency local path.
|
|
|
|
# dependency local path.
|
|
|
|
rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
|
|
|
|
rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
|
|
|
|
deps = rel_deps
|
|
|
|
deps = rel_deps
|
|
|
|
|
|
|
|
# TODO(maruel): Add these dependencies into self.dependencies.
|
|
|
|
|
|
|
|
return deps
|
|
|
|
|
|
|
|
|
|
|
|
# Convert the deps into real Dependency.
|
|
|
|
def _ParseAllDeps(self, solution_urls):
|
|
|
|
for name, url in deps.iteritems():
|
|
|
|
"""Parse the complete list of dependencies for the client.
|
|
|
|
if name in [s.name for s in self.dependencies]:
|
|
|
|
|
|
|
|
raise
|
|
|
|
Args:
|
|
|
|
self.dependencies.append(Dependency(self, name, url))
|
|
|
|
solution_urls: A dict mapping module names (as relative paths) to URLs
|
|
|
|
# Sort by name.
|
|
|
|
corresponding to the solutions specified by the client. This parameter
|
|
|
|
self.dependencies.sort(key=lambda x: x.name)
|
|
|
|
is passed as an optimization.
|
|
|
|
logging.info('Loaded: %s' % str(self))
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
def RunCommandRecursively(self, options, revision_overrides,
|
|
|
|
A dict mapping module names (as relative paths) to URLs corresponding
|
|
|
|
command, args, pm):
|
|
|
|
to the entire set of dependencies to checkout for the given client.
|
|
|
|
"""Runs 'command' before parsing the DEPS in case it's a initial checkout
|
|
|
|
|
|
|
|
or a revert."""
|
|
|
|
Raises:
|
|
|
|
assert self.file_list == []
|
|
|
|
Error: If a dependency conflicts with another dependency or of a solution.
|
|
|
|
# When running runhooks, there's no need to consult the SCM.
|
|
|
|
"""
|
|
|
|
# All known hooks are expected to run unconditionally regardless of working
|
|
|
|
deps = {}
|
|
|
|
# copy state, so skip the SCM status check.
|
|
|
|
for solution in self.dependencies:
|
|
|
|
run_scm = command not in ('runhooks', None)
|
|
|
|
solution_deps = solution.ParseDepsFile(True)
|
|
|
|
self.LateOverride(self.url)
|
|
|
|
|
|
|
|
if run_scm and self.parsed_url:
|
|
|
|
# If a line is in custom_deps, but not in the solution, we want to append
|
|
|
|
if isinstance(self.parsed_url, self.FileImpl):
|
|
|
|
# this line to the solution.
|
|
|
|
# Special support for single-file checkout.
|
|
|
|
for d in solution.custom_deps:
|
|
|
|
if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
|
|
|
|
if d not in solution_deps:
|
|
|
|
options.revision = self.parsed_url.GetRevision()
|
|
|
|
solution_deps[d] = solution.custom_deps[d]
|
|
|
|
scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
|
|
|
|
|
|
|
|
self.root_dir(),
|
|
|
|
for d in solution_deps:
|
|
|
|
self.name)
|
|
|
|
if d in solution.custom_deps:
|
|
|
|
scm.RunCommand('updatesingle', options,
|
|
|
|
# Dependency is overriden.
|
|
|
|
args + [self.parsed_url.GetFilename()],
|
|
|
|
url = solution.custom_deps[d]
|
|
|
|
self.file_list)
|
|
|
|
if url is None:
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
options.revision = revision_overrides.get(self.name)
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
|
|
|
|
|
|
|
|
scm.RunCommand(command, options, args, self.file_list)
|
|
|
|
|
|
|
|
self.file_list = [os.path.join(self.name, f.strip())
|
|
|
|
|
|
|
|
for f in self.file_list]
|
|
|
|
|
|
|
|
options.revision = None
|
|
|
|
|
|
|
|
if pm:
|
|
|
|
|
|
|
|
# The + 1 comes from the fact that .gclient is considered a step in
|
|
|
|
|
|
|
|
# itself, .i.e. this code is called one time for the .gclient. This is not
|
|
|
|
|
|
|
|
# conceptually correct but it simplifies code.
|
|
|
|
|
|
|
|
pm._total = len(self.tree(False)) + 1
|
|
|
|
|
|
|
|
pm.update()
|
|
|
|
|
|
|
|
if self.recursion_limit():
|
|
|
|
|
|
|
|
# Then we can parse the DEPS file.
|
|
|
|
|
|
|
|
self.ParseDepsFile(True)
|
|
|
|
|
|
|
|
if pm:
|
|
|
|
|
|
|
|
pm._total = len(self.tree(False)) + 1
|
|
|
|
|
|
|
|
pm.update(0)
|
|
|
|
|
|
|
|
# Parse the dependencies of this dependency.
|
|
|
|
|
|
|
|
for s in self.dependencies:
|
|
|
|
|
|
|
|
# TODO(maruel): All these can run concurrently! No need for threads,
|
|
|
|
|
|
|
|
# just buffer stdout&stderr on pipes and flush as they complete.
|
|
|
|
|
|
|
|
# Watch out for stdin.
|
|
|
|
|
|
|
|
s.RunCommandRecursively(options, revision_overrides, command, args, pm)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def RunHooksRecursively(self, options):
|
|
|
|
|
|
|
|
"""Evaluates all hooks, running actions as needed. RunCommandRecursively()
|
|
|
|
|
|
|
|
must have been called before to load the DEPS."""
|
|
|
|
|
|
|
|
# If "--force" was specified, run all hooks regardless of what files have
|
|
|
|
|
|
|
|
# changed.
|
|
|
|
|
|
|
|
if self.deps_hooks:
|
|
|
|
|
|
|
|
# TODO(maruel): If the user is using git or git-svn, then we don't know
|
|
|
|
|
|
|
|
# what files have changed so we always run all hooks. It'd be nice to fix
|
|
|
|
|
|
|
|
# that.
|
|
|
|
|
|
|
|
if (options.force or
|
|
|
|
|
|
|
|
gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
|
|
|
|
|
|
|
|
os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
|
|
|
|
|
|
|
|
for hook_dict in self.deps_hooks:
|
|
|
|
|
|
|
|
self._RunHookAction(hook_dict, [])
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
# TODO(phajdan.jr): We should know exactly when the paths are absolute.
|
|
|
|
url = solution_deps[d]
|
|
|
|
# Convert all absolute paths to relative.
|
|
|
|
# if we have a From reference dependent on another solution, then
|
|
|
|
for i in range(len(self.file_list)):
|
|
|
|
# just skip the From reference. When we pull deps for the solution,
|
|
|
|
# It depends on the command being executed (like runhooks vs sync).
|
|
|
|
# we will take care of this dependency.
|
|
|
|
if not os.path.isabs(self.file_list[i]):
|
|
|
|
#
|
|
|
|
|
|
|
|
# If multiple solutions all have the same From reference, then we
|
|
|
|
|
|
|
|
# should only add one to our list of dependencies.
|
|
|
|
|
|
|
|
if isinstance(url, self.FromImpl):
|
|
|
|
|
|
|
|
if url.module_name in solution_urls:
|
|
|
|
|
|
|
|
# Already parsed.
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
|
|
|
|
if d in deps and type(deps[d]) != str:
|
|
|
|
|
|
|
|
if url.module_name == deps[d].module_name:
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
elif isinstance(url, str):
|
|
|
|
|
|
|
|
parsed_url = urlparse.urlparse(url)
|
|
|
|
|
|
|
|
scheme = parsed_url[0]
|
|
|
|
|
|
|
|
if not scheme:
|
|
|
|
|
|
|
|
# A relative url. Fetch the real base.
|
|
|
|
|
|
|
|
path = parsed_url[2]
|
|
|
|
|
|
|
|
if path[0] != "/":
|
|
|
|
|
|
|
|
raise gclient_utils.Error(
|
|
|
|
|
|
|
|
"relative DEPS entry \"%s\" must begin with a slash" % d)
|
|
|
|
|
|
|
|
# Create a scm just to query the full url.
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
|
|
|
|
|
|
|
|
None)
|
|
|
|
|
|
|
|
url = scm.FullUrlForRelativeUrl(url)
|
|
|
|
|
|
|
|
if d in deps and deps[d] != url:
|
|
|
|
|
|
|
|
raise gclient_utils.Error(
|
|
|
|
|
|
|
|
"Solutions have conflicting versions of dependency \"%s\"" % d)
|
|
|
|
|
|
|
|
if d in solution_urls and solution_urls[d] != url:
|
|
|
|
|
|
|
|
raise gclient_utils.Error(
|
|
|
|
|
|
|
|
"Dependency \"%s\" conflicts with specified solution" % d)
|
|
|
|
|
|
|
|
# Grab the dependency.
|
|
|
|
|
|
|
|
deps[d] = url
|
|
|
|
|
|
|
|
return deps
|
|
|
|
|
|
|
|
|
|
|
|
prefix = os.path.commonprefix([self.root_dir().lower(),
|
|
|
|
def _RunHooks(self, command, file_list, is_using_git):
|
|
|
|
self.file_list[i].lower()])
|
|
|
|
"""Evaluates all hooks, running actions as needed.
|
|
|
|
self.file_list[i] = self.file_list[i][len(prefix):]
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Hooks only run for these command types.
|
|
|
|
|
|
|
|
if not command in ('update', 'revert', 'runhooks'):
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Strip any leading path separators.
|
|
|
|
# Hooks only run when --nohooks is not specified
|
|
|
|
while (self.file_list[i].startswith('\\') or
|
|
|
|
if self._options.nohooks:
|
|
|
|
self.file_list[i].startswith('/')):
|
|
|
|
return
|
|
|
|
self.file_list[i] = self.file_list[i][1:]
|
|
|
|
|
|
|
|
|
|
|
|
# Get any hooks from the .gclient file.
|
|
|
|
|
|
|
|
hooks = self.deps_hooks[:]
|
|
|
|
|
|
|
|
# Add any hooks found in DEPS files.
|
|
|
|
|
|
|
|
for d in self.dependencies:
|
|
|
|
|
|
|
|
hooks.extend(d.deps_hooks)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# If "--force" was specified, run all hooks regardless of what files have
|
|
|
|
|
|
|
|
# changed. If the user is using git, then we don't know what files have
|
|
|
|
|
|
|
|
# changed so we always run all hooks.
|
|
|
|
|
|
|
|
if self._options.force or is_using_git:
|
|
|
|
|
|
|
|
for hook_dict in hooks:
|
|
|
|
|
|
|
|
self._RunHookAction(hook_dict, [])
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Run hooks on the basis of whether the files from the gclient operation
|
|
|
|
# Run hooks on the basis of whether the files from the gclient operation
|
|
|
|
# match each hook's pattern.
|
|
|
|
# match each hook's pattern.
|
|
|
|
for hook_dict in self.deps_hooks:
|
|
|
|
for hook_dict in hooks:
|
|
|
|
pattern = re.compile(hook_dict['pattern'])
|
|
|
|
pattern = re.compile(hook_dict['pattern'])
|
|
|
|
matching_file_list = [f for f in self.file_list if pattern.search(f)]
|
|
|
|
matching_file_list = [f for f in file_list if pattern.search(f)]
|
|
|
|
if matching_file_list:
|
|
|
|
if matching_file_list:
|
|
|
|
self._RunHookAction(hook_dict, matching_file_list)
|
|
|
|
self._RunHookAction(hook_dict, matching_file_list)
|
|
|
|
if self.recursion_limit():
|
|
|
|
|
|
|
|
for s in self.dependencies:
|
|
|
|
|
|
|
|
s.RunHooksRecursively(options)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _RunHookAction(self, hook_dict, matching_file_list):
|
|
|
|
def _RunHookAction(self, hook_dict, matching_file_list):
|
|
|
|
"""Runs the action from a single hook."""
|
|
|
|
"""Runs the action from a single hook."""
|
|
|
@ -422,7 +389,7 @@ class Dependency(GClientKeywords):
|
|
|
|
def __str__(self):
|
|
|
|
def __str__(self):
|
|
|
|
out = []
|
|
|
|
out = []
|
|
|
|
for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
|
|
|
|
for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
|
|
|
|
'deps_hooks', 'file_list'):
|
|
|
|
'deps_hooks'):
|
|
|
|
# 'deps_file'
|
|
|
|
# 'deps_file'
|
|
|
|
if self.__dict__[i]:
|
|
|
|
if self.__dict__[i]:
|
|
|
|
out.append('%s: %s' % (i, self.__dict__[i]))
|
|
|
|
out.append('%s: %s' % (i, self.__dict__[i]))
|
|
|
@ -538,23 +505,19 @@ solutions = [
|
|
|
|
'safesync_url' : safesync_url,
|
|
|
|
'safesync_url' : safesync_url,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
def _SaveEntries(self):
|
|
|
|
def _SaveEntries(self, entries):
|
|
|
|
"""Creates a .gclient_entries file to record the list of unique checkouts.
|
|
|
|
"""Creates a .gclient_entries file to record the list of unique checkouts.
|
|
|
|
|
|
|
|
|
|
|
|
The .gclient_entries file lives in the same directory as .gclient.
|
|
|
|
The .gclient_entries file lives in the same directory as .gclient.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
|
|
|
|
# Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
|
|
|
|
# makes testing a bit too fun.
|
|
|
|
# makes testing a bit too fun.
|
|
|
|
result = 'entries = {\n'
|
|
|
|
result = pprint.pformat(entries, 2)
|
|
|
|
for entry in self.tree(False):
|
|
|
|
if result.startswith('{\''):
|
|
|
|
# Skip over File() dependencies as we can't version them.
|
|
|
|
result = '{ \'' + result[2:]
|
|
|
|
if not isinstance(entry.parsed_url, self.FileImpl):
|
|
|
|
text = 'entries = \\\n' + result + '\n'
|
|
|
|
result += ' %s: %s,\n' % (pprint.pformat(entry.name),
|
|
|
|
|
|
|
|
pprint.pformat(entry.parsed_url))
|
|
|
|
|
|
|
|
result += '}\n'
|
|
|
|
|
|
|
|
file_path = os.path.join(self.root_dir(), self._options.entries_filename)
|
|
|
|
file_path = os.path.join(self.root_dir(), self._options.entries_filename)
|
|
|
|
logging.info(result)
|
|
|
|
gclient_utils.FileWrite(file_path, text)
|
|
|
|
gclient_utils.FileWrite(file_path, result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ReadEntries(self):
|
|
|
|
def _ReadEntries(self):
|
|
|
|
"""Read the .gclient_entries file for the given client.
|
|
|
|
"""Read the .gclient_entries file for the given client.
|
|
|
@ -615,32 +578,115 @@ solutions = [
|
|
|
|
if not self.dependencies:
|
|
|
|
if not self.dependencies:
|
|
|
|
raise gclient_utils.Error('No solution specified')
|
|
|
|
raise gclient_utils.Error('No solution specified')
|
|
|
|
revision_overrides = self._EnforceRevisions()
|
|
|
|
revision_overrides = self._EnforceRevisions()
|
|
|
|
pm = None
|
|
|
|
|
|
|
|
|
|
|
|
# When running runhooks --force, there's no need to consult the SCM.
|
|
|
|
|
|
|
|
# All known hooks are expected to run unconditionally regardless of working
|
|
|
|
|
|
|
|
# copy state, so skip the SCM status check.
|
|
|
|
|
|
|
|
run_scm = not (command == 'runhooks' and self._options.force)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entries = {}
|
|
|
|
|
|
|
|
file_list = []
|
|
|
|
|
|
|
|
# Run on the base solutions first.
|
|
|
|
|
|
|
|
for solution in self.dependencies:
|
|
|
|
|
|
|
|
name = solution.name
|
|
|
|
|
|
|
|
if name in entries:
|
|
|
|
|
|
|
|
raise gclient_utils.Error("solution %s specified more than once" % name)
|
|
|
|
|
|
|
|
url = solution.url
|
|
|
|
|
|
|
|
entries[name] = url
|
|
|
|
|
|
|
|
if run_scm and url:
|
|
|
|
|
|
|
|
self._options.revision = revision_overrides.get(name)
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
|
|
|
|
|
|
|
|
scm.RunCommand(command, self._options, args, file_list)
|
|
|
|
|
|
|
|
file_list = [os.path.join(name, f.strip()) for f in file_list]
|
|
|
|
|
|
|
|
self._options.revision = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Process the dependencies next (sort alphanumerically to ensure that
|
|
|
|
|
|
|
|
# containing directories get populated first and for readability)
|
|
|
|
|
|
|
|
deps = self._ParseAllDeps(entries)
|
|
|
|
|
|
|
|
deps_to_process = deps.keys()
|
|
|
|
|
|
|
|
deps_to_process.sort()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# First pass for direct dependencies.
|
|
|
|
|
|
|
|
if command == 'update' and not self._options.verbose:
|
|
|
|
|
|
|
|
pm = Progress('Syncing projects', len(deps_to_process))
|
|
|
|
|
|
|
|
for d in deps_to_process:
|
|
|
|
|
|
|
|
if command == 'update' and not self._options.verbose:
|
|
|
|
|
|
|
|
pm.update()
|
|
|
|
|
|
|
|
if type(deps[d]) == str:
|
|
|
|
|
|
|
|
url = deps[d]
|
|
|
|
|
|
|
|
entries[d] = url
|
|
|
|
|
|
|
|
if run_scm:
|
|
|
|
|
|
|
|
self._options.revision = revision_overrides.get(d)
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
|
|
|
|
|
|
|
|
scm.RunCommand(command, self._options, args, file_list)
|
|
|
|
|
|
|
|
self._options.revision = None
|
|
|
|
|
|
|
|
elif isinstance(deps[d], self.FileImpl):
|
|
|
|
|
|
|
|
if command in (None, 'cleanup', 'diff', 'pack', 'status'):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
file_dep = deps[d]
|
|
|
|
|
|
|
|
self._options.revision = file_dep.GetRevision()
|
|
|
|
|
|
|
|
if run_scm:
|
|
|
|
|
|
|
|
scm = gclient_scm.SVNWrapper(file_dep.GetPath(), self.root_dir(), d)
|
|
|
|
|
|
|
|
scm.RunCommand('updatesingle', self._options,
|
|
|
|
|
|
|
|
args + [file_dep.GetFilename()], file_list)
|
|
|
|
|
|
|
|
|
|
|
|
if command == 'update' and not self._options.verbose:
|
|
|
|
if command == 'update' and not self._options.verbose:
|
|
|
|
pm = Progress('Syncing projects', len(self.tree(False)) + 1)
|
|
|
|
|
|
|
|
self.RunCommandRecursively(self._options, revision_overrides,
|
|
|
|
|
|
|
|
command, args, pm)
|
|
|
|
|
|
|
|
if pm:
|
|
|
|
|
|
|
|
pm.end()
|
|
|
|
pm.end()
|
|
|
|
|
|
|
|
|
|
|
|
# Once all the dependencies have been processed, it's now safe to run the
|
|
|
|
# Second pass for inherited deps (via the From keyword)
|
|
|
|
# hooks.
|
|
|
|
for d in deps_to_process:
|
|
|
|
if not self._options.nohooks:
|
|
|
|
if isinstance(deps[d], self.FromImpl):
|
|
|
|
self.RunHooksRecursively(self._options)
|
|
|
|
# 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
|
|
|
|
|
|
|
|
# URLs, we need to pass in the orignal sub deps URL.
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
entries[d] = url
|
|
|
|
|
|
|
|
if run_scm:
|
|
|
|
|
|
|
|
self._options.revision = revision_overrides.get(d)
|
|
|
|
|
|
|
|
scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
|
|
|
|
|
|
|
|
scm.RunCommand(command, self._options, args, file_list)
|
|
|
|
|
|
|
|
self._options.revision = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Convert all absolute paths to relative.
|
|
|
|
|
|
|
|
for i in range(len(file_list)):
|
|
|
|
|
|
|
|
# TODO(phajdan.jr): We should know exactly when the paths are absolute.
|
|
|
|
|
|
|
|
# It depends on the command being executed (like runhooks vs sync).
|
|
|
|
|
|
|
|
if not os.path.isabs(file_list[i]):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prefix = os.path.commonprefix([self.root_dir().lower(),
|
|
|
|
|
|
|
|
file_list[i].lower()])
|
|
|
|
|
|
|
|
file_list[i] = file_list[i][len(prefix):]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Strip any leading path separators.
|
|
|
|
|
|
|
|
while file_list[i].startswith('\\') or file_list[i].startswith('/'):
|
|
|
|
|
|
|
|
file_list[i] = file_list[i][1:]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
|
|
|
|
|
|
|
|
self._RunHooks(command, file_list, is_using_git)
|
|
|
|
|
|
|
|
|
|
|
|
if command == 'update':
|
|
|
|
if command == 'update':
|
|
|
|
# Notify the user if there is an orphaned entry in their working copy.
|
|
|
|
# Notify the user if there is an orphaned entry in their working copy.
|
|
|
|
# Only delete the directory if there are no changes in it, and
|
|
|
|
# Only delete the directory if there are no changes in it, and
|
|
|
|
# delete_unversioned_trees is set to true.
|
|
|
|
# delete_unversioned_trees is set to true.
|
|
|
|
entries = [i.name for i in self.tree(False)]
|
|
|
|
prev_entries = self._ReadEntries()
|
|
|
|
for entry, prev_url in self._ReadEntries().iteritems():
|
|
|
|
for entry in prev_entries:
|
|
|
|
# Fix path separator on Windows.
|
|
|
|
# Fix path separator on Windows.
|
|
|
|
entry_fixed = entry.replace('/', os.path.sep)
|
|
|
|
entry_fixed = entry.replace('/', os.path.sep)
|
|
|
|
e_dir = os.path.join(self.root_dir(), entry_fixed)
|
|
|
|
e_dir = os.path.join(self.root_dir(), entry_fixed)
|
|
|
|
# Use entry and not entry_fixed there.
|
|
|
|
# Use entry and not entry_fixed there.
|
|
|
|
if entry not in entries and os.path.exists(e_dir):
|
|
|
|
if entry not in entries and os.path.exists(e_dir):
|
|
|
|
|
|
|
|
modified_files = False
|
|
|
|
|
|
|
|
if isinstance(prev_entries, list):
|
|
|
|
|
|
|
|
# old .gclient_entries format was list, now dict
|
|
|
|
|
|
|
|
modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
|
|
|
|
|
|
|
|
else:
|
|
|
|
file_list = []
|
|
|
|
file_list = []
|
|
|
|
scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
|
|
|
|
scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
|
|
|
|
|
|
|
|
entry_fixed)
|
|
|
|
scm.status(self._options, [], file_list)
|
|
|
|
scm.status(self._options, [], file_list)
|
|
|
|
modified_files = file_list != []
|
|
|
|
modified_files = file_list != []
|
|
|
|
if not self._options.delete_unversioned_trees or modified_files:
|
|
|
|
if not self._options.delete_unversioned_trees or modified_files:
|
|
|
@ -655,7 +701,7 @@ solutions = [
|
|
|
|
entry_fixed, self.root_dir()))
|
|
|
|
entry_fixed, self.root_dir()))
|
|
|
|
gclient_utils.RemoveDirectory(e_dir)
|
|
|
|
gclient_utils.RemoveDirectory(e_dir)
|
|
|
|
# record the current list of entries for next time
|
|
|
|
# record the current list of entries for next time
|
|
|
|
self._SaveEntries()
|
|
|
|
self._SaveEntries(entries)
|
|
|
|
return 0
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def PrintRevInfo(self):
|
|
|
|
def PrintRevInfo(self):
|
|
|
@ -671,53 +717,76 @@ solutions = [
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if not self.dependencies:
|
|
|
|
if not self.dependencies:
|
|
|
|
raise gclient_utils.Error('No solution specified')
|
|
|
|
raise gclient_utils.Error('No solution specified')
|
|
|
|
# Load all the settings.
|
|
|
|
|
|
|
|
self.RunCommandRecursively(self._options, {}, None, [], None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Inner helper to generate base url and rev tuple
|
|
|
|
def GetURLAndRev(name, original_url):
|
|
|
|
def GetURLAndRev(name, original_url):
|
|
|
|
"""Returns the revision-qualified SCM url."""
|
|
|
|
|
|
|
|
if isinstance(original_url, self.FileImpl):
|
|
|
|
|
|
|
|
return original_url.file_location
|
|
|
|
|
|
|
|
url, _ = gclient_utils.SplitUrlRevision(original_url)
|
|
|
|
url, _ = gclient_utils.SplitUrlRevision(original_url)
|
|
|
|
scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
|
|
|
|
scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
|
|
|
|
if not os.path.isdir(scm.checkout_path):
|
|
|
|
return (url, scm.revinfo(self._options, [], None))
|
|
|
|
return None
|
|
|
|
|
|
|
|
return '%s@%s' % (url, scm.revinfo(self._options, [], None))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self._options.snapshot:
|
|
|
|
# text of the snapshot gclient file
|
|
|
|
new_gclient = ''
|
|
|
|
new_gclient = ""
|
|
|
|
# First level at .gclient
|
|
|
|
# Dictionary of { path : SCM url } to ensure no duplicate solutions
|
|
|
|
for d in self.dependencies:
|
|
|
|
solution_names = {}
|
|
|
|
entries = {}
|
|
|
|
entries = {}
|
|
|
|
def GrabDeps(sol):
|
|
|
|
# Run on the base solutions first.
|
|
|
|
"""Recursively grab dependencies."""
|
|
|
|
for solution in self.dependencies:
|
|
|
|
for i in sol.dependencies:
|
|
|
|
# Dictionary of { path : SCM url } to describe the gclient checkout
|
|
|
|
entries[i.name] = GetURLAndRev(i.name, i.parsed_url)
|
|
|
|
name = solution.name
|
|
|
|
GrabDeps(i)
|
|
|
|
if name in solution_names:
|
|
|
|
GrabDeps(d)
|
|
|
|
raise gclient_utils.Error("solution %s specified more than once" % name)
|
|
|
|
custom_deps = []
|
|
|
|
(url, rev) = GetURLAndRev(name, solution.url)
|
|
|
|
for k in sorted(entries.keys()):
|
|
|
|
entries[name] = "%s@%s" % (url, rev)
|
|
|
|
if entries[k]:
|
|
|
|
solution_names[name] = "%s@%s" % (url, rev)
|
|
|
|
# Quotes aren't escaped...
|
|
|
|
|
|
|
|
custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
|
|
|
|
# Process the dependencies next (sort alphanumerically to ensure that
|
|
|
|
else:
|
|
|
|
# containing directories get populated first and for readability)
|
|
|
|
custom_deps.append(' \"%s\": None,\n' % k)
|
|
|
|
deps = self._ParseAllDeps(entries)
|
|
|
|
|
|
|
|
deps_to_process = deps.keys()
|
|
|
|
|
|
|
|
deps_to_process.sort()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# First pass for direct dependencies.
|
|
|
|
|
|
|
|
for d in deps_to_process:
|
|
|
|
|
|
|
|
if type(deps[d]) == str:
|
|
|
|
|
|
|
|
(url, rev) = GetURLAndRev(d, deps[d])
|
|
|
|
|
|
|
|
entries[d] = "%s@%s" % (url, rev)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Second pass for inherited deps (via the From keyword)
|
|
|
|
|
|
|
|
for d in deps_to_process:
|
|
|
|
|
|
|
|
if isinstance(deps[d], self.FromImpl):
|
|
|
|
|
|
|
|
deps_parent_url = entries[deps[d].module_name]
|
|
|
|
|
|
|
|
if deps_parent_url.find("@") < 0:
|
|
|
|
|
|
|
|
raise gclient_utils.Error("From %s missing revisioned url" %
|
|
|
|
|
|
|
|
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, rev) = GetURLAndRev(d, url)
|
|
|
|
|
|
|
|
entries[d] = "%s@%s" % (url, rev)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Build the snapshot configuration string
|
|
|
|
|
|
|
|
if self._options.snapshot:
|
|
|
|
|
|
|
|
url = entries.pop(name)
|
|
|
|
|
|
|
|
custom_deps = ''.join([' \"%s\": \"%s\",\n' % (x, entries[x])
|
|
|
|
|
|
|
|
for x in sorted(entries.keys())])
|
|
|
|
|
|
|
|
|
|
|
|
new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
|
|
|
|
new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
|
|
|
|
'solution_name': d.name,
|
|
|
|
'solution_name': name,
|
|
|
|
'solution_url': d.url,
|
|
|
|
'solution_url': url,
|
|
|
|
'safesync_url' : d.safesync_url or '',
|
|
|
|
'safesync_url' : '',
|
|
|
|
'solution_deps': ''.join(custom_deps),
|
|
|
|
'solution_deps': custom_deps,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# Print the snapshot configuration file
|
|
|
|
|
|
|
|
print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
entries = sorted(self.tree(False), key=lambda i: i.name)
|
|
|
|
print(';\n'.join(['%s: %s' % (x, entries[x])
|
|
|
|
for entry in entries:
|
|
|
|
for x in sorted(entries.keys())]))
|
|
|
|
revision = GetURLAndRev(entry.name, entry.parsed_url)
|
|
|
|
|
|
|
|
line = '%s: %s' % (entry.name, revision)
|
|
|
|
# Print the snapshot configuration file
|
|
|
|
if not entry is entries[-1]:
|
|
|
|
if self._options.snapshot:
|
|
|
|
line += ';'
|
|
|
|
config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
|
|
|
|
print line
|
|
|
|
snapclient = GClient(self.root_dir(), self._options)
|
|
|
|
|
|
|
|
snapclient.SetConfig(config)
|
|
|
|
|
|
|
|
print(snapclient.config_content)
|
|
|
|
|
|
|
|
|
|
|
|
def ParseDepsFile(self, direct_reference):
|
|
|
|
def ParseDepsFile(self, direct_reference):
|
|
|
|
"""No DEPS to parse for a .gclient file."""
|
|
|
|
"""No DEPS to parse for a .gclient file."""
|
|
|
@ -1038,7 +1107,8 @@ def CMDrevinfo(parser, args):
|
|
|
|
'references')
|
|
|
|
'references')
|
|
|
|
parser.add_option('-s', '--snapshot', action='store_true',
|
|
|
|
parser.add_option('-s', '--snapshot', action='store_true',
|
|
|
|
help='creates a snapshot .gclient file of the current '
|
|
|
|
help='creates a snapshot .gclient file of the current '
|
|
|
|
'version of all repositories to reproduce the tree')
|
|
|
|
'version of all repositories to reproduce the tree, '
|
|
|
|
|
|
|
|
'implies -a')
|
|
|
|
(options, args) = parser.parse_args(args)
|
|
|
|
(options, args) = parser.parse_args(args)
|
|
|
|
client = GClient.LoadCurrentConfig(options)
|
|
|
|
client = GClient.LoadCurrentConfig(options)
|
|
|
|
if not client:
|
|
|
|
if not client:
|
|
|
|