blob: f568662ba0db7c3811983bbbcef730b330ce4f76 [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
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000227 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000228 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):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000233 if j.should_process:
234 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000235
236 for obj in yield_full_tree(self.root_parent()):
237 if obj is self or not obj.name:
238 continue
239 # Step 1: Find any requirements self may need.
240 if self.name.startswith(posixpath.join(obj.name, '')):
241 self.requirements.add(obj.name)
242 # Step 2: Find any requirements self may impose.
243 if obj.name.startswith(posixpath.join(self.name, '')):
244 obj.requirements.add(self.name)
245
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000246 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000247 """Resolves the parsed url from url.
248
249 Manages From() keyword accordingly. Do not touch self.parsed_url nor
250 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000251 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000252 overriden_url = self.get_custom_deps(self.name, url)
253 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000254 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000255 overriden_url))
256 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000257 elif isinstance(url, self.FromImpl):
258 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000259 if not ref:
260 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
261 url.module_name, ref))
262 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000263 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000264 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000265 # Make sure the referenced dependency DEPS file is loaded and file the
266 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000267 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000268 found_dep = None
269 for d in ref.dependencies:
270 if d.name == sub_target:
271 found_dep = d
272 break
273 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000274 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000275 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
276 sub_target, ref.name, self.name, self.parent.name,
277 str(self.root_parent())))
278
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000279 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000280 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000281 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000282 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000283 elif isinstance(url, basestring):
284 parsed_url = urlparse.urlparse(url)
285 if not parsed_url[0]:
286 # A relative url. Fetch the real base.
287 path = parsed_url[2]
288 if not path.startswith('/'):
289 raise gclient_utils.Error(
290 'relative DEPS entry \'%s\' must begin with a slash' % url)
291 # Create a scm just to query the full url.
292 parent_url = self.parent.parsed_url
293 if isinstance(parent_url, self.FileImpl):
294 parent_url = parent_url.file_location
295 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000296 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000297 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000298 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000299 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000300 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000301 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000302 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000303 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000304 return parsed_url
305 elif url is None:
306 return None
307 else:
308 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000309
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000310 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000311 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000312 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000313 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000314 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000315 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000316 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000317 # One thing is unintuitive, vars= {} must happen before Var() use.
318 local_scope = {}
319 var = self.VarImpl(self.custom_vars, local_scope)
320 global_scope = {
321 'File': self.FileImpl,
322 'From': self.FromImpl,
323 'Var': var.Lookup,
324 'deps_os': {},
325 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000326 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
327 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000328 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
329 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000330 else:
331 deps_content = gclient_utils.FileRead(filepath)
332 logging.debug(deps_content)
333 # Eval the content.
334 try:
335 exec(deps_content, global_scope, local_scope)
336 except SyntaxError, e:
337 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000338 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000339 # load os specific dependencies if defined. these dependencies may
340 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000341 if 'deps_os' in local_scope:
342 for deps_os_key in self.enforced_os():
343 os_deps = local_scope['deps_os'].get(deps_os_key, {})
344 if len(self.enforced_os()) > 1:
345 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000346 # platform, so we collect the broadest set of dependencies
347 # available. We may end up with the wrong revision of something for
348 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349 deps.update([x for x in os_deps.items() if not x[0] in deps])
350 else:
351 deps.update(os_deps)
352
maruel@chromium.org271375b2010-06-23 19:17:38 +0000353 self.deps_hooks.extend(local_scope.get('hooks', []))
354
355 # If a line is in custom_deps, but not in the solution, we want to append
356 # this line to the solution.
357 for d in self.custom_deps:
358 if d not in deps:
359 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000360
361 # If use_relative_paths is set in the DEPS file, regenerate
362 # the dictionary using paths relative to the directory containing
363 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000364 use_relative_paths = local_scope.get('use_relative_paths', False)
365 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000366 rel_deps = {}
367 for d, url in deps.items():
368 # normpath is required to allow DEPS to use .. in their
369 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000370 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
371 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000372
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000373 # Convert the deps into real Dependency.
374 for name, url in deps.iteritems():
375 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000376 raise gclient_utils.Error(
377 'The same name "%s" appears multiple times in the deps section' %
378 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000379 should_process = self.recursion_limit() > 0 and self.should_process
380 if should_process:
381 tree = dict((d.name, d) for d in self.tree(False))
382 if name in tree:
383 if url == tree[name].url:
384 logging.info('Won\'t process duplicate dependency %s' % tree[name])
385 # In theory we could keep it as a shadow of the other one. In
386 # practice, simply ignore it.
387 #should_process = False
388 continue
389 else:
390 raise gclient_utils.Error(
391 'Dependency %s specified more than once:\n %s\nvs\n %s' %
392 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000393 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000394 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000395 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000396
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000397 # Arguments number differs from overridden method
398 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000399 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000400 """Runs 'command' before parsing the DEPS in case it's a initial checkout
401 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000402
403 def maybeGetParentRevision(options):
404 """If we are performing an update and --transitive is set, set the
405 revision to the parent's revision. If we have an explicit revision
406 do nothing."""
407 if command == 'update' and options.transitive and not options.revision:
408 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
409 if not revision:
410 options.revision = revision_overrides.get(self.parent.name)
411 if options.verbose and options.revision:
412 print("Using parent's revision date: %s" % options.revision)
413 # If the parent has a revision override, then it must have been
414 # converted to date format.
415 assert (not options.revision or
416 gclient_utils.IsDateRevision(options.revision))
417
418 def maybeConvertToDateRevision(options):
419 """If we are performing an update and --transitive is set, convert the
420 revision to a date-revision (if necessary). Instead of having
421 -r 101 replace the revision with the time stamp of 101 (e.g.
422 "{2011-18-04}").
423 This way dependencies are upgraded to the revision they had at the
424 check-in of revision 101."""
425 if (command == 'update' and
426 options.transitive and
427 options.revision and
428 not gclient_utils.IsDateRevision(options.revision)):
429 revision_date = scm.GetRevisionDate(options.revision)
430 revision = gclient_utils.MakeDateRevision(revision_date)
431 if options.verbose:
432 print("Updating revision override from %s to %s." %
433 (options.revision, revision))
434 revision_overrides[self.name] = revision
435
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000436 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000437 if not self.should_process:
438 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000439 # When running runhooks, there's no need to consult the SCM.
440 # All known hooks are expected to run unconditionally regardless of working
441 # copy state, so skip the SCM status check.
442 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000443 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000444 if run_scm and self.parsed_url:
445 if isinstance(self.parsed_url, self.FileImpl):
446 # Special support for single-file checkout.
447 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
448 options.revision = self.parsed_url.GetRevision()
449 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
450 self.root_dir(),
451 self.name)
452 scm.RunCommand('updatesingle', options,
453 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000454 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000456 # Create a shallow copy to mutate revision.
457 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000459 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000460 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000461 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000462 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000463 self._file_list = [os.path.join(self.name, f.strip())
464 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000465 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000466 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000467 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000468 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000469
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000470 # Parse the dependencies of this dependency.
471 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000472 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000473
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000474 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000475 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000477 assert self.hooks_ran == False
478 if not self.should_process or self.recursion_limit() <= 0:
479 # Don't run the hook when it is above recursion_limit.
480 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000481 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000482 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000483 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000484 # TODO(maruel): If the user is using git or git-svn, then we don't know
485 # what files have changed so we always run all hooks. It'd be nice to fix
486 # that.
487 if (options.force or
488 isinstance(self.parsed_url, self.FileImpl) or
489 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
490 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
491 for hook_dict in self.deps_hooks:
492 self._RunHookAction(hook_dict, [])
493 else:
494 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
495 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000496 file_list = self.file_list()
497 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000499 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000500 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000501
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000503 file_list[i].lower()])
504 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505
506 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000507 while (file_list[i].startswith('\\') or
508 file_list[i].startswith('/')):
509 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000510
511 # Run hooks on the basis of whether the files from the gclient operation
512 # match each hook's pattern.
513 for hook_dict in self.deps_hooks:
514 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000515 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 if matching_file_list:
517 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000518 for s in self.dependencies:
519 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000520
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000521 def _RunHookAction(self, hook_dict, matching_file_list):
522 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000523 # A single DEPS file can specify multiple hooks so this function can be
524 # called multiple times on a single Dependency.
525 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000526 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000527 logging.debug(hook_dict)
528 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000529 command = hook_dict['action'][:]
530 if command[0] == 'python':
531 # If the hook specified "python" as the first item, the action is a
532 # Python script. Run it by starting a new copy of the same
533 # interpreter.
534 command[0] = sys.executable
535
536 if '$matching_files' in command:
537 splice_index = command.index('$matching_files')
538 command[splice_index:splice_index + 1] = matching_file_list
539
maruel@chromium.org17d01792010-09-01 18:07:10 +0000540 try:
541 gclient_utils.CheckCallAndFilterAndHeader(
542 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000543 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000544 # Use a discrete exit status code of 2 to indicate that a hook action
545 # failed. Users of this script may wish to treat hook action failures
546 # differently from VC failures.
547 print >> sys.stderr, 'Error: %s' % str(e)
548 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000549
maruel@chromium.org271375b2010-06-23 19:17:38 +0000550 def root_dir(self):
551 return self.parent.root_dir()
552
553 def enforced_os(self):
554 return self.parent.enforced_os()
555
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000556 def recursion_limit(self):
557 return self.parent.recursion_limit() - 1
558
maruel@chromium.org0d812442010-08-10 12:41:08 +0000559 def tree(self, include_all):
560 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000561
maruel@chromium.org0d812442010-08-10 12:41:08 +0000562 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000563 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000564 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000565 for d in self.dependencies:
566 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000567 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000568 for d in self.dependencies:
569 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000570 return result
571
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000572 def get_custom_deps(self, name, url):
573 """Returns a custom deps if applicable."""
574 if self.parent:
575 url = self.parent.get_custom_deps(name, url)
576 # None is a valid return value to disable a dependency.
577 return self.custom_deps.get(name, url)
578
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000579 def file_list(self):
580 result = self._file_list[:]
581 for d in self.dependencies:
582 result.extend(d.file_list())
583 return result
584
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000585 def __str__(self):
586 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000587 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000588 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
589 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000590 # 'deps_file'
591 if self.__dict__[i]:
592 out.append('%s: %s' % (i, self.__dict__[i]))
593
594 for d in self.dependencies:
595 out.extend([' ' + x for x in str(d).splitlines()])
596 out.append('')
597 return '\n'.join(out)
598
599 def __repr__(self):
600 return '%s: %s' % (self.name, self.url)
601
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000602 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000603 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000604 out = '%s(%s)' % (self.name, self.url)
605 i = self.parent
606 while i and i.name:
607 out = '%s(%s) -> %s' % (i.name, i.url, out)
608 i = i.parent
609 return out
610
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000611 def root_parent(self):
612 """Returns the root object, normally a GClient object."""
613 d = self
614 while d.parent:
615 d = d.parent
616 return d
617
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000618
619class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000620 """Object that represent a gclient checkout. A tree of Dependency(), one per
621 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000622
623 DEPS_OS_CHOICES = {
624 "win32": "win",
625 "win": "win",
626 "cygwin": "win",
627 "darwin": "mac",
628 "mac": "mac",
629 "unix": "unix",
630 "linux": "unix",
631 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000632 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000633 }
634
635 DEFAULT_CLIENT_FILE_TEXT = ("""\
636solutions = [
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" : {
641 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000642 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643 },
644]
645""")
646
647 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
648 { "name" : "%(solution_name)s",
649 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000650 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000651 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000652%(solution_deps)s },
653 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000654 },
655""")
656
657 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
658# Snapshot generated with gclient revinfo --snapshot
659solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000660%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000661""")
662
663 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000664 # Do not change previous behavior. Only solution level and immediate DEPS
665 # are processed.
666 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000667 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
668 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000669 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000670 if options.deps_os:
671 enforced_os = options.deps_os.split(',')
672 else:
673 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
674 if 'all' in enforced_os:
675 enforced_os = self.DEPS_OS_CHOICES.itervalues()
676 self._enforced_os = list(set(enforced_os))
677 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000678 self.config_content = None
679
680 def SetConfig(self, content):
681 assert self.dependencies == []
682 config_dict = {}
683 self.config_content = content
684 try:
685 exec(content, config_dict)
686 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000687 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000688 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000689 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000690 tree = dict((d.name, d) for d in self.tree(False))
691 if s['name'] in tree:
692 raise gclient_utils.Error(
693 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000694 self.dependencies.append(Dependency(
695 self, s['name'], s['url'],
696 s.get('safesync_url', None),
697 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000698 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000699 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000700 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000701 except KeyError:
702 raise gclient_utils.Error('Invalid .gclient file. Solution is '
703 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000704 # .gclient can have hooks.
705 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000706 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000707
708 def SaveConfig(self):
709 gclient_utils.FileWrite(os.path.join(self.root_dir(),
710 self._options.config_filename),
711 self.config_content)
712
713 @staticmethod
714 def LoadCurrentConfig(options):
715 """Searches for and loads a .gclient file relative to the current working
716 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000717 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000718 if not path:
719 return None
720 client = GClient(path, options)
721 client.SetConfig(gclient_utils.FileRead(
722 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000723 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000724
nsylvain@google.comefc80932011-05-31 21:27:56 +0000725 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
726 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000727 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
728 'solution_name': solution_name,
729 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000730 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000731 'safesync_url' : safesync_url,
732 })
733
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000734 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000735 """Creates a .gclient_entries file to record the list of unique checkouts.
736
737 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000738 """
739 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
740 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000741 result = 'entries = {\n'
742 for entry in self.tree(False):
743 # Skip over File() dependencies as we can't version them.
744 if not isinstance(entry.parsed_url, self.FileImpl):
745 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
746 pprint.pformat(entry.parsed_url))
747 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000748 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000749 logging.info(result)
750 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000751
752 def _ReadEntries(self):
753 """Read the .gclient_entries file for the given client.
754
755 Returns:
756 A sequence of solution names, which will be empty if there is the
757 entries file hasn't been created yet.
758 """
759 scope = {}
760 filename = os.path.join(self.root_dir(), self._options.entries_filename)
761 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000762 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000763 try:
764 exec(gclient_utils.FileRead(filename), scope)
765 except SyntaxError, e:
766 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000767 return scope['entries']
768
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000769 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000770 """Checks for revision overrides."""
771 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000772 if self._options.head:
773 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000774 # Do not check safesync_url if one or more --revision flag is specified.
775 if not self._options.revisions:
776 for s in self.dependencies:
777 if not s.safesync_url:
778 continue
779 handle = urllib.urlopen(s.safesync_url)
780 rev = handle.read().strip()
781 handle.close()
782 if len(rev):
783 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000784 if not self._options.revisions:
785 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000786 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000787 index = 0
788 for revision in self._options.revisions:
789 if not '@' in revision:
790 # Support for --revision 123
791 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000792 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000793 if not sol in solutions_names:
794 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
795 print >> sys.stderr, ('Please fix your script, having invalid '
796 '--revision flags will soon considered an error.')
797 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000798 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000799 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000800 return revision_overrides
801
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802 def RunOnDeps(self, command, args):
803 """Runs a command on each dependency in a client and its dependencies.
804
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805 Args:
806 command: The command to use (e.g., 'status' or 'diff')
807 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000809 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000810 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000811 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000812 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000813 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000814 if (command in ('update', 'revert') and sys.stdout.isatty() and not
815 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000816 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000817 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000818 for s in self.dependencies:
819 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000820 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000821
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000822 # Once all the dependencies have been processed, it's now safe to run the
823 # hooks.
824 if not self._options.nohooks:
825 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000826
827 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000828 # Notify the user if there is an orphaned entry in their working copy.
829 # Only delete the directory if there are no changes in it, and
830 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000831 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000832 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000833 if not prev_url:
834 # entry must have been overridden via .gclient custom_deps
835 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000836 # Fix path separator on Windows.
837 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000838 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000839 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000840 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000841 file_list = []
842 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
843 scm.status(self._options, [], file_list)
844 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000845 if (not self._options.delete_unversioned_trees or
846 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000847 # There are modified files in this entry. Keep warning until
848 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000849 print(('\nWARNING: \'%s\' is no longer part of this client. '
850 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000851 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852 else:
853 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000854 print('\n________ deleting \'%s\' in \'%s\'' % (
855 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000856 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000857 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000858 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000859 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000860
861 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000862 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000863 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000864 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000865 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000866 for s in self.dependencies:
867 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000868 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000869
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000870 def GetURLAndRev(dep):
871 """Returns the revision-qualified SCM url for a Dependency."""
872 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000873 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000874 if isinstance(dep.parsed_url, self.FileImpl):
875 original_url = dep.parsed_url.file_location
876 else:
877 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000878 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000879 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000880 if not os.path.isdir(scm.checkout_path):
881 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000882 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000884 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000885 new_gclient = ''
886 # First level at .gclient
887 for d in self.dependencies:
888 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000889 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000890 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000891 for d in dep.dependencies:
892 entries[d.name] = GetURLAndRev(d)
893 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000894 GrabDeps(d)
895 custom_deps = []
896 for k in sorted(entries.keys()):
897 if entries[k]:
898 # Quotes aren't escaped...
899 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
900 else:
901 custom_deps.append(' \"%s\": None,\n' % k)
902 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
903 'solution_name': d.name,
904 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000905 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000906 'safesync_url' : d.safesync_url or '',
907 'solution_deps': ''.join(custom_deps),
908 }
909 # Print the snapshot configuration file
910 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000911 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000912 entries = {}
913 for d in self.tree(False):
914 if self._options.actual:
915 entries[d.name] = GetURLAndRev(d)
916 else:
917 entries[d.name] = d.parsed_url
918 keys = sorted(entries.keys())
919 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000920 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000921 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000922
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000923 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000924 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000925 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000926
maruel@chromium.org75a59272010-06-11 22:34:03 +0000927 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000928 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000929 return self._root_dir
930
maruel@chromium.org271375b2010-06-23 19:17:38 +0000931 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000932 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000933 return self._enforced_os
934
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000935 def recursion_limit(self):
936 """How recursive can each dependencies in DEPS file can load DEPS file."""
937 return self._recursion_limit
938
maruel@chromium.org0d812442010-08-10 12:41:08 +0000939 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000940 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000941 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000942
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000944#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000945
946
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000947def CMDcleanup(parser, args):
948 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000949
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000950Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000951"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000952 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
953 help='override deps for the specified (comma-separated) '
954 'platform(s); \'all\' will process all deps_os '
955 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000956 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000957 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000958 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000959 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000960 if options.verbose:
961 # Print out the .gclient file. This is longer than if we just printed the
962 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000963 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000964 return client.RunOnDeps('cleanup', args)
965
966
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000967@attr('usage', '[command] [args ...]')
968def CMDrecurse(parser, args):
969 """Operates on all the entries.
970
971 Runs a shell command on all entries.
972 """
973 # Stop parsing at the first non-arg so that these go through to the command
974 parser.disable_interspersed_args()
975 parser.add_option('-s', '--scm', action='append', default=[],
976 help='choose scm types to operate upon')
977 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000978 if not args:
979 print >> sys.stderr, 'Need to supply a command!'
980 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000981 root_and_entries = gclient_utils.GetGClientRootAndEntries()
982 if not root_and_entries:
983 print >> sys.stderr, (
984 'You need to run gclient sync at least once to use \'recurse\'.\n'
985 'This is because .gclient_entries needs to exist and be up to date.')
986 return 1
987 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000988 scm_set = set()
989 for scm in options.scm:
990 scm_set.update(scm.split(','))
991
992 # Pass in the SCM type as an env variable
993 env = os.environ.copy()
994
995 for path, url in entries.iteritems():
996 scm = gclient_scm.GetScmName(url)
997 if scm_set and scm not in scm_set:
998 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000999 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001000 if scm:
1001 env['GCLIENT_SCM'] = scm
1002 if url:
1003 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001004 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001005 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001006
1007
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001008@attr('usage', '[url] [safesync url]')
1009def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001010 """Create a .gclient file in the current directory.
1011
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001012This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001013top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001014modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001015provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001016URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001017"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001018 parser.add_option('--spec',
1019 help='create a gclient file containing the provided '
1020 'string. Due to Cygwin/Python brokenness, it '
1021 'probably can\'t contain any newlines.')
1022 parser.add_option('--name',
1023 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001024 parser.add_option('--deps-file', default='DEPS',
1025 help='overrides the default name for the DEPS file for the'
1026 'main solutions and all sub-dependencies')
1027 parser.add_option('--git-deps', action='store_true',
1028 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001029 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001030 if ((options.spec and args) or len(args) > 2 or
1031 (not options.spec and not args)):
1032 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1033
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001034 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001035 if options.spec:
1036 client.SetConfig(options.spec)
1037 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001038 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001039 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001040 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001041 if name.endswith('.git'):
1042 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001043 else:
1044 # specify an alternate relpath for the given URL.
1045 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001046 deps_file = options.deps_file
1047 if options.git_deps:
1048 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001049 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 if len(args) > 1:
1051 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001052 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001054 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055
1056
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001057@attr('epilog', """Example:
1058 gclient pack > patch.txt
1059 generate simple patch for configured client and dependences
1060""")
1061def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001062 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001063
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001064Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001065dependencies, and performs minimal postprocessing of the output. The
1066resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001067checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001068"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001069 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1070 help='override deps for the specified (comma-separated) '
1071 'platform(s); \'all\' will process all deps_os '
1072 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001074 client = GClient.LoadCurrentConfig(options)
1075 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001076 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001077 if options.verbose:
1078 # Print out the .gclient file. This is longer than if we just printed the
1079 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001080 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001081 return client.RunOnDeps('pack', args)
1082
1083
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001084def CMDstatus(parser, args):
1085 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001086 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1087 help='override deps for the specified (comma-separated) '
1088 'platform(s); \'all\' will process all deps_os '
1089 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001090 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001091 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001093 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 if options.verbose:
1095 # Print out the .gclient file. This is longer than if we just printed the
1096 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001097 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098 return client.RunOnDeps('status', args)
1099
1100
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001101@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001102 gclient sync
1103 update files from SCM according to current configuration,
1104 *for modules which have changed since last update or sync*
1105 gclient sync --force
1106 update files from SCM according to current configuration, for
1107 all modules (useful for recovering files deleted from local copy)
1108 gclient sync --revision src@31000
1109 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110""")
1111def CMDsync(parser, args):
1112 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001113 parser.add_option('-f', '--force', action='store_true',
1114 help='force update even for unchanged modules')
1115 parser.add_option('-n', '--nohooks', action='store_true',
1116 help='don\'t run hooks after the update is complete')
1117 parser.add_option('-r', '--revision', action='append',
1118 dest='revisions', metavar='REV', default=[],
1119 help='Enforces revision/hash for the solutions with the '
1120 'format src@rev. The src@ part is optional and can be '
1121 'skipped. -r can be used multiple times when .gclient '
1122 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001123 'if the src@ part is skipped. Note that specifying '
1124 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001125 parser.add_option('-t', '--transitive', action='store_true',
1126 help='When a revision is specified (in the DEPS file or '
1127 'with the command-line flag), transitively update '
1128 'the dependencies to the date of the given revision. '
1129 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001130 parser.add_option('-H', '--head', action='store_true',
1131 help='skips any safesync_urls specified in '
1132 'configured solutions and sync to head instead')
1133 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001134 help='delete any dependency that have been removed from '
1135 'last sync as long as there is no local modification. '
1136 'Coupled with --force, it will remove them even with '
1137 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001138 parser.add_option('-R', '--reset', action='store_true',
1139 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001140 parser.add_option('-M', '--merge', action='store_true',
1141 help='merge upstream changes instead of trying to '
1142 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1144 help='override deps for the specified (comma-separated) '
1145 'platform(s); \'all\' will process all deps_os '
1146 'references')
1147 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1148 help='Skip svn up whenever possible by requesting '
1149 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001150 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001151 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152
1153 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001154 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001155
maruel@chromium.org307d1792010-05-31 20:03:13 +00001156 if options.revisions and options.head:
1157 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001158 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159
1160 if options.verbose:
1161 # Print out the .gclient file. This is longer than if we just printed the
1162 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001163 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164 return client.RunOnDeps('update', args)
1165
1166
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001167def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001168 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001169 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001171def CMDdiff(parser, args):
1172 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001173 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1174 help='override deps for the specified (comma-separated) '
1175 'platform(s); \'all\' will process all deps_os '
1176 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001178 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001180 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001181 if options.verbose:
1182 # Print out the .gclient file. This is longer than if we just printed the
1183 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001184 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 return client.RunOnDeps('diff', args)
1186
1187
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001188def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001189 """Revert all modifications in every dependencies.
1190
1191 That's the nuclear option to get back to a 'clean' state. It removes anything
1192 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001193 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1194 help='override deps for the specified (comma-separated) '
1195 'platform(s); \'all\' will process all deps_os '
1196 'references')
1197 parser.add_option('-n', '--nohooks', action='store_true',
1198 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001199 (options, args) = parser.parse_args(args)
1200 # --force is implied.
1201 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001202 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001204 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001205 return client.RunOnDeps('revert', args)
1206
1207
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001208def CMDrunhooks(parser, args):
1209 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001210 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1211 help='override deps for the specified (comma-separated) '
1212 'platform(s); \'all\' will process all deps_os '
1213 'references')
1214 parser.add_option('-f', '--force', action='store_true', default=True,
1215 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001216 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001217 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001218 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001219 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220 if options.verbose:
1221 # Print out the .gclient file. This is longer than if we just printed the
1222 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001223 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001224 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001225 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001226 return client.RunOnDeps('runhooks', args)
1227
1228
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001229def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001230 """Output revision info mapping for the client and its dependencies.
1231
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001232 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001233 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001234 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1235 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001236 commit can change.
1237 """
1238 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1239 help='override deps for the specified (comma-separated) '
1240 'platform(s); \'all\' will process all deps_os '
1241 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001242 parser.add_option('-a', '--actual', action='store_true',
1243 help='gets the actual checked out revisions instead of the '
1244 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001245 parser.add_option('-s', '--snapshot', action='store_true',
1246 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001247 'version of all repositories to reproduce the tree, '
1248 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001249 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001250 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001252 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001253 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001254 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001255
1256
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001257def Command(name):
1258 return getattr(sys.modules[__name__], 'CMD' + name, None)
1259
1260
1261def CMDhelp(parser, args):
1262 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001263 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001264 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001265 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001266 parser.print_help()
1267 return 0
1268
1269
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001270def GenUsage(parser, command):
1271 """Modify an OptParse object with the function's documentation."""
1272 obj = Command(command)
1273 if command == 'help':
1274 command = '<command>'
1275 # OptParser.description prefer nicely non-formatted strings.
1276 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1277 usage = getattr(obj, 'usage', '')
1278 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1279 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001280
1281
maruel@chromium.org0895b752011-08-26 20:40:33 +00001282def Parser():
1283 """Returns the default parser."""
1284 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgad9c97b2011-09-08 17:04:49 +00001285 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001286 help='Specify how many SCM commands can run in parallel; '
1287 'default=%default')
1288 parser.add_option('-v', '--verbose', action='count', default=0,
1289 help='Produces additional output for diagnostics. Can be '
1290 'used up to three times for more logging info.')
1291 parser.add_option('--gclientfile', dest='config_filename',
1292 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1293 help='Specify an alternate %default file')
1294 # Integrate standard options processing.
1295 old_parser = parser.parse_args
1296 def Parse(args):
1297 (options, args) = old_parser(args)
1298 level = None
1299 if options.verbose == 2:
1300 level = logging.INFO
1301 elif options.verbose > 2:
1302 level = logging.DEBUG
1303 logging.basicConfig(level=level,
1304 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1305 options.entries_filename = options.config_filename + '_entries'
1306 if options.jobs < 1:
1307 parser.error('--jobs must be 1 or higher')
1308
1309 # These hacks need to die.
1310 if not hasattr(options, 'revisions'):
1311 # GClient.RunOnDeps expects it even if not applicable.
1312 options.revisions = []
1313 if not hasattr(options, 'head'):
1314 options.head = None
1315 if not hasattr(options, 'nohooks'):
1316 options.nohooks = True
1317 if not hasattr(options, 'deps_os'):
1318 options.deps_os = None
1319 if not hasattr(options, 'manually_grab_svn_rev'):
1320 options.manually_grab_svn_rev = None
1321 if not hasattr(options, 'force'):
1322 options.force = None
1323 return (options, args)
1324 parser.parse_args = Parse
1325 # We don't want wordwrapping in epilog (usually examples)
1326 parser.format_epilog = lambda _: parser.epilog or ''
1327 return parser
1328
1329
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001330def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001331 """Doesn't parse the arguments here, just find the right subcommand to
1332 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001333 if sys.hexversion < 0x02050000:
1334 print >> sys.stderr, (
1335 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001336 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001337 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1338 # operations. Python as a strong tendency to buffer sys.stdout.
1339 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001340 # Make stdout annotated with the thread ids.
1341 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001342 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001343 # Unused variable 'usage'
1344 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001345 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1346 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1347 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001348 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001349 if argv:
1350 command = Command(argv[0])
1351 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001352 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001353 GenUsage(parser, argv[0])
1354 return command(parser, argv[1:])
1355 # Not a known command. Default to help.
1356 GenUsage(parser, 'help')
1357 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001358 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001359 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001360 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361
1362
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001363if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001364 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001365 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001366
1367# vim: ts=2:sw=2:tw=80:et: