Revert recent gclient.py changes to fix Chrome releases.

Reason for revert: This is breaking official release scripts, which have long
used "None" URL functionality to allow gclient commands to run against
"local" DEPS (buildspec) files. Please do not remove this behavior.

This reverts the following commits:
  ebdd0db493b20f0abeab8960e6ea0ceb7c6b379a: "gclient: Remove URLs from hierarchy."
  54a5c2ba8ac2f9b8f4a32fe79913f13545e4aab9: "gclient: Refactor PrintRevInfo"
  083eb25f9acbe034db94a1bd5c1659125b6ebf98: "gclient: Don't allow URL to be None."

BUG=846194

TBR=agable@chromium.org,ehmaldonado@chromium.org

Change-Id: Ibdd5581889bd4afd86474199c7b64555f01bbbca
Reviewed-on: https://chromium-review.googlesource.com/1070893
Commit-Queue: Michael Moss <mmoss@chromium.org>
Reviewed-by: Michael Moss <mmoss@chromium.org>
changes/93/1070893/5
Michael Moss 7 years ago committed by Commit Bot
parent 1f156687a2
commit 4e9b50ab86

@ -241,7 +241,7 @@ class DependencySettings(object):
"""Immutable configuration settings."""
def __init__(
self, parent, raw_url, url, managed, custom_deps, custom_vars,
custom_hooks, deps_file, relative, condition):
custom_hooks, deps_file, should_process, relative, condition):
# These are not mutable:
self._parent = parent
self._deps_file = deps_file
@ -250,9 +250,13 @@ class DependencySettings(object):
# The condition as string (or None). Useful to keep e.g. for flatten.
self._condition = condition
# 'managed' determines whether or not this dependency is synced/updated by
# gclient after gclient checks it out initially. The user specifies
# 'managed' via the --unmanaged command-line flag or a .gclient config.
# gclient after gclient checks it out initially. The difference between
# 'managed' and 'should_process' is that the user specifies 'managed' via
# the --unmanaged command-line flag or a .gclient config, where
# 'should_process' is dynamically set by gclient if it goes over its
# recursion limit and controls gclient's behavior so it does not misbehave.
self._managed = managed
self._should_process = should_process
# If this is a recursed-upon sub-dependency, and the parent has
# use_relative_paths set, then this dependency should check out its own
# dependencies relative to that parent's path for this, rather than
@ -267,10 +271,15 @@ class DependencySettings(object):
self._custom_deps = custom_deps or {}
self._custom_hooks = custom_hooks or []
assert self.url is not None, (self.name, self.url)
# urls are sometime incorrectly written as proto://host/path/@rev. Replace
# it to proto://host/path@rev.
self.set_url(self.url.replace('/@', '@'))
# Post process the url to remove trailing slashes.
if isinstance(self.url, basestring):
# urls are sometime incorrectly written as proto://host/path/@rev. Replace
# it to proto://host/path@rev.
self.set_url(self.url.replace('/@', '@'))
elif not isinstance(self.url, (None.__class__)):
raise gclient_utils.Error(
('dependency url must be either string or None, '
'instead of %s') % self.url.__class__.__name__)
# Make any deps_file path platform-appropriate.
if self._deps_file:
@ -297,6 +306,11 @@ class DependencySettings(object):
return self or GClient(None, None)
return self.parent.root
@property
def should_process(self):
"""True if this dependency should be processed, i.e. checked out."""
return self._should_process
@property
def custom_vars(self):
return self._custom_vars.copy()
@ -352,12 +366,12 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
"""Object that represents a dependency checkout."""
def __init__(self, parent, name, raw_url, url, managed, custom_deps,
custom_vars, custom_hooks, deps_file, relative, condition,
print_outbuf=False):
custom_vars, custom_hooks, deps_file, should_process,
relative, condition, print_outbuf=False):
gclient_utils.WorkItem.__init__(self, name)
DependencySettings.__init__(
self, parent, raw_url, url, managed, custom_deps, custom_vars,
custom_hooks, deps_file, relative, condition)
custom_hooks, deps_file, should_process, relative, condition)
# This is in both .gclient and DEPS files:
self._deps_hooks = []
@ -409,10 +423,12 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
# None.
self.recursedeps = None
self._OverrideUrl()
# This is inherited from WorkItem. We want the URL to be a resource.
# The url is usually given to gclient either as https://blah@123
# or just https://blah. The @123 portion is irrelevant.
self.resources.append(self.url.split('@')[0])
if self.url and isinstance(self.url, basestring):
# The url is usually given to gclient either as https://blah@123
# or just https://blah. The @123 portion is irrelevant.
self.resources.append(self.url.split('@')[0])
# Controls whether we want to print git's output when we first clone the
# dependency
@ -421,8 +437,40 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
if not self.name and self.parent:
raise gclient_utils.Error('Dependency without name')
def _OverrideUrl(self):
"""Resolves the parsed url from the parent hierarchy."""
parsed_url = self.get_custom_deps(self._name, self.url)
if parsed_url != self.url:
logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
self.url, parsed_url)
self.set_url(parsed_url)
elif isinstance(self.url, basestring):
parsed_url = urlparse.urlparse(self.url)
if (not parsed_url[0] and
not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
path = parsed_url[2]
if not path.startswith('/'):
raise gclient_utils.Error(
'relative DEPS entry \'%s\' must begin with a slash' % self.url)
# A relative url. Get the parent url, strip from the last '/'
# (equivalent to unix basename), and append the relative url.
parent_url = self.parent.url
parsed_url = parent_url[:parent_url.rfind('/')] + self.url
logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
self.url, parsed_url)
self.set_url(parsed_url)
elif self.url is None:
logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
else:
raise gclient_utils.Error('Unknown url type')
def PinToActualRevision(self):
"""Updates self.url and self.raw_url to the revision checked out on disk."""
if self.url is None:
return
url = raw_url = None
scm = self.CreateSCM()
if os.path.isdir(scm.checkout_path):
@ -438,7 +486,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
condition_part = ([' "condition": %r,' % self.condition]
if self.condition else [])
s.extend([
' # %s' % self.hierarchy(),
' # %s' % self.hierarchy(include_url=False),
' "%s": {' % (self.name,),
' "url": "%s",' % (self.raw_url,),
] + condition_part + [
@ -472,7 +520,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
if self.name:
requirements |= set(
obj.name for obj in self.root.subtree()
obj.name for obj in self.root.subtree(False)
if (obj is not self
and obj.name and
self.name.startswith(posixpath.join(obj.name, ''))))
@ -511,11 +559,15 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
raise gclient_utils.Error(
'The same name "%s" appears multiple times in the deps section' %
self.name)
if not self.should_process:
# Return early, no need to set requirements.
return True
# This require a full tree traversal with locks.
siblings = [d for d in self.root.subtree() if d.name == self.name]
siblings = [d for d in self.root.subtree(False) if d.name == self.name]
for sibling in siblings:
if self.url != sibling.url:
# Allow to have only one to be None or ''.
if self.url != sibling.url and bool(self.url) == bool(sibling.url):
raise gclient_utils.Error(
('Dependency %s specified more than once:\n'
' %s [%s]\n'
@ -565,30 +617,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
return deps
def FormatUrl(self, name, url):
custom_deps_url = self.get_custom_deps(name, url)
if url != custom_deps_url:
return custom_deps_url
if url is None:
return None
if not isinstance(url, basestring):
raise gclient_utils.Error(
('dependency url must be either string or None, '
'instead of %s') % self.url.__class__.__name__)
url = url.format(**self.get_vars())
# For relative URLs, strip the parent url (self.url) from the last '/'
# and append the relative url.
if url[0] == '/':
url = self.url[:self.url.rfind('/')] + url
return url
def _deps_to_objects(self, deps, use_relative_paths):
"""Convert a deps dict to a dict of Dependency objects."""
if not self.recursion_limit:
return []
deps_to_add = []
for name, dep_value in deps.iteritems():
should_process = self.recursion_limit and self.should_process
deps_file = self.deps_file
if self.recursedeps is not None:
ent = self.recursedeps.get(name)
@ -598,32 +631,31 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
continue
condition = dep_value.get('condition')
dep_type = dep_value.get('dep_type', 'git')
dep_type = dep_value.get('dep_type')
should_process = True
if condition and not self.get_option('process_all_deps', False):
should_process = gclient_eval.EvaluateCondition(
if condition and not self._get_option('process_all_deps', False):
should_process = should_process and gclient_eval.EvaluateCondition(
condition, self.get_vars())
if not should_process:
continue
if dep_type == 'cipd':
cipd_root = self.GetCipdRoot()
for package in dep_value.get('packages', []):
package['version'] = package['version'].format(**self.get_vars())
if 'version' in package:
# Matches version to vars value.
raw_version = package['version']
version = raw_version.format(**self.get_vars())
package['version'] = version
deps_to_add.append(
CipdDependency(
self, name, package, cipd_root, self.custom_vars,
use_relative_paths, condition))
should_process, use_relative_paths, condition))
else:
raw_url = dep_value.get('url')
url = self.FormatUrl(name, raw_url)
if url:
deps_to_add.append(
GitDependency(
self, name, raw_url, url, None, None, self.custom_vars, None,
deps_file, use_relative_paths, condition))
url = raw_url.format(**self.get_vars()) if raw_url else None
deps_to_add.append(
GitDependency(
self, name, raw_url, url, None, None, self.custom_vars, None,
deps_file, should_process, use_relative_paths, condition))
deps_to_add.sort(key=lambda x: x.name)
return deps_to_add
@ -660,7 +692,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
try:
local_scope = gclient_eval.Parse(
deps_content, expand_vars,
self.get_option('validate_syntax', False),
self._get_option('validate_syntax', False),
filepath, self.get_vars())
except SyntaxError as e:
gclient_utils.SyntaxErrorToError(filepath, e)
@ -766,8 +798,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
self.add_dependencies_and_close(deps_to_add, hooks_to_run)
logging.info('ParseDepsFile(%s) done' % self.name)
def get_option(self, attr, default=None):
return getattr(self.root._options, attr, default)
def _get_option(self, attr, default):
obj = self
while not hasattr(obj, '_options'):
obj = obj.parent
return getattr(obj._options, attr, default)
def add_dependencies_and_close(self, deps_to_add, hooks):
"""Adds the dependencies, hooks and mark the parsing as done."""
@ -793,9 +828,10 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
# Don't enforce this for custom_deps.
if dep.name in self._custom_deps:
continue
parsed_url = urlparse.urlparse(dep.url)
if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
bad_deps.append(dep)
if isinstance(dep.url, basestring):
parsed_url = urlparse.urlparse(dep.url)
if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
bad_deps.append(dep)
return bad_deps
def FuzzyMatchUrl(self, candidates):
@ -837,6 +873,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
"""Runs |command| then parse the DEPS file."""
logging.info('Dependency(%s).run()' % self.name)
assert self._file_list == []
if not self.should_process:
return
# When running runhooks, 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.
@ -886,7 +924,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
self.RunPreDepsHooks()
# Parse the dependencies of this dependency.
for s in self.dependencies:
work_queue.enqueue(s)
if s.should_process:
work_queue.enqueue(s)
if command == 'recurse':
# Skip file only checkout.
@ -896,8 +935,10 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
# Pass in the SCM type as an env variable. Make sure we don't put
# unicode strings in the environment.
env = os.environ.copy()
env['GCLIENT_SCM'] = str(scm)
env['GCLIENT_URL'] = str(self.url)
if scm:
env['GCLIENT_SCM'] = str(scm)
if self.url:
env['GCLIENT_URL'] = str(self.url)
env['GCLIENT_DEP_PATH'] = str(self.name)
if options.prepend_dir and scm == 'git':
print_stdout = False
@ -928,7 +969,9 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
print_stdout = True
filter_fn = None
if os.path.isdir(cwd):
if self.url is None:
print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
elif os.path.isdir(cwd):
try:
gclient_utils.CheckCallAndFilter(
args, cwd=cwd, env=env, print_stdout=print_stdout,
@ -972,7 +1015,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
RunOnDeps() must have been called before to load the DEPS.
"""
result = []
if not self.recursion_limit:
if not self.should_process or not self.recursion_limit:
# Don't run the hook when it is above recursion_limit.
return result
# If "--force" was specified, run all hooks regardless of what files have
@ -1017,13 +1060,14 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
return None
return self.root.GetCipdRoot()
def subtree(self):
def subtree(self, include_all):
"""Breadth first recursion excluding root node."""
dependencies = self.dependencies
for d in dependencies:
yield d
if d.should_process or include_all:
yield d
for d in dependencies:
for i in d.subtree():
for i in d.subtree(include_all):
yield i
@gclient_utils.lockedmethod
@ -1101,7 +1145,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
def __str__(self):
out = []
for i in ('name', 'url', 'custom_deps',
'custom_vars', 'deps_hooks', 'file_list',
'custom_vars', 'deps_hooks', 'file_list', 'should_process',
'processed', 'hooks_ran', 'deps_parsed', 'requirements',
'allowed_hosts'):
# First try the native property if it exists.
@ -1120,12 +1164,16 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
def __repr__(self):
return '%s: %s' % (self.name, self.url)
def hierarchy(self):
def hierarchy(self, include_url=True):
"""Returns a human-readable hierarchical reference to a Dependency."""
out = self.name
def format_name(d):
if include_url:
return '%s(%s)' % (d.name, d.url)
return d.name
out = format_name(self)
i = self.parent
while i and i.name:
out = '%s -> %s' % (i.name, out)
out = '%s -> %s' % (format_name(i), out)
i = i.parent
return out
@ -1245,8 +1293,8 @@ solutions = %(solution_list)s
# Do not change previous behavior. Only solution level and immediate DEPS
# are processed.
self._recursion_limit = 2
GitDependency.__init__(self, None, None, '', '', True, None, None, None,
'unused', None, None, True)
GitDependency.__init__(self, None, None, None, None, True, None, None, None,
'unused', True, None, None, True)
self._options = options
if options.deps_os:
enforced_os = options.deps_os.split(',')
@ -1264,7 +1312,7 @@ solutions = %(solution_list)s
"""Verify that the config matches the state of the existing checked-out
solutions."""
for dep in self.dependencies:
if dep.managed:
if dep.managed and dep.url:
scm = dep.CreateSCM()
actual_url = scm.GetActualRemoteURL(self._options)
if actual_url and not scm.DoesRemoteURLMatch(self._options):
@ -1336,24 +1384,21 @@ it or fix the checkout.
deps_to_add = []
for s in config_dict.get('solutions', []):
try:
if s['url']:
deps_to_add.append(GitDependency(
self, s['name'], s['url'], s['url'],
s.get('managed', True),
s.get('custom_deps', {}),
s.get('custom_vars', {}),
s.get('custom_hooks', []),
s.get('deps_file', 'DEPS'),
None,
None,
True))
deps_to_add.append(GitDependency(
self, s['name'], s['url'], s['url'],
s.get('managed', True),
s.get('custom_deps', {}),
s.get('custom_vars', {}),
s.get('custom_hooks', []),
s.get('deps_file', 'DEPS'),
True,
None,
None,
True))
except KeyError:
raise gclient_utils.Error('Invalid .gclient file. Solution is '
'incomplete: %s' % s)
self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
if not self.dependencies:
raise gclient_utils.Error('No solution specified')
logging.info('SetConfig() done')
def SaveConfig(self):
@ -1413,7 +1458,7 @@ it or fix the checkout.
# Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
# makes testing a bit too fun.
result = 'entries = {\n'
for entry in self.root.subtree():
for entry in self.root.subtree(False):
result += ' %s: %s,\n' % (pprint.pformat(entry.name),
pprint.pformat(entry.url))
result += '}\n'
@ -1481,6 +1526,9 @@ it or fix the checkout.
command: The command to use (e.g., 'status' or 'diff')
args: list of str - extra arguments to add to the command line.
"""
if not self.dependencies:
raise gclient_utils.Error('No solution specified')
revision_overrides = {}
patch_refs = {}
# It's unnecessary to check for revision overrides for 'recurse'.
@ -1505,7 +1553,8 @@ it or fix the checkout.
self._options.jobs, pm, ignore_requirements=ignore_requirements,
verbose=self._options.verbose)
for s in self.dependencies:
work_queue.enqueue(s)
if s.should_process:
work_queue.enqueue(s)
work_queue.flush(revision_overrides, command, args, options=self._options,
patch_refs=patch_refs)
@ -1542,7 +1591,7 @@ it or fix the checkout.
# 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
# delete_unversioned_trees is set to true.
entries = [i.name for i in self.root.subtree()]
entries = [i.name for i in self.root.subtree(False) if i.url]
full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
for e in entries]
@ -1639,59 +1688,68 @@ it or fix the checkout.
work_queue = gclient_utils.ExecutionQueue(
self._options.jobs, None, False, verbose=self._options.verbose)
for s in self.dependencies:
work_queue.enqueue(s)
if s.should_process:
work_queue.enqueue(s)
work_queue.flush({}, None, [], options=self._options, patch_refs=None)
def ShouldPrint(dep):
def ShouldPrintRevision(dep):
return (not self._options.filter
or dep.FuzzyMatchUrl(self._options.filter))
for dep in self.subtree():
if self._options.snapshot or self._options.actual:
dep.PinToActualRevision()
if self._options.snapshot:
json_output = [
{
'name': d.name,
'solution_url': d.url,
'deps_file': d.deps_file,
'managed': d.managed,
'custom_deps': {
subdep.name: subdep.url
for subdep in d.subtree()
if ShouldPrint(subdep)
},
}
for d in self.dependencies
if ShouldPrint(d)
]
output = json.dumps(json_output, indent=2, separators=(',', ': '))
if not self._options.output_json:
output = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': output}
elif self._options.output_json:
json_output = {
d.name: {
'url': d.url.split('@')[0],
'rev': d.url.split('@')[1] if '@' in d.url else None,
}
for d in self.subtree()
if ShouldPrint(d)
}
output = json.dumps(json_output, indent=2, separators=(',', ': '))
else:
output = '\n'.join(
'%s: %s' % (d.name, d.url)
for d in self.subtree()
if ShouldPrint(d)
)
if self._options.output_json and self._options.output_json != '-':
with open(self._options.output_json, 'w') as f:
f.write(output)
json_output = []
# First level at .gclient
for d in self.dependencies:
entries = {}
def GrabDeps(dep):
"""Recursively grab dependencies."""
for d in dep.dependencies:
d.PinToActualRevision()
if ShouldPrintRevision(d):
entries[d.name] = d.url
GrabDeps(d)
GrabDeps(d)
json_output.append({
'name': d.name,
'solution_url': d.url,
'deps_file': d.deps_file,
'managed': d.managed,
'custom_deps': entries,
})
if self._options.output_json == '-':
print(json.dumps(json_output, indent=2, separators=(',', ': ')))
elif self._options.output_json:
with open(self._options.output_json, 'w') as f:
json.dump(json_output, f)
else:
# Print the snapshot configuration file
print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
'solution_list': pprint.pformat(json_output, indent=2),
})
else:
print(output)
entries = {}
for d in self.root.subtree(False):
if self._options.actual:
d.PinToActualRevision()
if ShouldPrintRevision(d):
entries[d.name] = d.url
if self._options.output_json:
json_output = {
name: {
'url': rev.split('@')[0] if rev else None,
'rev': rev.split('@')[1] if rev and '@' in rev else None,
}
for name, rev in entries.iteritems()
}
if self._options.output_json == '-':
print(json.dumps(json_output, indent=2, separators=(',', ': ')))
else:
with open(self._options.output_json, 'w') as f:
json.dump(json_output, f)
else:
keys = sorted(entries.keys())
for x in keys:
print('%s: %s' % (x, entries[x]))
logging.info(str(self))
def ParseDepsFile(self, expand_vars=None):
@ -1749,14 +1807,14 @@ class CipdDependency(Dependency):
def __init__(
self, parent, name, dep_value, cipd_root,
custom_vars, relative, condition):
custom_vars, should_process, relative, condition):
package = dep_value['package']
version = dep_value['version']
url = urlparse.urljoin(
cipd_root.service_url, '%s@%s' % (package, version))
super(CipdDependency, self).__init__(
parent, name + ':' + package, url, url, None, None, custom_vars,
None, None, relative, condition)
None, None, should_process, relative, condition)
if relative:
# TODO(jbudorick): Implement relative if necessary.
raise gclient_utils.Error(
@ -1773,6 +1831,8 @@ class CipdDependency(Dependency):
patch_refs):
"""Runs |command| then parse the DEPS file."""
logging.info('CipdDependency(%s).run()' % self.name)
if not self.should_process:
return
self._CreatePackageIfNecessary()
super(CipdDependency, self).run(revision_overrides, command, args,
work_queue, options, patch_refs)
@ -1816,7 +1876,7 @@ class CipdDependency(Dependency):
condition_part = ([' "condition": %r,' % self.condition]
if self.condition else [])
s.extend([
' # %s' % self.hierarchy(),
' # %s' % self.hierarchy(include_url=False),
' "%s": {' % (self.name.split(':')[0],),
' "packages": [',
])
@ -1935,6 +1995,9 @@ class Flattener(object):
Arguments:
dep (Dependency): dependency to process
"""
if dep.url is None:
return
# Make sure the revision is always fully specified (a hash),
# as opposed to refs or tags which might change. Similarly,
# shortened shas might become ambiguous; make sure to always
@ -1972,6 +2035,7 @@ class Flattener(object):
deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
if not os.path.exists(deps_path):
return
assert dep.url
self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
for dep in self._deps.itervalues():
add_deps_file(dep)
@ -1997,7 +2061,8 @@ class Flattener(object):
"""
assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
dep.name, self._deps.get(dep.name))
self._deps[dep.name] = dep
if dep.url:
self._deps[dep.name] = dep
def _flatten_dep(self, dep):
"""Visits a dependency in order to flatten it (see CMDflatten).
@ -2014,7 +2079,7 @@ class Flattener(object):
# Only include vars explicitly listed in the DEPS files or gclient solution,
# not automatic, local overrides (i.e. not all of dep.get_vars()).
hierarchy = dep.hierarchy()
hierarchy = dep.hierarchy(include_url=False)
for key, value in dep._vars.iteritems():
# Make sure there are no conflicting variables. It is fine however
# to use same variable name, as long as the value is consistent.
@ -2128,7 +2193,7 @@ def _DepsOsToLines(deps_os):
condition_part = ([' "condition": %r,' % dep.condition]
if dep.condition else [])
s.extend([
' # %s' % dep.hierarchy(),
' # %s' % dep.hierarchy(include_url=False),
' "%s": {' % (name,),
' "url": "%s",' % (dep.raw_url,),
] + condition_part + [
@ -2147,7 +2212,7 @@ def _HooksToLines(name, hooks):
s = ['%s = [' % name]
for dep, hook in hooks:
s.extend([
' # %s' % dep.hierarchy(),
' # %s' % dep.hierarchy(include_url=False),
' {',
])
if hook.name is not None:
@ -2176,7 +2241,7 @@ def _HooksOsToLines(hooks_os):
s.append(' "%s": [' % hook_os)
for dep, hook in os_hooks:
s.extend([
' # %s' % dep.hierarchy(),
' # %s' % dep.hierarchy(include_url=False),
' {',
])
if hook.name is not None:
@ -2508,12 +2573,16 @@ def CMDsync(parser, args):
ret = client.RunOnDeps('update', args)
if options.output_json:
slns = {}
for d in client.subtree():
for d in client.subtree(True):
normed = d.name.replace('\\', '/').rstrip('/') + '/'
if normed in slns and not d.should_process:
# If an unprocessed dependency would override an existing dependency,
# ignore it.
continue
slns[normed] = {
'revision': d.got_revision,
'scm': d.used_scm.name if d.used_scm else None,
'url': str(d.url),
'url': str(d.url) if d.url else None,
}
with open(options.output_json, 'wb') as f:
json.dump({'solutions': slns}, f)

@ -9,4 +9,4 @@ As the CI needs of the browser grew, Batty, the Build and Test Yeti, got
a new friend:
The End.
The End!

@ -246,14 +246,9 @@ class GClientSmoke(GClientSmokeBase):
']\n'
'cache_dir = None\n') % self.git_base)
os.remove(p)
results = self.gclient(['config', '--spec', '["blah blah"]'])
self.assertEqual(('', 'Error: No solution specified\n', 1), results)
results = self.gclient(['config', '--spec',
'solutions=[{"name": "./", "url": None}]'])
self.assertEqual(('', 'Error: No solution specified\n', 1), results)
test(['config', '--spec', '["blah blah"]'], '["blah blah"]')
os.remove(p)
results = self.gclient(['config', 'foo', 'faa', 'fuu'])
err = ('Usage: gclient.py config [options] [url]\n\n'
'gclient.py: error: Inconsistent arguments. Use either --spec or one'
@ -261,6 +256,24 @@ class GClientSmoke(GClientSmokeBase):
self.check(('', err, 2), results)
self.assertFalse(os.path.exists(join(self.root_dir, '.gclient')))
def testSolutionNone(self):
results = self.gclient(['config', '--spec',
'solutions=[{"name": "./", "url": None}]'])
self.check(('', '', 0), results)
results = self.gclient(['sync'])
self.check(('', '', 0), results)
self.assertTree({})
results = self.gclient(['revinfo'])
self.check(('./: None\n', '', 0), results)
self.check(('', '', 0), self.gclient(['diff']))
self.assertTree({})
self.check(('', '', 0), self.gclient(['pack']))
self.check(('', '', 0), self.gclient(['revert']))
self.assertTree({})
self.check(('', '', 0), self.gclient(['runhooks']))
self.assertTree({})
self.check(('', '', 0), self.gclient(['status']))
def testDifferentTopLevelDirectory(self):
# Check that even if the .gclient file does not mention the directory src
# itself, but it is included via dependencies, the .gclient file is used.
@ -796,10 +809,8 @@ class GClientSmokeGIT(GClientSmokeBase):
with open(output_json) as f:
output_json = json.load(f)
self.maxDiff = None
out = [{
'solution_url': '%srepo_1@%s' % (
self.git_base, self.githash('repo_1', 2)),
'solution_url': self.git_base + 'repo_1',
'managed': True,
'name': 'src',
'deps_file': 'DEPS',

@ -202,7 +202,7 @@ class GclientTest(trial_dir.TestCase):
url = 'proto://host/path/@revision'
d = gclient.Dependency(
None, 'name', url, url, None, None, None,
None, '', False, None, True)
None, '', True, False, None, True)
self.assertEquals('proto://host/path@revision', d.url)
def testStr(self):
@ -213,17 +213,17 @@ class GclientTest(trial_dir.TestCase):
[
gclient.Dependency(
obj, 'foo', 'svn://example.com/foo', 'svn://example.com/foo', None,
None, None, None, 'DEPS', False, None, True),
None, None, None, 'DEPS', True, False, None, True),
gclient.Dependency(
obj, 'bar', 'svn://example.com/bar', 'svn://example.com/bar', None,
None, None, None, 'DEPS', False, None, True),
None, None, None, 'DEPS', True, False, None, True),
],
[])
obj.dependencies[0].add_dependencies_and_close(
[
gclient.Dependency(
obj.dependencies[0], 'foo/dir1', 'svn://example.com/foo/dir1',
'svn://example.com/foo/dir1', None, None, None, None, 'DEPS',
'svn://example.com/foo/dir1', None, None, None, None, 'DEPS', True,
False, None, True),
],
[])
@ -232,7 +232,7 @@ class GclientTest(trial_dir.TestCase):
# pylint: disable=protected-access
obj.dependencies[0]._file_list.append('foo')
str_obj = str(obj)
self.assertEquals(230, len(str_obj), '%d\n%s' % (len(str_obj), str_obj))
self.assertEquals(322, len(str_obj), '%d\n%s' % (len(str_obj), str_obj))
def testHooks(self):
topdir = self.root_dir
@ -543,12 +543,14 @@ class GclientTest(trial_dir.TestCase):
('foo/rel', 'svn://example.com/rel'),
], self._get_processed())
self.assertEqual(4, len(sol.dependencies))
self.assertEqual(6, len(sol.dependencies))
self.assertEqual([
('foo/bar', 'svn://example.com/override'),
('foo/baz', 'svn://example.com/baz'),
('foo/new', 'svn://example.com/new'),
('foo/rel', 'svn://example.com/rel'),
('foo/skip', None),
('foo/skip2', None),
], [(dep.name, dep.url) for dep in sol.dependencies])
def testDepsOsOverrideDepsInDepsFile(self):
@ -1129,7 +1131,8 @@ class GclientTest(trial_dir.TestCase):
[
gclient.Dependency(
obj, 'foo', 'svn://example.com/foo', 'svn://example.com/foo', None,
None, None, None, 'DEPS', False, None, True),
None, None, None, 'DEPS', True,
False, None, True),
],
[])
obj.dependencies[0].add_dependencies_and_close(
@ -1137,12 +1140,12 @@ class GclientTest(trial_dir.TestCase):
gclient.CipdDependency(obj.dependencies[0], 'foo',
{'package': 'foo_package',
'version': 'foo_version'},
cipd_root, None, False,
cipd_root, None, True, False,
'fake_condition'),
gclient.CipdDependency(obj.dependencies[0], 'foo',
{'package': 'bar_package',
'version': 'bar_version'},
cipd_root, None, False,
cipd_root, None, True, False,
'fake_condition'),
],
[])

Loading…
Cancel
Save