diff --git a/gclient.py b/gclient.py index 56c408f6c..a2a6b53bd 100755 --- a/gclient.py +++ b/gclient.py @@ -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) diff --git a/recipes/trigger_recipe_roller.txt b/recipes/trigger_recipe_roller.txt index 31d563eb2..fabc0d143 100644 --- a/recipes/trigger_recipe_roller.txt +++ b/recipes/trigger_recipe_roller.txt @@ -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! diff --git a/tests/gclient_smoketest.py b/tests/gclient_smoketest.py index f3170bde0..16a511f36 100755 --- a/tests/gclient_smoketest.py +++ b/tests/gclient_smoketest.py @@ -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', diff --git a/tests/gclient_test.py b/tests/gclient_test.py index dd663b67e..7fb21950c 100755 --- a/tests/gclient_test.py +++ b/tests/gclient_test.py @@ -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'), ], [])