Index: gclient.py |
diff --git a/gclient.py b/gclient.py |
index 9ea7ce08c5b27382836d94aaa5d0622434d23e8c..9721c55ff50db194276929c98a02c467dee42916 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.') |