blob: 28f590d905e17e23ecf342ba3dc10fb82d05c249 [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
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.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 +0000993def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000994 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000995 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
996 help='override deps for the specified (comma-separated) '
997 'platform(s); \'all\' will process all deps_os '
998 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000999 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001000 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001001 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001002 client = GClient.LoadCurrentConfig(options)
1003
1004 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001005 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001006
1007 if options.verbose:
1008 # Print out the .gclient file. This is longer than if we just printed the
1009 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001010 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001011 return client.RunOnDeps('export', args)
1012
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001013
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001014@attr('epilog', """Example:
1015 gclient pack > patch.txt
1016 generate simple patch for configured client and dependences
1017""")
1018def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001019 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001020
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001021Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001022dependencies, and performs minimal postprocessing of the output. The
1023resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001024checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001025"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001026 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1027 help='override deps for the specified (comma-separated) '
1028 'platform(s); \'all\' will process all deps_os '
1029 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001030 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001031 client = GClient.LoadCurrentConfig(options)
1032 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001033 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001034 if options.verbose:
1035 # Print out the .gclient file. This is longer than if we just printed the
1036 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001037 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001038 return client.RunOnDeps('pack', args)
1039
1040
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001041def CMDstatus(parser, args):
1042 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001043 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1044 help='override deps for the specified (comma-separated) '
1045 'platform(s); \'all\' will process all deps_os '
1046 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001047 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001048 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001050 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 if options.verbose:
1052 # Print out the .gclient file. This is longer than if we just printed the
1053 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001054 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055 return client.RunOnDeps('status', args)
1056
1057
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001058@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001059 gclient sync
1060 update files from SCM according to current configuration,
1061 *for modules which have changed since last update or sync*
1062 gclient sync --force
1063 update files from SCM according to current configuration, for
1064 all modules (useful for recovering files deleted from local copy)
1065 gclient sync --revision src@31000
1066 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001067""")
1068def CMDsync(parser, args):
1069 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001070 parser.add_option('-f', '--force', action='store_true',
1071 help='force update even for unchanged modules')
1072 parser.add_option('-n', '--nohooks', action='store_true',
1073 help='don\'t run hooks after the update is complete')
1074 parser.add_option('-r', '--revision', action='append',
1075 dest='revisions', metavar='REV', default=[],
1076 help='Enforces revision/hash for the solutions with the '
1077 'format src@rev. The src@ part is optional and can be '
1078 'skipped. -r can be used multiple times when .gclient '
1079 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001080 'if the src@ part is skipped. Note that specifying '
1081 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001082 parser.add_option('-t', '--transitive', action='store_true',
1083 help='When a revision is specified (in the DEPS file or '
1084 'with the command-line flag), transitively update '
1085 'the dependencies to the date of the given revision. '
1086 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001087 parser.add_option('-H', '--head', action='store_true',
1088 help='skips any safesync_urls specified in '
1089 'configured solutions and sync to head instead')
1090 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001091 help='delete any dependency that have been removed from '
1092 'last sync as long as there is no local modification. '
1093 'Coupled with --force, it will remove them even with '
1094 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001095 parser.add_option('-R', '--reset', action='store_true',
1096 help='resets any local changes before updating (git only)')
1097 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1098 help='override deps for the specified (comma-separated) '
1099 'platform(s); \'all\' will process all deps_os '
1100 'references')
1101 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1102 help='Skip svn up whenever possible by requesting '
1103 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001104 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001105 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001106
1107 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001108 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001109
maruel@chromium.org307d1792010-05-31 20:03:13 +00001110 if options.revisions and options.head:
1111 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001112 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001113
1114 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('update', args)
1119
1120
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001122 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001123 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001124
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001125def CMDdiff(parser, args):
1126 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001127 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1128 help='override deps for the specified (comma-separated) '
1129 'platform(s); \'all\' will process all deps_os '
1130 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001132 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001134 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135 if options.verbose:
1136 # Print out the .gclient file. This is longer than if we just printed the
1137 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001138 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001139 return client.RunOnDeps('diff', args)
1140
1141
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001142def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001143 """Revert all modifications in every dependencies.
1144
1145 That's the nuclear option to get back to a 'clean' state. It removes anything
1146 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1148 help='override deps for the specified (comma-separated) '
1149 'platform(s); \'all\' will process all deps_os '
1150 'references')
1151 parser.add_option('-n', '--nohooks', action='store_true',
1152 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001153 (options, args) = parser.parse_args(args)
1154 # --force is implied.
1155 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001156 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001157 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001158 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159 return client.RunOnDeps('revert', args)
1160
1161
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001162def CMDrunhooks(parser, args):
1163 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001164 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1165 help='override deps for the specified (comma-separated) '
1166 'platform(s); \'all\' will process all deps_os '
1167 'references')
1168 parser.add_option('-f', '--force', action='store_true', default=True,
1169 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001170 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001171 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001172 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001173 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001174 if options.verbose:
1175 # Print out the .gclient file. This is longer than if we just printed the
1176 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001177 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001178 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001179 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 return client.RunOnDeps('runhooks', args)
1181
1182
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001183def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001184 """Output revision info mapping for the client and its dependencies.
1185
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001186 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001187 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001188 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1189 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001190 commit can change.
1191 """
1192 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1193 help='override deps for the specified (comma-separated) '
1194 'platform(s); \'all\' will process all deps_os '
1195 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001196 parser.add_option('-a', '--actual', action='store_true',
1197 help='gets the actual checked out revisions instead of the '
1198 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001199 parser.add_option('-s', '--snapshot', action='store_true',
1200 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001201 'version of all repositories to reproduce the tree, '
1202 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001204 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001205 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001206 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001207 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001208 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001209
1210
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001211def Command(name):
1212 return getattr(sys.modules[__name__], 'CMD' + name, None)
1213
1214
1215def CMDhelp(parser, args):
1216 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001217 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001218 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001219 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001220 parser.print_help()
1221 return 0
1222
1223
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001224def GenUsage(parser, command):
1225 """Modify an OptParse object with the function's documentation."""
1226 obj = Command(command)
1227 if command == 'help':
1228 command = '<command>'
1229 # OptParser.description prefer nicely non-formatted strings.
1230 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1231 usage = getattr(obj, 'usage', '')
1232 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1233 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001234
1235
1236def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001237 """Doesn't parse the arguments here, just find the right subcommand to
1238 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001239 if sys.hexversion < 0x02050000:
1240 print >> sys.stderr, (
1241 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001242 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001243 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1244 # operations. Python as a strong tendency to buffer sys.stdout.
1245 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001246 # Make stdout annotated with the thread ids.
1247 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001248 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001249 # Unused variable 'usage'
1250 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001251 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1252 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1253 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1254 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001255 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001256 help='Specify how many SCM commands can run in parallel; '
1257 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001258 parser.add_option('-v', '--verbose', action='count', default=0,
1259 help='Produces additional output for diagnostics. Can be '
1260 'used up to three times for more logging info.')
1261 parser.add_option('--gclientfile', dest='config_filename',
1262 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1263 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001264 # Integrate standard options processing.
1265 old_parser = parser.parse_args
1266 def Parse(args):
1267 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001268 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001269 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001270 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001271 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001272 level = logging.DEBUG
1273 logging.basicConfig(level=level,
1274 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1275 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001276 if options.jobs < 1:
1277 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001278
1279 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001280 if not hasattr(options, 'revisions'):
1281 # GClient.RunOnDeps expects it even if not applicable.
1282 options.revisions = []
1283 if not hasattr(options, 'head'):
1284 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001285 if not hasattr(options, 'nohooks'):
1286 options.nohooks = True
1287 if not hasattr(options, 'deps_os'):
1288 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001289 if not hasattr(options, 'manually_grab_svn_rev'):
1290 options.manually_grab_svn_rev = None
1291 if not hasattr(options, 'force'):
1292 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001293 return (options, args)
1294 parser.parse_args = Parse
1295 # We don't want wordwrapping in epilog (usually examples)
1296 parser.format_epilog = lambda _: parser.epilog or ''
1297 if argv:
1298 command = Command(argv[0])
1299 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001300 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001301 GenUsage(parser, argv[0])
1302 return command(parser, argv[1:])
1303 # Not a known command. Default to help.
1304 GenUsage(parser, 'help')
1305 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001306 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001307 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001308 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001309
1310
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001311if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001312 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001313 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001314
1315# vim: ts=2:sw=2:tw=80:et: