blob: c836d9ec3b3c2df4e78a6f9b0c43e91aa23e1d69 [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.org89d51c22011-09-14 20:12:52 +0000148 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000149 self.parent = parent
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000150 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000151 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000152 # These 2 are only set in .gclient and not in DEPS files.
153 self.safesync_url = safesync_url
154 self.custom_vars = custom_vars or {}
155 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000156 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000157 self.dependencies = []
nsylvain@google.comefc80932011-05-31 21:27:56 +0000158 self.deps_file = deps_file
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000159 # A cache of the files affected by the current operation, necessary for
160 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000161 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000162 # If it is not set to True, the dependency wasn't processed for its child
163 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000164 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000165 # This dependency should be processed, i.e. checked out
166 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000167 # This dependency has been processed, i.e. checked out
168 self.processed = False
169 # This dependency had its hook run
170 self.hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000171
maruel@chromium.org98023df2011-09-07 18:44:47 +0000172 # Post process the url to remove trailing slashes.
173 if isinstance(self.url, basestring):
174 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
175 # it to proto://host/path@rev.
176 if self.url.count('@') > 1:
177 raise gclient_utils.Error('Invalid url "%s"' % self.url)
178 self.url = self.url.replace('/@', '@')
179
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000180 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000181
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000182 # Sanity checks
183 if not self.name and self.parent:
184 raise gclient_utils.Error('Dependency without name')
185 if not isinstance(self.url,
186 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
187 raise gclient_utils.Error('dependency url must be either a string, None, '
188 'File() or From() instead of %s' %
189 self.url.__class__.__name__)
190 if '/' in self.deps_file or '\\' in self.deps_file:
191 raise gclient_utils.Error('deps_file name must not be a path, just a '
192 'filename. %s' % self.deps_file)
193
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000194 def _FindDependencies(self):
195 """Setup self.requirements and find any other dependency who would have self
196 as a requirement.
197 """
198 # self.parent is implicitly a requirement. This will be recursive by
199 # definition.
200 if self.parent and self.parent.name:
maruel@chromium.org89d51c22011-09-14 20:12:52 +0000201 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000202
203 # For a tree with at least 2 levels*, the leaf node needs to depend
204 # on the level higher up in an orderly way.
205 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
206 # thus unsorted, while the .gclient format is a list thus sorted.
207 #
208 # * _recursion_limit is hard coded 2 and there is no hope to change this
209 # value.
210 #
211 # Interestingly enough, the following condition only works in the case we
212 # want: self is a 2nd level node. 3nd level node wouldn't need this since
213 # they already have their parent as a requirement.
214 if self.parent in self.root_parent().dependencies:
215 root_deps = self.root_parent().dependencies
216 for i in range(0, root_deps.index(self.parent)):
217 value = root_deps[i]
218 if value.name:
maruel@chromium.org89d51c22011-09-14 20:12:52 +0000219 self._requirements.add(value.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000220
221 if isinstance(self.url, self.FromImpl):
maruel@chromium.org89d51c22011-09-14 20:12:52 +0000222 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000223
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000224 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000225 def yield_full_tree(root):
226 """Depth-first recursion."""
227 yield root
228 for i in root.dependencies:
229 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000230 if j.should_process:
231 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000232
233 for obj in yield_full_tree(self.root_parent()):
234 if obj is self or not obj.name:
235 continue
236 # Step 1: Find any requirements self may need.
237 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org89d51c22011-09-14 20:12:52 +0000238 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000239 # Step 2: Find any requirements self may impose.
240 if obj.name.startswith(posixpath.join(self.name, '')):
241 obj.requirements.add(self.name)
242
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000243 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000244 """Resolves the parsed url from url.
245
246 Manages From() keyword accordingly. Do not touch self.parsed_url nor
247 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000248 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000249 overriden_url = self.get_custom_deps(self.name, url)
250 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000251 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000252 overriden_url))
253 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000254 elif isinstance(url, self.FromImpl):
255 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000256 if not ref:
257 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
258 url.module_name, ref))
259 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000260 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000261 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000262 # Make sure the referenced dependency DEPS file is loaded and file the
263 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000264 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000265 found_dep = None
266 for d in ref.dependencies:
267 if d.name == sub_target:
268 found_dep = d
269 break
270 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000271 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000272 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
273 sub_target, ref.name, self.name, self.parent.name,
274 str(self.root_parent())))
275
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000276 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000277 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000278 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000279 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000280 elif isinstance(url, basestring):
281 parsed_url = urlparse.urlparse(url)
282 if not parsed_url[0]:
283 # A relative url. Fetch the real base.
284 path = parsed_url[2]
285 if not path.startswith('/'):
286 raise gclient_utils.Error(
287 'relative DEPS entry \'%s\' must begin with a slash' % url)
288 # Create a scm just to query the full url.
289 parent_url = self.parent.parsed_url
290 if isinstance(parent_url, self.FileImpl):
291 parent_url = parent_url.file_location
292 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000293 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000294 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000295 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000296 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000297 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000298 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000299 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000300 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000301 return parsed_url
302 elif url is None:
303 return None
304 else:
305 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000306
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000307 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000308 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000309 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000310 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000311 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000313 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000314 # One thing is unintuitive, vars= {} must happen before Var() use.
315 local_scope = {}
316 var = self.VarImpl(self.custom_vars, local_scope)
317 global_scope = {
318 'File': self.FileImpl,
319 'From': self.FromImpl,
320 'Var': var.Lookup,
321 'deps_os': {},
322 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000323 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
324 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000325 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
326 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000327 else:
328 deps_content = gclient_utils.FileRead(filepath)
329 logging.debug(deps_content)
330 # Eval the content.
331 try:
332 exec(deps_content, global_scope, local_scope)
333 except SyntaxError, e:
334 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000335 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000336 # load os specific dependencies if defined. these dependencies may
337 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000338 if 'deps_os' in local_scope:
339 for deps_os_key in self.enforced_os():
340 os_deps = local_scope['deps_os'].get(deps_os_key, {})
341 if len(self.enforced_os()) > 1:
342 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000343 # platform, so we collect the broadest set of dependencies
344 # available. We may end up with the wrong revision of something for
345 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000346 deps.update([x for x in os_deps.items() if not x[0] in deps])
347 else:
348 deps.update(os_deps)
349
maruel@chromium.org271375b2010-06-23 19:17:38 +0000350 self.deps_hooks.extend(local_scope.get('hooks', []))
351
352 # If a line is in custom_deps, but not in the solution, we want to append
353 # this line to the solution.
354 for d in self.custom_deps:
355 if d not in deps:
356 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000357
358 # If use_relative_paths is set in the DEPS file, regenerate
359 # the dictionary using paths relative to the directory containing
360 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000361 use_relative_paths = local_scope.get('use_relative_paths', False)
362 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000363 rel_deps = {}
364 for d, url in deps.items():
365 # normpath is required to allow DEPS to use .. in their
366 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000367 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
368 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000369
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000370 # Convert the deps into real Dependency.
371 for name, url in deps.iteritems():
372 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000373 raise gclient_utils.Error(
374 'The same name "%s" appears multiple times in the deps section' %
375 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000376 should_process = self.recursion_limit() > 0 and self.should_process
377 if should_process:
378 tree = dict((d.name, d) for d in self.tree(False))
379 if name in tree:
380 if url == tree[name].url:
381 logging.info('Won\'t process duplicate dependency %s' % tree[name])
382 # In theory we could keep it as a shadow of the other one. In
383 # practice, simply ignore it.
384 #should_process = False
385 continue
386 else:
387 raise gclient_utils.Error(
388 'Dependency %s specified more than once:\n %s\nvs\n %s' %
389 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000390 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000391 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000392 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000393
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000394 # Arguments number differs from overridden method
395 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000396 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000397 """Runs 'command' before parsing the DEPS in case it's a initial checkout
398 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000399
400 def maybeGetParentRevision(options):
401 """If we are performing an update and --transitive is set, set the
402 revision to the parent's revision. If we have an explicit revision
403 do nothing."""
404 if command == 'update' and options.transitive and not options.revision:
405 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
406 if not revision:
407 options.revision = revision_overrides.get(self.parent.name)
408 if options.verbose and options.revision:
409 print("Using parent's revision date: %s" % options.revision)
410 # If the parent has a revision override, then it must have been
411 # converted to date format.
412 assert (not options.revision or
413 gclient_utils.IsDateRevision(options.revision))
414
415 def maybeConvertToDateRevision(options):
416 """If we are performing an update and --transitive is set, convert the
417 revision to a date-revision (if necessary). Instead of having
418 -r 101 replace the revision with the time stamp of 101 (e.g.
419 "{2011-18-04}").
420 This way dependencies are upgraded to the revision they had at the
421 check-in of revision 101."""
422 if (command == 'update' and
423 options.transitive and
424 options.revision and
425 not gclient_utils.IsDateRevision(options.revision)):
426 revision_date = scm.GetRevisionDate(options.revision)
427 revision = gclient_utils.MakeDateRevision(revision_date)
428 if options.verbose:
429 print("Updating revision override from %s to %s." %
430 (options.revision, revision))
431 revision_overrides[self.name] = revision
432
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000433 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000434 if not self.should_process:
435 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000436 # When running runhooks, there's no need to consult the SCM.
437 # All known hooks are expected to run unconditionally regardless of working
438 # copy state, so skip the SCM status check.
439 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000440 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000441 if run_scm and self.parsed_url:
442 if isinstance(self.parsed_url, self.FileImpl):
443 # Special support for single-file checkout.
444 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
445 options.revision = self.parsed_url.GetRevision()
446 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
447 self.root_dir(),
448 self.name)
449 scm.RunCommand('updatesingle', options,
450 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000451 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000452 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000453 # Create a shallow copy to mutate revision.
454 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000456 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000457 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000458 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000459 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000460 self._file_list = [os.path.join(self.name, f.strip())
461 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000462 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000463 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000464 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000465 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000466
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000467 # Parse the dependencies of this dependency.
468 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000469 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000470
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000472 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000474 assert self.hooks_ran == False
475 if not self.should_process or self.recursion_limit() <= 0:
476 # Don't run the hook when it is above recursion_limit.
477 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000478 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000479 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000480 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000481 # TODO(maruel): If the user is using git or git-svn, then we don't know
482 # what files have changed so we always run all hooks. It'd be nice to fix
483 # that.
484 if (options.force or
485 isinstance(self.parsed_url, self.FileImpl) or
486 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
487 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
488 for hook_dict in self.deps_hooks:
489 self._RunHookAction(hook_dict, [])
490 else:
491 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
492 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000493 file_list = self.file_list()
494 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000495 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000496 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000497 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000498
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000499 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000500 file_list[i].lower()])
501 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502
503 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000504 while (file_list[i].startswith('\\') or
505 file_list[i].startswith('/')):
506 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507
508 # Run hooks on the basis of whether the files from the gclient operation
509 # match each hook's pattern.
510 for hook_dict in self.deps_hooks:
511 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000512 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000513 if matching_file_list:
514 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000515 for s in self.dependencies:
516 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000517
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000518 def _RunHookAction(self, hook_dict, matching_file_list):
519 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000520 # A single DEPS file can specify multiple hooks so this function can be
521 # called multiple times on a single Dependency.
522 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000523 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000524 logging.debug(hook_dict)
525 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000526 command = hook_dict['action'][:]
527 if command[0] == 'python':
528 # If the hook specified "python" as the first item, the action is a
529 # Python script. Run it by starting a new copy of the same
530 # interpreter.
531 command[0] = sys.executable
532
533 if '$matching_files' in command:
534 splice_index = command.index('$matching_files')
535 command[splice_index:splice_index + 1] = matching_file_list
536
maruel@chromium.org17d01792010-09-01 18:07:10 +0000537 try:
538 gclient_utils.CheckCallAndFilterAndHeader(
539 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000540 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000541 # Use a discrete exit status code of 2 to indicate that a hook action
542 # failed. Users of this script may wish to treat hook action failures
543 # differently from VC failures.
544 print >> sys.stderr, 'Error: %s' % str(e)
545 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000546
maruel@chromium.org271375b2010-06-23 19:17:38 +0000547 def root_dir(self):
548 return self.parent.root_dir()
549
550 def enforced_os(self):
551 return self.parent.enforced_os()
552
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000553 def recursion_limit(self):
554 return self.parent.recursion_limit() - 1
555
maruel@chromium.org0d812442010-08-10 12:41:08 +0000556 def tree(self, include_all):
557 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000558
maruel@chromium.org0d812442010-08-10 12:41:08 +0000559 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000560 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000561 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000562 for d in self.dependencies:
563 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000564 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000565 for d in self.dependencies:
566 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000567 return result
568
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000569 def get_custom_deps(self, name, url):
570 """Returns a custom deps if applicable."""
571 if self.parent:
572 url = self.parent.get_custom_deps(name, url)
573 # None is a valid return value to disable a dependency.
574 return self.custom_deps.get(name, url)
575
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000576 def file_list(self):
577 result = self._file_list[:]
578 for d in self.dependencies:
579 result.extend(d.file_list())
580 return result
581
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000582 def __str__(self):
583 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000584 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000585 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
586 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000587 # 'deps_file'
588 if self.__dict__[i]:
589 out.append('%s: %s' % (i, self.__dict__[i]))
590
591 for d in self.dependencies:
592 out.extend([' ' + x for x in str(d).splitlines()])
593 out.append('')
594 return '\n'.join(out)
595
596 def __repr__(self):
597 return '%s: %s' % (self.name, self.url)
598
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000599 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000600 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000601 out = '%s(%s)' % (self.name, self.url)
602 i = self.parent
603 while i and i.name:
604 out = '%s(%s) -> %s' % (i.name, i.url, out)
605 i = i.parent
606 return out
607
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000608 def root_parent(self):
609 """Returns the root object, normally a GClient object."""
610 d = self
611 while d.parent:
612 d = d.parent
613 return d
614
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000615
616class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000617 """Object that represent a gclient checkout. A tree of Dependency(), one per
618 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000619
620 DEPS_OS_CHOICES = {
621 "win32": "win",
622 "win": "win",
623 "cygwin": "win",
624 "darwin": "mac",
625 "mac": "mac",
626 "unix": "unix",
627 "linux": "unix",
628 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000629 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000630 }
631
632 DEFAULT_CLIENT_FILE_TEXT = ("""\
633solutions = [
634 { "name" : "%(solution_name)s",
635 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000636 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000637 "custom_deps" : {
638 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000639 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000640 },
641]
642""")
643
644 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
645 { "name" : "%(solution_name)s",
646 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000647 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000648 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000649%(solution_deps)s },
650 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000651 },
652""")
653
654 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
655# Snapshot generated with gclient revinfo --snapshot
656solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000657%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000658""")
659
660 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000661 # Do not change previous behavior. Only solution level and immediate DEPS
662 # are processed.
663 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000664 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
665 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000666 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000667 if options.deps_os:
668 enforced_os = options.deps_os.split(',')
669 else:
670 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
671 if 'all' in enforced_os:
672 enforced_os = self.DEPS_OS_CHOICES.itervalues()
673 self._enforced_os = list(set(enforced_os))
674 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000675 self.config_content = None
676
677 def SetConfig(self, content):
678 assert self.dependencies == []
679 config_dict = {}
680 self.config_content = content
681 try:
682 exec(content, config_dict)
683 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000684 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000685 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000686 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000687 tree = dict((d.name, d) for d in self.tree(False))
688 if s['name'] in tree:
689 raise gclient_utils.Error(
690 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000691 self.dependencies.append(Dependency(
692 self, s['name'], s['url'],
693 s.get('safesync_url', None),
694 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000695 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000696 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000697 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000698 except KeyError:
699 raise gclient_utils.Error('Invalid .gclient file. Solution is '
700 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000701 # .gclient can have hooks.
702 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000703 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000704
705 def SaveConfig(self):
706 gclient_utils.FileWrite(os.path.join(self.root_dir(),
707 self._options.config_filename),
708 self.config_content)
709
710 @staticmethod
711 def LoadCurrentConfig(options):
712 """Searches for and loads a .gclient file relative to the current working
713 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000714 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000715 if not path:
716 return None
717 client = GClient(path, options)
718 client.SetConfig(gclient_utils.FileRead(
719 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000720 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000721
nsylvain@google.comefc80932011-05-31 21:27:56 +0000722 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
723 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000724 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
725 'solution_name': solution_name,
726 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000727 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000728 'safesync_url' : safesync_url,
729 })
730
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000731 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000732 """Creates a .gclient_entries file to record the list of unique checkouts.
733
734 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000735 """
736 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
737 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 result = 'entries = {\n'
739 for entry in self.tree(False):
740 # Skip over File() dependencies as we can't version them.
741 if not isinstance(entry.parsed_url, self.FileImpl):
742 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
743 pprint.pformat(entry.parsed_url))
744 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000745 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000746 logging.info(result)
747 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000748
749 def _ReadEntries(self):
750 """Read the .gclient_entries file for the given client.
751
752 Returns:
753 A sequence of solution names, which will be empty if there is the
754 entries file hasn't been created yet.
755 """
756 scope = {}
757 filename = os.path.join(self.root_dir(), self._options.entries_filename)
758 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000759 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000760 try:
761 exec(gclient_utils.FileRead(filename), scope)
762 except SyntaxError, e:
763 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000764 return scope['entries']
765
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000766 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000767 """Checks for revision overrides."""
768 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000769 if self._options.head:
770 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000771 # Do not check safesync_url if one or more --revision flag is specified.
772 if not self._options.revisions:
773 for s in self.dependencies:
774 if not s.safesync_url:
775 continue
776 handle = urllib.urlopen(s.safesync_url)
777 rev = handle.read().strip()
778 handle.close()
779 if len(rev):
780 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000781 if not self._options.revisions:
782 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000783 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000784 index = 0
785 for revision in self._options.revisions:
786 if not '@' in revision:
787 # Support for --revision 123
788 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000789 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000790 if not sol in solutions_names:
791 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
792 print >> sys.stderr, ('Please fix your script, having invalid '
793 '--revision flags will soon considered an error.')
794 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000795 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000796 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000797 return revision_overrides
798
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000799 def RunOnDeps(self, command, args):
800 """Runs a command on each dependency in a client and its dependencies.
801
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802 Args:
803 command: The command to use (e.g., 'status' or 'diff')
804 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000806 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000807 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000808 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000809 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000810 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000811 if (command in ('update', 'revert') and sys.stdout.isatty() and not
812 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000813 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000814 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000815 for s in self.dependencies:
816 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000817 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000818
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000819 # Once all the dependencies have been processed, it's now safe to run the
820 # hooks.
821 if not self._options.nohooks:
822 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823
824 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000825 # Notify the user if there is an orphaned entry in their working copy.
826 # Only delete the directory if there are no changes in it, and
827 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000828 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000830 if not prev_url:
831 # entry must have been overridden via .gclient custom_deps
832 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000833 # Fix path separator on Windows.
834 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000835 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000836 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000837 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000838 file_list = []
839 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
840 scm.status(self._options, [], file_list)
841 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000842 if (not self._options.delete_unversioned_trees or
843 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000844 # There are modified files in this entry. Keep warning until
845 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000846 print(('\nWARNING: \'%s\' is no longer part of this client. '
847 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000848 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000849 else:
850 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000851 print('\n________ deleting \'%s\' in \'%s\'' % (
852 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000853 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000854 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000855 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000856 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000857
858 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000859 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000860 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000861 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000862 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000863 for s in self.dependencies:
864 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000865 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000867 def GetURLAndRev(dep):
868 """Returns the revision-qualified SCM url for a Dependency."""
869 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000870 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000871 if isinstance(dep.parsed_url, self.FileImpl):
872 original_url = dep.parsed_url.file_location
873 else:
874 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000875 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000876 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000877 if not os.path.isdir(scm.checkout_path):
878 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000879 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000880
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000881 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000882 new_gclient = ''
883 # First level at .gclient
884 for d in self.dependencies:
885 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000886 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000887 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000888 for d in dep.dependencies:
889 entries[d.name] = GetURLAndRev(d)
890 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000891 GrabDeps(d)
892 custom_deps = []
893 for k in sorted(entries.keys()):
894 if entries[k]:
895 # Quotes aren't escaped...
896 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
897 else:
898 custom_deps.append(' \"%s\": None,\n' % k)
899 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
900 'solution_name': d.name,
901 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000902 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000903 'safesync_url' : d.safesync_url or '',
904 'solution_deps': ''.join(custom_deps),
905 }
906 # Print the snapshot configuration file
907 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000908 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000909 entries = {}
910 for d in self.tree(False):
911 if self._options.actual:
912 entries[d.name] = GetURLAndRev(d)
913 else:
914 entries[d.name] = d.parsed_url
915 keys = sorted(entries.keys())
916 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000917 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000918 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000919
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000920 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000921 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000922 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000923
maruel@chromium.org75a59272010-06-11 22:34:03 +0000924 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000925 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000926 return self._root_dir
927
maruel@chromium.org271375b2010-06-23 19:17:38 +0000928 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000929 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000930 return self._enforced_os
931
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000932 def recursion_limit(self):
933 """How recursive can each dependencies in DEPS file can load DEPS file."""
934 return self._recursion_limit
935
maruel@chromium.org0d812442010-08-10 12:41:08 +0000936 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000937 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000938 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000939
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000940
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000941#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942
943
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000944def CMDcleanup(parser, args):
945 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000946
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000947Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000948"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000949 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
950 help='override deps for the specified (comma-separated) '
951 'platform(s); \'all\' will process all deps_os '
952 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000953 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000954 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000956 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000957 if options.verbose:
958 # Print out the .gclient file. This is longer than if we just printed the
959 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000960 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000961 return client.RunOnDeps('cleanup', args)
962
963
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000964@attr('usage', '[command] [args ...]')
965def CMDrecurse(parser, args):
966 """Operates on all the entries.
967
968 Runs a shell command on all entries.
969 """
970 # Stop parsing at the first non-arg so that these go through to the command
971 parser.disable_interspersed_args()
972 parser.add_option('-s', '--scm', action='append', default=[],
973 help='choose scm types to operate upon')
974 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000975 if not args:
976 print >> sys.stderr, 'Need to supply a command!'
977 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000978 root_and_entries = gclient_utils.GetGClientRootAndEntries()
979 if not root_and_entries:
980 print >> sys.stderr, (
981 'You need to run gclient sync at least once to use \'recurse\'.\n'
982 'This is because .gclient_entries needs to exist and be up to date.')
983 return 1
984 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000985 scm_set = set()
986 for scm in options.scm:
987 scm_set.update(scm.split(','))
988
989 # Pass in the SCM type as an env variable
990 env = os.environ.copy()
991
992 for path, url in entries.iteritems():
993 scm = gclient_scm.GetScmName(url)
994 if scm_set and scm not in scm_set:
995 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000996 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000997 if scm:
998 env['GCLIENT_SCM'] = scm
999 if url:
1000 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001001 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001002 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001003
1004
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001005@attr('usage', '[url] [safesync url]')
1006def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001007 """Create a .gclient file in the current directory.
1008
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001009This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001010top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001011modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001012provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001013URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001014"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001015 parser.add_option('--spec',
1016 help='create a gclient file containing the provided '
1017 'string. Due to Cygwin/Python brokenness, it '
1018 'probably can\'t contain any newlines.')
1019 parser.add_option('--name',
1020 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001021 parser.add_option('--deps-file', default='DEPS',
1022 help='overrides the default name for the DEPS file for the'
1023 'main solutions and all sub-dependencies')
1024 parser.add_option('--git-deps', action='store_true',
1025 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001026 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001027 if ((options.spec and args) or len(args) > 2 or
1028 (not options.spec and not args)):
1029 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1030
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001031 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032 if options.spec:
1033 client.SetConfig(options.spec)
1034 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001035 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001036 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001037 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001038 if name.endswith('.git'):
1039 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001040 else:
1041 # specify an alternate relpath for the given URL.
1042 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001043 deps_file = options.deps_file
1044 if options.git_deps:
1045 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001046 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 if len(args) > 1:
1048 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001049 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001051 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052
1053
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001054@attr('epilog', """Example:
1055 gclient pack > patch.txt
1056 generate simple patch for configured client and dependences
1057""")
1058def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001059 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001060
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001061Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001062dependencies, and performs minimal postprocessing of the output. The
1063resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001064checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001065"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001066 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1067 help='override deps for the specified (comma-separated) '
1068 'platform(s); \'all\' will process all deps_os '
1069 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001070 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001071 client = GClient.LoadCurrentConfig(options)
1072 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001073 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001074 if options.verbose:
1075 # Print out the .gclient file. This is longer than if we just printed the
1076 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001077 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001078 return client.RunOnDeps('pack', args)
1079
1080
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001081def CMDstatus(parser, args):
1082 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001083 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1084 help='override deps for the specified (comma-separated) '
1085 'platform(s); \'all\' will process all deps_os '
1086 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001087 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001088 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001090 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091 if options.verbose:
1092 # Print out the .gclient file. This is longer than if we just printed the
1093 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001094 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001095 return client.RunOnDeps('status', args)
1096
1097
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001098@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001099 gclient sync
1100 update files from SCM according to current configuration,
1101 *for modules which have changed since last update or sync*
1102 gclient sync --force
1103 update files from SCM according to current configuration, for
1104 all modules (useful for recovering files deleted from local copy)
1105 gclient sync --revision src@31000
1106 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001107""")
1108def CMDsync(parser, args):
1109 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001110 parser.add_option('-f', '--force', action='store_true',
1111 help='force update even for unchanged modules')
1112 parser.add_option('-n', '--nohooks', action='store_true',
1113 help='don\'t run hooks after the update is complete')
1114 parser.add_option('-r', '--revision', action='append',
1115 dest='revisions', metavar='REV', default=[],
1116 help='Enforces revision/hash for the solutions with the '
1117 'format src@rev. The src@ part is optional and can be '
1118 'skipped. -r can be used multiple times when .gclient '
1119 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001120 'if the src@ part is skipped. Note that specifying '
1121 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001122 parser.add_option('-t', '--transitive', action='store_true',
1123 help='When a revision is specified (in the DEPS file or '
1124 'with the command-line flag), transitively update '
1125 'the dependencies to the date of the given revision. '
1126 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001127 parser.add_option('-H', '--head', action='store_true',
1128 help='skips any safesync_urls specified in '
1129 'configured solutions and sync to head instead')
1130 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001131 help='delete any dependency that have been removed from '
1132 'last sync as long as there is no local modification. '
1133 'Coupled with --force, it will remove them even with '
1134 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001135 parser.add_option('-R', '--reset', action='store_true',
1136 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001137 parser.add_option('-M', '--merge', action='store_true',
1138 help='merge upstream changes instead of trying to '
1139 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001140 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1141 help='override deps for the specified (comma-separated) '
1142 'platform(s); \'all\' will process all deps_os '
1143 'references')
1144 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1145 help='Skip svn up whenever possible by requesting '
1146 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001147 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001148 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149
1150 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001151 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152
maruel@chromium.org307d1792010-05-31 20:03:13 +00001153 if options.revisions and options.head:
1154 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001155 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156
1157 if options.verbose:
1158 # Print out the .gclient file. This is longer than if we just printed the
1159 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001160 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161 return client.RunOnDeps('update', args)
1162
1163
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001164def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001165 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001167
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001168def CMDdiff(parser, args):
1169 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001170 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1171 help='override deps for the specified (comma-separated) '
1172 'platform(s); \'all\' will process all deps_os '
1173 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001174 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001175 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001176 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001177 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001178 if options.verbose:
1179 # Print out the .gclient file. This is longer than if we just printed the
1180 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001181 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001182 return client.RunOnDeps('diff', args)
1183
1184
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001185def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001186 """Revert all modifications in every dependencies.
1187
1188 That's the nuclear option to get back to a 'clean' state. It removes anything
1189 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001190 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1191 help='override deps for the specified (comma-separated) '
1192 'platform(s); \'all\' will process all deps_os '
1193 'references')
1194 parser.add_option('-n', '--nohooks', action='store_true',
1195 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001196 (options, args) = parser.parse_args(args)
1197 # --force is implied.
1198 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001199 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001201 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202 return client.RunOnDeps('revert', args)
1203
1204
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001205def CMDrunhooks(parser, args):
1206 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001207 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1208 help='override deps for the specified (comma-separated) '
1209 'platform(s); \'all\' will process all deps_os '
1210 'references')
1211 parser.add_option('-f', '--force', action='store_true', default=True,
1212 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001213 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001214 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001215 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001216 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001217 if options.verbose:
1218 # Print out the .gclient file. This is longer than if we just printed the
1219 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001220 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001221 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001222 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001223 return client.RunOnDeps('runhooks', args)
1224
1225
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001226def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001227 """Output revision info mapping for the client and its dependencies.
1228
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001229 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001230 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001231 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1232 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001233 commit can change.
1234 """
1235 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1236 help='override deps for the specified (comma-separated) '
1237 'platform(s); \'all\' will process all deps_os '
1238 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001239 parser.add_option('-a', '--actual', action='store_true',
1240 help='gets the actual checked out revisions instead of the '
1241 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001242 parser.add_option('-s', '--snapshot', action='store_true',
1243 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001244 'version of all repositories to reproduce the tree, '
1245 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001246 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001247 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001249 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001250 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001251 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252
1253
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001254def Command(name):
1255 return getattr(sys.modules[__name__], 'CMD' + name, None)
1256
1257
1258def CMDhelp(parser, args):
1259 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001260 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001261 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001263 parser.print_help()
1264 return 0
1265
1266
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001267def GenUsage(parser, command):
1268 """Modify an OptParse object with the function's documentation."""
1269 obj = Command(command)
1270 if command == 'help':
1271 command = '<command>'
1272 # OptParser.description prefer nicely non-formatted strings.
1273 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1274 usage = getattr(obj, 'usage', '')
1275 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1276 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277
1278
maruel@chromium.org0895b752011-08-26 20:40:33 +00001279def Parser():
1280 """Returns the default parser."""
1281 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001282 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001283 help='Specify how many SCM commands can run in parallel; '
1284 'default=%default')
1285 parser.add_option('-v', '--verbose', action='count', default=0,
1286 help='Produces additional output for diagnostics. Can be '
1287 'used up to three times for more logging info.')
1288 parser.add_option('--gclientfile', dest='config_filename',
1289 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1290 help='Specify an alternate %default file')
1291 # Integrate standard options processing.
1292 old_parser = parser.parse_args
1293 def Parse(args):
1294 (options, args) = old_parser(args)
1295 level = None
1296 if options.verbose == 2:
1297 level = logging.INFO
1298 elif options.verbose > 2:
1299 level = logging.DEBUG
1300 logging.basicConfig(level=level,
1301 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1302 options.entries_filename = options.config_filename + '_entries'
1303 if options.jobs < 1:
1304 parser.error('--jobs must be 1 or higher')
1305
1306 # These hacks need to die.
1307 if not hasattr(options, 'revisions'):
1308 # GClient.RunOnDeps expects it even if not applicable.
1309 options.revisions = []
1310 if not hasattr(options, 'head'):
1311 options.head = None
1312 if not hasattr(options, 'nohooks'):
1313 options.nohooks = True
1314 if not hasattr(options, 'deps_os'):
1315 options.deps_os = None
1316 if not hasattr(options, 'manually_grab_svn_rev'):
1317 options.manually_grab_svn_rev = None
1318 if not hasattr(options, 'force'):
1319 options.force = None
1320 return (options, args)
1321 parser.parse_args = Parse
1322 # We don't want wordwrapping in epilog (usually examples)
1323 parser.format_epilog = lambda _: parser.epilog or ''
1324 return parser
1325
1326
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001327def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001328 """Doesn't parse the arguments here, just find the right subcommand to
1329 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001330 if sys.hexversion < 0x02050000:
1331 print >> sys.stderr, (
1332 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001333 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001334 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1335 # operations. Python as a strong tendency to buffer sys.stdout.
1336 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001337 # Make stdout annotated with the thread ids.
1338 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001339 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001340 # Unused variable 'usage'
1341 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001342 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1343 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1344 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001345 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001346 if argv:
1347 command = Command(argv[0])
1348 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001349 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001350 GenUsage(parser, argv[0])
1351 return command(parser, argv[1:])
1352 # Not a known command. Default to help.
1353 GenUsage(parser, 'help')
1354 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001355 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001356 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001357 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001358
1359
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001360if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001361 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001362 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001363
1364# vim: ts=2:sw=2:tw=80:et: