blob: 04bbd9ff2e110b1c9a5b136b8b539137e9f3c2f6 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# 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
phajdan.jr@chromium.org6e043f72011-05-02 07:24:32 +000052__version__ = "0.6.2"
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.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000075 """Sets an attribute on a function."""
76 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 return fn
79 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082## GClient implementation.
83
84
maruel@chromium.org116704f2010-06-11 17:34:38 +000085class GClientKeywords(object):
86 class FromImpl(object):
87 """Used to implement the From() syntax."""
88
89 def __init__(self, module_name, sub_target_name=None):
90 """module_name is the dep module we want to include from. It can also be
91 the name of a subdirectory to include from.
92
93 sub_target_name is an optional parameter if the module name in the other
94 DEPS file is different. E.g., you might want to map src/net to net."""
95 self.module_name = module_name
96 self.sub_target_name = sub_target_name
97
98 def __str__(self):
99 return 'From(%s, %s)' % (repr(self.module_name),
100 repr(self.sub_target_name))
101
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000104 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105
106 def __init__(self, file_location):
107 self.file_location = file_location
108
109 def __str__(self):
110 return 'File("%s")' % self.file_location
111
112 def GetPath(self):
113 return os.path.split(self.file_location)[0]
114
115 def GetFilename(self):
116 rev_tokens = self.file_location.split('@')
117 return os.path.split(rev_tokens[0])[1]
118
119 def GetRevision(self):
120 rev_tokens = self.file_location.split('@')
121 if len(rev_tokens) > 1:
122 return rev_tokens[1]
123 return None
124
125 class VarImpl(object):
126 def __init__(self, custom_vars, local_scope):
127 self._custom_vars = custom_vars
128 self._local_scope = local_scope
129
130 def Lookup(self, var_name):
131 """Implements the Var syntax."""
132 if var_name in self._custom_vars:
133 return self._custom_vars[var_name]
134 elif var_name in self._local_scope.get("vars", {}):
135 return self._local_scope["vars"][var_name]
136 raise gclient_utils.Error("Var is not defined: %s" % var_name)
137
138
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000139class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000140 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000141 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000142
maruel@chromium.org0d812442010-08-10 12:41:08 +0000143 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000144 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000145 GClientKeywords.__init__(self)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000146 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000147 self.parent = parent
148 self.name = name
149 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000150 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000151 # These 2 are only set in .gclient and not in DEPS files.
152 self.safesync_url = safesync_url
153 self.custom_vars = custom_vars or {}
154 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000155 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000156 self.dependencies = []
157 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000158 # A cache of the files affected by the current operation, necessary for
159 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000160 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000161 # If it is not set to True, the dependency wasn't processed for its child
162 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000163 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000164 # This dependency should be processed, i.e. checked out
165 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000166 # This dependency has been processed, i.e. checked out
167 self.processed = False
168 # This dependency had its hook run
169 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000170 # Required dependencies to run before running this one:
171 self.requirements = []
172 if self.parent and self.parent.name:
173 self.requirements.append(self.parent.name)
174 if isinstance(self.url, self.FromImpl):
175 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000176
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000177 # Sanity checks
178 if not self.name and self.parent:
179 raise gclient_utils.Error('Dependency without name')
180 if not isinstance(self.url,
181 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
182 raise gclient_utils.Error('dependency url must be either a string, None, '
183 'File() or From() instead of %s' %
184 self.url.__class__.__name__)
185 if '/' in self.deps_file or '\\' in self.deps_file:
186 raise gclient_utils.Error('deps_file name must not be a path, just a '
187 'filename. %s' % self.deps_file)
188
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000189 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000190 """Resolves the parsed url from url.
191
192 Manages From() keyword accordingly. Do not touch self.parsed_url nor
193 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000194 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000195 overriden_url = self.get_custom_deps(self.name, url)
196 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000197 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000198 overriden_url))
199 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000200 elif isinstance(url, self.FromImpl):
201 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000202 if not ref:
203 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
204 url.module_name, ref))
205 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000206 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000207 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000208 # Make sure the referenced dependency DEPS file is loaded and file the
209 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000210 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000211 found_dep = None
212 for d in ref.dependencies:
213 if d.name == sub_target:
214 found_dep = d
215 break
216 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000217 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000218 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
219 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000220 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000221 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000222 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000223 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000224 elif isinstance(url, basestring):
225 parsed_url = urlparse.urlparse(url)
226 if not parsed_url[0]:
227 # A relative url. Fetch the real base.
228 path = parsed_url[2]
229 if not path.startswith('/'):
230 raise gclient_utils.Error(
231 'relative DEPS entry \'%s\' must begin with a slash' % url)
232 # Create a scm just to query the full url.
233 parent_url = self.parent.parsed_url
234 if isinstance(parent_url, self.FileImpl):
235 parent_url = parent_url.file_location
236 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000237 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000238 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000239 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000240 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000241 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000242 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000243 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000244 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000245 return parsed_url
246 elif url is None:
247 return None
248 else:
249 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000251 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000252 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000253 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000254 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000255 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000256 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000257 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000258 # One thing is unintuitive, vars= {} must happen before Var() use.
259 local_scope = {}
260 var = self.VarImpl(self.custom_vars, local_scope)
261 global_scope = {
262 'File': self.FileImpl,
263 'From': self.FromImpl,
264 'Var': var.Lookup,
265 'deps_os': {},
266 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000267 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
268 if not os.path.isfile(filepath):
269 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
270 else:
271 deps_content = gclient_utils.FileRead(filepath)
272 logging.debug(deps_content)
273 # Eval the content.
274 try:
275 exec(deps_content, global_scope, local_scope)
276 except SyntaxError, e:
277 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000278 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 # load os specific dependencies if defined. these dependencies may
280 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000281 if 'deps_os' in local_scope:
282 for deps_os_key in self.enforced_os():
283 os_deps = local_scope['deps_os'].get(deps_os_key, {})
284 if len(self.enforced_os()) > 1:
285 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000286 # platform, so we collect the broadest set of dependencies
287 # available. We may end up with the wrong revision of something for
288 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000289 deps.update([x for x in os_deps.items() if not x[0] in deps])
290 else:
291 deps.update(os_deps)
292
maruel@chromium.org271375b2010-06-23 19:17:38 +0000293 self.deps_hooks.extend(local_scope.get('hooks', []))
294
295 # If a line is in custom_deps, but not in the solution, we want to append
296 # this line to the solution.
297 for d in self.custom_deps:
298 if d not in deps:
299 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000300
301 # If use_relative_paths is set in the DEPS file, regenerate
302 # the dictionary using paths relative to the directory containing
303 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000304 use_relative_paths = local_scope.get('use_relative_paths', False)
305 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000306 rel_deps = {}
307 for d, url in deps.items():
308 # normpath is required to allow DEPS to use .. in their
309 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000310 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
311 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000313 # Convert the deps into real Dependency.
314 for name, url in deps.iteritems():
315 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000316 raise gclient_utils.Error(
317 'The same name "%s" appears multiple times in the deps section' %
318 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000319 should_process = self.recursion_limit() > 0 and self.should_process
320 if should_process:
321 tree = dict((d.name, d) for d in self.tree(False))
322 if name in tree:
323 if url == tree[name].url:
324 logging.info('Won\'t process duplicate dependency %s' % tree[name])
325 # In theory we could keep it as a shadow of the other one. In
326 # practice, simply ignore it.
327 #should_process = False
328 continue
329 else:
330 raise gclient_utils.Error(
331 'Dependency %s specified more than once:\n %s\nvs\n %s' %
332 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000333 self.dependencies.append(Dependency(self, name, url, None, None, None,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000334 None, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000335 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000336
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000337 # Arguments number differs from overridden method
338 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000339 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000340 """Runs 'command' before parsing the DEPS in case it's a initial checkout
341 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000342
343 def maybeGetParentRevision(options):
344 """If we are performing an update and --transitive is set, set the
345 revision to the parent's revision. If we have an explicit revision
346 do nothing."""
347 if command == 'update' and options.transitive and not options.revision:
348 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
349 if not revision:
350 options.revision = revision_overrides.get(self.parent.name)
351 if options.verbose and options.revision:
352 print("Using parent's revision date: %s" % options.revision)
353 # If the parent has a revision override, then it must have been
354 # converted to date format.
355 assert (not options.revision or
356 gclient_utils.IsDateRevision(options.revision))
357
358 def maybeConvertToDateRevision(options):
359 """If we are performing an update and --transitive is set, convert the
360 revision to a date-revision (if necessary). Instead of having
361 -r 101 replace the revision with the time stamp of 101 (e.g.
362 "{2011-18-04}").
363 This way dependencies are upgraded to the revision they had at the
364 check-in of revision 101."""
365 if (command == 'update' and
366 options.transitive and
367 options.revision and
368 not gclient_utils.IsDateRevision(options.revision)):
369 revision_date = scm.GetRevisionDate(options.revision)
370 revision = gclient_utils.MakeDateRevision(revision_date)
371 if options.verbose:
372 print("Updating revision override from %s to %s." %
373 (options.revision, revision))
374 revision_overrides[self.name] = revision
375
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000376 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000377 if not self.should_process:
378 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000379 # When running runhooks, there's no need to consult the SCM.
380 # All known hooks are expected to run unconditionally regardless of working
381 # copy state, so skip the SCM status check.
382 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 if run_scm and self.parsed_url:
385 if isinstance(self.parsed_url, self.FileImpl):
386 # Special support for single-file checkout.
387 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
388 options.revision = self.parsed_url.GetRevision()
389 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
390 self.root_dir(),
391 self.name)
392 scm.RunCommand('updatesingle', options,
393 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000394 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000396 # Create a shallow copy to mutate revision.
397 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000398 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000399 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000400 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000401 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000402 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000403 self._file_list = [os.path.join(self.name, f.strip())
404 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000405 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000406 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000407 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000408 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000409 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
410 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000411 # src/foo. Yes, it's O(n^2)... It's important to do that before
412 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000413 for s in self.dependencies:
414 for s2 in self.dependencies:
415 if s is s2:
416 continue
417 if s.name.startswith(posixpath.join(s2.name, '')):
418 s.requirements.append(s2.name)
419
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420 # Parse the dependencies of this dependency.
421 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000422 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000423
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000424 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000425 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000426 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000427 assert self.hooks_ran == False
428 if not self.should_process or self.recursion_limit() <= 0:
429 # Don't run the hook when it is above recursion_limit.
430 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000431 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000432 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000433 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000434 # TODO(maruel): If the user is using git or git-svn, then we don't know
435 # what files have changed so we always run all hooks. It'd be nice to fix
436 # that.
437 if (options.force or
438 isinstance(self.parsed_url, self.FileImpl) or
439 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
440 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
441 for hook_dict in self.deps_hooks:
442 self._RunHookAction(hook_dict, [])
443 else:
444 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
445 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000446 file_list = self.file_list()
447 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000448 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000449 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000451
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000452 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000453 file_list[i].lower()])
454 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455
456 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000457 while (file_list[i].startswith('\\') or
458 file_list[i].startswith('/')):
459 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000460
461 # Run hooks on the basis of whether the files from the gclient operation
462 # match each hook's pattern.
463 for hook_dict in self.deps_hooks:
464 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000465 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466 if matching_file_list:
467 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000468 for s in self.dependencies:
469 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000470
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000471 def _RunHookAction(self, hook_dict, matching_file_list):
472 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000473 # A single DEPS file can specify multiple hooks so this function can be
474 # called multiple times on a single Dependency.
475 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000476 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000477 logging.debug(hook_dict)
478 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000479 command = hook_dict['action'][:]
480 if command[0] == 'python':
481 # If the hook specified "python" as the first item, the action is a
482 # Python script. Run it by starting a new copy of the same
483 # interpreter.
484 command[0] = sys.executable
485
486 if '$matching_files' in command:
487 splice_index = command.index('$matching_files')
488 command[splice_index:splice_index + 1] = matching_file_list
489
maruel@chromium.org17d01792010-09-01 18:07:10 +0000490 try:
491 gclient_utils.CheckCallAndFilterAndHeader(
492 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000493 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000494 # Use a discrete exit status code of 2 to indicate that a hook action
495 # failed. Users of this script may wish to treat hook action failures
496 # differently from VC failures.
497 print >> sys.stderr, 'Error: %s' % str(e)
498 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000499
maruel@chromium.org271375b2010-06-23 19:17:38 +0000500 def root_dir(self):
501 return self.parent.root_dir()
502
503 def enforced_os(self):
504 return self.parent.enforced_os()
505
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000506 def recursion_limit(self):
507 return self.parent.recursion_limit() - 1
508
maruel@chromium.org0d812442010-08-10 12:41:08 +0000509 def tree(self, include_all):
510 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000511
maruel@chromium.org0d812442010-08-10 12:41:08 +0000512 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000513 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000514 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000515 for d in self.dependencies:
516 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000517 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000518 for d in self.dependencies:
519 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000520 return result
521
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000522 def get_custom_deps(self, name, url):
523 """Returns a custom deps if applicable."""
524 if self.parent:
525 url = self.parent.get_custom_deps(name, url)
526 # None is a valid return value to disable a dependency.
527 return self.custom_deps.get(name, url)
528
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000529 def file_list(self):
530 result = self._file_list[:]
531 for d in self.dependencies:
532 result.extend(d.file_list())
533 return result
534
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000535 def __str__(self):
536 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000537 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000538 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
539 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000540 # 'deps_file'
541 if self.__dict__[i]:
542 out.append('%s: %s' % (i, self.__dict__[i]))
543
544 for d in self.dependencies:
545 out.extend([' ' + x for x in str(d).splitlines()])
546 out.append('')
547 return '\n'.join(out)
548
549 def __repr__(self):
550 return '%s: %s' % (self.name, self.url)
551
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000552 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000553 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000554 out = '%s(%s)' % (self.name, self.url)
555 i = self.parent
556 while i and i.name:
557 out = '%s(%s) -> %s' % (i.name, i.url, out)
558 i = i.parent
559 return out
560
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000561 def root_parent(self):
562 """Returns the root object, normally a GClient object."""
563 d = self
564 while d.parent:
565 d = d.parent
566 return d
567
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000568
569class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000570 """Object that represent a gclient checkout. A tree of Dependency(), one per
571 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000572
573 DEPS_OS_CHOICES = {
574 "win32": "win",
575 "win": "win",
576 "cygwin": "win",
577 "darwin": "mac",
578 "mac": "mac",
579 "unix": "unix",
580 "linux": "unix",
581 "linux2": "unix",
582 }
583
584 DEFAULT_CLIENT_FILE_TEXT = ("""\
585solutions = [
586 { "name" : "%(solution_name)s",
587 "url" : "%(solution_url)s",
588 "custom_deps" : {
589 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000590 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000591 },
592]
593""")
594
595 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
596 { "name" : "%(solution_name)s",
597 "url" : "%(solution_url)s",
598 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000599%(solution_deps)s },
600 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000601 },
602""")
603
604 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
605# Snapshot generated with gclient revinfo --snapshot
606solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000607%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000608""")
609
610 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000611 # Do not change previous behavior. Only solution level and immediate DEPS
612 # are processed.
613 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000614 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000615 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000616 if options.deps_os:
617 enforced_os = options.deps_os.split(',')
618 else:
619 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
620 if 'all' in enforced_os:
621 enforced_os = self.DEPS_OS_CHOICES.itervalues()
622 self._enforced_os = list(set(enforced_os))
623 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000624 self.config_content = None
625
626 def SetConfig(self, content):
627 assert self.dependencies == []
628 config_dict = {}
629 self.config_content = content
630 try:
631 exec(content, config_dict)
632 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000633 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000634 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000635 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000636 tree = dict((d.name, d) for d in self.tree(False))
637 if s['name'] in tree:
638 raise gclient_utils.Error(
639 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000640 self.dependencies.append(Dependency(
641 self, s['name'], s['url'],
642 s.get('safesync_url', None),
643 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000644 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000645 None,
646 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000647 except KeyError:
648 raise gclient_utils.Error('Invalid .gclient file. Solution is '
649 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650 # .gclient can have hooks.
651 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000652 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000653
654 def SaveConfig(self):
655 gclient_utils.FileWrite(os.path.join(self.root_dir(),
656 self._options.config_filename),
657 self.config_content)
658
659 @staticmethod
660 def LoadCurrentConfig(options):
661 """Searches for and loads a .gclient file relative to the current working
662 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000663 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000664 if not path:
665 return None
666 client = GClient(path, options)
667 client.SetConfig(gclient_utils.FileRead(
668 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000669 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000670
671 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
672 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
673 'solution_name': solution_name,
674 'solution_url': solution_url,
675 'safesync_url' : safesync_url,
676 })
677
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000678 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000679 """Creates a .gclient_entries file to record the list of unique checkouts.
680
681 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000682 """
683 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
684 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000685 result = 'entries = {\n'
686 for entry in self.tree(False):
687 # Skip over File() dependencies as we can't version them.
688 if not isinstance(entry.parsed_url, self.FileImpl):
689 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
690 pprint.pformat(entry.parsed_url))
691 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000692 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000693 logging.info(result)
694 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000695
696 def _ReadEntries(self):
697 """Read the .gclient_entries file for the given client.
698
699 Returns:
700 A sequence of solution names, which will be empty if there is the
701 entries file hasn't been created yet.
702 """
703 scope = {}
704 filename = os.path.join(self.root_dir(), self._options.entries_filename)
705 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000706 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000707 try:
708 exec(gclient_utils.FileRead(filename), scope)
709 except SyntaxError, e:
710 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000711 return scope['entries']
712
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000713 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000714 """Checks for revision overrides."""
715 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000716 if self._options.head:
717 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000718 # Do not check safesync_url if one or more --revision flag is specified.
719 if not self._options.revisions:
720 for s in self.dependencies:
721 if not s.safesync_url:
722 continue
723 handle = urllib.urlopen(s.safesync_url)
724 rev = handle.read().strip()
725 handle.close()
726 if len(rev):
727 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000728 if not self._options.revisions:
729 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000730 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000731 index = 0
732 for revision in self._options.revisions:
733 if not '@' in revision:
734 # Support for --revision 123
735 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000736 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000737 if not sol in solutions_names:
738 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
739 print >> sys.stderr, ('Please fix your script, having invalid '
740 '--revision flags will soon considered an error.')
741 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000742 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000743 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000744 return revision_overrides
745
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000746 def RunOnDeps(self, command, args):
747 """Runs a command on each dependency in a client and its dependencies.
748
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000749 Args:
750 command: The command to use (e.g., 'status' or 'diff')
751 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000752 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000753 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000754 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000755 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000756 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000757 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000758 if (command in ('update', 'revert') and sys.stdout.isatty() and not
759 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000760 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000761 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000762 for s in self.dependencies:
763 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000764 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000765
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000766 # Once all the dependencies have been processed, it's now safe to run the
767 # hooks.
768 if not self._options.nohooks:
769 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000770
771 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000772 # Notify the user if there is an orphaned entry in their working copy.
773 # Only delete the directory if there are no changes in it, and
774 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000775 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000776 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000777 if not prev_url:
778 # entry must have been overridden via .gclient custom_deps
779 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000780 # Fix path separator on Windows.
781 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000782 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000783 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000784 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000785 file_list = []
786 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
787 scm.status(self._options, [], file_list)
788 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000789 if (not self._options.delete_unversioned_trees or
790 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000791 # There are modified files in this entry. Keep warning until
792 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000793 print(('\nWARNING: \'%s\' is no longer part of this client. '
794 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000795 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000796 else:
797 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000798 print('\n________ deleting \'%s\' in \'%s\'' % (
799 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000800 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000801 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000802 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000803 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000804
805 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000806 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000807 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000808 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000809 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000810 for s in self.dependencies:
811 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000812 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000813
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000814 def GetURLAndRev(dep):
815 """Returns the revision-qualified SCM url for a Dependency."""
816 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000817 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000818 if isinstance(dep.parsed_url, self.FileImpl):
819 original_url = dep.parsed_url.file_location
820 else:
821 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000822 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000823 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 if not os.path.isdir(scm.checkout_path):
825 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000826 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000828 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 new_gclient = ''
830 # First level at .gclient
831 for d in self.dependencies:
832 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000833 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000835 for d in dep.dependencies:
836 entries[d.name] = GetURLAndRev(d)
837 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000838 GrabDeps(d)
839 custom_deps = []
840 for k in sorted(entries.keys()):
841 if entries[k]:
842 # Quotes aren't escaped...
843 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
844 else:
845 custom_deps.append(' \"%s\": None,\n' % k)
846 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
847 'solution_name': d.name,
848 'solution_url': d.url,
849 'safesync_url' : d.safesync_url or '',
850 'solution_deps': ''.join(custom_deps),
851 }
852 # Print the snapshot configuration file
853 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000854 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000855 entries = {}
856 for d in self.tree(False):
857 if self._options.actual:
858 entries[d.name] = GetURLAndRev(d)
859 else:
860 entries[d.name] = d.parsed_url
861 keys = sorted(entries.keys())
862 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000863 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000864 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000865
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000866 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000867 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000868 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000869
maruel@chromium.org75a59272010-06-11 22:34:03 +0000870 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000871 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000872 return self._root_dir
873
maruel@chromium.org271375b2010-06-23 19:17:38 +0000874 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000875 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000876 return self._enforced_os
877
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000878 def recursion_limit(self):
879 """How recursive can each dependencies in DEPS file can load DEPS file."""
880 return self._recursion_limit
881
maruel@chromium.org0d812442010-08-10 12:41:08 +0000882 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000883 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000884 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000885
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000887#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888
889
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000890def CMDcleanup(parser, args):
891 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000892
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000893Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000894"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000895 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
896 help='override deps for the specified (comma-separated) '
897 'platform(s); \'all\' will process all deps_os '
898 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000899 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000900 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000901 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000902 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000903 if options.verbose:
904 # Print out the .gclient file. This is longer than if we just printed the
905 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000906 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000907 return client.RunOnDeps('cleanup', args)
908
909
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000910@attr('usage', '[command] [args ...]')
911def CMDrecurse(parser, args):
912 """Operates on all the entries.
913
914 Runs a shell command on all entries.
915 """
916 # Stop parsing at the first non-arg so that these go through to the command
917 parser.disable_interspersed_args()
918 parser.add_option('-s', '--scm', action='append', default=[],
919 help='choose scm types to operate upon')
920 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000921 if not args:
922 print >> sys.stderr, 'Need to supply a command!'
923 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000924 root_and_entries = gclient_utils.GetGClientRootAndEntries()
925 if not root_and_entries:
926 print >> sys.stderr, (
927 'You need to run gclient sync at least once to use \'recurse\'.\n'
928 'This is because .gclient_entries needs to exist and be up to date.')
929 return 1
930 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000931 scm_set = set()
932 for scm in options.scm:
933 scm_set.update(scm.split(','))
934
935 # Pass in the SCM type as an env variable
936 env = os.environ.copy()
937
938 for path, url in entries.iteritems():
939 scm = gclient_scm.GetScmName(url)
940 if scm_set and scm not in scm_set:
941 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000942 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000943 if scm:
944 env['GCLIENT_SCM'] = scm
945 if url:
946 env['GCLIENT_URL'] = url
947 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
948 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000949
950
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000951@attr('usage', '[url] [safesync url]')
952def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000953 """Create a .gclient file in the current directory.
954
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000955This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000956top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000958provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000959URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000960"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000961 parser.add_option('--spec',
962 help='create a gclient file containing the provided '
963 'string. Due to Cygwin/Python brokenness, it '
964 'probably can\'t contain any newlines.')
965 parser.add_option('--name',
966 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000967 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000968 if ((options.spec and args) or len(args) > 2 or
969 (not options.spec and not args)):
970 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
971
maruel@chromium.org0329e672009-05-13 18:41:04 +0000972 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000973 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000974 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000975 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976 if options.spec:
977 client.SetConfig(options.spec)
978 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000979 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000980 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000981 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000982 else:
983 # specify an alternate relpath for the given URL.
984 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000985 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986 if len(args) > 1:
987 safesync_url = args[1]
988 client.SetDefaultConfig(name, base_url, safesync_url)
989 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000990 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000991
992
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000993@attr('epilog', """Example:
994 gclient pack > patch.txt
995 generate simple patch for configured client and dependences
996""")
997def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000998 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000999
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001000Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001001dependencies, and performs minimal postprocessing of the output. The
1002resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001003checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001004"""
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)
kbr@google.comab318592009-09-04 00:54:55 +00001010 client = GClient.LoadCurrentConfig(options)
1011 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001012 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +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)
kbr@google.comab318592009-09-04 00:54:55 +00001017 return client.RunOnDeps('pack', args)
1018
1019
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001020def CMDstatus(parser, args):
1021 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001022 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1023 help='override deps for the specified (comma-separated) '
1024 'platform(s); \'all\' will process all deps_os '
1025 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001026 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001027 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001029 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001030 if options.verbose:
1031 # Print out the .gclient file. This is longer than if we just printed the
1032 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001033 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034 return client.RunOnDeps('status', args)
1035
1036
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001037@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001038 gclient sync
1039 update files from SCM according to current configuration,
1040 *for modules which have changed since last update or sync*
1041 gclient sync --force
1042 update files from SCM according to current configuration, for
1043 all modules (useful for recovering files deleted from local copy)
1044 gclient sync --revision src@31000
1045 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001046""")
1047def CMDsync(parser, args):
1048 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001049 parser.add_option('-f', '--force', action='store_true',
1050 help='force update even for unchanged modules')
1051 parser.add_option('-n', '--nohooks', action='store_true',
1052 help='don\'t run hooks after the update is complete')
1053 parser.add_option('-r', '--revision', action='append',
1054 dest='revisions', metavar='REV', default=[],
1055 help='Enforces revision/hash for the solutions with the '
1056 'format src@rev. The src@ part is optional and can be '
1057 'skipped. -r can be used multiple times when .gclient '
1058 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001059 'if the src@ part is skipped. Note that specifying '
1060 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001061 parser.add_option('-t', '--transitive', action='store_true',
1062 help='When a revision is specified (in the DEPS file or '
1063 'with the command-line flag), transitively update '
1064 'the dependencies to the date of the given revision. '
1065 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001066 parser.add_option('-H', '--head', action='store_true',
1067 help='skips any safesync_urls specified in '
1068 'configured solutions and sync to head instead')
1069 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001070 help='delete any dependency that have been removed from '
1071 'last sync as long as there is no local modification. '
1072 'Coupled with --force, it will remove them even with '
1073 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001074 parser.add_option('-R', '--reset', action='store_true',
1075 help='resets any local changes before updating (git only)')
1076 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1077 help='override deps for the specified (comma-separated) '
1078 'platform(s); \'all\' will process all deps_os '
1079 'references')
1080 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1081 help='Skip svn up whenever possible by requesting '
1082 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001083 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001084 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001085
1086 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001087 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088
maruel@chromium.org307d1792010-05-31 20:03:13 +00001089 if options.revisions and options.head:
1090 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092
1093 if options.verbose:
1094 # Print out the .gclient file. This is longer than if we just printed the
1095 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001096 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 return client.RunOnDeps('update', args)
1098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001101 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001102 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001103
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001104def CMDdiff(parser, args):
1105 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001106 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1107 help='override deps for the specified (comma-separated) '
1108 'platform(s); \'all\' will process all deps_os '
1109 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001111 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001112 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001113 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 if options.verbose:
1115 # Print out the .gclient file. This is longer than if we just printed the
1116 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001117 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001118 return client.RunOnDeps('diff', args)
1119
1120
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001122 """Revert all modifications in every dependencies.
1123
1124 That's the nuclear option to get back to a 'clean' state. It removes anything
1125 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001126 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1127 help='override deps for the specified (comma-separated) '
1128 'platform(s); \'all\' will process all deps_os '
1129 'references')
1130 parser.add_option('-n', '--nohooks', action='store_true',
1131 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001132 (options, args) = parser.parse_args(args)
1133 # --force is implied.
1134 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001135 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001137 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138 return client.RunOnDeps('revert', args)
1139
1140
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001141def CMDrunhooks(parser, args):
1142 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1144 help='override deps for the specified (comma-separated) '
1145 'platform(s); \'all\' will process all deps_os '
1146 'references')
1147 parser.add_option('-f', '--force', action='store_true', default=True,
1148 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001149 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001150 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001152 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153 if options.verbose:
1154 # Print out the .gclient file. This is longer than if we just printed the
1155 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001156 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001157 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001158 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159 return client.RunOnDeps('runhooks', args)
1160
1161
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001162def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001163 """Output revision info mapping for the client and its dependencies.
1164
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001165 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001166 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001167 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1168 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001169 commit can change.
1170 """
1171 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1172 help='override deps for the specified (comma-separated) '
1173 'platform(s); \'all\' will process all deps_os '
1174 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001175 parser.add_option('-a', '--actual', action='store_true',
1176 help='gets the actual checked out revisions instead of the '
1177 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001178 parser.add_option('-s', '--snapshot', action='store_true',
1179 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001180 'version of all repositories to reproduce the tree, '
1181 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001183 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001184 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001185 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001187 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188
1189
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001190def Command(name):
1191 return getattr(sys.modules[__name__], 'CMD' + name, None)
1192
1193
1194def CMDhelp(parser, args):
1195 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001196 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001197 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001198 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001199 parser.print_help()
1200 return 0
1201
1202
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203def GenUsage(parser, command):
1204 """Modify an OptParse object with the function's documentation."""
1205 obj = Command(command)
1206 if command == 'help':
1207 command = '<command>'
1208 # OptParser.description prefer nicely non-formatted strings.
1209 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1210 usage = getattr(obj, 'usage', '')
1211 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1212 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001213
1214
1215def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001216 """Doesn't parse the arguments here, just find the right subcommand to
1217 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001218 if sys.hexversion < 0x02050000:
1219 print >> sys.stderr, (
1220 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001221 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001222 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1223 # operations. Python as a strong tendency to buffer sys.stdout.
1224 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001225 # Make stdout annotated with the thread ids.
1226 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001227 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001228 # Unused variable 'usage'
1229 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001230 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1231 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1232 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1233 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001234 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001235 help='Specify how many SCM commands can run in parallel; '
1236 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001237 parser.add_option('-v', '--verbose', action='count', default=0,
1238 help='Produces additional output for diagnostics. Can be '
1239 'used up to three times for more logging info.')
1240 parser.add_option('--gclientfile', dest='config_filename',
1241 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1242 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001243 # Integrate standard options processing.
1244 old_parser = parser.parse_args
1245 def Parse(args):
1246 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001247 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001248 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001249 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001250 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001251 level = logging.DEBUG
1252 logging.basicConfig(level=level,
1253 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1254 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001255 if options.jobs < 1:
1256 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001257
1258 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001259 if not hasattr(options, 'revisions'):
1260 # GClient.RunOnDeps expects it even if not applicable.
1261 options.revisions = []
1262 if not hasattr(options, 'head'):
1263 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001264 if not hasattr(options, 'nohooks'):
1265 options.nohooks = True
1266 if not hasattr(options, 'deps_os'):
1267 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001268 if not hasattr(options, 'manually_grab_svn_rev'):
1269 options.manually_grab_svn_rev = None
1270 if not hasattr(options, 'force'):
1271 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001272 return (options, args)
1273 parser.parse_args = Parse
1274 # We don't want wordwrapping in epilog (usually examples)
1275 parser.format_epilog = lambda _: parser.epilog or ''
1276 if argv:
1277 command = Command(argv[0])
1278 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001279 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001280 GenUsage(parser, argv[0])
1281 return command(parser, argv[1:])
1282 # Not a known command. Default to help.
1283 GenUsage(parser, 'help')
1284 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001285 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001286 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001287 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001288
1289
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001290if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001291 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001292 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293
1294# vim: ts=2:sw=2:tw=80:et: