blob: e2257724e535cc6c646f84f6ab637851dac89b43 [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.org118fb1c2011-09-01 20:04:24 +0000144 # Warning: this function can be called from any thread. Both
145 # self.dependencies and self.requirements are read and modified from
146 # multiple threads at the same time. Sad.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000147 GClientKeywords.__init__(self)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000148 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000149 self.parent = parent
150 self.name = name
151 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000152 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000153 # These 2 are only set in .gclient and not in DEPS files.
154 self.safesync_url = safesync_url
155 self.custom_vars = custom_vars or {}
156 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000157 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000158 self.dependencies = []
nsylvain@google.comefc80932011-05-31 21:27:56 +0000159 self.deps_file = deps_file
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000160 # A cache of the files affected by the current operation, necessary for
161 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000162 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000163 # If it is not set to True, the dependency wasn't processed for its child
164 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000165 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000166 # This dependency should be processed, i.e. checked out
167 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000168 # This dependency has been processed, i.e. checked out
169 self.processed = False
170 # This dependency had its hook run
171 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000172 # Required dependencies to run before running this one:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000173 self.requirements = set()
174
175 self._FindDependencies()
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.org118fb1c2011-09-01 20:04:24 +0000189 def _FindDependencies(self):
190 """Setup self.requirements and find any other dependency who would have self
191 as a requirement.
192 """
193 # self.parent is implicitly a requirement. This will be recursive by
194 # definition.
195 if self.parent and self.parent.name:
196 self.requirements.add(self.parent.name)
197
198 # For a tree with at least 2 levels*, the leaf node needs to depend
199 # on the level higher up in an orderly way.
200 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
201 # thus unsorted, while the .gclient format is a list thus sorted.
202 #
203 # * _recursion_limit is hard coded 2 and there is no hope to change this
204 # value.
205 #
206 # Interestingly enough, the following condition only works in the case we
207 # want: self is a 2nd level node. 3nd level node wouldn't need this since
208 # they already have their parent as a requirement.
209 if self.parent in self.root_parent().dependencies:
210 root_deps = self.root_parent().dependencies
211 for i in range(0, root_deps.index(self.parent)):
212 value = root_deps[i]
213 if value.name:
214 self.requirements.add(value.name)
215
216 if isinstance(self.url, self.FromImpl):
217 self.requirements.add(self.url.module_name)
218
219 if self.name:
220 def yield_full_tree(root):
221 """Depth-first recursion."""
222 yield root
223 for i in root.dependencies:
224 for j in yield_full_tree(i):
225 yield j
226
227 for obj in yield_full_tree(self.root_parent()):
228 if obj is self or not obj.name:
229 continue
230 # Step 1: Find any requirements self may need.
231 if self.name.startswith(posixpath.join(obj.name, '')):
232 self.requirements.add(obj.name)
233 # Step 2: Find any requirements self may impose.
234 if obj.name.startswith(posixpath.join(self.name, '')):
235 obj.requirements.add(self.name)
236
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000237 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000238 """Resolves the parsed url from url.
239
240 Manages From() keyword accordingly. Do not touch self.parsed_url nor
241 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000242 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000243 overriden_url = self.get_custom_deps(self.name, url)
244 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000245 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000246 overriden_url))
247 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000248 elif isinstance(url, self.FromImpl):
249 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000250 if not ref:
251 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
252 url.module_name, ref))
253 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000254 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000255 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000256 # Make sure the referenced dependency DEPS file is loaded and file the
257 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000258 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000259 found_dep = None
260 for d in ref.dependencies:
261 if d.name == sub_target:
262 found_dep = d
263 break
264 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000265 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000266 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
267 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000268 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000269 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000270 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000271 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000272 elif isinstance(url, basestring):
273 parsed_url = urlparse.urlparse(url)
274 if not parsed_url[0]:
275 # A relative url. Fetch the real base.
276 path = parsed_url[2]
277 if not path.startswith('/'):
278 raise gclient_utils.Error(
279 'relative DEPS entry \'%s\' must begin with a slash' % url)
280 # Create a scm just to query the full url.
281 parent_url = self.parent.parsed_url
282 if isinstance(parent_url, self.FileImpl):
283 parent_url = parent_url.file_location
284 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000285 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000286 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000287 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000288 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000289 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000290 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000291 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000292 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000293 return parsed_url
294 elif url is None:
295 return None
296 else:
297 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000298
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000299 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000300 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000301 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000302 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000303 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000304 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000305 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000306 # One thing is unintuitive, vars= {} must happen before Var() use.
307 local_scope = {}
308 var = self.VarImpl(self.custom_vars, local_scope)
309 global_scope = {
310 'File': self.FileImpl,
311 'From': self.FromImpl,
312 'Var': var.Lookup,
313 'deps_os': {},
314 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000315 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
316 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000317 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
318 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000319 else:
320 deps_content = gclient_utils.FileRead(filepath)
321 logging.debug(deps_content)
322 # Eval the content.
323 try:
324 exec(deps_content, global_scope, local_scope)
325 except SyntaxError, e:
326 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000327 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000328 # load os specific dependencies if defined. these dependencies may
329 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000330 if 'deps_os' in local_scope:
331 for deps_os_key in self.enforced_os():
332 os_deps = local_scope['deps_os'].get(deps_os_key, {})
333 if len(self.enforced_os()) > 1:
334 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000335 # platform, so we collect the broadest set of dependencies
336 # available. We may end up with the wrong revision of something for
337 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000338 deps.update([x for x in os_deps.items() if not x[0] in deps])
339 else:
340 deps.update(os_deps)
341
maruel@chromium.org271375b2010-06-23 19:17:38 +0000342 self.deps_hooks.extend(local_scope.get('hooks', []))
343
344 # If a line is in custom_deps, but not in the solution, we want to append
345 # this line to the solution.
346 for d in self.custom_deps:
347 if d not in deps:
348 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349
350 # If use_relative_paths is set in the DEPS file, regenerate
351 # the dictionary using paths relative to the directory containing
352 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000353 use_relative_paths = local_scope.get('use_relative_paths', False)
354 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000355 rel_deps = {}
356 for d, url in deps.items():
357 # normpath is required to allow DEPS to use .. in their
358 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000359 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
360 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000361
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 # Convert the deps into real Dependency.
363 for name, url in deps.iteritems():
364 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000365 raise gclient_utils.Error(
366 'The same name "%s" appears multiple times in the deps section' %
367 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000368 should_process = self.recursion_limit() > 0 and self.should_process
369 if should_process:
370 tree = dict((d.name, d) for d in self.tree(False))
371 if name in tree:
372 if url == tree[name].url:
373 logging.info('Won\'t process duplicate dependency %s' % tree[name])
374 # In theory we could keep it as a shadow of the other one. In
375 # practice, simply ignore it.
376 #should_process = False
377 continue
378 else:
379 raise gclient_utils.Error(
380 'Dependency %s specified more than once:\n %s\nvs\n %s' %
381 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000382 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000383 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000384 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000385
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000386 # Arguments number differs from overridden method
387 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000388 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000389 """Runs 'command' before parsing the DEPS in case it's a initial checkout
390 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000391
392 def maybeGetParentRevision(options):
393 """If we are performing an update and --transitive is set, set the
394 revision to the parent's revision. If we have an explicit revision
395 do nothing."""
396 if command == 'update' and options.transitive and not options.revision:
397 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
398 if not revision:
399 options.revision = revision_overrides.get(self.parent.name)
400 if options.verbose and options.revision:
401 print("Using parent's revision date: %s" % options.revision)
402 # If the parent has a revision override, then it must have been
403 # converted to date format.
404 assert (not options.revision or
405 gclient_utils.IsDateRevision(options.revision))
406
407 def maybeConvertToDateRevision(options):
408 """If we are performing an update and --transitive is set, convert the
409 revision to a date-revision (if necessary). Instead of having
410 -r 101 replace the revision with the time stamp of 101 (e.g.
411 "{2011-18-04}").
412 This way dependencies are upgraded to the revision they had at the
413 check-in of revision 101."""
414 if (command == 'update' and
415 options.transitive and
416 options.revision and
417 not gclient_utils.IsDateRevision(options.revision)):
418 revision_date = scm.GetRevisionDate(options.revision)
419 revision = gclient_utils.MakeDateRevision(revision_date)
420 if options.verbose:
421 print("Updating revision override from %s to %s." %
422 (options.revision, revision))
423 revision_overrides[self.name] = revision
424
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000425 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000426 if not self.should_process:
427 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000428 # When running runhooks, there's no need to consult the SCM.
429 # All known hooks are expected to run unconditionally regardless of working
430 # copy state, so skip the SCM status check.
431 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000432 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000433 if run_scm and self.parsed_url:
434 if isinstance(self.parsed_url, self.FileImpl):
435 # Special support for single-file checkout.
436 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
437 options.revision = self.parsed_url.GetRevision()
438 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
439 self.root_dir(),
440 self.name)
441 scm.RunCommand('updatesingle', options,
442 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000443 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000444 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000445 # Create a shallow copy to mutate revision.
446 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000447 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000448 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000449 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000450 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000451 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000452 self._file_list = [os.path.join(self.name, f.strip())
453 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000454 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000455 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000457 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000458
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000459 # Parse the dependencies of this dependency.
460 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000461 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000462
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000463 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000464 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000465 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000466 assert self.hooks_ran == False
467 if not self.should_process or self.recursion_limit() <= 0:
468 # Don't run the hook when it is above recursion_limit.
469 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000470 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000472 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 # TODO(maruel): If the user is using git or git-svn, then we don't know
474 # what files have changed so we always run all hooks. It'd be nice to fix
475 # that.
476 if (options.force or
477 isinstance(self.parsed_url, self.FileImpl) or
478 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
479 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
480 for hook_dict in self.deps_hooks:
481 self._RunHookAction(hook_dict, [])
482 else:
483 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
484 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000485 file_list = self.file_list()
486 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000487 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000488 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000489 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000490
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000491 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000492 file_list[i].lower()])
493 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000494
495 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000496 while (file_list[i].startswith('\\') or
497 file_list[i].startswith('/')):
498 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000499
500 # Run hooks on the basis of whether the files from the gclient operation
501 # match each hook's pattern.
502 for hook_dict in self.deps_hooks:
503 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000504 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505 if matching_file_list:
506 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000507 for s in self.dependencies:
508 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000509
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000510 def _RunHookAction(self, hook_dict, matching_file_list):
511 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000512 # A single DEPS file can specify multiple hooks so this function can be
513 # called multiple times on a single Dependency.
514 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000515 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000516 logging.debug(hook_dict)
517 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000518 command = hook_dict['action'][:]
519 if command[0] == 'python':
520 # If the hook specified "python" as the first item, the action is a
521 # Python script. Run it by starting a new copy of the same
522 # interpreter.
523 command[0] = sys.executable
524
525 if '$matching_files' in command:
526 splice_index = command.index('$matching_files')
527 command[splice_index:splice_index + 1] = matching_file_list
528
maruel@chromium.org17d01792010-09-01 18:07:10 +0000529 try:
530 gclient_utils.CheckCallAndFilterAndHeader(
531 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000532 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000533 # Use a discrete exit status code of 2 to indicate that a hook action
534 # failed. Users of this script may wish to treat hook action failures
535 # differently from VC failures.
536 print >> sys.stderr, 'Error: %s' % str(e)
537 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000538
maruel@chromium.org271375b2010-06-23 19:17:38 +0000539 def root_dir(self):
540 return self.parent.root_dir()
541
542 def enforced_os(self):
543 return self.parent.enforced_os()
544
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000545 def recursion_limit(self):
546 return self.parent.recursion_limit() - 1
547
maruel@chromium.org0d812442010-08-10 12:41:08 +0000548 def tree(self, include_all):
549 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000550
maruel@chromium.org0d812442010-08-10 12:41:08 +0000551 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000552 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000553 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000554 for d in self.dependencies:
555 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000556 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000557 for d in self.dependencies:
558 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000559 return result
560
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000561 def get_custom_deps(self, name, url):
562 """Returns a custom deps if applicable."""
563 if self.parent:
564 url = self.parent.get_custom_deps(name, url)
565 # None is a valid return value to disable a dependency.
566 return self.custom_deps.get(name, url)
567
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000568 def file_list(self):
569 result = self._file_list[:]
570 for d in self.dependencies:
571 result.extend(d.file_list())
572 return result
573
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000574 def __str__(self):
575 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000576 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000577 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
578 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000579 # 'deps_file'
580 if self.__dict__[i]:
581 out.append('%s: %s' % (i, self.__dict__[i]))
582
583 for d in self.dependencies:
584 out.extend([' ' + x for x in str(d).splitlines()])
585 out.append('')
586 return '\n'.join(out)
587
588 def __repr__(self):
589 return '%s: %s' % (self.name, self.url)
590
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000591 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000592 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000593 out = '%s(%s)' % (self.name, self.url)
594 i = self.parent
595 while i and i.name:
596 out = '%s(%s) -> %s' % (i.name, i.url, out)
597 i = i.parent
598 return out
599
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000600 def root_parent(self):
601 """Returns the root object, normally a GClient object."""
602 d = self
603 while d.parent:
604 d = d.parent
605 return d
606
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000607
608class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000609 """Object that represent a gclient checkout. A tree of Dependency(), one per
610 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000611
612 DEPS_OS_CHOICES = {
613 "win32": "win",
614 "win": "win",
615 "cygwin": "win",
616 "darwin": "mac",
617 "mac": "mac",
618 "unix": "unix",
619 "linux": "unix",
620 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000621 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000622 }
623
624 DEFAULT_CLIENT_FILE_TEXT = ("""\
625solutions = [
626 { "name" : "%(solution_name)s",
627 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000628 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000629 "custom_deps" : {
630 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000631 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000632 },
633]
634""")
635
636 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
637 { "name" : "%(solution_name)s",
638 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000639 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000640 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000641%(solution_deps)s },
642 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643 },
644""")
645
646 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
647# Snapshot generated with gclient revinfo --snapshot
648solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000649%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650""")
651
652 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000653 # Do not change previous behavior. Only solution level and immediate DEPS
654 # are processed.
655 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000656 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
657 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000658 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000659 if options.deps_os:
660 enforced_os = options.deps_os.split(',')
661 else:
662 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
663 if 'all' in enforced_os:
664 enforced_os = self.DEPS_OS_CHOICES.itervalues()
665 self._enforced_os = list(set(enforced_os))
666 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000667 self.config_content = None
668
669 def SetConfig(self, content):
670 assert self.dependencies == []
671 config_dict = {}
672 self.config_content = content
673 try:
674 exec(content, config_dict)
675 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000676 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000677 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000678 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000679 tree = dict((d.name, d) for d in self.tree(False))
680 if s['name'] in tree:
681 raise gclient_utils.Error(
682 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000683 self.dependencies.append(Dependency(
684 self, s['name'], s['url'],
685 s.get('safesync_url', None),
686 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000687 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000688 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000689 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000690 except KeyError:
691 raise gclient_utils.Error('Invalid .gclient file. Solution is '
692 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000693 # .gclient can have hooks.
694 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000695 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000696
697 def SaveConfig(self):
698 gclient_utils.FileWrite(os.path.join(self.root_dir(),
699 self._options.config_filename),
700 self.config_content)
701
702 @staticmethod
703 def LoadCurrentConfig(options):
704 """Searches for and loads a .gclient file relative to the current working
705 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000706 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000707 if not path:
708 return None
709 client = GClient(path, options)
710 client.SetConfig(gclient_utils.FileRead(
711 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000712 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000713
nsylvain@google.comefc80932011-05-31 21:27:56 +0000714 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
715 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000716 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
717 'solution_name': solution_name,
718 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000719 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000720 'safesync_url' : safesync_url,
721 })
722
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000723 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000724 """Creates a .gclient_entries file to record the list of unique checkouts.
725
726 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000727 """
728 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
729 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000730 result = 'entries = {\n'
731 for entry in self.tree(False):
732 # Skip over File() dependencies as we can't version them.
733 if not isinstance(entry.parsed_url, self.FileImpl):
734 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
735 pprint.pformat(entry.parsed_url))
736 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000737 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 logging.info(result)
739 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000740
741 def _ReadEntries(self):
742 """Read the .gclient_entries file for the given client.
743
744 Returns:
745 A sequence of solution names, which will be empty if there is the
746 entries file hasn't been created yet.
747 """
748 scope = {}
749 filename = os.path.join(self.root_dir(), self._options.entries_filename)
750 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000751 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000752 try:
753 exec(gclient_utils.FileRead(filename), scope)
754 except SyntaxError, e:
755 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000756 return scope['entries']
757
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000758 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000759 """Checks for revision overrides."""
760 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000761 if self._options.head:
762 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000763 # Do not check safesync_url if one or more --revision flag is specified.
764 if not self._options.revisions:
765 for s in self.dependencies:
766 if not s.safesync_url:
767 continue
768 handle = urllib.urlopen(s.safesync_url)
769 rev = handle.read().strip()
770 handle.close()
771 if len(rev):
772 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000773 if not self._options.revisions:
774 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000775 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000776 index = 0
777 for revision in self._options.revisions:
778 if not '@' in revision:
779 # Support for --revision 123
780 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000781 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000782 if not sol in solutions_names:
783 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
784 print >> sys.stderr, ('Please fix your script, having invalid '
785 '--revision flags will soon considered an error.')
786 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000787 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000788 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000789 return revision_overrides
790
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000791 def RunOnDeps(self, command, args):
792 """Runs a command on each dependency in a client and its dependencies.
793
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000794 Args:
795 command: The command to use (e.g., 'status' or 'diff')
796 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000797 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000798 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000799 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000800 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000801 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000802 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000803 if (command in ('update', 'revert') and sys.stdout.isatty() and not
804 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000805 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000806 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000807 for s in self.dependencies:
808 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000809 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000810
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000811 # Once all the dependencies have been processed, it's now safe to run the
812 # hooks.
813 if not self._options.nohooks:
814 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000815
816 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000817 # Notify the user if there is an orphaned entry in their working copy.
818 # Only delete the directory if there are no changes in it, and
819 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000820 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000821 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000822 if not prev_url:
823 # entry must have been overridden via .gclient custom_deps
824 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000825 # Fix path separator on Windows.
826 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000827 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000828 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000829 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000830 file_list = []
831 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
832 scm.status(self._options, [], file_list)
833 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000834 if (not self._options.delete_unversioned_trees or
835 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000836 # There are modified files in this entry. Keep warning until
837 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000838 print(('\nWARNING: \'%s\' is no longer part of this client. '
839 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000840 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000841 else:
842 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000843 print('\n________ deleting \'%s\' in \'%s\'' % (
844 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000845 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000846 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000847 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000848 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000849
850 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000851 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000852 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000853 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000854 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000855 for s in self.dependencies:
856 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000857 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000858
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000859 def GetURLAndRev(dep):
860 """Returns the revision-qualified SCM url for a Dependency."""
861 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000862 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000863 if isinstance(dep.parsed_url, self.FileImpl):
864 original_url = dep.parsed_url.file_location
865 else:
866 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000867 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000868 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000869 if not os.path.isdir(scm.checkout_path):
870 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000871 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000872
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000873 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000874 new_gclient = ''
875 # First level at .gclient
876 for d in self.dependencies:
877 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000878 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000879 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000880 for d in dep.dependencies:
881 entries[d.name] = GetURLAndRev(d)
882 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 GrabDeps(d)
884 custom_deps = []
885 for k in sorted(entries.keys()):
886 if entries[k]:
887 # Quotes aren't escaped...
888 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
889 else:
890 custom_deps.append(' \"%s\": None,\n' % k)
891 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
892 'solution_name': d.name,
893 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000894 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000895 'safesync_url' : d.safesync_url or '',
896 'solution_deps': ''.join(custom_deps),
897 }
898 # Print the snapshot configuration file
899 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000900 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000901 entries = {}
902 for d in self.tree(False):
903 if self._options.actual:
904 entries[d.name] = GetURLAndRev(d)
905 else:
906 entries[d.name] = d.parsed_url
907 keys = sorted(entries.keys())
908 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000909 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000910 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000911
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000912 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000913 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000914 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000915
maruel@chromium.org75a59272010-06-11 22:34:03 +0000916 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000917 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000918 return self._root_dir
919
maruel@chromium.org271375b2010-06-23 19:17:38 +0000920 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000921 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000922 return self._enforced_os
923
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000924 def recursion_limit(self):
925 """How recursive can each dependencies in DEPS file can load DEPS file."""
926 return self._recursion_limit
927
maruel@chromium.org0d812442010-08-10 12:41:08 +0000928 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000929 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000930 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000931
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000932
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000933#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000934
935
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000936def CMDcleanup(parser, args):
937 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000938
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000939Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000940"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000941 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
942 help='override deps for the specified (comma-separated) '
943 'platform(s); \'all\' will process all deps_os '
944 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000945 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000946 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000947 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000948 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949 if options.verbose:
950 # Print out the .gclient file. This is longer than if we just printed the
951 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000952 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000953 return client.RunOnDeps('cleanup', args)
954
955
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000956@attr('usage', '[command] [args ...]')
957def CMDrecurse(parser, args):
958 """Operates on all the entries.
959
960 Runs a shell command on all entries.
961 """
962 # Stop parsing at the first non-arg so that these go through to the command
963 parser.disable_interspersed_args()
964 parser.add_option('-s', '--scm', action='append', default=[],
965 help='choose scm types to operate upon')
966 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000967 if not args:
968 print >> sys.stderr, 'Need to supply a command!'
969 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000970 root_and_entries = gclient_utils.GetGClientRootAndEntries()
971 if not root_and_entries:
972 print >> sys.stderr, (
973 'You need to run gclient sync at least once to use \'recurse\'.\n'
974 'This is because .gclient_entries needs to exist and be up to date.')
975 return 1
976 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000977 scm_set = set()
978 for scm in options.scm:
979 scm_set.update(scm.split(','))
980
981 # Pass in the SCM type as an env variable
982 env = os.environ.copy()
983
984 for path, url in entries.iteritems():
985 scm = gclient_scm.GetScmName(url)
986 if scm_set and scm not in scm_set:
987 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000988 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000989 if scm:
990 env['GCLIENT_SCM'] = scm
991 if url:
992 env['GCLIENT_URL'] = url
993 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
994 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000995
996
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000997@attr('usage', '[url] [safesync url]')
998def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000999 """Create a .gclient file in the current directory.
1000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001001This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001002top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001003modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001004provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001005URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001006"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001007 parser.add_option('--spec',
1008 help='create a gclient file containing the provided '
1009 'string. Due to Cygwin/Python brokenness, it '
1010 'probably can\'t contain any newlines.')
1011 parser.add_option('--name',
1012 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001013 parser.add_option('--deps-file', default='DEPS',
1014 help='overrides the default name for the DEPS file for the'
1015 'main solutions and all sub-dependencies')
1016 parser.add_option('--git-deps', action='store_true',
1017 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001018 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001019 if ((options.spec and args) or len(args) > 2 or
1020 (not options.spec and not args)):
1021 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1022
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001023 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024 if options.spec:
1025 client.SetConfig(options.spec)
1026 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001027 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001028 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001029 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001030 if name.endswith('.git'):
1031 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001032 else:
1033 # specify an alternate relpath for the given URL.
1034 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001035 deps_file = options.deps_file
1036 if options.git_deps:
1037 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001038 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 if len(args) > 1:
1040 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001041 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001043 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044
1045
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001046@attr('epilog', """Example:
1047 gclient pack > patch.txt
1048 generate simple patch for configured client and dependences
1049""")
1050def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001051 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001052
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001053Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001054dependencies, and performs minimal postprocessing of the output. The
1055resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001056checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001057"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001058 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1059 help='override deps for the specified (comma-separated) '
1060 'platform(s); \'all\' will process all deps_os '
1061 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001062 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001063 client = GClient.LoadCurrentConfig(options)
1064 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001065 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001066 if options.verbose:
1067 # Print out the .gclient file. This is longer than if we just printed the
1068 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001069 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001070 return client.RunOnDeps('pack', args)
1071
1072
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073def CMDstatus(parser, args):
1074 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001075 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1076 help='override deps for the specified (comma-separated) '
1077 'platform(s); \'all\' will process all deps_os '
1078 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001079 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001080 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001082 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001083 if options.verbose:
1084 # Print out the .gclient file. This is longer than if we just printed the
1085 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001086 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001087 return client.RunOnDeps('status', args)
1088
1089
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001090@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001091 gclient sync
1092 update files from SCM according to current configuration,
1093 *for modules which have changed since last update or sync*
1094 gclient sync --force
1095 update files from SCM according to current configuration, for
1096 all modules (useful for recovering files deleted from local copy)
1097 gclient sync --revision src@31000
1098 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001099""")
1100def CMDsync(parser, args):
1101 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001102 parser.add_option('-f', '--force', action='store_true',
1103 help='force update even for unchanged modules')
1104 parser.add_option('-n', '--nohooks', action='store_true',
1105 help='don\'t run hooks after the update is complete')
1106 parser.add_option('-r', '--revision', action='append',
1107 dest='revisions', metavar='REV', default=[],
1108 help='Enforces revision/hash for the solutions with the '
1109 'format src@rev. The src@ part is optional and can be '
1110 'skipped. -r can be used multiple times when .gclient '
1111 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001112 'if the src@ part is skipped. Note that specifying '
1113 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001114 parser.add_option('-t', '--transitive', action='store_true',
1115 help='When a revision is specified (in the DEPS file or '
1116 'with the command-line flag), transitively update '
1117 'the dependencies to the date of the given revision. '
1118 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001119 parser.add_option('-H', '--head', action='store_true',
1120 help='skips any safesync_urls specified in '
1121 'configured solutions and sync to head instead')
1122 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001123 help='delete any dependency that have been removed from '
1124 'last sync as long as there is no local modification. '
1125 'Coupled with --force, it will remove them even with '
1126 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001127 parser.add_option('-R', '--reset', action='store_true',
1128 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001129 parser.add_option('-M', '--merge', action='store_true',
1130 help='merge upstream changes instead of trying to '
1131 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001132 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1133 help='override deps for the specified (comma-separated) '
1134 'platform(s); \'all\' will process all deps_os '
1135 'references')
1136 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1137 help='Skip svn up whenever possible by requesting '
1138 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001139 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001140 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001141
1142 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144
maruel@chromium.org307d1792010-05-31 20:03:13 +00001145 if options.revisions and options.head:
1146 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001148
1149 if options.verbose:
1150 # Print out the .gclient file. This is longer than if we just printed the
1151 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001152 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153 return client.RunOnDeps('update', args)
1154
1155
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001156def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001157 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001158 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001160def CMDdiff(parser, args):
1161 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001162 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1163 help='override deps for the specified (comma-separated) '
1164 'platform(s); \'all\' will process all deps_os '
1165 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001167 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001169 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170 if options.verbose:
1171 # Print out the .gclient file. This is longer than if we just printed the
1172 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001173 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001174 return client.RunOnDeps('diff', args)
1175
1176
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001178 """Revert all modifications in every dependencies.
1179
1180 That's the nuclear option to get back to a 'clean' state. It removes anything
1181 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001182 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1183 help='override deps for the specified (comma-separated) '
1184 'platform(s); \'all\' will process all deps_os '
1185 'references')
1186 parser.add_option('-n', '--nohooks', action='store_true',
1187 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001188 (options, args) = parser.parse_args(args)
1189 # --force is implied.
1190 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001191 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001192 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001193 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001194 return client.RunOnDeps('revert', args)
1195
1196
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001197def CMDrunhooks(parser, args):
1198 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001199 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1200 help='override deps for the specified (comma-separated) '
1201 'platform(s); \'all\' will process all deps_os '
1202 'references')
1203 parser.add_option('-f', '--force', action='store_true', default=True,
1204 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001205 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001206 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001207 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001208 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001209 if options.verbose:
1210 # Print out the .gclient file. This is longer than if we just printed the
1211 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001212 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001213 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001214 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001215 return client.RunOnDeps('runhooks', args)
1216
1217
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001218def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001219 """Output revision info mapping for the client and its dependencies.
1220
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001221 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001222 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001223 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1224 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001225 commit can change.
1226 """
1227 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1228 help='override deps for the specified (comma-separated) '
1229 'platform(s); \'all\' will process all deps_os '
1230 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001231 parser.add_option('-a', '--actual', action='store_true',
1232 help='gets the actual checked out revisions instead of the '
1233 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001234 parser.add_option('-s', '--snapshot', action='store_true',
1235 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001236 'version of all repositories to reproduce the tree, '
1237 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001238 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001239 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001240 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001241 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001242 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001243 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244
1245
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001246def Command(name):
1247 return getattr(sys.modules[__name__], 'CMD' + name, None)
1248
1249
1250def CMDhelp(parser, args):
1251 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001252 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001253 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001254 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001255 parser.print_help()
1256 return 0
1257
1258
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001259def GenUsage(parser, command):
1260 """Modify an OptParse object with the function's documentation."""
1261 obj = Command(command)
1262 if command == 'help':
1263 command = '<command>'
1264 # OptParser.description prefer nicely non-formatted strings.
1265 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1266 usage = getattr(obj, 'usage', '')
1267 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1268 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001269
1270
maruel@chromium.org0895b752011-08-26 20:40:33 +00001271def Parser():
1272 """Returns the default parser."""
1273 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org3187f702011-09-06 17:42:48 +00001274 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001275 help='Specify how many SCM commands can run in parallel; '
1276 'default=%default')
1277 parser.add_option('-v', '--verbose', action='count', default=0,
1278 help='Produces additional output for diagnostics. Can be '
1279 'used up to three times for more logging info.')
1280 parser.add_option('--gclientfile', dest='config_filename',
1281 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1282 help='Specify an alternate %default file')
1283 # Integrate standard options processing.
1284 old_parser = parser.parse_args
1285 def Parse(args):
1286 (options, args) = old_parser(args)
1287 level = None
1288 if options.verbose == 2:
1289 level = logging.INFO
1290 elif options.verbose > 2:
1291 level = logging.DEBUG
1292 logging.basicConfig(level=level,
1293 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1294 options.entries_filename = options.config_filename + '_entries'
1295 if options.jobs < 1:
1296 parser.error('--jobs must be 1 or higher')
1297
1298 # These hacks need to die.
1299 if not hasattr(options, 'revisions'):
1300 # GClient.RunOnDeps expects it even if not applicable.
1301 options.revisions = []
1302 if not hasattr(options, 'head'):
1303 options.head = None
1304 if not hasattr(options, 'nohooks'):
1305 options.nohooks = True
1306 if not hasattr(options, 'deps_os'):
1307 options.deps_os = None
1308 if not hasattr(options, 'manually_grab_svn_rev'):
1309 options.manually_grab_svn_rev = None
1310 if not hasattr(options, 'force'):
1311 options.force = None
1312 return (options, args)
1313 parser.parse_args = Parse
1314 # We don't want wordwrapping in epilog (usually examples)
1315 parser.format_epilog = lambda _: parser.epilog or ''
1316 return parser
1317
1318
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001319def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001320 """Doesn't parse the arguments here, just find the right subcommand to
1321 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001322 if sys.hexversion < 0x02050000:
1323 print >> sys.stderr, (
1324 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001325 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001326 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1327 # operations. Python as a strong tendency to buffer sys.stdout.
1328 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001329 # Make stdout annotated with the thread ids.
1330 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001331 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001332 # Unused variable 'usage'
1333 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001334 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1335 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1336 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001337 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001338 if argv:
1339 command = Command(argv[0])
1340 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001341 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001342 GenUsage(parser, argv[0])
1343 return command(parser, argv[1:])
1344 # Not a known command. Default to help.
1345 GenUsage(parser, 'help')
1346 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001347 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001348 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001349 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001350
1351
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001352if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001353 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001354 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001355
1356# vim: ts=2:sw=2:tw=80:et: