blob: b36f5816d1d091597b01fb75f881c7a93972b20f [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.org0b6a0842010-06-15 14:34:19 +0000141
maruel@chromium.org0d812442010-08-10 12:41:08 +0000142 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000143 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000144 GClientKeywords.__init__(self)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000145 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000146 self.parent = parent
147 self.name = name
148 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000149 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000150 # These 2 are only set in .gclient and not in DEPS files.
151 self.safesync_url = safesync_url
152 self.custom_vars = custom_vars or {}
153 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000154 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000155 self.dependencies = []
nsylvain@google.comefc80932011-05-31 21:27:56 +0000156 self.deps_file = deps_file
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000157 # A cache of the files affected by the current operation, necessary for
158 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000159 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000160 # If it is not set to True, the dependency wasn't processed for its child
161 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000162 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000163 # This dependency should be processed, i.e. checked out
164 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000165 # This dependency has been processed, i.e. checked out
166 self.processed = False
167 # This dependency had its hook run
168 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000169 # Required dependencies to run before running this one:
170 self.requirements = []
171 if self.parent and self.parent.name:
172 self.requirements.append(self.parent.name)
173 if isinstance(self.url, self.FromImpl):
174 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000175
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000176 # Sanity checks
177 if not self.name and self.parent:
178 raise gclient_utils.Error('Dependency without name')
179 if not isinstance(self.url,
180 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
181 raise gclient_utils.Error('dependency url must be either a string, None, '
182 'File() or From() instead of %s' %
183 self.url.__class__.__name__)
184 if '/' in self.deps_file or '\\' in self.deps_file:
185 raise gclient_utils.Error('deps_file name must not be a path, just a '
186 'filename. %s' % self.deps_file)
187
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000188 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000189 """Resolves the parsed url from url.
190
191 Manages From() keyword accordingly. Do not touch self.parsed_url nor
192 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000193 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000194 overriden_url = self.get_custom_deps(self.name, url)
195 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000196 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000197 overriden_url))
198 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000199 elif isinstance(url, self.FromImpl):
200 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000201 if not ref:
202 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
203 url.module_name, ref))
204 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000205 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000206 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000207 # Make sure the referenced dependency DEPS file is loaded and file the
208 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000209 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000210 found_dep = None
211 for d in ref.dependencies:
212 if d.name == sub_target:
213 found_dep = d
214 break
215 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000216 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000217 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
218 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000219 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000220 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000221 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000222 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000223 elif isinstance(url, basestring):
224 parsed_url = urlparse.urlparse(url)
225 if not parsed_url[0]:
226 # A relative url. Fetch the real base.
227 path = parsed_url[2]
228 if not path.startswith('/'):
229 raise gclient_utils.Error(
230 'relative DEPS entry \'%s\' must begin with a slash' % url)
231 # Create a scm just to query the full url.
232 parent_url = self.parent.parsed_url
233 if isinstance(parent_url, self.FileImpl):
234 parent_url = parent_url.file_location
235 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000236 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000237 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000238 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000239 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000240 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000241 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000242 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000243 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000244 return parsed_url
245 elif url is None:
246 return None
247 else:
248 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000249
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000250 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000251 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000252 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000253 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000254 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000256 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000257 # One thing is unintuitive, vars= {} must happen before Var() use.
258 local_scope = {}
259 var = self.VarImpl(self.custom_vars, local_scope)
260 global_scope = {
261 'File': self.FileImpl,
262 'From': self.FromImpl,
263 'Var': var.Lookup,
264 'deps_os': {},
265 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000266 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
267 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000268 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
269 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000270 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,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000334 self.deps_file, 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",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000588 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000589 "custom_deps" : {
590 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000591 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000592 },
593]
594""")
595
596 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
597 { "name" : "%(solution_name)s",
598 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000599 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000600 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000601%(solution_deps)s },
602 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000603 },
604""")
605
606 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
607# Snapshot generated with gclient revinfo --snapshot
608solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000609%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000610""")
611
612 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000613 # Do not change previous behavior. Only solution level and immediate DEPS
614 # are processed.
615 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000616 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
617 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000618 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000619 if options.deps_os:
620 enforced_os = options.deps_os.split(',')
621 else:
622 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
623 if 'all' in enforced_os:
624 enforced_os = self.DEPS_OS_CHOICES.itervalues()
625 self._enforced_os = list(set(enforced_os))
626 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000627 self.config_content = None
628
629 def SetConfig(self, content):
630 assert self.dependencies == []
631 config_dict = {}
632 self.config_content = content
633 try:
634 exec(content, config_dict)
635 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000636 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000637 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000638 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000639 tree = dict((d.name, d) for d in self.tree(False))
640 if s['name'] in tree:
641 raise gclient_utils.Error(
642 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000643 self.dependencies.append(Dependency(
644 self, s['name'], s['url'],
645 s.get('safesync_url', None),
646 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000647 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000648 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000649 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000650 except KeyError:
651 raise gclient_utils.Error('Invalid .gclient file. Solution is '
652 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000653 # .gclient can have hooks.
654 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000655 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000656
657 def SaveConfig(self):
658 gclient_utils.FileWrite(os.path.join(self.root_dir(),
659 self._options.config_filename),
660 self.config_content)
661
662 @staticmethod
663 def LoadCurrentConfig(options):
664 """Searches for and loads a .gclient file relative to the current working
665 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000666 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000667 if not path:
668 return None
669 client = GClient(path, options)
670 client.SetConfig(gclient_utils.FileRead(
671 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000672 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000673
nsylvain@google.comefc80932011-05-31 21:27:56 +0000674 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
675 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000676 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
677 'solution_name': solution_name,
678 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000679 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000680 'safesync_url' : safesync_url,
681 })
682
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000683 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000684 """Creates a .gclient_entries file to record the list of unique checkouts.
685
686 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000687 """
688 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
689 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000690 result = 'entries = {\n'
691 for entry in self.tree(False):
692 # Skip over File() dependencies as we can't version them.
693 if not isinstance(entry.parsed_url, self.FileImpl):
694 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
695 pprint.pformat(entry.parsed_url))
696 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000697 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000698 logging.info(result)
699 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000700
701 def _ReadEntries(self):
702 """Read the .gclient_entries file for the given client.
703
704 Returns:
705 A sequence of solution names, which will be empty if there is the
706 entries file hasn't been created yet.
707 """
708 scope = {}
709 filename = os.path.join(self.root_dir(), self._options.entries_filename)
710 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000711 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000712 try:
713 exec(gclient_utils.FileRead(filename), scope)
714 except SyntaxError, e:
715 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000716 return scope['entries']
717
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000718 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000719 """Checks for revision overrides."""
720 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000721 if self._options.head:
722 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000723 # Do not check safesync_url if one or more --revision flag is specified.
724 if not self._options.revisions:
725 for s in self.dependencies:
726 if not s.safesync_url:
727 continue
728 handle = urllib.urlopen(s.safesync_url)
729 rev = handle.read().strip()
730 handle.close()
731 if len(rev):
732 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000733 if not self._options.revisions:
734 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000735 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000736 index = 0
737 for revision in self._options.revisions:
738 if not '@' in revision:
739 # Support for --revision 123
740 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000741 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000742 if not sol in solutions_names:
743 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
744 print >> sys.stderr, ('Please fix your script, having invalid '
745 '--revision flags will soon considered an error.')
746 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000747 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000748 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000749 return revision_overrides
750
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000751 def RunOnDeps(self, command, args):
752 """Runs a command on each dependency in a client and its dependencies.
753
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000754 Args:
755 command: The command to use (e.g., 'status' or 'diff')
756 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000757 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000758 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000759 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000760 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000761 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000762 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000763 if (command in ('update', 'revert') and sys.stdout.isatty() and not
764 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000765 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000766 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000767 for s in self.dependencies:
768 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000769 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000770
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000771 # Once all the dependencies have been processed, it's now safe to run the
772 # hooks.
773 if not self._options.nohooks:
774 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000775
776 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000777 # Notify the user if there is an orphaned entry in their working copy.
778 # Only delete the directory if there are no changes in it, and
779 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000780 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000781 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000782 if not prev_url:
783 # entry must have been overridden via .gclient custom_deps
784 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000785 # Fix path separator on Windows.
786 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000787 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000788 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000789 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000790 file_list = []
791 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
792 scm.status(self._options, [], file_list)
793 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000794 if (not self._options.delete_unversioned_trees or
795 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000796 # There are modified files in this entry. Keep warning until
797 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000798 print(('\nWARNING: \'%s\' is no longer part of this client. '
799 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000800 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000801 else:
802 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000803 print('\n________ deleting \'%s\' in \'%s\'' % (
804 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000805 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000806 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000807 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000808 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809
810 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000811 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000812 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000813 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000814 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000815 for s in self.dependencies:
816 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000817 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000818
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000819 def GetURLAndRev(dep):
820 """Returns the revision-qualified SCM url for a Dependency."""
821 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000822 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000823 if isinstance(dep.parsed_url, self.FileImpl):
824 original_url = dep.parsed_url.file_location
825 else:
826 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000827 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000828 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 if not os.path.isdir(scm.checkout_path):
830 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000831 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000832
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000833 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 new_gclient = ''
835 # First level at .gclient
836 for d in self.dependencies:
837 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000838 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000839 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000840 for d in dep.dependencies:
841 entries[d.name] = GetURLAndRev(d)
842 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000843 GrabDeps(d)
844 custom_deps = []
845 for k in sorted(entries.keys()):
846 if entries[k]:
847 # Quotes aren't escaped...
848 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
849 else:
850 custom_deps.append(' \"%s\": None,\n' % k)
851 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
852 'solution_name': d.name,
853 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000854 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000855 'safesync_url' : d.safesync_url or '',
856 'solution_deps': ''.join(custom_deps),
857 }
858 # Print the snapshot configuration file
859 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000860 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000861 entries = {}
862 for d in self.tree(False):
863 if self._options.actual:
864 entries[d.name] = GetURLAndRev(d)
865 else:
866 entries[d.name] = d.parsed_url
867 keys = sorted(entries.keys())
868 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000869 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000870 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000871
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000872 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000873 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000874 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000875
maruel@chromium.org75a59272010-06-11 22:34:03 +0000876 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000877 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000878 return self._root_dir
879
maruel@chromium.org271375b2010-06-23 19:17:38 +0000880 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000881 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000882 return self._enforced_os
883
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000884 def recursion_limit(self):
885 """How recursive can each dependencies in DEPS file can load DEPS file."""
886 return self._recursion_limit
887
maruel@chromium.org0d812442010-08-10 12:41:08 +0000888 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000889 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000890 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000891
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000892
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000893#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000894
895
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000896def CMDcleanup(parser, args):
897 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000898
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000899Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000900"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000901 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
902 help='override deps for the specified (comma-separated) '
903 'platform(s); \'all\' will process all deps_os '
904 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000905 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000906 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000907 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000908 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909 if options.verbose:
910 # Print out the .gclient file. This is longer than if we just printed the
911 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000912 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000913 return client.RunOnDeps('cleanup', args)
914
915
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000916@attr('usage', '[command] [args ...]')
917def CMDrecurse(parser, args):
918 """Operates on all the entries.
919
920 Runs a shell command on all entries.
921 """
922 # Stop parsing at the first non-arg so that these go through to the command
923 parser.disable_interspersed_args()
924 parser.add_option('-s', '--scm', action='append', default=[],
925 help='choose scm types to operate upon')
926 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000927 if not args:
928 print >> sys.stderr, 'Need to supply a command!'
929 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000930 root_and_entries = gclient_utils.GetGClientRootAndEntries()
931 if not root_and_entries:
932 print >> sys.stderr, (
933 'You need to run gclient sync at least once to use \'recurse\'.\n'
934 'This is because .gclient_entries needs to exist and be up to date.')
935 return 1
936 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000937 scm_set = set()
938 for scm in options.scm:
939 scm_set.update(scm.split(','))
940
941 # Pass in the SCM type as an env variable
942 env = os.environ.copy()
943
944 for path, url in entries.iteritems():
945 scm = gclient_scm.GetScmName(url)
946 if scm_set and scm not in scm_set:
947 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000948 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000949 if scm:
950 env['GCLIENT_SCM'] = scm
951 if url:
952 env['GCLIENT_URL'] = url
953 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
954 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000955
956
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957@attr('usage', '[url] [safesync url]')
958def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000959 """Create a .gclient file in the current directory.
960
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000961This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000962top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000963modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000964provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000965URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000966"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000967 parser.add_option('--spec',
968 help='create a gclient file containing the provided '
969 'string. Due to Cygwin/Python brokenness, it '
970 'probably can\'t contain any newlines.')
971 parser.add_option('--name',
972 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +0000973 parser.add_option('--deps-file', default='DEPS',
974 help='overrides the default name for the DEPS file for the'
975 'main solutions and all sub-dependencies')
976 parser.add_option('--git-deps', action='store_true',
977 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000978 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000979 if ((options.spec and args) or len(args) > 2 or
980 (not options.spec and not args)):
981 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
982
maruel@chromium.org0329e672009-05-13 18:41:04 +0000983 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000984 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000985 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000986 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000987 if options.spec:
988 client.SetConfig(options.spec)
989 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000990 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000991 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000992 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +0000993 if name.endswith('.git'):
994 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000995 else:
996 # specify an alternate relpath for the given URL.
997 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +0000998 deps_file = options.deps_file
999 if options.git_deps:
1000 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001001 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002 if len(args) > 1:
1003 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001004 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001005 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001006 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001007
1008
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001009@attr('epilog', """Example:
1010 gclient pack > patch.txt
1011 generate simple patch for configured client and dependences
1012""")
1013def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001014 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001015
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001016Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001017dependencies, and performs minimal postprocessing of the output. The
1018resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001019checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001020"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001021 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1022 help='override deps for the specified (comma-separated) '
1023 'platform(s); \'all\' will process all deps_os '
1024 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001025 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001026 client = GClient.LoadCurrentConfig(options)
1027 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001028 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001029 if options.verbose:
1030 # Print out the .gclient file. This is longer than if we just printed the
1031 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001032 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001033 return client.RunOnDeps('pack', args)
1034
1035
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001036def CMDstatus(parser, args):
1037 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001038 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1039 help='override deps for the specified (comma-separated) '
1040 'platform(s); \'all\' will process all deps_os '
1041 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001042 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001043 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001045 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001046 if options.verbose:
1047 # Print out the .gclient file. This is longer than if we just printed the
1048 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001049 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 return client.RunOnDeps('status', args)
1051
1052
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001053@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001054 gclient sync
1055 update files from SCM according to current configuration,
1056 *for modules which have changed since last update or sync*
1057 gclient sync --force
1058 update files from SCM according to current configuration, for
1059 all modules (useful for recovering files deleted from local copy)
1060 gclient sync --revision src@31000
1061 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001062""")
1063def CMDsync(parser, args):
1064 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001065 parser.add_option('-f', '--force', action='store_true',
1066 help='force update even for unchanged modules')
1067 parser.add_option('-n', '--nohooks', action='store_true',
1068 help='don\'t run hooks after the update is complete')
1069 parser.add_option('-r', '--revision', action='append',
1070 dest='revisions', metavar='REV', default=[],
1071 help='Enforces revision/hash for the solutions with the '
1072 'format src@rev. The src@ part is optional and can be '
1073 'skipped. -r can be used multiple times when .gclient '
1074 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001075 'if the src@ part is skipped. Note that specifying '
1076 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001077 parser.add_option('-t', '--transitive', action='store_true',
1078 help='When a revision is specified (in the DEPS file or '
1079 'with the command-line flag), transitively update '
1080 'the dependencies to the date of the given revision. '
1081 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001082 parser.add_option('-H', '--head', action='store_true',
1083 help='skips any safesync_urls specified in '
1084 'configured solutions and sync to head instead')
1085 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001086 help='delete any dependency that have been removed from '
1087 'last sync as long as there is no local modification. '
1088 'Coupled with --force, it will remove them even with '
1089 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001090 parser.add_option('-R', '--reset', action='store_true',
1091 help='resets any local changes before updating (git only)')
1092 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1093 help='override deps for the specified (comma-separated) '
1094 'platform(s); \'all\' will process all deps_os '
1095 'references')
1096 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1097 help='Skip svn up whenever possible by requesting '
1098 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001099 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001100 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001101
1102 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001103 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104
maruel@chromium.org307d1792010-05-31 20:03:13 +00001105 if options.revisions and options.head:
1106 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001107 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108
1109 if options.verbose:
1110 # Print out the .gclient file. This is longer than if we just printed the
1111 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001112 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001113 return client.RunOnDeps('update', args)
1114
1115
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001116def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001117 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001118 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001119
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001120def CMDdiff(parser, args):
1121 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001122 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1123 help='override deps for the specified (comma-separated) '
1124 'platform(s); \'all\' will process all deps_os '
1125 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001126 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001127 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001128 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001129 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130 if options.verbose:
1131 # Print out the .gclient file. This is longer than if we just printed the
1132 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001133 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134 return client.RunOnDeps('diff', args)
1135
1136
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001137def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001138 """Revert all modifications in every dependencies.
1139
1140 That's the nuclear option to get back to a 'clean' state. It removes anything
1141 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001142 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1143 help='override deps for the specified (comma-separated) '
1144 'platform(s); \'all\' will process all deps_os '
1145 'references')
1146 parser.add_option('-n', '--nohooks', action='store_true',
1147 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001148 (options, args) = parser.parse_args(args)
1149 # --force is implied.
1150 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001151 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001153 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001154 return client.RunOnDeps('revert', args)
1155
1156
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001157def CMDrunhooks(parser, args):
1158 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001159 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1160 help='override deps for the specified (comma-separated) '
1161 'platform(s); \'all\' will process all deps_os '
1162 'references')
1163 parser.add_option('-f', '--force', action='store_true', default=True,
1164 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001166 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001167 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001168 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001169 if options.verbose:
1170 # Print out the .gclient file. This is longer than if we just printed the
1171 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001172 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001173 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001174 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175 return client.RunOnDeps('runhooks', args)
1176
1177
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001178def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001179 """Output revision info mapping for the client and its dependencies.
1180
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001181 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001182 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001183 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1184 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001185 commit can change.
1186 """
1187 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1188 help='override deps for the specified (comma-separated) '
1189 'platform(s); \'all\' will process all deps_os '
1190 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001191 parser.add_option('-a', '--actual', action='store_true',
1192 help='gets the actual checked out revisions instead of the '
1193 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001194 parser.add_option('-s', '--snapshot', action='store_true',
1195 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001196 'version of all repositories to reproduce the tree, '
1197 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001198 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001199 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001201 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001203 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001204
1205
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001206def Command(name):
1207 return getattr(sys.modules[__name__], 'CMD' + name, None)
1208
1209
1210def CMDhelp(parser, args):
1211 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001212 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001213 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001214 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001215 parser.print_help()
1216 return 0
1217
1218
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001219def GenUsage(parser, command):
1220 """Modify an OptParse object with the function's documentation."""
1221 obj = Command(command)
1222 if command == 'help':
1223 command = '<command>'
1224 # OptParser.description prefer nicely non-formatted strings.
1225 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1226 usage = getattr(obj, 'usage', '')
1227 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1228 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001229
1230
1231def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001232 """Doesn't parse the arguments here, just find the right subcommand to
1233 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001234 if sys.hexversion < 0x02050000:
1235 print >> sys.stderr, (
1236 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001237 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001238 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1239 # operations. Python as a strong tendency to buffer sys.stdout.
1240 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001241 # Make stdout annotated with the thread ids.
1242 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001243 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001244 # Unused variable 'usage'
1245 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001246 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1247 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1248 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1249 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001250 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001251 help='Specify how many SCM commands can run in parallel; '
1252 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001253 parser.add_option('-v', '--verbose', action='count', default=0,
1254 help='Produces additional output for diagnostics. Can be '
1255 'used up to three times for more logging info.')
1256 parser.add_option('--gclientfile', dest='config_filename',
1257 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1258 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001259 # Integrate standard options processing.
1260 old_parser = parser.parse_args
1261 def Parse(args):
1262 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001263 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001264 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001265 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001266 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001267 level = logging.DEBUG
1268 logging.basicConfig(level=level,
1269 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1270 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001271 if options.jobs < 1:
1272 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001273
1274 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001275 if not hasattr(options, 'revisions'):
1276 # GClient.RunOnDeps expects it even if not applicable.
1277 options.revisions = []
1278 if not hasattr(options, 'head'):
1279 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001280 if not hasattr(options, 'nohooks'):
1281 options.nohooks = True
1282 if not hasattr(options, 'deps_os'):
1283 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001284 if not hasattr(options, 'manually_grab_svn_rev'):
1285 options.manually_grab_svn_rev = None
1286 if not hasattr(options, 'force'):
1287 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001288 return (options, args)
1289 parser.parse_args = Parse
1290 # We don't want wordwrapping in epilog (usually examples)
1291 parser.format_epilog = lambda _: parser.epilog or ''
1292 if argv:
1293 command = Command(argv[0])
1294 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001295 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001296 GenUsage(parser, argv[0])
1297 return command(parser, argv[1:])
1298 # Not a known command. Default to help.
1299 GenUsage(parser, 'help')
1300 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001301 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001302 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001303 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001304
1305
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001306if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001307 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001308 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001309
1310# vim: ts=2:sw=2:tw=80:et: