blob: 054f52d93bbd76040372adb835333f6140666ddf [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.org46304292010-10-28 11:42:00 +000052__version__ = "0.6.1"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
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.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
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.orgcb2985f2010-11-03 14:08:31 +000072def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000073 """Sets an attribute on a function."""
74 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000075 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000076 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)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000144 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000145 self.parent = parent
146 self.name = name
147 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000148 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000149 # These 2 are only set in .gclient and not in DEPS files.
150 self.safesync_url = safesync_url
151 self.custom_vars = custom_vars or {}
152 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000153 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000154 self.dependencies = []
155 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000156 # A cache of the files affected by the current operation, necessary for
157 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000158 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000159 # If it is not set to True, the dependency wasn't processed for its child
160 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000161 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000162 # This dependency should be processed, i.e. checked out
163 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000164 # This dependency has been processed, i.e. checked out
165 self.processed = False
166 # This dependency had its hook run
167 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000168 # Required dependencies to run before running this one:
169 self.requirements = []
170 if self.parent and self.parent.name:
171 self.requirements.append(self.parent.name)
172 if isinstance(self.url, self.FromImpl):
173 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000174
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000175 # Sanity checks
176 if not self.name and self.parent:
177 raise gclient_utils.Error('Dependency without name')
178 if not isinstance(self.url,
179 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
180 raise gclient_utils.Error('dependency url must be either a string, None, '
181 'File() or From() instead of %s' %
182 self.url.__class__.__name__)
183 if '/' in self.deps_file or '\\' in self.deps_file:
184 raise gclient_utils.Error('deps_file name must not be a path, just a '
185 'filename. %s' % self.deps_file)
186
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000187 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000188 """Resolves the parsed url from url.
189
190 Manages From() keyword accordingly. Do not touch self.parsed_url nor
191 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000192 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000193 overriden_url = self.get_custom_deps(self.name, url)
194 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000195 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000196 overriden_url))
197 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000198 elif isinstance(url, self.FromImpl):
199 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000200 if not ref:
201 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
202 url.module_name, ref))
203 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000204 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000205 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000206 # Make sure the referenced dependency DEPS file is loaded and file the
207 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000208 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000209 found_dep = None
210 for d in ref.dependencies:
211 if d.name == sub_target:
212 found_dep = d
213 break
214 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000215 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000216 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
217 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000218 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000219 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000220 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000221 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000222 elif isinstance(url, basestring):
223 parsed_url = urlparse.urlparse(url)
224 if not parsed_url[0]:
225 # A relative url. Fetch the real base.
226 path = parsed_url[2]
227 if not path.startswith('/'):
228 raise gclient_utils.Error(
229 'relative DEPS entry \'%s\' must begin with a slash' % url)
230 # Create a scm just to query the full url.
231 parent_url = self.parent.parsed_url
232 if isinstance(parent_url, self.FileImpl):
233 parent_url = parent_url.file_location
234 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000235 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000236 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000237 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000238 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000239 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000240 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000241 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000242 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000243 return parsed_url
244 elif url is None:
245 return None
246 else:
247 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000248
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000249 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000250 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000251 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000252 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000253 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000254 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000255 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000256 # One thing is unintuitive, vars= {} must happen before Var() use.
257 local_scope = {}
258 var = self.VarImpl(self.custom_vars, local_scope)
259 global_scope = {
260 'File': self.FileImpl,
261 'From': self.FromImpl,
262 'Var': var.Lookup,
263 'deps_os': {},
264 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000265 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
266 if not os.path.isfile(filepath):
267 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
268 else:
269 deps_content = gclient_utils.FileRead(filepath)
270 logging.debug(deps_content)
271 # Eval the content.
272 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@chromium.org46304292010-10-28 11:42:00 +0000284 # platform, so we collect the broadest set of dependencies
285 # available. We may end up with the wrong revision of something for
286 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000287 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.org3742c842010-09-09 19:27:14 +0000335 def run(self, revision_overrides, command, args, work_queue, options):
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:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000358 # Create a shallow copy to mutate revision.
359 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 options.revision = revision_overrides.get(self.name)
361 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000362 scm.RunCommand(command, options, args, self._file_list)
363 self._file_list = [os.path.join(self.name, f.strip())
364 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000365 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000366 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000367 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000368 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000369 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
370 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000371 # src/foo. Yes, it's O(n^2)... It's important to do that before
372 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000373 for s in self.dependencies:
374 for s2 in self.dependencies:
375 if s is s2:
376 continue
377 if s.name.startswith(posixpath.join(s2.name, '')):
378 s.requirements.append(s2.name)
379
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000380 # Parse the dependencies of this dependency.
381 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000382 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000383
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000385 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000386 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000387 assert self.hooks_ran == False
388 if not self.should_process or self.recursion_limit() <= 0:
389 # Don't run the hook when it is above recursion_limit.
390 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000391 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000392 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000393 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394 # TODO(maruel): If the user is using git or git-svn, then we don't know
395 # what files have changed so we always run all hooks. It'd be nice to fix
396 # that.
397 if (options.force or
398 isinstance(self.parsed_url, self.FileImpl) or
399 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
400 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
401 for hook_dict in self.deps_hooks:
402 self._RunHookAction(hook_dict, [])
403 else:
404 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
405 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000406 file_list = self.file_list()
407 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000408 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000409 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000410 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000411
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000412 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000413 file_list[i].lower()])
414 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000415
416 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000417 while (file_list[i].startswith('\\') or
418 file_list[i].startswith('/')):
419 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420
421 # Run hooks on the basis of whether the files from the gclient operation
422 # match each hook's pattern.
423 for hook_dict in self.deps_hooks:
424 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000425 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000426 if matching_file_list:
427 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000428 for s in self.dependencies:
429 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000430
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000431 def _RunHookAction(self, hook_dict, matching_file_list):
432 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000433 # A single DEPS file can specify multiple hooks so this function can be
434 # called multiple times on a single Dependency.
435 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000436 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000437 logging.debug(hook_dict)
438 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000439 command = hook_dict['action'][:]
440 if command[0] == 'python':
441 # If the hook specified "python" as the first item, the action is a
442 # Python script. Run it by starting a new copy of the same
443 # interpreter.
444 command[0] = sys.executable
445
446 if '$matching_files' in command:
447 splice_index = command.index('$matching_files')
448 command[splice_index:splice_index + 1] = matching_file_list
449
maruel@chromium.org17d01792010-09-01 18:07:10 +0000450 try:
451 gclient_utils.CheckCallAndFilterAndHeader(
452 command, cwd=self.root_dir(), always=True)
453 except gclient_utils.Error, e:
454 # Use a discrete exit status code of 2 to indicate that a hook action
455 # failed. Users of this script may wish to treat hook action failures
456 # differently from VC failures.
457 print >> sys.stderr, 'Error: %s' % str(e)
458 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000459
maruel@chromium.org271375b2010-06-23 19:17:38 +0000460 def root_dir(self):
461 return self.parent.root_dir()
462
463 def enforced_os(self):
464 return self.parent.enforced_os()
465
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000466 def recursion_limit(self):
467 return self.parent.recursion_limit() - 1
468
maruel@chromium.org0d812442010-08-10 12:41:08 +0000469 def tree(self, include_all):
470 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000471
maruel@chromium.org0d812442010-08-10 12:41:08 +0000472 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000473 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000474 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000475 for d in self.dependencies:
476 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000477 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000478 for d in self.dependencies:
479 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000480 return result
481
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000482 def get_custom_deps(self, name, url):
483 """Returns a custom deps if applicable."""
484 if self.parent:
485 url = self.parent.get_custom_deps(name, url)
486 # None is a valid return value to disable a dependency.
487 return self.custom_deps.get(name, url)
488
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000489 def file_list(self):
490 result = self._file_list[:]
491 for d in self.dependencies:
492 result.extend(d.file_list())
493 return result
494
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000495 def __str__(self):
496 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000497 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000498 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
499 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000500 # 'deps_file'
501 if self.__dict__[i]:
502 out.append('%s: %s' % (i, self.__dict__[i]))
503
504 for d in self.dependencies:
505 out.extend([' ' + x for x in str(d).splitlines()])
506 out.append('')
507 return '\n'.join(out)
508
509 def __repr__(self):
510 return '%s: %s' % (self.name, self.url)
511
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000512 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000513 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000514 out = '%s(%s)' % (self.name, self.url)
515 i = self.parent
516 while i and i.name:
517 out = '%s(%s) -> %s' % (i.name, i.url, out)
518 i = i.parent
519 return out
520
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000521 def root_parent(self):
522 """Returns the root object, normally a GClient object."""
523 d = self
524 while d.parent:
525 d = d.parent
526 return d
527
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000528
529class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000530 """Object that represent a gclient checkout. A tree of Dependency(), one per
531 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000532
533 DEPS_OS_CHOICES = {
534 "win32": "win",
535 "win": "win",
536 "cygwin": "win",
537 "darwin": "mac",
538 "mac": "mac",
539 "unix": "unix",
540 "linux": "unix",
541 "linux2": "unix",
542 }
543
544 DEFAULT_CLIENT_FILE_TEXT = ("""\
545solutions = [
546 { "name" : "%(solution_name)s",
547 "url" : "%(solution_url)s",
548 "custom_deps" : {
549 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000550 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000551 },
552]
553""")
554
555 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
556 { "name" : "%(solution_name)s",
557 "url" : "%(solution_url)s",
558 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000559%(solution_deps)s },
560 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000561 },
562""")
563
564 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
565# Snapshot generated with gclient revinfo --snapshot
566solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000567%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000568""")
569
570 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000571 # Do not change previous behavior. Only solution level and immediate DEPS
572 # are processed.
573 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000574 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000575 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000576 if options.deps_os:
577 enforced_os = options.deps_os.split(',')
578 else:
579 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
580 if 'all' in enforced_os:
581 enforced_os = self.DEPS_OS_CHOICES.itervalues()
582 self._enforced_os = list(set(enforced_os))
583 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000584 self.config_content = None
585
586 def SetConfig(self, content):
587 assert self.dependencies == []
588 config_dict = {}
589 self.config_content = content
590 try:
591 exec(content, config_dict)
592 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000593 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000594 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000595 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000596 tree = dict((d.name, d) for d in self.tree(False))
597 if s['name'] in tree:
598 raise gclient_utils.Error(
599 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000600 self.dependencies.append(Dependency(
601 self, s['name'], s['url'],
602 s.get('safesync_url', None),
603 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000604 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000605 None,
606 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000607 except KeyError:
608 raise gclient_utils.Error('Invalid .gclient file. Solution is '
609 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000610 # .gclient can have hooks.
611 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000612 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000613
614 def SaveConfig(self):
615 gclient_utils.FileWrite(os.path.join(self.root_dir(),
616 self._options.config_filename),
617 self.config_content)
618
619 @staticmethod
620 def LoadCurrentConfig(options):
621 """Searches for and loads a .gclient file relative to the current working
622 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000623 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000624 if not path:
625 return None
626 client = GClient(path, options)
627 client.SetConfig(gclient_utils.FileRead(
628 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000629 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000630
631 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
632 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
633 'solution_name': solution_name,
634 'solution_url': solution_url,
635 'safesync_url' : safesync_url,
636 })
637
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000638 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000639 """Creates a .gclient_entries file to record the list of unique checkouts.
640
641 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000642 """
643 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
644 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000645 result = 'entries = {\n'
646 for entry in self.tree(False):
647 # Skip over File() dependencies as we can't version them.
648 if not isinstance(entry.parsed_url, self.FileImpl):
649 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
650 pprint.pformat(entry.parsed_url))
651 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000652 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000653 logging.info(result)
654 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000655
656 def _ReadEntries(self):
657 """Read the .gclient_entries file for the given client.
658
659 Returns:
660 A sequence of solution names, which will be empty if there is the
661 entries file hasn't been created yet.
662 """
663 scope = {}
664 filename = os.path.join(self.root_dir(), self._options.entries_filename)
665 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000666 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000667 try:
668 exec(gclient_utils.FileRead(filename), scope)
669 except SyntaxError, e:
670 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000671 return scope['entries']
672
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000673 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000674 """Checks for revision overrides."""
675 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000676 if self._options.head:
677 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000678 for s in self.dependencies:
679 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000680 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000681 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000682 rev = handle.read().strip()
683 handle.close()
684 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000685 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000686 if not self._options.revisions:
687 return revision_overrides
688 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000689 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000690 index = 0
691 for revision in self._options.revisions:
692 if not '@' in revision:
693 # Support for --revision 123
694 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000695 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000696 if not sol in solutions_names:
697 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
698 print >> sys.stderr, ('Please fix your script, having invalid '
699 '--revision flags will soon considered an error.')
700 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000701 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000702 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000703 return revision_overrides
704
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705 def RunOnDeps(self, command, args):
706 """Runs a command on each dependency in a client and its dependencies.
707
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000708 Args:
709 command: The command to use (e.g., 'status' or 'diff')
710 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000711 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000712 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000713 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000714 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000715 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000716 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000717 if (command in ('update', 'revert') and sys.stdout.isatty() and not
718 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000719 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000720 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000721 for s in self.dependencies:
722 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000723 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000724
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000725 # Once all the dependencies have been processed, it's now safe to run the
726 # hooks.
727 if not self._options.nohooks:
728 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000729
730 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000731 # Notify the user if there is an orphaned entry in their working copy.
732 # Only delete the directory if there are no changes in it, and
733 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000734 entries = [i.name for i in self.tree(False)]
735 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000736 if not prev_url:
737 # entry must have been overridden via .gclient custom_deps
738 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000739 # Fix path separator on Windows.
740 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000741 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000742 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000743 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000744 file_list = []
745 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
746 scm.status(self._options, [], file_list)
747 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000748 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000749 # There are modified files in this entry. Keep warning until
750 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000751 print(('\nWARNING: \'%s\' is no longer part of this client. '
752 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000753 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000754 else:
755 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000756 print('\n________ deleting \'%s\' in \'%s\'' % (
757 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000758 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000759 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000760 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000761 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000762
763 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000764 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000765 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000766 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000767 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000768 for s in self.dependencies:
769 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000770 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000771
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000772 def GetURLAndRev(dep):
773 """Returns the revision-qualified SCM url for a Dependency."""
774 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000775 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000776 if isinstance(dep.parsed_url, self.FileImpl):
777 original_url = dep.parsed_url.file_location
778 else:
779 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000780 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000781 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000782 if not os.path.isdir(scm.checkout_path):
783 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000784 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000785
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000786 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000787 new_gclient = ''
788 # First level at .gclient
789 for d in self.dependencies:
790 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000791 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000792 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000793 for d in dep.dependencies:
794 entries[d.name] = GetURLAndRev(d)
795 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000796 GrabDeps(d)
797 custom_deps = []
798 for k in sorted(entries.keys()):
799 if entries[k]:
800 # Quotes aren't escaped...
801 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
802 else:
803 custom_deps.append(' \"%s\": None,\n' % k)
804 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
805 'solution_name': d.name,
806 'solution_url': d.url,
807 'safesync_url' : d.safesync_url or '',
808 'solution_deps': ''.join(custom_deps),
809 }
810 # Print the snapshot configuration file
811 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000812 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000813 entries = {}
814 for d in self.tree(False):
815 if self._options.actual:
816 entries[d.name] = GetURLAndRev(d)
817 else:
818 entries[d.name] = d.parsed_url
819 keys = sorted(entries.keys())
820 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000821 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000822 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000824 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000825 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000826 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000827
maruel@chromium.org75a59272010-06-11 22:34:03 +0000828 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000829 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000830 return self._root_dir
831
maruel@chromium.org271375b2010-06-23 19:17:38 +0000832 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000833 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000834 return self._enforced_os
835
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000836 def recursion_limit(self):
837 """How recursive can each dependencies in DEPS file can load DEPS file."""
838 return self._recursion_limit
839
maruel@chromium.org0d812442010-08-10 12:41:08 +0000840 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000841 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000842 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000843
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000844
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000845#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000846
847
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000848def CMDcleanup(parser, args):
849 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000850
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000851Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000852"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000853 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
854 help='override deps for the specified (comma-separated) '
855 'platform(s); \'all\' will process all deps_os '
856 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000857 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000858 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000859 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000860 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861 if options.verbose:
862 # Print out the .gclient file. This is longer than if we just printed the
863 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000864 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000865 return client.RunOnDeps('cleanup', args)
866
867
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000868@attr('usage', '[command] [args ...]')
869def CMDrecurse(parser, args):
870 """Operates on all the entries.
871
872 Runs a shell command on all entries.
873 """
874 # Stop parsing at the first non-arg so that these go through to the command
875 parser.disable_interspersed_args()
876 parser.add_option('-s', '--scm', action='append', default=[],
877 help='choose scm types to operate upon')
878 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000879 if not args:
880 print >> sys.stderr, 'Need to supply a command!'
881 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000882 root_and_entries = gclient_utils.GetGClientRootAndEntries()
883 if not root_and_entries:
884 print >> sys.stderr, (
885 'You need to run gclient sync at least once to use \'recurse\'.\n'
886 'This is because .gclient_entries needs to exist and be up to date.')
887 return 1
888 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000889 scm_set = set()
890 for scm in options.scm:
891 scm_set.update(scm.split(','))
892
893 # Pass in the SCM type as an env variable
894 env = os.environ.copy()
895
896 for path, url in entries.iteritems():
897 scm = gclient_scm.GetScmName(url)
898 if scm_set and scm not in scm_set:
899 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000900 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000901 if scm:
902 env['GCLIENT_SCM'] = scm
903 if url:
904 env['GCLIENT_URL'] = url
905 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
906 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000907
908
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000909@attr('usage', '[url] [safesync url]')
910def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000911 """Create a .gclient file in the current directory.
912
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000913This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000914top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000915modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000916provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000917URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000918"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000919 parser.add_option('--spec',
920 help='create a gclient file containing the provided '
921 'string. Due to Cygwin/Python brokenness, it '
922 'probably can\'t contain any newlines.')
923 parser.add_option('--name',
924 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000925 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000926 if ((options.spec and args) or len(args) > 2 or
927 (not options.spec and not args)):
928 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
929
maruel@chromium.org0329e672009-05-13 18:41:04 +0000930 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000931 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000932 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000933 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000934 if options.spec:
935 client.SetConfig(options.spec)
936 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000937 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000938 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000939 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000940 else:
941 # specify an alternate relpath for the given URL.
942 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000943 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944 if len(args) > 1:
945 safesync_url = args[1]
946 client.SetDefaultConfig(name, base_url, safesync_url)
947 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000948 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949
950
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000951def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000952 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000953 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
954 help='override deps for the specified (comma-separated) '
955 'platform(s); \'all\' will process all deps_os '
956 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000958 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000959 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000960 client = GClient.LoadCurrentConfig(options)
961
962 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000963 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000964
965 if options.verbose:
966 # Print out the .gclient file. This is longer than if we just printed the
967 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000968 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000969 return client.RunOnDeps('export', args)
970
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000971
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000972@attr('epilog', """Example:
973 gclient pack > patch.txt
974 generate simple patch for configured client and dependences
975""")
976def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000977 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000978
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000979Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000980dependencies, and performs minimal postprocessing of the output. The
981resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000982checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000983"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000984 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
985 help='override deps for the specified (comma-separated) '
986 'platform(s); \'all\' will process all deps_os '
987 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000988 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000989 client = GClient.LoadCurrentConfig(options)
990 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000991 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000992 if options.verbose:
993 # Print out the .gclient file. This is longer than if we just printed the
994 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000995 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000996 return client.RunOnDeps('pack', args)
997
998
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000999def CMDstatus(parser, args):
1000 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001001 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1002 help='override deps for the specified (comma-separated) '
1003 'platform(s); \'all\' will process all deps_os '
1004 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001005 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001006 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001007 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001008 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009 if options.verbose:
1010 # Print out the .gclient file. This is longer than if we just printed the
1011 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001012 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001013 return client.RunOnDeps('status', args)
1014
1015
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001016@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001017 gclient sync
1018 update files from SCM according to current configuration,
1019 *for modules which have changed since last update or sync*
1020 gclient sync --force
1021 update files from SCM according to current configuration, for
1022 all modules (useful for recovering files deleted from local copy)
1023 gclient sync --revision src@31000
1024 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001025""")
1026def CMDsync(parser, args):
1027 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001028 parser.add_option('-f', '--force', action='store_true',
1029 help='force update even for unchanged modules')
1030 parser.add_option('-n', '--nohooks', action='store_true',
1031 help='don\'t run hooks after the update is complete')
1032 parser.add_option('-r', '--revision', action='append',
1033 dest='revisions', metavar='REV', default=[],
1034 help='Enforces revision/hash for the solutions with the '
1035 'format src@rev. The src@ part is optional and can be '
1036 'skipped. -r can be used multiple times when .gclient '
1037 'has multiple solutions configured and will work even '
1038 'if the src@ part is skipped.')
1039 parser.add_option('-H', '--head', action='store_true',
1040 help='skips any safesync_urls specified in '
1041 'configured solutions and sync to head instead')
1042 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1043 help='delete any unexpected unversioned trees '
1044 'that are in the checkout')
1045 parser.add_option('-R', '--reset', action='store_true',
1046 help='resets any local changes before updating (git only)')
1047 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1048 help='override deps for the specified (comma-separated) '
1049 'platform(s); \'all\' will process all deps_os '
1050 'references')
1051 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1052 help='Skip svn up whenever possible by requesting '
1053 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001054 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001055 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001056
1057 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001058 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001059
maruel@chromium.org307d1792010-05-31 20:03:13 +00001060 if options.revisions and options.head:
1061 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001062 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063
1064 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('update', args)
1069
1070
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001071def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001072 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001074
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001075def CMDdiff(parser, args):
1076 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001077 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1078 help='override deps for the specified (comma-separated) '
1079 'platform(s); \'all\' will process all deps_os '
1080 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001081 (options, args) = parser.parse_args(args)
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 if options.verbose:
1086 # Print out the .gclient file. This is longer than if we just printed the
1087 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001088 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089 return client.RunOnDeps('diff', args)
1090
1091
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001092def CMDrevert(parser, args):
1093 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001094 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1095 help='override deps for the specified (comma-separated) '
1096 'platform(s); \'all\' will process all deps_os '
1097 'references')
1098 parser.add_option('-n', '--nohooks', action='store_true',
1099 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100 (options, args) = parser.parse_args(args)
1101 # --force is implied.
1102 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001103 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001105 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001106 return client.RunOnDeps('revert', args)
1107
1108
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109def CMDrunhooks(parser, args):
1110 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001111 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1112 help='override deps for the specified (comma-separated) '
1113 'platform(s); \'all\' will process all deps_os '
1114 'references')
1115 parser.add_option('-f', '--force', action='store_true', default=True,
1116 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001117 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001118 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001119 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001120 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121 if options.verbose:
1122 # Print out the .gclient file. This is longer than if we just printed the
1123 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001124 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001125 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001126 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001127 return client.RunOnDeps('runhooks', args)
1128
1129
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001130def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001131 """Output revision info mapping for the client and its dependencies.
1132
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001133 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001134 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001135 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1136 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001137 commit can change.
1138 """
1139 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1140 help='override deps for the specified (comma-separated) '
1141 'platform(s); \'all\' will process all deps_os '
1142 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001143 parser.add_option('-a', '--actual', action='store_true',
1144 help='gets the actual checked out revisions instead of the '
1145 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001146 parser.add_option('-s', '--snapshot', action='store_true',
1147 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001148 'version of all repositories to reproduce the tree, '
1149 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001150 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001151 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001153 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001154 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001155 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156
1157
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001158def Command(name):
1159 return getattr(sys.modules[__name__], 'CMD' + name, None)
1160
1161
1162def CMDhelp(parser, args):
1163 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001164 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001165 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001167 parser.print_help()
1168 return 0
1169
1170
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001171def GenUsage(parser, command):
1172 """Modify an OptParse object with the function's documentation."""
1173 obj = Command(command)
1174 if command == 'help':
1175 command = '<command>'
1176 # OptParser.description prefer nicely non-formatted strings.
1177 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1178 usage = getattr(obj, 'usage', '')
1179 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1180 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001181
1182
1183def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001184 """Doesn't parse the arguments here, just find the right subcommand to
1185 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001186 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001187 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1188 # operations. Python as a strong tendency to buffer sys.stdout.
1189 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001190 # Make stdout annotated with the thread ids.
1191 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001192 # Do it late so all commands are listed.
1193 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1194 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1195 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1196 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001197 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001198 help='Specify how many SCM commands can run in parallel; '
1199 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001200 parser.add_option('-v', '--verbose', action='count', default=0,
1201 help='Produces additional output for diagnostics. Can be '
1202 'used up to three times for more logging info.')
1203 parser.add_option('--gclientfile', dest='config_filename',
1204 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1205 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001206 # Integrate standard options processing.
1207 old_parser = parser.parse_args
1208 def Parse(args):
1209 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001210 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001211 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001212 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001213 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001214 level = logging.DEBUG
1215 logging.basicConfig(level=level,
1216 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1217 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001218 if options.jobs < 1:
1219 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001220
1221 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001222 if not hasattr(options, 'revisions'):
1223 # GClient.RunOnDeps expects it even if not applicable.
1224 options.revisions = []
1225 if not hasattr(options, 'head'):
1226 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001227 if not hasattr(options, 'nohooks'):
1228 options.nohooks = True
1229 if not hasattr(options, 'deps_os'):
1230 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001231 if not hasattr(options, 'manually_grab_svn_rev'):
1232 options.manually_grab_svn_rev = None
1233 if not hasattr(options, 'force'):
1234 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001235 return (options, args)
1236 parser.parse_args = Parse
1237 # We don't want wordwrapping in epilog (usually examples)
1238 parser.format_epilog = lambda _: parser.epilog or ''
1239 if argv:
1240 command = Command(argv[0])
1241 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001242 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001243 GenUsage(parser, argv[0])
1244 return command(parser, argv[1:])
1245 # Not a known command. Default to help.
1246 GenUsage(parser, 'help')
1247 return CMDhelp(parser, argv)
1248 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001249 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001250 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251
1252
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001253if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001254 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001255
1256# vim: ts=2:sw=2:tw=80:et: