From 54a07a2f8f7cd53f53a939b32cb0a232d3375446 Mon Sep 17 00:00:00 2001 From: "maruel@chromium.org" Date: Mon, 14 Jun 2010 19:07:39 +0000 Subject: [PATCH] Make each solution dictionary an object. The change is partial to keep this change still correct but readable/reviewable. Followup changes will further move functions into the Dependency class. If it was done in one change, it would be unreviewable. Fix GetScmName() for protocol svn+ssh://. TEST=didn't break the smoke tests. Review URL: http://codereview.chromium.org/2837001 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@49707 0039d316-1c4b-4281-b951-d872f2087c98 --- gclient.py | 128 ++++++++++++++++++++++++++++++------------------- gclient_scm.py | 2 +- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/gclient.py b/gclient.py index 9ea7ce08c..9721c55ff 100644 --- a/gclient.py +++ b/gclient.py @@ -155,10 +155,38 @@ class GClientKeywords(object): raise gclient_utils.Error("Var is not defined: %s" % var_name) -class GClient(GClientKeywords): - """Object that represent a gclient checkout.""" +class Dependency(GClientKeywords): + """Object that represents a dependency checkout.""" DEPS_FILE = 'DEPS' + def __init__(self, parent, name, url, safesync_url=None, custom_deps=None, + custom_vars=None, deps_file=None): + GClientKeywords.__init__(self) + self.parent = parent + self.name = name + self.url = url + # These 2 are only set in .gclient and not in DEPS files. + self.safesync_url = safesync_url + self.custom_vars = custom_vars or {} + self.custom_deps = custom_deps or {} + self.dependencies = [] + self.deps_file = deps_file or self.DEPS_FILE + self._deps_hooks = [] + # Sanity checks + if not self.name and self.parent: + raise gclient_utils.Error('Dependency without name') + if not isinstance(self.url, + (basestring, self.FromImpl, self.FileImpl, None.__class__)): + raise gclient_utils.Error('dependency url must be either a string, None, ' + 'File() or From() instead of %s' % + self.url.__class__.__name__) + if '/' in self.deps_file or '\\' in self.deps_file: + raise gclient_utils.Error('deps_file name must not be a path, just a ' + 'filename. %s' % self.deps_file) + + +class GClient(Dependency): + """Main gclient checkout root where .gclient resides.""" SUPPORTED_COMMANDS = [ 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update', 'runhooks' @@ -204,17 +232,16 @@ solutions = [ """) def __init__(self, root_dir, options): + Dependency.__init__(self, None, None, None) self._root_dir = root_dir self._options = options self.config_content = None - self._config_dict = {} - self._deps_hooks = [] def SetConfig(self, content): - self._config_dict = {} + config_dict = {} self.config_content = content try: - exec(content, self._config_dict) + exec(content, config_dict) except SyntaxError, e: try: # Try to construct a human readable error message @@ -228,6 +255,14 @@ solutions = [ else: # Raise a new exception with the human readable message: raise gclient_utils.Error('\n'.join(error_message)) + for s in config_dict.get('solutions', []): + self.dependencies.append(Dependency( + self, s['name'], s['url'], + s.get('safesync_url', None), + s.get('custom_deps', {}), + s.get('custom_vars', {}))) + # .gclient can have hooks. + self._deps_hooks = config_dict.get('hooks', []) def SaveConfig(self): gclient_utils.FileWrite(os.path.join(self.root_dir(), @@ -239,9 +274,6 @@ solutions = [ os.path.join(self.root_dir(), self._options.config_filename)) self.SetConfig(client_source) - def GetVar(self, key, default=None): - return self._config_dict.get(key, default) - @staticmethod def LoadCurrentConfig(options, from_dir=None): """Searches for and loads a .gclient file relative to the current working @@ -355,7 +387,12 @@ solutions = [ deps.update(os_deps) if 'hooks' in local_scope and parse_hooks: - self._deps_hooks.extend(local_scope['hooks']) + # TODO(maruel): Temporary Hack. Since this function is misplaced, find the + # right 'self' to add the hooks. + for d in self.dependencies: + if d.name == solution_name: + d._deps_hooks.extend(local_scope['hooks']) + break # If use_relative_paths is set in the DEPS file, regenerate # the dictionary using paths relative to the directory containing @@ -388,25 +425,23 @@ solutions = [ Error: If a dependency conflicts with another dependency or of a solution. """ deps = {} - for solution in self.GetVar("solutions"): - custom_vars = solution.get("custom_vars", {}) + for solution in self.dependencies: solution_deps = self._ParseSolutionDeps( - solution["name"], - solution_deps_content[solution["name"]], - custom_vars, + solution.name, + solution_deps_content[solution.name], + solution.custom_vars, True) # If a line is in custom_deps, but not in the solution, we want to append # this line to the solution. - if "custom_deps" in solution: - for d in solution["custom_deps"]: - if d not in solution_deps: - solution_deps[d] = solution["custom_deps"][d] + for d in solution.custom_deps: + if d not in solution_deps: + solution_deps[d] = solution.custom_deps[d] for d in solution_deps: - if "custom_deps" in solution and d in solution["custom_deps"]: + if d in solution.custom_deps: # Dependency is overriden. - url = solution["custom_deps"][d] + url = solution.custom_deps[d] if url is None: continue else: @@ -434,8 +469,8 @@ solutions = [ 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) + 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( @@ -480,9 +515,10 @@ solutions = [ return # Get any hooks from the .gclient file. - hooks = self.GetVar("hooks", []) + hooks = self._deps_hooks[:] # Add any hooks found in DEPS files. - hooks.extend(self._deps_hooks) + 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 @@ -500,29 +536,29 @@ solutions = [ if matching_file_list: self._RunHookAction(hook_dict, matching_file_list) - def _EnforceRevisions(self, solutions): + def _EnforceRevisions(self): """Checks for revision overrides.""" revision_overrides = {} if self._options.head: return revision_overrides - for s in solutions: - if not s.get('safesync_url', None): + for s in self.dependencies: + if not s.safesync_url: continue - handle = urllib.urlopen(s['safesync_url']) + handle = urllib.urlopen(s.safesync_url) rev = handle.read().strip() handle.close() if len(rev): - self._options.revisions.append('%s@%s' % (s['name'], rev)) + self._options.revisions.append('%s@%s' % (s.name, rev)) if not self._options.revisions: return revision_overrides # --revision will take over safesync_url. - solutions_names = [s['name'] for s in solutions] + solutions_names = [s.name for s in self.dependencies] index = 0 for revision in self._options.revisions: if not '@' in revision: # Support for --revision 123 revision = '%s@%s' % (solutions_names[index], revision) - sol, rev = revision.split("@", 1) + sol, rev = revision.split('@', 1) if not sol in solutions_names: #raise gclient_utils.Error('%s is not a valid solution.' % sol) print >> sys.stderr, ('Please fix your script, having invalid ' @@ -547,10 +583,9 @@ solutions = [ if not command in self.SUPPORTED_COMMANDS: raise gclient_utils.Error("'%s' is an unsupported command" % command) - solutions = self.GetVar("solutions") - if not solutions: + if not self.dependencies: raise gclient_utils.Error("No solution specified") - revision_overrides = self._EnforceRevisions(solutions) + revision_overrides = self._EnforceRevisions() # When running runhooks --force, there's no need to consult the SCM. # All known hooks are expected to run unconditionally regardless of working @@ -561,15 +596,11 @@ solutions = [ entries_deps_content = {} file_list = [] # Run on the base solutions first. - for solution in solutions: - name = solution["name"] - deps_file = solution.get("deps_file", self.DEPS_FILE) - if '/' in deps_file or '\\' in deps_file: - raise gclient_utils.Error('deps_file name must not be a path, just a ' - 'filename.') + 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"] + url = solution.url entries[name] = url if run_scm and url: self._options.revision = revision_overrides.get(name) @@ -579,7 +610,7 @@ solutions = [ self._options.revision = None try: deps_content = gclient_utils.FileRead( - os.path.join(self.root_dir(), name, deps_file)) + os.path.join(self.root_dir(), name, solution.deps_file)) except IOError, e: if e.errno != errno.ENOENT: raise @@ -703,8 +734,7 @@ solutions = [ The --snapshot option allows creating a .gclient file to reproduce the tree. """ - solutions = self.GetVar("solutions") - if not solutions: + if not self.dependencies: raise gclient_utils.Error("No solution specified") # Inner helper to generate base url and rev tuple @@ -720,15 +750,15 @@ solutions = [ entries = {} entries_deps_content = {} # Run on the base solutions first. - for solution in solutions: + for solution in self.dependencies: # Dictionary of { path : SCM url } to describe the gclient checkout - name = solution["name"] + name = solution.name if name in solution_names: raise gclient_utils.Error("solution %s specified more than once" % name) - (url, rev) = GetURLAndRev(name, solution["url"]) + (url, rev) = GetURLAndRev(name, solution.url) entries[name] = "%s@%s" % (url, rev) solution_names[name] = "%s@%s" % (url, rev) - deps_file = solution.get("deps_file", self.DEPS_FILE) + deps_file = solution.deps_file if '/' in deps_file or '\\' in deps_file: raise gclient_utils.Error('deps_file name must not be a path, just a ' 'filename.') diff --git a/gclient_scm.py b/gclient_scm.py index b6240a7ba..a76d2d0c2 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -62,7 +62,7 @@ def GetScmName(url): url.endswith('.git')): return 'git' elif (url.startswith('http://') or url.startswith('https://') or - url.startswith('svn://') or url.startswith('ssh+svn://')): + url.startswith('svn://') or url.startswith('svn+ssh://')): return 'svn' return None