blob: 4b9d94cac81c2acfde96e6b3c2974075cad46d6f [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.orgb17b55b2010-11-03 14:42:37 +0000335 # Arguments number differs from overridden method
336 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000337 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000338 """Runs 'command' before parsing the DEPS in case it's a initial checkout
339 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000340 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000341 if not self.should_process:
342 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000343 # When running runhooks, there's no need to consult the SCM.
344 # All known hooks are expected to run unconditionally regardless of working
345 # copy state, so skip the SCM status check.
346 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000347 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000348 if run_scm and self.parsed_url:
349 if isinstance(self.parsed_url, self.FileImpl):
350 # Special support for single-file checkout.
351 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
352 options.revision = self.parsed_url.GetRevision()
353 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
354 self.root_dir(),
355 self.name)
356 scm.RunCommand('updatesingle', options,
357 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000358 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000359 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000360 # Create a shallow copy to mutate revision.
361 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 options.revision = revision_overrides.get(self.name)
363 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000364 scm.RunCommand(command, options, args, self._file_list)
365 self._file_list = [os.path.join(self.name, f.strip())
366 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000367 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000368 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000369 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000370 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000371 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
372 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000373 # src/foo. Yes, it's O(n^2)... It's important to do that before
374 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000375 for s in self.dependencies:
376 for s2 in self.dependencies:
377 if s is s2:
378 continue
379 if s.name.startswith(posixpath.join(s2.name, '')):
380 s.requirements.append(s2.name)
381
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 # Parse the dependencies of this dependency.
383 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000384 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000385
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000386 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000387 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000388 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000389 assert self.hooks_ran == False
390 if not self.should_process or self.recursion_limit() <= 0:
391 # Don't run the hook when it is above recursion_limit.
392 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000393 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000395 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000396 # TODO(maruel): If the user is using git or git-svn, then we don't know
397 # what files have changed so we always run all hooks. It'd be nice to fix
398 # that.
399 if (options.force or
400 isinstance(self.parsed_url, self.FileImpl) or
401 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
402 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
403 for hook_dict in self.deps_hooks:
404 self._RunHookAction(hook_dict, [])
405 else:
406 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
407 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000408 file_list = self.file_list()
409 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000410 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000411 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000412 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000413
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000414 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000415 file_list[i].lower()])
416 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000417
418 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000419 while (file_list[i].startswith('\\') or
420 file_list[i].startswith('/')):
421 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000422
423 # Run hooks on the basis of whether the files from the gclient operation
424 # match each hook's pattern.
425 for hook_dict in self.deps_hooks:
426 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000427 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000428 if matching_file_list:
429 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000430 for s in self.dependencies:
431 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000432
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000433 def _RunHookAction(self, hook_dict, matching_file_list):
434 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000435 # A single DEPS file can specify multiple hooks so this function can be
436 # called multiple times on a single Dependency.
437 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000438 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000439 logging.debug(hook_dict)
440 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000441 command = hook_dict['action'][:]
442 if command[0] == 'python':
443 # If the hook specified "python" as the first item, the action is a
444 # Python script. Run it by starting a new copy of the same
445 # interpreter.
446 command[0] = sys.executable
447
448 if '$matching_files' in command:
449 splice_index = command.index('$matching_files')
450 command[splice_index:splice_index + 1] = matching_file_list
451
maruel@chromium.org17d01792010-09-01 18:07:10 +0000452 try:
453 gclient_utils.CheckCallAndFilterAndHeader(
454 command, cwd=self.root_dir(), always=True)
455 except gclient_utils.Error, e:
456 # Use a discrete exit status code of 2 to indicate that a hook action
457 # failed. Users of this script may wish to treat hook action failures
458 # differently from VC failures.
459 print >> sys.stderr, 'Error: %s' % str(e)
460 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000461
maruel@chromium.org271375b2010-06-23 19:17:38 +0000462 def root_dir(self):
463 return self.parent.root_dir()
464
465 def enforced_os(self):
466 return self.parent.enforced_os()
467
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000468 def recursion_limit(self):
469 return self.parent.recursion_limit() - 1
470
maruel@chromium.org0d812442010-08-10 12:41:08 +0000471 def tree(self, include_all):
472 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000473
maruel@chromium.org0d812442010-08-10 12:41:08 +0000474 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000475 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000476 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000477 for d in self.dependencies:
478 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000479 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000480 for d in self.dependencies:
481 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000482 return result
483
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000484 def get_custom_deps(self, name, url):
485 """Returns a custom deps if applicable."""
486 if self.parent:
487 url = self.parent.get_custom_deps(name, url)
488 # None is a valid return value to disable a dependency.
489 return self.custom_deps.get(name, url)
490
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000491 def file_list(self):
492 result = self._file_list[:]
493 for d in self.dependencies:
494 result.extend(d.file_list())
495 return result
496
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000497 def __str__(self):
498 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000499 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000500 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
501 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000502 # 'deps_file'
503 if self.__dict__[i]:
504 out.append('%s: %s' % (i, self.__dict__[i]))
505
506 for d in self.dependencies:
507 out.extend([' ' + x for x in str(d).splitlines()])
508 out.append('')
509 return '\n'.join(out)
510
511 def __repr__(self):
512 return '%s: %s' % (self.name, self.url)
513
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000514 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000515 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000516 out = '%s(%s)' % (self.name, self.url)
517 i = self.parent
518 while i and i.name:
519 out = '%s(%s) -> %s' % (i.name, i.url, out)
520 i = i.parent
521 return out
522
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000523 def root_parent(self):
524 """Returns the root object, normally a GClient object."""
525 d = self
526 while d.parent:
527 d = d.parent
528 return d
529
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000530
531class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000532 """Object that represent a gclient checkout. A tree of Dependency(), one per
533 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000534
535 DEPS_OS_CHOICES = {
536 "win32": "win",
537 "win": "win",
538 "cygwin": "win",
539 "darwin": "mac",
540 "mac": "mac",
541 "unix": "unix",
542 "linux": "unix",
543 "linux2": "unix",
544 }
545
546 DEFAULT_CLIENT_FILE_TEXT = ("""\
547solutions = [
548 { "name" : "%(solution_name)s",
549 "url" : "%(solution_url)s",
550 "custom_deps" : {
551 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000552 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000553 },
554]
555""")
556
557 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
558 { "name" : "%(solution_name)s",
559 "url" : "%(solution_url)s",
560 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000561%(solution_deps)s },
562 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000563 },
564""")
565
566 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
567# Snapshot generated with gclient revinfo --snapshot
568solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000569%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000570""")
571
572 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000573 # Do not change previous behavior. Only solution level and immediate DEPS
574 # are processed.
575 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000576 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000577 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000578 if options.deps_os:
579 enforced_os = options.deps_os.split(',')
580 else:
581 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
582 if 'all' in enforced_os:
583 enforced_os = self.DEPS_OS_CHOICES.itervalues()
584 self._enforced_os = list(set(enforced_os))
585 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000586 self.config_content = None
587
588 def SetConfig(self, content):
589 assert self.dependencies == []
590 config_dict = {}
591 self.config_content = content
592 try:
593 exec(content, config_dict)
594 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000595 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000596 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000597 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000598 tree = dict((d.name, d) for d in self.tree(False))
599 if s['name'] in tree:
600 raise gclient_utils.Error(
601 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000602 self.dependencies.append(Dependency(
603 self, s['name'], s['url'],
604 s.get('safesync_url', None),
605 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000606 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000607 None,
608 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000609 except KeyError:
610 raise gclient_utils.Error('Invalid .gclient file. Solution is '
611 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000612 # .gclient can have hooks.
613 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000614 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000615
616 def SaveConfig(self):
617 gclient_utils.FileWrite(os.path.join(self.root_dir(),
618 self._options.config_filename),
619 self.config_content)
620
621 @staticmethod
622 def LoadCurrentConfig(options):
623 """Searches for and loads a .gclient file relative to the current working
624 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000625 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000626 if not path:
627 return None
628 client = GClient(path, options)
629 client.SetConfig(gclient_utils.FileRead(
630 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000631 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000632
633 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
634 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
635 'solution_name': solution_name,
636 'solution_url': solution_url,
637 'safesync_url' : safesync_url,
638 })
639
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000640 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000641 """Creates a .gclient_entries file to record the list of unique checkouts.
642
643 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000644 """
645 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
646 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000647 result = 'entries = {\n'
648 for entry in self.tree(False):
649 # Skip over File() dependencies as we can't version them.
650 if not isinstance(entry.parsed_url, self.FileImpl):
651 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
652 pprint.pformat(entry.parsed_url))
653 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000654 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000655 logging.info(result)
656 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000657
658 def _ReadEntries(self):
659 """Read the .gclient_entries file for the given client.
660
661 Returns:
662 A sequence of solution names, which will be empty if there is the
663 entries file hasn't been created yet.
664 """
665 scope = {}
666 filename = os.path.join(self.root_dir(), self._options.entries_filename)
667 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000668 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000669 try:
670 exec(gclient_utils.FileRead(filename), scope)
671 except SyntaxError, e:
672 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000673 return scope['entries']
674
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000675 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000676 """Checks for revision overrides."""
677 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000678 if self._options.head:
679 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000680 # Do not check safesync_url if one or more --revision flag is specified.
681 if not self._options.revisions:
682 for s in self.dependencies:
683 if not s.safesync_url:
684 continue
685 handle = urllib.urlopen(s.safesync_url)
686 rev = handle.read().strip()
687 handle.close()
688 if len(rev):
689 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000690 if not self._options.revisions:
691 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000692 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000693 index = 0
694 for revision in self._options.revisions:
695 if not '@' in revision:
696 # Support for --revision 123
697 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000698 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000699 if not sol in solutions_names:
700 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
701 print >> sys.stderr, ('Please fix your script, having invalid '
702 '--revision flags will soon considered an error.')
703 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000704 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000705 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000706 return revision_overrides
707
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000708 def RunOnDeps(self, command, args):
709 """Runs a command on each dependency in a client and its dependencies.
710
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000711 Args:
712 command: The command to use (e.g., 'status' or 'diff')
713 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000714 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000715 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000716 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000717 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000718 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000719 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000720 if (command in ('update', 'revert') and sys.stdout.isatty() and not
721 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000722 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000723 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000724 for s in self.dependencies:
725 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000726 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000727
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000728 # Once all the dependencies have been processed, it's now safe to run the
729 # hooks.
730 if not self._options.nohooks:
731 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000732
733 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000734 # Notify the user if there is an orphaned entry in their working copy.
735 # Only delete the directory if there are no changes in it, and
736 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000737 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000739 if not prev_url:
740 # entry must have been overridden via .gclient custom_deps
741 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000742 # Fix path separator on Windows.
743 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000744 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000745 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000746 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000747 file_list = []
748 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
749 scm.status(self._options, [], file_list)
750 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000751 if (not self._options.delete_unversioned_trees or
752 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000753 # There are modified files in this entry. Keep warning until
754 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000755 print(('\nWARNING: \'%s\' is no longer part of this client. '
756 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000757 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000758 else:
759 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000760 print('\n________ deleting \'%s\' in \'%s\'' % (
761 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000762 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000763 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000764 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000765 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000766
767 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000768 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000769 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000770 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000771 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000772 for s in self.dependencies:
773 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000774 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000775
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000776 def GetURLAndRev(dep):
777 """Returns the revision-qualified SCM url for a Dependency."""
778 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000779 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000780 if isinstance(dep.parsed_url, self.FileImpl):
781 original_url = dep.parsed_url.file_location
782 else:
783 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000784 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000785 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000786 if not os.path.isdir(scm.checkout_path):
787 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000788 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000789
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000790 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000791 new_gclient = ''
792 # First level at .gclient
793 for d in self.dependencies:
794 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000795 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000796 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000797 for d in dep.dependencies:
798 entries[d.name] = GetURLAndRev(d)
799 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000800 GrabDeps(d)
801 custom_deps = []
802 for k in sorted(entries.keys()):
803 if entries[k]:
804 # Quotes aren't escaped...
805 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
806 else:
807 custom_deps.append(' \"%s\": None,\n' % k)
808 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
809 'solution_name': d.name,
810 'solution_url': d.url,
811 'safesync_url' : d.safesync_url or '',
812 'solution_deps': ''.join(custom_deps),
813 }
814 # Print the snapshot configuration file
815 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000816 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000817 entries = {}
818 for d in self.tree(False):
819 if self._options.actual:
820 entries[d.name] = GetURLAndRev(d)
821 else:
822 entries[d.name] = d.parsed_url
823 keys = sorted(entries.keys())
824 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000825 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000826 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000828 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000829 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000830 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000831
maruel@chromium.org75a59272010-06-11 22:34:03 +0000832 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000833 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000834 return self._root_dir
835
maruel@chromium.org271375b2010-06-23 19:17:38 +0000836 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000837 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000838 return self._enforced_os
839
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000840 def recursion_limit(self):
841 """How recursive can each dependencies in DEPS file can load DEPS file."""
842 return self._recursion_limit
843
maruel@chromium.org0d812442010-08-10 12:41:08 +0000844 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000845 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000846 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000847
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000848
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000849#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000850
851
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000852def CMDcleanup(parser, args):
853 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000854
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000855Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000856"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000857 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
858 help='override deps for the specified (comma-separated) '
859 'platform(s); \'all\' will process all deps_os '
860 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000861 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000862 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000864 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000865 if options.verbose:
866 # Print out the .gclient file. This is longer than if we just printed the
867 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000868 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000869 return client.RunOnDeps('cleanup', args)
870
871
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000872@attr('usage', '[command] [args ...]')
873def CMDrecurse(parser, args):
874 """Operates on all the entries.
875
876 Runs a shell command on all entries.
877 """
878 # Stop parsing at the first non-arg so that these go through to the command
879 parser.disable_interspersed_args()
880 parser.add_option('-s', '--scm', action='append', default=[],
881 help='choose scm types to operate upon')
882 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000883 if not args:
884 print >> sys.stderr, 'Need to supply a command!'
885 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000886 root_and_entries = gclient_utils.GetGClientRootAndEntries()
887 if not root_and_entries:
888 print >> sys.stderr, (
889 'You need to run gclient sync at least once to use \'recurse\'.\n'
890 'This is because .gclient_entries needs to exist and be up to date.')
891 return 1
892 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000893 scm_set = set()
894 for scm in options.scm:
895 scm_set.update(scm.split(','))
896
897 # Pass in the SCM type as an env variable
898 env = os.environ.copy()
899
900 for path, url in entries.iteritems():
901 scm = gclient_scm.GetScmName(url)
902 if scm_set and scm not in scm_set:
903 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000904 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000905 if scm:
906 env['GCLIENT_SCM'] = scm
907 if url:
908 env['GCLIENT_URL'] = url
909 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
910 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000911
912
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000913@attr('usage', '[url] [safesync url]')
914def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000915 """Create a .gclient file in the current directory.
916
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000917This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000918top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000919modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000920provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000921URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000922"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000923 parser.add_option('--spec',
924 help='create a gclient file containing the provided '
925 'string. Due to Cygwin/Python brokenness, it '
926 'probably can\'t contain any newlines.')
927 parser.add_option('--name',
928 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000929 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000930 if ((options.spec and args) or len(args) > 2 or
931 (not options.spec and not args)):
932 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
933
maruel@chromium.org0329e672009-05-13 18:41:04 +0000934 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000935 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000936 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000937 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000938 if options.spec:
939 client.SetConfig(options.spec)
940 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000941 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000942 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000943 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000944 else:
945 # specify an alternate relpath for the given URL.
946 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000947 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000948 if len(args) > 1:
949 safesync_url = args[1]
950 client.SetDefaultConfig(name, base_url, safesync_url)
951 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000952 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000953
954
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000955def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000956 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000957 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
958 help='override deps for the specified (comma-separated) '
959 'platform(s); \'all\' will process all deps_os '
960 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000961 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000962 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000963 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000964 client = GClient.LoadCurrentConfig(options)
965
966 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000967 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000968
969 if options.verbose:
970 # Print out the .gclient file. This is longer than if we just printed the
971 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000972 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000973 return client.RunOnDeps('export', args)
974
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000975
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000976@attr('epilog', """Example:
977 gclient pack > patch.txt
978 generate simple patch for configured client and dependences
979""")
980def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000981 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000982
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000983Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000984dependencies, and performs minimal postprocessing of the output. The
985resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000986checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000987"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000988 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
989 help='override deps for the specified (comma-separated) '
990 'platform(s); \'all\' will process all deps_os '
991 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000992 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000993 client = GClient.LoadCurrentConfig(options)
994 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000995 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000996 if options.verbose:
997 # Print out the .gclient file. This is longer than if we just printed the
998 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000999 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001000 return client.RunOnDeps('pack', args)
1001
1002
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001003def CMDstatus(parser, args):
1004 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001005 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1006 help='override deps for the specified (comma-separated) '
1007 'platform(s); \'all\' will process all deps_os '
1008 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001009 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001010 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001011 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001012 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001013 if options.verbose:
1014 # Print out the .gclient file. This is longer than if we just printed the
1015 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001016 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001017 return client.RunOnDeps('status', args)
1018
1019
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001020@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001021 gclient sync
1022 update files from SCM according to current configuration,
1023 *for modules which have changed since last update or sync*
1024 gclient sync --force
1025 update files from SCM according to current configuration, for
1026 all modules (useful for recovering files deleted from local copy)
1027 gclient sync --revision src@31000
1028 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001029""")
1030def CMDsync(parser, args):
1031 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001032 parser.add_option('-f', '--force', action='store_true',
1033 help='force update even for unchanged modules')
1034 parser.add_option('-n', '--nohooks', action='store_true',
1035 help='don\'t run hooks after the update is complete')
1036 parser.add_option('-r', '--revision', action='append',
1037 dest='revisions', metavar='REV', default=[],
1038 help='Enforces revision/hash for the solutions with the '
1039 'format src@rev. The src@ part is optional and can be '
1040 'skipped. -r can be used multiple times when .gclient '
1041 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001042 'if the src@ part is skipped. Note that specifying '
1043 '--revision means your safesync_url gets ignored.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001044 parser.add_option('-H', '--head', action='store_true',
1045 help='skips any safesync_urls specified in '
1046 'configured solutions and sync to head instead')
1047 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001048 help='delete any dependency that have been removed from '
1049 'last sync as long as there is no local modification. '
1050 'Coupled with --force, it will remove them even with '
1051 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001052 parser.add_option('-R', '--reset', action='store_true',
1053 help='resets any local changes before updating (git only)')
1054 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1055 help='override deps for the specified (comma-separated) '
1056 'platform(s); \'all\' will process all deps_os '
1057 'references')
1058 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1059 help='Skip svn up whenever possible by requesting '
1060 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001061 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001062 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063
1064 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001065 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001066
maruel@chromium.org307d1792010-05-31 20:03:13 +00001067 if options.revisions and options.head:
1068 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001069 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001070
1071 if options.verbose:
1072 # Print out the .gclient file. This is longer than if we just printed the
1073 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001074 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001075 return client.RunOnDeps('update', args)
1076
1077
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001078def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001079 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001080 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001082def CMDdiff(parser, args):
1083 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001084 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1085 help='override deps for the specified (comma-separated) '
1086 'platform(s); \'all\' will process all deps_os '
1087 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001088 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001089 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 if options.verbose:
1093 # Print out the .gclient file. This is longer than if we just printed the
1094 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001095 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 return client.RunOnDeps('diff', args)
1097
1098
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001099def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001100 """Revert all modifications in every dependencies.
1101
1102 That's the nuclear option to get back to a 'clean' state. It removes anything
1103 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001104 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1105 help='override deps for the specified (comma-separated) '
1106 'platform(s); \'all\' will process all deps_os '
1107 'references')
1108 parser.add_option('-n', '--nohooks', action='store_true',
1109 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110 (options, args) = parser.parse_args(args)
1111 # --force is implied.
1112 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001113 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001115 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 return client.RunOnDeps('revert', args)
1117
1118
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001119def CMDrunhooks(parser, args):
1120 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001121 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1122 help='override deps for the specified (comma-separated) '
1123 'platform(s); \'all\' will process all deps_os '
1124 'references')
1125 parser.add_option('-f', '--force', action='store_true', default=True,
1126 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001127 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001128 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001130 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 if options.verbose:
1132 # Print out the .gclient file. This is longer than if we just printed the
1133 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001134 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001135 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001137 return client.RunOnDeps('runhooks', args)
1138
1139
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001140def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001141 """Output revision info mapping for the client and its dependencies.
1142
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001144 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001145 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1146 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001147 commit can change.
1148 """
1149 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1150 help='override deps for the specified (comma-separated) '
1151 'platform(s); \'all\' will process all deps_os '
1152 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001153 parser.add_option('-a', '--actual', action='store_true',
1154 help='gets the actual checked out revisions instead of the '
1155 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001156 parser.add_option('-s', '--snapshot', action='store_true',
1157 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001158 'version of all repositories to reproduce the tree, '
1159 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001160 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001161 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001162 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001163 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001165 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001166
1167
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001168def Command(name):
1169 return getattr(sys.modules[__name__], 'CMD' + name, None)
1170
1171
1172def CMDhelp(parser, args):
1173 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001174 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001175 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001177 parser.print_help()
1178 return 0
1179
1180
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001181def GenUsage(parser, command):
1182 """Modify an OptParse object with the function's documentation."""
1183 obj = Command(command)
1184 if command == 'help':
1185 command = '<command>'
1186 # OptParser.description prefer nicely non-formatted strings.
1187 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1188 usage = getattr(obj, 'usage', '')
1189 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1190 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001191
1192
1193def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001194 """Doesn't parse the arguments here, just find the right subcommand to
1195 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001196 if sys.hexversion < 0x02050000:
1197 print >> sys.stderr, (
1198 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001199 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001200 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1201 # operations. Python as a strong tendency to buffer sys.stdout.
1202 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001203 # Make stdout annotated with the thread ids.
1204 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001205 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001206 # Unused variable 'usage'
1207 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001208 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1209 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1210 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1211 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001212 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001213 help='Specify how many SCM commands can run in parallel; '
1214 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001215 parser.add_option('-v', '--verbose', action='count', default=0,
1216 help='Produces additional output for diagnostics. Can be '
1217 'used up to three times for more logging info.')
1218 parser.add_option('--gclientfile', dest='config_filename',
1219 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1220 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001221 # Integrate standard options processing.
1222 old_parser = parser.parse_args
1223 def Parse(args):
1224 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001225 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001226 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001227 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001228 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001229 level = logging.DEBUG
1230 logging.basicConfig(level=level,
1231 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1232 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001233 if options.jobs < 1:
1234 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001235
1236 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001237 if not hasattr(options, 'revisions'):
1238 # GClient.RunOnDeps expects it even if not applicable.
1239 options.revisions = []
1240 if not hasattr(options, 'head'):
1241 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001242 if not hasattr(options, 'nohooks'):
1243 options.nohooks = True
1244 if not hasattr(options, 'deps_os'):
1245 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001246 if not hasattr(options, 'manually_grab_svn_rev'):
1247 options.manually_grab_svn_rev = None
1248 if not hasattr(options, 'force'):
1249 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001250 return (options, args)
1251 parser.parse_args = Parse
1252 # We don't want wordwrapping in epilog (usually examples)
1253 parser.format_epilog = lambda _: parser.epilog or ''
1254 if argv:
1255 command = Command(argv[0])
1256 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001257 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001258 GenUsage(parser, argv[0])
1259 return command(parser, argv[1:])
1260 # Not a known command. Default to help.
1261 GenUsage(parser, 'help')
1262 return CMDhelp(parser, argv)
1263 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001264 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001265 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001266
1267
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001268if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001269 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001270
1271# vim: ts=2:sw=2:tw=80:et: