blob: ef77cd4b73cc13fe352bc5d7afad2da9fdb17388 [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
maruel@chromium.org98023df2011-09-07 18:44:47 +0000175 # Post process the url to remove trailing slashes.
176 if isinstance(self.url, basestring):
177 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
178 # it to proto://host/path@rev.
179 if self.url.count('@') > 1:
180 raise gclient_utils.Error('Invalid url "%s"' % self.url)
181 self.url = self.url.replace('/@', '@')
182
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000183 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000184
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000185 # Sanity checks
186 if not self.name and self.parent:
187 raise gclient_utils.Error('Dependency without name')
188 if not isinstance(self.url,
189 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
190 raise gclient_utils.Error('dependency url must be either a string, None, '
191 'File() or From() instead of %s' %
192 self.url.__class__.__name__)
193 if '/' in self.deps_file or '\\' in self.deps_file:
194 raise gclient_utils.Error('deps_file name must not be a path, just a '
195 'filename. %s' % self.deps_file)
196
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000197 def _FindDependencies(self):
198 """Setup self.requirements and find any other dependency who would have self
199 as a requirement.
200 """
201 # self.parent is implicitly a requirement. This will be recursive by
202 # definition.
203 if self.parent and self.parent.name:
204 self.requirements.add(self.parent.name)
205
206 # For a tree with at least 2 levels*, the leaf node needs to depend
207 # on the level higher up in an orderly way.
208 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
209 # thus unsorted, while the .gclient format is a list thus sorted.
210 #
211 # * _recursion_limit is hard coded 2 and there is no hope to change this
212 # value.
213 #
214 # Interestingly enough, the following condition only works in the case we
215 # want: self is a 2nd level node. 3nd level node wouldn't need this since
216 # they already have their parent as a requirement.
217 if self.parent in self.root_parent().dependencies:
218 root_deps = self.root_parent().dependencies
219 for i in range(0, root_deps.index(self.parent)):
220 value = root_deps[i]
221 if value.name:
222 self.requirements.add(value.name)
223
224 if isinstance(self.url, self.FromImpl):
225 self.requirements.add(self.url.module_name)
226
227 if self.name:
228 def yield_full_tree(root):
229 """Depth-first recursion."""
230 yield root
231 for i in root.dependencies:
232 for j in yield_full_tree(i):
233 yield j
234
235 for obj in yield_full_tree(self.root_parent()):
236 if obj is self or not obj.name:
237 continue
238 # Step 1: Find any requirements self may need.
239 if self.name.startswith(posixpath.join(obj.name, '')):
240 self.requirements.add(obj.name)
241 # Step 2: Find any requirements self may impose.
242 if obj.name.startswith(posixpath.join(self.name, '')):
243 obj.requirements.add(self.name)
244
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000245 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000246 """Resolves the parsed url from url.
247
248 Manages From() keyword accordingly. Do not touch self.parsed_url nor
249 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000250 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000251 overriden_url = self.get_custom_deps(self.name, url)
252 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000253 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000254 overriden_url))
255 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000256 elif isinstance(url, self.FromImpl):
257 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 if not ref:
259 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
260 url.module_name, ref))
261 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000262 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000263 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000264 # Make sure the referenced dependency DEPS file is loaded and file the
265 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000266 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000267 found_dep = None
268 for d in ref.dependencies:
269 if d.name == sub_target:
270 found_dep = d
271 break
272 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000273 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000274 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
275 sub_target, ref.name, self.name, self.parent.name,
276 str(self.root_parent())))
277
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000278 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000279 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000280 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000281 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000282 elif isinstance(url, basestring):
283 parsed_url = urlparse.urlparse(url)
284 if not parsed_url[0]:
285 # A relative url. Fetch the real base.
286 path = parsed_url[2]
287 if not path.startswith('/'):
288 raise gclient_utils.Error(
289 'relative DEPS entry \'%s\' must begin with a slash' % url)
290 # Create a scm just to query the full url.
291 parent_url = self.parent.parsed_url
292 if isinstance(parent_url, self.FileImpl):
293 parent_url = parent_url.file_location
294 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000295 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000296 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000297 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000298 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000299 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000300 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000301 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000302 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000303 return parsed_url
304 elif url is None:
305 return None
306 else:
307 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000308
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000309 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000310 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000311 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000313 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000314 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000315 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000316 # One thing is unintuitive, vars= {} must happen before Var() use.
317 local_scope = {}
318 var = self.VarImpl(self.custom_vars, local_scope)
319 global_scope = {
320 'File': self.FileImpl,
321 'From': self.FromImpl,
322 'Var': var.Lookup,
323 'deps_os': {},
324 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000325 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
326 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000327 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
328 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000329 else:
330 deps_content = gclient_utils.FileRead(filepath)
331 logging.debug(deps_content)
332 # Eval the content.
333 try:
334 exec(deps_content, global_scope, local_scope)
335 except SyntaxError, e:
336 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000337 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000338 # load os specific dependencies if defined. these dependencies may
339 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000340 if 'deps_os' in local_scope:
341 for deps_os_key in self.enforced_os():
342 os_deps = local_scope['deps_os'].get(deps_os_key, {})
343 if len(self.enforced_os()) > 1:
344 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000345 # platform, so we collect the broadest set of dependencies
346 # available. We may end up with the wrong revision of something for
347 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000348 deps.update([x for x in os_deps.items() if not x[0] in deps])
349 else:
350 deps.update(os_deps)
351
maruel@chromium.org271375b2010-06-23 19:17:38 +0000352 self.deps_hooks.extend(local_scope.get('hooks', []))
353
354 # If a line is in custom_deps, but not in the solution, we want to append
355 # this line to the solution.
356 for d in self.custom_deps:
357 if d not in deps:
358 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000359
360 # If use_relative_paths is set in the DEPS file, regenerate
361 # the dictionary using paths relative to the directory containing
362 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000363 use_relative_paths = local_scope.get('use_relative_paths', False)
364 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000365 rel_deps = {}
366 for d, url in deps.items():
367 # normpath is required to allow DEPS to use .. in their
368 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000369 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
370 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000371
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000372 # Convert the deps into real Dependency.
373 for name, url in deps.iteritems():
374 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000375 raise gclient_utils.Error(
376 'The same name "%s" appears multiple times in the deps section' %
377 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000378 should_process = self.recursion_limit() > 0 and self.should_process
379 if should_process:
380 tree = dict((d.name, d) for d in self.tree(False))
381 if name in tree:
382 if url == tree[name].url:
383 logging.info('Won\'t process duplicate dependency %s' % tree[name])
384 # In theory we could keep it as a shadow of the other one. In
385 # practice, simply ignore it.
386 #should_process = False
387 continue
388 else:
389 raise gclient_utils.Error(
390 'Dependency %s specified more than once:\n %s\nvs\n %s' %
391 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000392 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000393 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000394 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000395
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000396 # Arguments number differs from overridden method
397 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000398 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000399 """Runs 'command' before parsing the DEPS in case it's a initial checkout
400 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000401
402 def maybeGetParentRevision(options):
403 """If we are performing an update and --transitive is set, set the
404 revision to the parent's revision. If we have an explicit revision
405 do nothing."""
406 if command == 'update' and options.transitive and not options.revision:
407 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
408 if not revision:
409 options.revision = revision_overrides.get(self.parent.name)
410 if options.verbose and options.revision:
411 print("Using parent's revision date: %s" % options.revision)
412 # If the parent has a revision override, then it must have been
413 # converted to date format.
414 assert (not options.revision or
415 gclient_utils.IsDateRevision(options.revision))
416
417 def maybeConvertToDateRevision(options):
418 """If we are performing an update and --transitive is set, convert the
419 revision to a date-revision (if necessary). Instead of having
420 -r 101 replace the revision with the time stamp of 101 (e.g.
421 "{2011-18-04}").
422 This way dependencies are upgraded to the revision they had at the
423 check-in of revision 101."""
424 if (command == 'update' and
425 options.transitive and
426 options.revision and
427 not gclient_utils.IsDateRevision(options.revision)):
428 revision_date = scm.GetRevisionDate(options.revision)
429 revision = gclient_utils.MakeDateRevision(revision_date)
430 if options.verbose:
431 print("Updating revision override from %s to %s." %
432 (options.revision, revision))
433 revision_overrides[self.name] = revision
434
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000435 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000436 if not self.should_process:
437 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000438 # When running runhooks, there's no need to consult the SCM.
439 # All known hooks are expected to run unconditionally regardless of working
440 # copy state, so skip the SCM status check.
441 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000442 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000443 if run_scm and self.parsed_url:
444 if isinstance(self.parsed_url, self.FileImpl):
445 # Special support for single-file checkout.
446 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
447 options.revision = self.parsed_url.GetRevision()
448 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
449 self.root_dir(),
450 self.name)
451 scm.RunCommand('updatesingle', options,
452 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000453 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000454 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000455 # Create a shallow copy to mutate revision.
456 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000457 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000458 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000459 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000460 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000461 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000462 self._file_list = [os.path.join(self.name, f.strip())
463 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000464 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000465 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000467 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000468
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469 # Parse the dependencies of this dependency.
470 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000471 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000472
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000474 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000475 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000476 assert self.hooks_ran == False
477 if not self.should_process or self.recursion_limit() <= 0:
478 # Don't run the hook when it is above recursion_limit.
479 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000480 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000481 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000482 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000483 # TODO(maruel): If the user is using git or git-svn, then we don't know
484 # what files have changed so we always run all hooks. It'd be nice to fix
485 # that.
486 if (options.force or
487 isinstance(self.parsed_url, self.FileImpl) or
488 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
489 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
490 for hook_dict in self.deps_hooks:
491 self._RunHookAction(hook_dict, [])
492 else:
493 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
494 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000495 file_list = self.file_list()
496 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000497 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000498 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000499 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000500
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000501 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000502 file_list[i].lower()])
503 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000504
505 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000506 while (file_list[i].startswith('\\') or
507 file_list[i].startswith('/')):
508 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509
510 # Run hooks on the basis of whether the files from the gclient operation
511 # match each hook's pattern.
512 for hook_dict in self.deps_hooks:
513 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000514 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000515 if matching_file_list:
516 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000517 for s in self.dependencies:
518 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000520 def _RunHookAction(self, hook_dict, matching_file_list):
521 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000522 # A single DEPS file can specify multiple hooks so this function can be
523 # called multiple times on a single Dependency.
524 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000525 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000526 logging.debug(hook_dict)
527 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000528 command = hook_dict['action'][:]
529 if command[0] == 'python':
530 # If the hook specified "python" as the first item, the action is a
531 # Python script. Run it by starting a new copy of the same
532 # interpreter.
533 command[0] = sys.executable
534
535 if '$matching_files' in command:
536 splice_index = command.index('$matching_files')
537 command[splice_index:splice_index + 1] = matching_file_list
538
maruel@chromium.org17d01792010-09-01 18:07:10 +0000539 try:
540 gclient_utils.CheckCallAndFilterAndHeader(
541 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000542 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000543 # Use a discrete exit status code of 2 to indicate that a hook action
544 # failed. Users of this script may wish to treat hook action failures
545 # differently from VC failures.
546 print >> sys.stderr, 'Error: %s' % str(e)
547 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000548
maruel@chromium.org271375b2010-06-23 19:17:38 +0000549 def root_dir(self):
550 return self.parent.root_dir()
551
552 def enforced_os(self):
553 return self.parent.enforced_os()
554
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000555 def recursion_limit(self):
556 return self.parent.recursion_limit() - 1
557
maruel@chromium.org0d812442010-08-10 12:41:08 +0000558 def tree(self, include_all):
559 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000560
maruel@chromium.org0d812442010-08-10 12:41:08 +0000561 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000562 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000563 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000564 for d in self.dependencies:
565 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000566 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000567 for d in self.dependencies:
568 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000569 return result
570
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000571 def get_custom_deps(self, name, url):
572 """Returns a custom deps if applicable."""
573 if self.parent:
574 url = self.parent.get_custom_deps(name, url)
575 # None is a valid return value to disable a dependency.
576 return self.custom_deps.get(name, url)
577
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000578 def file_list(self):
579 result = self._file_list[:]
580 for d in self.dependencies:
581 result.extend(d.file_list())
582 return result
583
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000584 def __str__(self):
585 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000586 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000587 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
588 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000589 # 'deps_file'
590 if self.__dict__[i]:
591 out.append('%s: %s' % (i, self.__dict__[i]))
592
593 for d in self.dependencies:
594 out.extend([' ' + x for x in str(d).splitlines()])
595 out.append('')
596 return '\n'.join(out)
597
598 def __repr__(self):
599 return '%s: %s' % (self.name, self.url)
600
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000601 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000602 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000603 out = '%s(%s)' % (self.name, self.url)
604 i = self.parent
605 while i and i.name:
606 out = '%s(%s) -> %s' % (i.name, i.url, out)
607 i = i.parent
608 return out
609
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000610 def root_parent(self):
611 """Returns the root object, normally a GClient object."""
612 d = self
613 while d.parent:
614 d = d.parent
615 return d
616
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000617
618class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000619 """Object that represent a gclient checkout. A tree of Dependency(), one per
620 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000621
622 DEPS_OS_CHOICES = {
623 "win32": "win",
624 "win": "win",
625 "cygwin": "win",
626 "darwin": "mac",
627 "mac": "mac",
628 "unix": "unix",
629 "linux": "unix",
630 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000631 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000632 }
633
634 DEFAULT_CLIENT_FILE_TEXT = ("""\
635solutions = [
636 { "name" : "%(solution_name)s",
637 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000638 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000639 "custom_deps" : {
640 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000641 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000642 },
643]
644""")
645
646 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
647 { "name" : "%(solution_name)s",
648 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000649 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000651%(solution_deps)s },
652 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000653 },
654""")
655
656 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
657# Snapshot generated with gclient revinfo --snapshot
658solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000659%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000660""")
661
662 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000663 # Do not change previous behavior. Only solution level and immediate DEPS
664 # are processed.
665 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000666 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
667 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000668 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000669 if options.deps_os:
670 enforced_os = options.deps_os.split(',')
671 else:
672 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
673 if 'all' in enforced_os:
674 enforced_os = self.DEPS_OS_CHOICES.itervalues()
675 self._enforced_os = list(set(enforced_os))
676 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000677 self.config_content = None
678
679 def SetConfig(self, content):
680 assert self.dependencies == []
681 config_dict = {}
682 self.config_content = content
683 try:
684 exec(content, config_dict)
685 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000686 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000687 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000688 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000689 tree = dict((d.name, d) for d in self.tree(False))
690 if s['name'] in tree:
691 raise gclient_utils.Error(
692 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000693 self.dependencies.append(Dependency(
694 self, s['name'], s['url'],
695 s.get('safesync_url', None),
696 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000697 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000698 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000699 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000700 except KeyError:
701 raise gclient_utils.Error('Invalid .gclient file. Solution is '
702 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000703 # .gclient can have hooks.
704 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000705 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000706
707 def SaveConfig(self):
708 gclient_utils.FileWrite(os.path.join(self.root_dir(),
709 self._options.config_filename),
710 self.config_content)
711
712 @staticmethod
713 def LoadCurrentConfig(options):
714 """Searches for and loads a .gclient file relative to the current working
715 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000716 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000717 if not path:
718 return None
719 client = GClient(path, options)
720 client.SetConfig(gclient_utils.FileRead(
721 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000722 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000723
nsylvain@google.comefc80932011-05-31 21:27:56 +0000724 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
725 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000726 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
727 'solution_name': solution_name,
728 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000729 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000730 'safesync_url' : safesync_url,
731 })
732
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000733 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000734 """Creates a .gclient_entries file to record the list of unique checkouts.
735
736 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000737 """
738 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
739 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000740 result = 'entries = {\n'
741 for entry in self.tree(False):
742 # Skip over File() dependencies as we can't version them.
743 if not isinstance(entry.parsed_url, self.FileImpl):
744 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
745 pprint.pformat(entry.parsed_url))
746 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000747 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000748 logging.info(result)
749 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000750
751 def _ReadEntries(self):
752 """Read the .gclient_entries file for the given client.
753
754 Returns:
755 A sequence of solution names, which will be empty if there is the
756 entries file hasn't been created yet.
757 """
758 scope = {}
759 filename = os.path.join(self.root_dir(), self._options.entries_filename)
760 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000761 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000762 try:
763 exec(gclient_utils.FileRead(filename), scope)
764 except SyntaxError, e:
765 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000766 return scope['entries']
767
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000768 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000769 """Checks for revision overrides."""
770 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000771 if self._options.head:
772 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000773 # Do not check safesync_url if one or more --revision flag is specified.
774 if not self._options.revisions:
775 for s in self.dependencies:
776 if not s.safesync_url:
777 continue
778 handle = urllib.urlopen(s.safesync_url)
779 rev = handle.read().strip()
780 handle.close()
781 if len(rev):
782 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000783 if not self._options.revisions:
784 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000785 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000786 index = 0
787 for revision in self._options.revisions:
788 if not '@' in revision:
789 # Support for --revision 123
790 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000791 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000792 if not sol in solutions_names:
793 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
794 print >> sys.stderr, ('Please fix your script, having invalid '
795 '--revision flags will soon considered an error.')
796 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000797 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000798 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000799 return revision_overrides
800
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000801 def RunOnDeps(self, command, args):
802 """Runs a command on each dependency in a client and its dependencies.
803
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000804 Args:
805 command: The command to use (e.g., 'status' or 'diff')
806 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000807 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000808 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000809 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000810 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000811 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000812 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000813 if (command in ('update', 'revert') and sys.stdout.isatty() and not
814 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000815 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000816 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000817 for s in self.dependencies:
818 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000819 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000820
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000821 # Once all the dependencies have been processed, it's now safe to run the
822 # hooks.
823 if not self._options.nohooks:
824 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825
826 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000827 # Notify the user if there is an orphaned entry in their working copy.
828 # Only delete the directory if there are no changes in it, and
829 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000830 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000831 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000832 if not prev_url:
833 # entry must have been overridden via .gclient custom_deps
834 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000835 # Fix path separator on Windows.
836 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000837 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000838 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000839 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000840 file_list = []
841 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
842 scm.status(self._options, [], file_list)
843 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000844 if (not self._options.delete_unversioned_trees or
845 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000846 # There are modified files in this entry. Keep warning until
847 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000848 print(('\nWARNING: \'%s\' is no longer part of this client. '
849 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000850 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851 else:
852 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000853 print('\n________ deleting \'%s\' in \'%s\'' % (
854 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000855 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000856 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000857 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000858 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000859
860 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000861 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000862 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000863 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000864 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000865 for s in self.dependencies:
866 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000867 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000868
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000869 def GetURLAndRev(dep):
870 """Returns the revision-qualified SCM url for a Dependency."""
871 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000872 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000873 if isinstance(dep.parsed_url, self.FileImpl):
874 original_url = dep.parsed_url.file_location
875 else:
876 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000877 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000878 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000879 if not os.path.isdir(scm.checkout_path):
880 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000881 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000882
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000883 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000884 new_gclient = ''
885 # First level at .gclient
886 for d in self.dependencies:
887 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000888 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000889 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000890 for d in dep.dependencies:
891 entries[d.name] = GetURLAndRev(d)
892 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000893 GrabDeps(d)
894 custom_deps = []
895 for k in sorted(entries.keys()):
896 if entries[k]:
897 # Quotes aren't escaped...
898 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
899 else:
900 custom_deps.append(' \"%s\": None,\n' % k)
901 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
902 'solution_name': d.name,
903 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000904 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 'safesync_url' : d.safesync_url or '',
906 'solution_deps': ''.join(custom_deps),
907 }
908 # Print the snapshot configuration file
909 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000910 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000911 entries = {}
912 for d in self.tree(False):
913 if self._options.actual:
914 entries[d.name] = GetURLAndRev(d)
915 else:
916 entries[d.name] = d.parsed_url
917 keys = sorted(entries.keys())
918 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000919 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000920 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000921
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000922 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000923 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000924 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000925
maruel@chromium.org75a59272010-06-11 22:34:03 +0000926 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000927 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000928 return self._root_dir
929
maruel@chromium.org271375b2010-06-23 19:17:38 +0000930 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000931 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000932 return self._enforced_os
933
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000934 def recursion_limit(self):
935 """How recursive can each dependencies in DEPS file can load DEPS file."""
936 return self._recursion_limit
937
maruel@chromium.org0d812442010-08-10 12:41:08 +0000938 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000939 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000940 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000941
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000943#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944
945
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000946def CMDcleanup(parser, args):
947 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000948
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000949Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000950"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000951 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
952 help='override deps for the specified (comma-separated) '
953 'platform(s); \'all\' will process all deps_os '
954 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000955 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000956 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000957 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000958 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959 if options.verbose:
960 # Print out the .gclient file. This is longer than if we just printed the
961 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000962 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000963 return client.RunOnDeps('cleanup', args)
964
965
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000966@attr('usage', '[command] [args ...]')
967def CMDrecurse(parser, args):
968 """Operates on all the entries.
969
970 Runs a shell command on all entries.
971 """
972 # Stop parsing at the first non-arg so that these go through to the command
973 parser.disable_interspersed_args()
974 parser.add_option('-s', '--scm', action='append', default=[],
975 help='choose scm types to operate upon')
976 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000977 if not args:
978 print >> sys.stderr, 'Need to supply a command!'
979 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000980 root_and_entries = gclient_utils.GetGClientRootAndEntries()
981 if not root_and_entries:
982 print >> sys.stderr, (
983 'You need to run gclient sync at least once to use \'recurse\'.\n'
984 'This is because .gclient_entries needs to exist and be up to date.')
985 return 1
986 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000987 scm_set = set()
988 for scm in options.scm:
989 scm_set.update(scm.split(','))
990
991 # Pass in the SCM type as an env variable
992 env = os.environ.copy()
993
994 for path, url in entries.iteritems():
995 scm = gclient_scm.GetScmName(url)
996 if scm_set and scm not in scm_set:
997 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000998 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000999 if scm:
1000 env['GCLIENT_SCM'] = scm
1001 if url:
1002 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001003 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001004 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001005
1006
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001007@attr('usage', '[url] [safesync url]')
1008def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001009 """Create a .gclient file in the current directory.
1010
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001011This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001012top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001013modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001014provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001015URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001016"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001017 parser.add_option('--spec',
1018 help='create a gclient file containing the provided '
1019 'string. Due to Cygwin/Python brokenness, it '
1020 'probably can\'t contain any newlines.')
1021 parser.add_option('--name',
1022 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001023 parser.add_option('--deps-file', default='DEPS',
1024 help='overrides the default name for the DEPS file for the'
1025 'main solutions and all sub-dependencies')
1026 parser.add_option('--git-deps', action='store_true',
1027 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001028 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001029 if ((options.spec and args) or len(args) > 2 or
1030 (not options.spec and not args)):
1031 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1032
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001033 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034 if options.spec:
1035 client.SetConfig(options.spec)
1036 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001037 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001038 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001039 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001040 if name.endswith('.git'):
1041 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001042 else:
1043 # specify an alternate relpath for the given URL.
1044 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001045 deps_file = options.deps_file
1046 if options.git_deps:
1047 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001048 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 if len(args) > 1:
1050 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001051 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001053 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001054
1055
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001056@attr('epilog', """Example:
1057 gclient pack > patch.txt
1058 generate simple patch for configured client and dependences
1059""")
1060def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001061 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001062
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001063Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001064dependencies, and performs minimal postprocessing of the output. The
1065resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001066checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001067"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001068 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1069 help='override deps for the specified (comma-separated) '
1070 'platform(s); \'all\' will process all deps_os '
1071 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001073 client = GClient.LoadCurrentConfig(options)
1074 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001075 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001076 if options.verbose:
1077 # Print out the .gclient file. This is longer than if we just printed the
1078 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001079 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001080 return client.RunOnDeps('pack', args)
1081
1082
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001083def CMDstatus(parser, args):
1084 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001085 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1086 help='override deps for the specified (comma-separated) '
1087 'platform(s); \'all\' will process all deps_os '
1088 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001090 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001092 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001093 if options.verbose:
1094 # Print out the .gclient file. This is longer than if we just printed the
1095 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001096 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 return client.RunOnDeps('status', args)
1098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001101 gclient sync
1102 update files from SCM according to current configuration,
1103 *for modules which have changed since last update or sync*
1104 gclient sync --force
1105 update files from SCM according to current configuration, for
1106 all modules (useful for recovering files deleted from local copy)
1107 gclient sync --revision src@31000
1108 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109""")
1110def CMDsync(parser, args):
1111 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001112 parser.add_option('-f', '--force', action='store_true',
1113 help='force update even for unchanged modules')
1114 parser.add_option('-n', '--nohooks', action='store_true',
1115 help='don\'t run hooks after the update is complete')
1116 parser.add_option('-r', '--revision', action='append',
1117 dest='revisions', metavar='REV', default=[],
1118 help='Enforces revision/hash for the solutions with the '
1119 'format src@rev. The src@ part is optional and can be '
1120 'skipped. -r can be used multiple times when .gclient '
1121 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001122 'if the src@ part is skipped. Note that specifying '
1123 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001124 parser.add_option('-t', '--transitive', action='store_true',
1125 help='When a revision is specified (in the DEPS file or '
1126 'with the command-line flag), transitively update '
1127 'the dependencies to the date of the given revision. '
1128 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001129 parser.add_option('-H', '--head', action='store_true',
1130 help='skips any safesync_urls specified in '
1131 'configured solutions and sync to head instead')
1132 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001133 help='delete any dependency that have been removed from '
1134 'last sync as long as there is no local modification. '
1135 'Coupled with --force, it will remove them even with '
1136 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001137 parser.add_option('-R', '--reset', action='store_true',
1138 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001139 parser.add_option('-M', '--merge', action='store_true',
1140 help='merge upstream changes instead of trying to '
1141 'fast-forward or rebase')
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('-m', '--manually_grab_svn_rev', action='store_true',
1147 help='Skip svn up whenever possible by requesting '
1148 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001149 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001150 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151
1152 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
maruel@chromium.org307d1792010-05-31 20:03:13 +00001155 if options.revisions and options.head:
1156 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001157 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158
1159 if options.verbose:
1160 # Print out the .gclient file. This is longer than if we just printed the
1161 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001162 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001163 return client.RunOnDeps('update', args)
1164
1165
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001167 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001168 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001169
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001170def CMDdiff(parser, args):
1171 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001172 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1173 help='override deps for the specified (comma-separated) '
1174 'platform(s); \'all\' will process all deps_os '
1175 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001177 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001178 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001179 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 if options.verbose:
1181 # Print out the .gclient file. This is longer than if we just printed the
1182 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001183 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001184 return client.RunOnDeps('diff', args)
1185
1186
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001187def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001188 """Revert all modifications in every dependencies.
1189
1190 That's the nuclear option to get back to a 'clean' state. It removes anything
1191 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001192 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1193 help='override deps for the specified (comma-separated) '
1194 'platform(s); \'all\' will process all deps_os '
1195 'references')
1196 parser.add_option('-n', '--nohooks', action='store_true',
1197 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001198 (options, args) = parser.parse_args(args)
1199 # --force is implied.
1200 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001201 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001203 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001204 return client.RunOnDeps('revert', args)
1205
1206
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001207def CMDrunhooks(parser, args):
1208 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001209 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1210 help='override deps for the specified (comma-separated) '
1211 'platform(s); \'all\' will process all deps_os '
1212 'references')
1213 parser.add_option('-f', '--force', action='store_true', default=True,
1214 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001215 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001216 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001217 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001218 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001219 if options.verbose:
1220 # Print out the .gclient file. This is longer than if we just printed the
1221 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001222 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001223 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001224 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001225 return client.RunOnDeps('runhooks', args)
1226
1227
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001228def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001229 """Output revision info mapping for the client and its dependencies.
1230
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001231 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001232 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001233 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1234 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001235 commit can change.
1236 """
1237 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1238 help='override deps for the specified (comma-separated) '
1239 'platform(s); \'all\' will process all deps_os '
1240 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001241 parser.add_option('-a', '--actual', action='store_true',
1242 help='gets the actual checked out revisions instead of the '
1243 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001244 parser.add_option('-s', '--snapshot', action='store_true',
1245 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001246 'version of all repositories to reproduce the tree, '
1247 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001248 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001249 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001250 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001251 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001253 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001254
1255
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001256def Command(name):
1257 return getattr(sys.modules[__name__], 'CMD' + name, None)
1258
1259
1260def CMDhelp(parser, args):
1261 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001262 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001263 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001264 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001265 parser.print_help()
1266 return 0
1267
1268
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001269def GenUsage(parser, command):
1270 """Modify an OptParse object with the function's documentation."""
1271 obj = Command(command)
1272 if command == 'help':
1273 command = '<command>'
1274 # OptParser.description prefer nicely non-formatted strings.
1275 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1276 usage = getattr(obj, 'usage', '')
1277 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1278 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001279
1280
maruel@chromium.org0895b752011-08-26 20:40:33 +00001281def Parser():
1282 """Returns the default parser."""
1283 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgad9c97b2011-09-08 17:04:49 +00001284 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001285 help='Specify how many SCM commands can run in parallel; '
1286 'default=%default')
1287 parser.add_option('-v', '--verbose', action='count', default=0,
1288 help='Produces additional output for diagnostics. Can be '
1289 'used up to three times for more logging info.')
1290 parser.add_option('--gclientfile', dest='config_filename',
1291 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1292 help='Specify an alternate %default file')
1293 # Integrate standard options processing.
1294 old_parser = parser.parse_args
1295 def Parse(args):
1296 (options, args) = old_parser(args)
1297 level = None
1298 if options.verbose == 2:
1299 level = logging.INFO
1300 elif options.verbose > 2:
1301 level = logging.DEBUG
1302 logging.basicConfig(level=level,
1303 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1304 options.entries_filename = options.config_filename + '_entries'
1305 if options.jobs < 1:
1306 parser.error('--jobs must be 1 or higher')
1307
1308 # These hacks need to die.
1309 if not hasattr(options, 'revisions'):
1310 # GClient.RunOnDeps expects it even if not applicable.
1311 options.revisions = []
1312 if not hasattr(options, 'head'):
1313 options.head = None
1314 if not hasattr(options, 'nohooks'):
1315 options.nohooks = True
1316 if not hasattr(options, 'deps_os'):
1317 options.deps_os = None
1318 if not hasattr(options, 'manually_grab_svn_rev'):
1319 options.manually_grab_svn_rev = None
1320 if not hasattr(options, 'force'):
1321 options.force = None
1322 return (options, args)
1323 parser.parse_args = Parse
1324 # We don't want wordwrapping in epilog (usually examples)
1325 parser.format_epilog = lambda _: parser.epilog or ''
1326 return parser
1327
1328
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001330 """Doesn't parse the arguments here, just find the right subcommand to
1331 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001332 if sys.hexversion < 0x02050000:
1333 print >> sys.stderr, (
1334 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001335 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001336 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1337 # operations. Python as a strong tendency to buffer sys.stdout.
1338 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001339 # Make stdout annotated with the thread ids.
1340 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001341 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001342 # Unused variable 'usage'
1343 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001344 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1345 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1346 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001347 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001348 if argv:
1349 command = Command(argv[0])
1350 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001351 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001352 GenUsage(parser, argv[0])
1353 return command(parser, argv[1:])
1354 # Not a known command. Default to help.
1355 GenUsage(parser, 'help')
1356 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001357 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001358 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001359 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001360
1361
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001362if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001363 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001364 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001365
1366# vim: ts=2:sw=2:tw=80:et: