blob: aa80b8660a56a65927c6750f756f0319821129f0 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
maruel@chromium.orgf50907b2010-08-12 17:05:48 +000052__version__ = "0.5.2"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org754960e2009-09-21 12:31:05 +000054import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055import optparse
56import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000057import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000058import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000060import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgada4c652009-12-03 15:32:01 +000065import breakpad
66
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000067import gclient_scm
68import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000069from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000072def attr(attr, data):
73 """Sets an attribute on a function."""
74 def hook(fn):
75 setattr(fn, attr, data)
76 return fn
77 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000078
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080## GClient implementation.
81
82
maruel@chromium.org116704f2010-06-11 17:34:38 +000083class GClientKeywords(object):
84 class FromImpl(object):
85 """Used to implement the From() syntax."""
86
87 def __init__(self, module_name, sub_target_name=None):
88 """module_name is the dep module we want to include from. It can also be
89 the name of a subdirectory to include from.
90
91 sub_target_name is an optional parameter if the module name in the other
92 DEPS file is different. E.g., you might want to map src/net to net."""
93 self.module_name = module_name
94 self.sub_target_name = sub_target_name
95
96 def __str__(self):
97 return 'From(%s, %s)' % (repr(self.module_name),
98 repr(self.sub_target_name))
99
maruel@chromium.org116704f2010-06-11 17:34:38 +0000100 class FileImpl(object):
101 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000102 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000103
104 def __init__(self, file_location):
105 self.file_location = file_location
106
107 def __str__(self):
108 return 'File("%s")' % self.file_location
109
110 def GetPath(self):
111 return os.path.split(self.file_location)[0]
112
113 def GetFilename(self):
114 rev_tokens = self.file_location.split('@')
115 return os.path.split(rev_tokens[0])[1]
116
117 def GetRevision(self):
118 rev_tokens = self.file_location.split('@')
119 if len(rev_tokens) > 1:
120 return rev_tokens[1]
121 return None
122
123 class VarImpl(object):
124 def __init__(self, custom_vars, local_scope):
125 self._custom_vars = custom_vars
126 self._local_scope = local_scope
127
128 def Lookup(self, var_name):
129 """Implements the Var syntax."""
130 if var_name in self._custom_vars:
131 return self._custom_vars[var_name]
132 elif var_name in self._local_scope.get("vars", {}):
133 return self._local_scope["vars"][var_name]
134 raise gclient_utils.Error("Var is not defined: %s" % var_name)
135
136
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000137class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000138 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000139 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000140
maruel@chromium.org0d812442010-08-10 12:41:08 +0000141 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000142 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000143 GClientKeywords.__init__(self)
144 self.parent = parent
145 self.name = name
146 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000147 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000148 # These 2 are only set in .gclient and not in DEPS files.
149 self.safesync_url = safesync_url
150 self.custom_vars = custom_vars or {}
151 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000152 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000153 self.dependencies = []
154 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000155 # A cache of the files affected by the current operation, necessary for
156 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000157 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000158 # If it is not set to True, the dependency wasn't processed for its child
159 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000160 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000161 # This dependency should be processed, i.e. checked out
162 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000163 # This dependency has been processed, i.e. checked out
164 self.processed = False
165 # This dependency had its hook run
166 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000167 # Required dependencies to run before running this one:
168 self.requirements = []
169 if self.parent and self.parent.name:
170 self.requirements.append(self.parent.name)
171 if isinstance(self.url, self.FromImpl):
172 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000173
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000174 # Sanity checks
175 if not self.name and self.parent:
176 raise gclient_utils.Error('Dependency without name')
177 if not isinstance(self.url,
178 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
179 raise gclient_utils.Error('dependency url must be either a string, None, '
180 'File() or From() instead of %s' %
181 self.url.__class__.__name__)
182 if '/' in self.deps_file or '\\' in self.deps_file:
183 raise gclient_utils.Error('deps_file name must not be a path, just a '
184 'filename. %s' % self.deps_file)
185
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000186 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000187 """Resolves the parsed url from url.
188
189 Manages From() keyword accordingly. Do not touch self.parsed_url nor
190 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000191 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000192 overriden_url = self.get_custom_deps(self.name, url)
193 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000194 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000195 overriden_url))
196 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000197 elif isinstance(url, self.FromImpl):
198 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000199 if not ref:
200 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
201 url.module_name, ref))
202 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000203 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000204 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000205 # Make sure the referenced dependency DEPS file is loaded and file the
206 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000207 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000208 found_dep = None
209 for d in ref.dependencies:
210 if d.name == sub_target:
211 found_dep = d
212 break
213 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000214 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000215 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
216 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000217 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000218 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000219 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000220 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000221 elif isinstance(url, basestring):
222 parsed_url = urlparse.urlparse(url)
223 if not parsed_url[0]:
224 # A relative url. Fetch the real base.
225 path = parsed_url[2]
226 if not path.startswith('/'):
227 raise gclient_utils.Error(
228 'relative DEPS entry \'%s\' must begin with a slash' % url)
229 # Create a scm just to query the full url.
230 parent_url = self.parent.parsed_url
231 if isinstance(parent_url, self.FileImpl):
232 parent_url = parent_url.file_location
233 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000234 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000235 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000236 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000237 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000238 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000239 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000240 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000241 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000242 return parsed_url
243 elif url is None:
244 return None
245 else:
246 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000247
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000248 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000249 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000250 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000251 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000252 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000253 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000254 self.deps_parsed = True
255 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
256 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000257 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000258 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000259 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000260 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000261
maruel@chromium.org271375b2010-06-23 19:17:38 +0000262 # Eval the content.
263 # One thing is unintuitive, vars= {} must happen before Var() use.
264 local_scope = {}
265 var = self.VarImpl(self.custom_vars, local_scope)
266 global_scope = {
267 'File': self.FileImpl,
268 'From': self.FromImpl,
269 'Var': var.Lookup,
270 'deps_os': {},
271 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000272 try:
273 exec(deps_content, global_scope, local_scope)
274 except SyntaxError, e:
275 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000276 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277 # load os specific dependencies if defined. these dependencies may
278 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000279 if 'deps_os' in local_scope:
280 for deps_os_key in self.enforced_os():
281 os_deps = local_scope['deps_os'].get(deps_os_key, {})
282 if len(self.enforced_os()) > 1:
283 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000284 # platform, so we collect the broadest set of dependencies available.
285 # We may end up with the wrong revision of something for our
286 # platform, but this is the best we can do.
287 deps.update([x for x in os_deps.items() if not x[0] in deps])
288 else:
289 deps.update(os_deps)
290
maruel@chromium.org271375b2010-06-23 19:17:38 +0000291 self.deps_hooks.extend(local_scope.get('hooks', []))
292
293 # If a line is in custom_deps, but not in the solution, we want to append
294 # this line to the solution.
295 for d in self.custom_deps:
296 if d not in deps:
297 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000298
299 # If use_relative_paths is set in the DEPS file, regenerate
300 # the dictionary using paths relative to the directory containing
301 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000302 use_relative_paths = local_scope.get('use_relative_paths', False)
303 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000304 rel_deps = {}
305 for d, url in deps.items():
306 # normpath is required to allow DEPS to use .. in their
307 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000308 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
309 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000310
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000311 # Convert the deps into real Dependency.
312 for name, url in deps.iteritems():
313 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000314 raise gclient_utils.Error(
315 'The same name "%s" appears multiple times in the deps section' %
316 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000317 should_process = self.recursion_limit() > 0 and self.should_process
318 if should_process:
319 tree = dict((d.name, d) for d in self.tree(False))
320 if name in tree:
321 if url == tree[name].url:
322 logging.info('Won\'t process duplicate dependency %s' % tree[name])
323 # In theory we could keep it as a shadow of the other one. In
324 # practice, simply ignore it.
325 #should_process = False
326 continue
327 else:
328 raise gclient_utils.Error(
329 'Dependency %s specified more than once:\n %s\nvs\n %s' %
330 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000331 self.dependencies.append(Dependency(self, name, url, None, None, None,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000332 None, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000333 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000334
maruel@chromium.org049bced2010-08-12 13:37:20 +0000335 def run(self, options, revision_overrides, command, args, work_queue):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000336 """Runs 'command' before parsing the DEPS in case it's a initial checkout
337 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000338 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000339 if not self.should_process:
340 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000341 # When running runhooks, there's no need to consult the SCM.
342 # All known hooks are expected to run unconditionally regardless of working
343 # copy state, so skip the SCM status check.
344 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000345 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000346 if run_scm and self.parsed_url:
347 if isinstance(self.parsed_url, self.FileImpl):
348 # Special support for single-file checkout.
349 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
350 options.revision = self.parsed_url.GetRevision()
351 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
352 self.root_dir(),
353 self.name)
354 scm.RunCommand('updatesingle', options,
355 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000356 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000357 else:
358 options.revision = revision_overrides.get(self.name)
359 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000360 scm.RunCommand(command, options, args, self._file_list)
361 self._file_list = [os.path.join(self.name, f.strip())
362 for f in self._file_list]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000363 options.revision = None
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000364 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000365 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000366 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000367 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000368 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
369 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000370 # src/foo. Yes, it's O(n^2)... It's important to do that before
371 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000372 for s in self.dependencies:
373 for s2 in self.dependencies:
374 if s is s2:
375 continue
376 if s.name.startswith(posixpath.join(s2.name, '')):
377 s.requirements.append(s2.name)
378
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000379 # Parse the dependencies of this dependency.
380 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000381 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000382
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000383 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000384 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000385 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000386 assert self.hooks_ran == False
387 if not self.should_process or self.recursion_limit() <= 0:
388 # Don't run the hook when it is above recursion_limit.
389 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000390 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000391 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000392 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 # TODO(maruel): If the user is using git or git-svn, then we don't know
394 # what files have changed so we always run all hooks. It'd be nice to fix
395 # that.
396 if (options.force or
397 isinstance(self.parsed_url, self.FileImpl) or
398 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
399 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
400 for hook_dict in self.deps_hooks:
401 self._RunHookAction(hook_dict, [])
402 else:
403 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
404 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000405 file_list = self.file_list()
406 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000407 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000408 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000409 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000410
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000412 file_list[i].lower()])
413 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000414
415 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000416 while (file_list[i].startswith('\\') or
417 file_list[i].startswith('/')):
418 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000419
420 # Run hooks on the basis of whether the files from the gclient operation
421 # match each hook's pattern.
422 for hook_dict in self.deps_hooks:
423 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000424 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000425 if matching_file_list:
426 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000427 for s in self.dependencies:
428 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000429
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000430 def _RunHookAction(self, hook_dict, matching_file_list):
431 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000432 # A single DEPS file can specify multiple hooks so this function can be
433 # called multiple times on a single Dependency.
434 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000435 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000436 logging.debug(hook_dict)
437 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000438 command = hook_dict['action'][:]
439 if command[0] == 'python':
440 # If the hook specified "python" as the first item, the action is a
441 # Python script. Run it by starting a new copy of the same
442 # interpreter.
443 command[0] = sys.executable
444
445 if '$matching_files' in command:
446 splice_index = command.index('$matching_files')
447 command[splice_index:splice_index + 1] = matching_file_list
448
449 # Use a discrete exit status code of 2 to indicate that a hook action
450 # failed. Users of this script may wish to treat hook action failures
451 # differently from VC failures.
452 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
453
maruel@chromium.org271375b2010-06-23 19:17:38 +0000454 def root_dir(self):
455 return self.parent.root_dir()
456
457 def enforced_os(self):
458 return self.parent.enforced_os()
459
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000460 def recursion_limit(self):
461 return self.parent.recursion_limit() - 1
462
maruel@chromium.org0d812442010-08-10 12:41:08 +0000463 def tree(self, include_all):
464 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000465
maruel@chromium.org0d812442010-08-10 12:41:08 +0000466 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000467 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000468 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000469 for d in self.dependencies:
470 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000471 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000472 for d in self.dependencies:
473 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000474 return result
475
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000476 def get_custom_deps(self, name, url):
477 """Returns a custom deps if applicable."""
478 if self.parent:
479 url = self.parent.get_custom_deps(name, url)
480 # None is a valid return value to disable a dependency.
481 return self.custom_deps.get(name, url)
482
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000483 def file_list(self):
484 result = self._file_list[:]
485 for d in self.dependencies:
486 result.extend(d.file_list())
487 return result
488
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000489 def __str__(self):
490 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000491 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000492 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
493 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000494 # 'deps_file'
495 if self.__dict__[i]:
496 out.append('%s: %s' % (i, self.__dict__[i]))
497
498 for d in self.dependencies:
499 out.extend([' ' + x for x in str(d).splitlines()])
500 out.append('')
501 return '\n'.join(out)
502
503 def __repr__(self):
504 return '%s: %s' % (self.name, self.url)
505
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000506 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000507 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000508 out = '%s(%s)' % (self.name, self.url)
509 i = self.parent
510 while i and i.name:
511 out = '%s(%s) -> %s' % (i.name, i.url, out)
512 i = i.parent
513 return out
514
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000515 def root_parent(self):
516 """Returns the root object, normally a GClient object."""
517 d = self
518 while d.parent:
519 d = d.parent
520 return d
521
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000522
523class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000524 """Object that represent a gclient checkout. A tree of Dependency(), one per
525 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000526
527 DEPS_OS_CHOICES = {
528 "win32": "win",
529 "win": "win",
530 "cygwin": "win",
531 "darwin": "mac",
532 "mac": "mac",
533 "unix": "unix",
534 "linux": "unix",
535 "linux2": "unix",
536 }
537
538 DEFAULT_CLIENT_FILE_TEXT = ("""\
539solutions = [
540 { "name" : "%(solution_name)s",
541 "url" : "%(solution_url)s",
542 "custom_deps" : {
543 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000544 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000545 },
546]
547""")
548
549 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
550 { "name" : "%(solution_name)s",
551 "url" : "%(solution_url)s",
552 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000553%(solution_deps)s },
554 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000555 },
556""")
557
558 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
559# Snapshot generated with gclient revinfo --snapshot
560solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000561%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000562""")
563
564 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000565 # Do not change previous behavior. Only solution level and immediate DEPS
566 # are processed.
567 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000568 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000569 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000570 if options.deps_os:
571 enforced_os = options.deps_os.split(',')
572 else:
573 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
574 if 'all' in enforced_os:
575 enforced_os = self.DEPS_OS_CHOICES.itervalues()
576 self._enforced_os = list(set(enforced_os))
577 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000578 self.config_content = None
579
580 def SetConfig(self, content):
581 assert self.dependencies == []
582 config_dict = {}
583 self.config_content = content
584 try:
585 exec(content, config_dict)
586 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000587 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000588 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000589 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000590 tree = dict((d.name, d) for d in self.tree(False))
591 if s['name'] in tree:
592 raise gclient_utils.Error(
593 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000594 self.dependencies.append(Dependency(
595 self, s['name'], s['url'],
596 s.get('safesync_url', None),
597 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000598 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000599 None,
600 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000601 except KeyError:
602 raise gclient_utils.Error('Invalid .gclient file. Solution is '
603 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000604 # .gclient can have hooks.
605 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000606 self.direct_reference = True
607 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000608
609 def SaveConfig(self):
610 gclient_utils.FileWrite(os.path.join(self.root_dir(),
611 self._options.config_filename),
612 self.config_content)
613
614 @staticmethod
615 def LoadCurrentConfig(options):
616 """Searches for and loads a .gclient file relative to the current working
617 dir. Returns a GClient object."""
618 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
619 if not path:
620 return None
621 client = GClient(path, options)
622 client.SetConfig(gclient_utils.FileRead(
623 os.path.join(path, options.config_filename)))
624 return client
625
626 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
627 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
628 'solution_name': solution_name,
629 'solution_url': solution_url,
630 'safesync_url' : safesync_url,
631 })
632
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000633 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000634 """Creates a .gclient_entries file to record the list of unique checkouts.
635
636 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000637 """
638 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
639 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000640 result = 'entries = {\n'
641 for entry in self.tree(False):
642 # Skip over File() dependencies as we can't version them.
643 if not isinstance(entry.parsed_url, self.FileImpl):
644 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
645 pprint.pformat(entry.parsed_url))
646 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000647 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000648 logging.info(result)
649 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650
651 def _ReadEntries(self):
652 """Read the .gclient_entries file for the given client.
653
654 Returns:
655 A sequence of solution names, which will be empty if there is the
656 entries file hasn't been created yet.
657 """
658 scope = {}
659 filename = os.path.join(self.root_dir(), self._options.entries_filename)
660 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000661 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000662 try:
663 exec(gclient_utils.FileRead(filename), scope)
664 except SyntaxError, e:
665 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000666 return scope['entries']
667
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000668 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000669 """Checks for revision overrides."""
670 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000671 if self._options.head:
672 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000673 for s in self.dependencies:
674 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000675 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000676 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000677 rev = handle.read().strip()
678 handle.close()
679 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000680 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000681 if not self._options.revisions:
682 return revision_overrides
683 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000684 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000685 index = 0
686 for revision in self._options.revisions:
687 if not '@' in revision:
688 # Support for --revision 123
689 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000690 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000691 if not sol in solutions_names:
692 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
693 print >> sys.stderr, ('Please fix your script, having invalid '
694 '--revision flags will soon considered an error.')
695 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000696 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000697 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000698 return revision_overrides
699
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000700 def RunOnDeps(self, command, args):
701 """Runs a command on each dependency in a client and its dependencies.
702
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000703 Args:
704 command: The command to use (e.g., 'status' or 'diff')
705 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000706 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000707 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000708 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000709 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000710 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000711 if command == 'update' and not self._options.verbose:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000712 pm = Progress('Syncing projects', 1)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000713 work_queue = gclient_utils.ExecutionQueue(pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000714 for s in self.dependencies:
715 work_queue.enqueue(s)
716 work_queue.flush(self._options, revision_overrides, command, args,
717 work_queue)
piman@chromium.org6f363722010-04-27 00:41:09 +0000718
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000719 # Once all the dependencies have been processed, it's now safe to run the
720 # hooks.
721 if not self._options.nohooks:
722 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000723
724 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000725 # Notify the user if there is an orphaned entry in their working copy.
726 # Only delete the directory if there are no changes in it, and
727 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000728 entries = [i.name for i in self.tree(False)]
729 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000730 # Fix path separator on Windows.
731 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000732 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000733 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000734 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000735 file_list = []
736 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
737 scm.status(self._options, [], file_list)
738 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000739 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000740 # There are modified files in this entry. Keep warning until
741 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000742 print(('\nWARNING: \'%s\' is no longer part of this client. '
743 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000744 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000745 else:
746 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000747 print('\n________ deleting \'%s\' in \'%s\'' % (
748 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000749 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000750 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000751 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000752 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753
754 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000755 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000756 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000757 # Load all the settings.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000758 work_queue = gclient_utils.ExecutionQueue(None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000759 for s in self.dependencies:
760 work_queue.enqueue(s)
761 work_queue.flush(self._options, {}, None, [], work_queue)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000762
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000763 def GetURLAndRev(dep):
764 """Returns the revision-qualified SCM url for a Dependency."""
765 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000766 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000767 if isinstance(dep.parsed_url, self.FileImpl):
768 original_url = dep.parsed_url.file_location
769 else:
770 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000771 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000772 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000773 if not os.path.isdir(scm.checkout_path):
774 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000775 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000776
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000777 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000778 new_gclient = ''
779 # First level at .gclient
780 for d in self.dependencies:
781 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000782 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000783 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000784 for d in dep.dependencies:
785 entries[d.name] = GetURLAndRev(d)
786 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000787 GrabDeps(d)
788 custom_deps = []
789 for k in sorted(entries.keys()):
790 if entries[k]:
791 # Quotes aren't escaped...
792 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
793 else:
794 custom_deps.append(' \"%s\": None,\n' % k)
795 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
796 'solution_name': d.name,
797 'solution_url': d.url,
798 'safesync_url' : d.safesync_url or '',
799 'solution_deps': ''.join(custom_deps),
800 }
801 # Print the snapshot configuration file
802 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000803 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000804 entries = {}
805 for d in self.tree(False):
806 if self._options.actual:
807 entries[d.name] = GetURLAndRev(d)
808 else:
809 entries[d.name] = d.parsed_url
810 keys = sorted(entries.keys())
811 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000812 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000813 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000814
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000815 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000816 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000817 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000818
maruel@chromium.org75a59272010-06-11 22:34:03 +0000819 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000820 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000821 return self._root_dir
822
maruel@chromium.org271375b2010-06-23 19:17:38 +0000823 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000824 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000825 return self._enforced_os
826
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000827 def recursion_limit(self):
828 """How recursive can each dependencies in DEPS file can load DEPS file."""
829 return self._recursion_limit
830
maruel@chromium.org0d812442010-08-10 12:41:08 +0000831 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000832 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000833 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000834
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000836#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000837
838
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000839def CMDcleanup(parser, args):
840 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000841
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000842Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000843"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000844 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
845 help='override deps for the specified (comma-separated) '
846 'platform(s); \'all\' will process all deps_os '
847 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000848 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000849 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000850 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000851 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852 if options.verbose:
853 # Print out the .gclient file. This is longer than if we just printed the
854 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000855 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000856 return client.RunOnDeps('cleanup', args)
857
858
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000859@attr('usage', '[command] [args ...]')
860def CMDrecurse(parser, args):
861 """Operates on all the entries.
862
863 Runs a shell command on all entries.
864 """
865 # Stop parsing at the first non-arg so that these go through to the command
866 parser.disable_interspersed_args()
867 parser.add_option('-s', '--scm', action='append', default=[],
868 help='choose scm types to operate upon')
869 options, args = parser.parse_args(args)
870 root, entries = gclient_utils.GetGClientRootAndEntries()
871 scm_set = set()
872 for scm in options.scm:
873 scm_set.update(scm.split(','))
874
875 # Pass in the SCM type as an env variable
876 env = os.environ.copy()
877
878 for path, url in entries.iteritems():
879 scm = gclient_scm.GetScmName(url)
880 if scm_set and scm not in scm_set:
881 continue
882 dir = os.path.normpath(os.path.join(root, path))
883 env['GCLIENT_SCM'] = scm
884 env['GCLIENT_URL'] = url
885 subprocess.Popen(args, cwd=dir, env=env).communicate()
886
887
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000888@attr('usage', '[url] [safesync url]')
889def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000890 """Create a .gclient file in the current directory.
891
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000892This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000893top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000894modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000895provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000896URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000897"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000898 parser.add_option('--spec',
899 help='create a gclient file containing the provided '
900 'string. Due to Cygwin/Python brokenness, it '
901 'probably can\'t contain any newlines.')
902 parser.add_option('--name',
903 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000904 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000905 if ((options.spec and args) or len(args) > 2 or
906 (not options.spec and not args)):
907 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
908
maruel@chromium.org0329e672009-05-13 18:41:04 +0000909 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000910 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000911 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000912 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000913 if options.spec:
914 client.SetConfig(options.spec)
915 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000916 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000917 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000918 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000919 else:
920 # specify an alternate relpath for the given URL.
921 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000922 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000923 if len(args) > 1:
924 safesync_url = args[1]
925 client.SetDefaultConfig(name, base_url, safesync_url)
926 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000927 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000928
929
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000930def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000931 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000932 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
933 help='override deps for the specified (comma-separated) '
934 'platform(s); \'all\' will process all deps_os '
935 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000936 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000937 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000938 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000939 client = GClient.LoadCurrentConfig(options)
940
941 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000942 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000943
944 if options.verbose:
945 # Print out the .gclient file. This is longer than if we just printed the
946 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000947 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000948 return client.RunOnDeps('export', args)
949
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000951@attr('epilog', """Example:
952 gclient pack > patch.txt
953 generate simple patch for configured client and dependences
954""")
955def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000956 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000957
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000958Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000959dependencies, and performs minimal postprocessing of the output. The
960resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000961checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000962"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000963 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
964 help='override deps for the specified (comma-separated) '
965 'platform(s); \'all\' will process all deps_os '
966 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000967 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000968 client = GClient.LoadCurrentConfig(options)
969 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000970 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000971 if options.verbose:
972 # Print out the .gclient file. This is longer than if we just printed the
973 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000974 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000975 return client.RunOnDeps('pack', args)
976
977
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000978def CMDstatus(parser, args):
979 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000980 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
981 help='override deps for the specified (comma-separated) '
982 'platform(s); \'all\' will process all deps_os '
983 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000984 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000985 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000987 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988 if options.verbose:
989 # Print out the .gclient file. This is longer than if we just printed the
990 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000991 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000992 return client.RunOnDeps('status', args)
993
994
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000995@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000996 gclient sync
997 update files from SCM according to current configuration,
998 *for modules which have changed since last update or sync*
999 gclient sync --force
1000 update files from SCM according to current configuration, for
1001 all modules (useful for recovering files deleted from local copy)
1002 gclient sync --revision src@31000
1003 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001004""")
1005def CMDsync(parser, args):
1006 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001007 parser.add_option('-f', '--force', action='store_true',
1008 help='force update even for unchanged modules')
1009 parser.add_option('-n', '--nohooks', action='store_true',
1010 help='don\'t run hooks after the update is complete')
1011 parser.add_option('-r', '--revision', action='append',
1012 dest='revisions', metavar='REV', default=[],
1013 help='Enforces revision/hash for the solutions with the '
1014 'format src@rev. The src@ part is optional and can be '
1015 'skipped. -r can be used multiple times when .gclient '
1016 'has multiple solutions configured and will work even '
1017 'if the src@ part is skipped.')
1018 parser.add_option('-H', '--head', action='store_true',
1019 help='skips any safesync_urls specified in '
1020 'configured solutions and sync to head instead')
1021 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1022 help='delete any unexpected unversioned trees '
1023 'that are in the checkout')
1024 parser.add_option('-R', '--reset', action='store_true',
1025 help='resets any local changes before updating (git only)')
1026 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1027 help='override deps for the specified (comma-separated) '
1028 'platform(s); \'all\' will process all deps_os '
1029 'references')
1030 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1031 help='Skip svn up whenever possible by requesting '
1032 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001033 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001034 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001035
1036 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001037 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038
maruel@chromium.org307d1792010-05-31 20:03:13 +00001039 if options.revisions and options.head:
1040 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001041 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042
1043 if options.verbose:
1044 # Print out the .gclient file. This is longer than if we just printed the
1045 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001046 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 return client.RunOnDeps('update', args)
1048
1049
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001050def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001051 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001052 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001054def CMDdiff(parser, args):
1055 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001056 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1057 help='override deps for the specified (comma-separated) '
1058 'platform(s); \'all\' will process all deps_os '
1059 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001060 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001061 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001062 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001063 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064 if options.verbose:
1065 # Print out the .gclient file. This is longer than if we just printed the
1066 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001067 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068 return client.RunOnDeps('diff', args)
1069
1070
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001071def CMDrevert(parser, args):
1072 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001073 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1074 help='override deps for the specified (comma-separated) '
1075 'platform(s); \'all\' will process all deps_os '
1076 'references')
1077 parser.add_option('-n', '--nohooks', action='store_true',
1078 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001079 (options, args) = parser.parse_args(args)
1080 # --force is implied.
1081 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001082 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001083 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001084 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001085 return client.RunOnDeps('revert', args)
1086
1087
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001088def CMDrunhooks(parser, args):
1089 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001090 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1091 help='override deps for the specified (comma-separated) '
1092 'platform(s); \'all\' will process all deps_os '
1093 'references')
1094 parser.add_option('-f', '--force', action='store_true', default=True,
1095 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001096 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001097 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001099 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001100 if options.verbose:
1101 # Print out the .gclient file. This is longer than if we just printed the
1102 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001103 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001104 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001105 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001106 return client.RunOnDeps('runhooks', args)
1107
1108
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001110 """Output revision info mapping for the client and its dependencies.
1111
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001112 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001113 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001114 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1115 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001116 commit can change.
1117 """
1118 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1119 help='override deps for the specified (comma-separated) '
1120 'platform(s); \'all\' will process all deps_os '
1121 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001122 parser.add_option('-a', '--actual', action='store_true',
1123 help='gets the actual checked out revisions instead of the '
1124 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001125 parser.add_option('-s', '--snapshot', action='store_true',
1126 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001127 'version of all repositories to reproduce the tree, '
1128 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001129 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001130 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001132 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001134 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135
1136
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001137def Command(name):
1138 return getattr(sys.modules[__name__], 'CMD' + name, None)
1139
1140
1141def CMDhelp(parser, args):
1142 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001143 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001144 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001145 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001146 parser.print_help()
1147 return 0
1148
1149
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001150def GenUsage(parser, command):
1151 """Modify an OptParse object with the function's documentation."""
1152 obj = Command(command)
1153 if command == 'help':
1154 command = '<command>'
1155 # OptParser.description prefer nicely non-formatted strings.
1156 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1157 usage = getattr(obj, 'usage', '')
1158 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1159 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001160
1161
1162def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001163 """Doesn't parse the arguments here, just find the right subcommand to
1164 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001165 try:
1166 # Do it late so all commands are listed.
1167 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1168 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1169 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1170 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001171 parser.add_option('-v', '--verbose', action='count', default=0,
1172 help='Produces additional output for diagnostics. Can be '
1173 'used up to three times for more logging info.')
1174 parser.add_option('--gclientfile', dest='config_filename',
1175 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1176 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001177 # Integrate standard options processing.
1178 old_parser = parser.parse_args
1179 def Parse(args):
1180 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001181 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001182 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001183 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001184 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001185 level = logging.DEBUG
1186 logging.basicConfig(level=level,
1187 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1188 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001189
1190 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001191 if not hasattr(options, 'revisions'):
1192 # GClient.RunOnDeps expects it even if not applicable.
1193 options.revisions = []
1194 if not hasattr(options, 'head'):
1195 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001196 if not hasattr(options, 'nohooks'):
1197 options.nohooks = True
1198 if not hasattr(options, 'deps_os'):
1199 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001200 if not hasattr(options, 'manually_grab_svn_rev'):
1201 options.manually_grab_svn_rev = None
1202 if not hasattr(options, 'force'):
1203 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001204 return (options, args)
1205 parser.parse_args = Parse
1206 # We don't want wordwrapping in epilog (usually examples)
1207 parser.format_epilog = lambda _: parser.epilog or ''
1208 if argv:
1209 command = Command(argv[0])
1210 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001211 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001212 GenUsage(parser, argv[0])
1213 return command(parser, argv[1:])
1214 # Not a known command. Default to help.
1215 GenUsage(parser, 'help')
1216 return CMDhelp(parser, argv)
1217 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001218 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001219 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220
1221
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001222if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001223 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224
1225# vim: ts=2:sw=2:tw=80:et: