blob: 046b4fc8907cff8df035222a833bd15a05a48342 [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.org6ca8bf82011-09-19 23:04:30 +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.org6ca8bf82011-09-19 23:04:30 +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.org6ca8bf82011-09-19 23:04:30 +0000219 self._requirements.add(value.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000220
221 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +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.org6ca8bf82011-09-19 23:04:30 +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, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000241 try:
242 # Access to a protected member _requirements of a client class
243 # pylint: disable=W0212
244 obj.lock.acquire()
245 obj._requirements.add(self.name)
246 finally:
247 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000248
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000249 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000250 """Resolves the parsed url from url.
251
252 Manages From() keyword accordingly. Do not touch self.parsed_url nor
253 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000254 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 overriden_url = self.get_custom_deps(self.name, url)
256 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000257 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000258 overriden_url))
259 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000260 elif isinstance(url, self.FromImpl):
261 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000262 if not ref:
263 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
264 url.module_name, ref))
265 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000266 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000267 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000268 # Make sure the referenced dependency DEPS file is loaded and file the
269 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000270 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000271 found_dep = None
272 for d in ref.dependencies:
273 if d.name == sub_target:
274 found_dep = d
275 break
276 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000277 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000278 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
279 sub_target, ref.name, self.name, self.parent.name,
280 str(self.root_parent())))
281
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000282 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000283 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000284 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000285 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000286 elif isinstance(url, basestring):
287 parsed_url = urlparse.urlparse(url)
288 if not parsed_url[0]:
289 # A relative url. Fetch the real base.
290 path = parsed_url[2]
291 if not path.startswith('/'):
292 raise gclient_utils.Error(
293 'relative DEPS entry \'%s\' must begin with a slash' % url)
294 # Create a scm just to query the full url.
295 parent_url = self.parent.parsed_url
296 if isinstance(parent_url, self.FileImpl):
297 parent_url = parent_url.file_location
298 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000299 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000300 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000301 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000302 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000303 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000304 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000305 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000306 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000307 return parsed_url
308 elif url is None:
309 return None
310 else:
311 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000313 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000314 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000315 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000316 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000317 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000318 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000319 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000320 # One thing is unintuitive, vars= {} must happen before Var() use.
321 local_scope = {}
322 var = self.VarImpl(self.custom_vars, local_scope)
323 global_scope = {
324 'File': self.FileImpl,
325 'From': self.FromImpl,
326 'Var': var.Lookup,
327 'deps_os': {},
328 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000329 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
330 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000331 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
332 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000333 else:
334 deps_content = gclient_utils.FileRead(filepath)
335 logging.debug(deps_content)
336 # Eval the content.
337 try:
338 exec(deps_content, global_scope, local_scope)
339 except SyntaxError, e:
340 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000341 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000342 # load os specific dependencies if defined. these dependencies may
343 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000344 if 'deps_os' in local_scope:
345 for deps_os_key in self.enforced_os():
346 os_deps = local_scope['deps_os'].get(deps_os_key, {})
347 if len(self.enforced_os()) > 1:
348 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000349 # platform, so we collect the broadest set of dependencies
350 # available. We may end up with the wrong revision of something for
351 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000352 deps.update([x for x in os_deps.items() if not x[0] in deps])
353 else:
354 deps.update(os_deps)
355
maruel@chromium.org271375b2010-06-23 19:17:38 +0000356 self.deps_hooks.extend(local_scope.get('hooks', []))
357
358 # If a line is in custom_deps, but not in the solution, we want to append
359 # this line to the solution.
360 for d in self.custom_deps:
361 if d not in deps:
362 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000363
364 # If use_relative_paths is set in the DEPS file, regenerate
365 # the dictionary using paths relative to the directory containing
366 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000367 use_relative_paths = local_scope.get('use_relative_paths', False)
368 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000369 rel_deps = {}
370 for d, url in deps.items():
371 # normpath is required to allow DEPS to use .. in their
372 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000373 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
374 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376 # Convert the deps into real Dependency.
377 for name, url in deps.iteritems():
378 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000379 raise gclient_utils.Error(
380 'The same name "%s" appears multiple times in the deps section' %
381 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000382 should_process = self.recursion_limit() > 0 and self.should_process
383 if should_process:
384 tree = dict((d.name, d) for d in self.tree(False))
385 if name in tree:
386 if url == tree[name].url:
387 logging.info('Won\'t process duplicate dependency %s' % tree[name])
388 # In theory we could keep it as a shadow of the other one. In
389 # practice, simply ignore it.
390 #should_process = False
391 continue
392 else:
393 raise gclient_utils.Error(
394 'Dependency %s specified more than once:\n %s\nvs\n %s' %
395 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000396 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000397 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000398 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000399
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000400 # Arguments number differs from overridden method
401 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000402 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000403 """Runs 'command' before parsing the DEPS in case it's a initial checkout
404 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000405
406 def maybeGetParentRevision(options):
407 """If we are performing an update and --transitive is set, set the
408 revision to the parent's revision. If we have an explicit revision
409 do nothing."""
410 if command == 'update' and options.transitive and not options.revision:
411 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
412 if not revision:
413 options.revision = revision_overrides.get(self.parent.name)
414 if options.verbose and options.revision:
415 print("Using parent's revision date: %s" % options.revision)
416 # If the parent has a revision override, then it must have been
417 # converted to date format.
418 assert (not options.revision or
419 gclient_utils.IsDateRevision(options.revision))
420
421 def maybeConvertToDateRevision(options):
422 """If we are performing an update and --transitive is set, convert the
423 revision to a date-revision (if necessary). Instead of having
424 -r 101 replace the revision with the time stamp of 101 (e.g.
425 "{2011-18-04}").
426 This way dependencies are upgraded to the revision they had at the
427 check-in of revision 101."""
428 if (command == 'update' and
429 options.transitive and
430 options.revision and
431 not gclient_utils.IsDateRevision(options.revision)):
432 revision_date = scm.GetRevisionDate(options.revision)
433 revision = gclient_utils.MakeDateRevision(revision_date)
434 if options.verbose:
435 print("Updating revision override from %s to %s." %
436 (options.revision, revision))
437 revision_overrides[self.name] = revision
438
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000439 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000440 if not self.should_process:
441 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000442 # When running runhooks, there's no need to consult the SCM.
443 # All known hooks are expected to run unconditionally regardless of working
444 # copy state, so skip the SCM status check.
445 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000446 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000447 if run_scm and self.parsed_url:
448 if isinstance(self.parsed_url, self.FileImpl):
449 # Special support for single-file checkout.
450 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
451 options.revision = self.parsed_url.GetRevision()
452 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
453 self.root_dir(),
454 self.name)
455 scm.RunCommand('updatesingle', options,
456 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000457 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000459 # Create a shallow copy to mutate revision.
460 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000461 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000462 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000463 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000464 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000465 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000466 self._file_list = [os.path.join(self.name, f.strip())
467 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000468 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000469 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000470 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000471 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000472
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 # Parse the dependencies of this dependency.
474 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000475 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000476
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000477 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000478 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000479 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000480 assert self.hooks_ran == False
481 if not self.should_process or self.recursion_limit() <= 0:
482 # Don't run the hook when it is above recursion_limit.
483 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000484 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000485 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000486 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000487 # TODO(maruel): If the user is using git or git-svn, then we don't know
488 # what files have changed so we always run all hooks. It'd be nice to fix
489 # that.
490 if (options.force or
491 isinstance(self.parsed_url, self.FileImpl) or
492 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
493 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
494 for hook_dict in self.deps_hooks:
495 self._RunHookAction(hook_dict, [])
496 else:
497 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
498 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000499 file_list = self.file_list()
500 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000501 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000502 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000504
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000506 file_list[i].lower()])
507 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508
509 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000510 while (file_list[i].startswith('\\') or
511 file_list[i].startswith('/')):
512 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000513
514 # Run hooks on the basis of whether the files from the gclient operation
515 # match each hook's pattern.
516 for hook_dict in self.deps_hooks:
517 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000518 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 if matching_file_list:
520 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000521 for s in self.dependencies:
522 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000523
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000524 def _RunHookAction(self, hook_dict, matching_file_list):
525 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000526 # A single DEPS file can specify multiple hooks so this function can be
527 # called multiple times on a single Dependency.
528 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000529 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000530 logging.debug(hook_dict)
531 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000532 command = hook_dict['action'][:]
533 if command[0] == 'python':
534 # If the hook specified "python" as the first item, the action is a
535 # Python script. Run it by starting a new copy of the same
536 # interpreter.
537 command[0] = sys.executable
538
539 if '$matching_files' in command:
540 splice_index = command.index('$matching_files')
541 command[splice_index:splice_index + 1] = matching_file_list
542
maruel@chromium.org17d01792010-09-01 18:07:10 +0000543 try:
544 gclient_utils.CheckCallAndFilterAndHeader(
545 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000546 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000547 # Use a discrete exit status code of 2 to indicate that a hook action
548 # failed. Users of this script may wish to treat hook action failures
549 # differently from VC failures.
550 print >> sys.stderr, 'Error: %s' % str(e)
551 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000552
maruel@chromium.org271375b2010-06-23 19:17:38 +0000553 def root_dir(self):
554 return self.parent.root_dir()
555
556 def enforced_os(self):
557 return self.parent.enforced_os()
558
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000559 def recursion_limit(self):
560 return self.parent.recursion_limit() - 1
561
maruel@chromium.org0d812442010-08-10 12:41:08 +0000562 def tree(self, include_all):
563 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000564
maruel@chromium.org0d812442010-08-10 12:41:08 +0000565 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000566 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000567 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000568 for d in self.dependencies:
569 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000570 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000571 for d in self.dependencies:
572 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000573 return result
574
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000575 def get_custom_deps(self, name, url):
576 """Returns a custom deps if applicable."""
577 if self.parent:
578 url = self.parent.get_custom_deps(name, url)
579 # None is a valid return value to disable a dependency.
580 return self.custom_deps.get(name, url)
581
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000582 def file_list(self):
583 result = self._file_list[:]
584 for d in self.dependencies:
585 result.extend(d.file_list())
586 return result
587
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000588 def __str__(self):
589 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000590 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000591 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000592 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000593 # First try the native property if it exists.
594 if hasattr(self, '_' + i):
595 value = getattr(self, '_' + i, False)
596 else:
597 value = getattr(self, i, False)
598 if value:
599 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000600
601 for d in self.dependencies:
602 out.extend([' ' + x for x in str(d).splitlines()])
603 out.append('')
604 return '\n'.join(out)
605
606 def __repr__(self):
607 return '%s: %s' % (self.name, self.url)
608
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000609 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000610 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000611 out = '%s(%s)' % (self.name, self.url)
612 i = self.parent
613 while i and i.name:
614 out = '%s(%s) -> %s' % (i.name, i.url, out)
615 i = i.parent
616 return out
617
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000618 def root_parent(self):
619 """Returns the root object, normally a GClient object."""
620 d = self
621 while d.parent:
622 d = d.parent
623 return d
624
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000625
626class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000627 """Object that represent a gclient checkout. A tree of Dependency(), one per
628 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000629
630 DEPS_OS_CHOICES = {
631 "win32": "win",
632 "win": "win",
633 "cygwin": "win",
634 "darwin": "mac",
635 "mac": "mac",
636 "unix": "unix",
637 "linux": "unix",
638 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000639 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000640 }
641
642 DEFAULT_CLIENT_FILE_TEXT = ("""\
643solutions = [
644 { "name" : "%(solution_name)s",
645 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000646 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000647 "custom_deps" : {
648 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000649 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650 },
651]
652""")
653
654 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
655 { "name" : "%(solution_name)s",
656 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000657 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000658 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000659%(solution_deps)s },
660 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000661 },
662""")
663
664 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
665# Snapshot generated with gclient revinfo --snapshot
666solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000667%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000668""")
669
670 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000671 # Do not change previous behavior. Only solution level and immediate DEPS
672 # are processed.
673 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000674 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
675 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000676 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000677 if options.deps_os:
678 enforced_os = options.deps_os.split(',')
679 else:
680 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
681 if 'all' in enforced_os:
682 enforced_os = self.DEPS_OS_CHOICES.itervalues()
683 self._enforced_os = list(set(enforced_os))
684 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000685 self.config_content = None
686
687 def SetConfig(self, content):
688 assert self.dependencies == []
689 config_dict = {}
690 self.config_content = content
691 try:
692 exec(content, config_dict)
693 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000694 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000695 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000696 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000697 tree = dict((d.name, d) for d in self.tree(False))
698 if s['name'] in tree:
699 raise gclient_utils.Error(
700 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000701 self.dependencies.append(Dependency(
702 self, s['name'], s['url'],
703 s.get('safesync_url', None),
704 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000705 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000706 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000707 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000708 except KeyError:
709 raise gclient_utils.Error('Invalid .gclient file. Solution is '
710 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000711 # .gclient can have hooks.
712 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000713 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000714
715 def SaveConfig(self):
716 gclient_utils.FileWrite(os.path.join(self.root_dir(),
717 self._options.config_filename),
718 self.config_content)
719
720 @staticmethod
721 def LoadCurrentConfig(options):
722 """Searches for and loads a .gclient file relative to the current working
723 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000724 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000725 if not path:
726 return None
727 client = GClient(path, options)
728 client.SetConfig(gclient_utils.FileRead(
729 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000730 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000731
nsylvain@google.comefc80932011-05-31 21:27:56 +0000732 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
733 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000734 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
735 'solution_name': solution_name,
736 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000737 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000738 'safesync_url' : safesync_url,
739 })
740
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000741 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742 """Creates a .gclient_entries file to record the list of unique checkouts.
743
744 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000745 """
746 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
747 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000748 result = 'entries = {\n'
749 for entry in self.tree(False):
750 # Skip over File() dependencies as we can't version them.
751 if not isinstance(entry.parsed_url, self.FileImpl):
752 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
753 pprint.pformat(entry.parsed_url))
754 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000755 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000756 logging.info(result)
757 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000758
759 def _ReadEntries(self):
760 """Read the .gclient_entries file for the given client.
761
762 Returns:
763 A sequence of solution names, which will be empty if there is the
764 entries file hasn't been created yet.
765 """
766 scope = {}
767 filename = os.path.join(self.root_dir(), self._options.entries_filename)
768 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000769 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000770 try:
771 exec(gclient_utils.FileRead(filename), scope)
772 except SyntaxError, e:
773 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000774 return scope['entries']
775
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000776 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000777 """Checks for revision overrides."""
778 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000779 if self._options.head:
780 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000781 # Do not check safesync_url if one or more --revision flag is specified.
782 if not self._options.revisions:
783 for s in self.dependencies:
784 if not s.safesync_url:
785 continue
786 handle = urllib.urlopen(s.safesync_url)
787 rev = handle.read().strip()
788 handle.close()
789 if len(rev):
790 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000791 if not self._options.revisions:
792 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000793 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000794 index = 0
795 for revision in self._options.revisions:
796 if not '@' in revision:
797 # Support for --revision 123
798 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000799 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000800 if not sol in solutions_names:
801 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
802 print >> sys.stderr, ('Please fix your script, having invalid '
803 '--revision flags will soon considered an error.')
804 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000805 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000806 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000807 return revision_overrides
808
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809 def RunOnDeps(self, command, args):
810 """Runs a command on each dependency in a client and its dependencies.
811
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000812 Args:
813 command: The command to use (e.g., 'status' or 'diff')
814 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000815 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000816 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000817 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000818 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000819 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000820 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000821 if (command in ('update', 'revert') and sys.stdout.isatty() and not
822 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000823 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000824 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000825 for s in self.dependencies:
826 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000827 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000828
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 # Once all the dependencies have been processed, it's now safe to run the
830 # hooks.
831 if not self._options.nohooks:
832 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833
834 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000835 # Notify the user if there is an orphaned entry in their working copy.
836 # Only delete the directory if there are no changes in it, and
837 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000838 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000839 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000840 if not prev_url:
841 # entry must have been overridden via .gclient custom_deps
842 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000843 # Fix path separator on Windows.
844 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000845 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000846 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000847 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000848 file_list = []
849 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
850 scm.status(self._options, [], file_list)
851 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000852 if (not self._options.delete_unversioned_trees or
853 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000854 # There are modified files in this entry. Keep warning until
855 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000856 print(('\nWARNING: \'%s\' is no longer part of this client. '
857 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000858 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000859 else:
860 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000861 print('\n________ deleting \'%s\' in \'%s\'' % (
862 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000863 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000864 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000865 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000866 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867
868 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000869 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000870 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000871 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000872 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000873 for s in self.dependencies:
874 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000875 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000876
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000877 def GetURLAndRev(dep):
878 """Returns the revision-qualified SCM url for a Dependency."""
879 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000880 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000881 if isinstance(dep.parsed_url, self.FileImpl):
882 original_url = dep.parsed_url.file_location
883 else:
884 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000885 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000886 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000887 if not os.path.isdir(scm.checkout_path):
888 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000889 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000890
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000891 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000892 new_gclient = ''
893 # First level at .gclient
894 for d in self.dependencies:
895 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000896 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000897 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000898 for d in dep.dependencies:
899 entries[d.name] = GetURLAndRev(d)
900 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 GrabDeps(d)
902 custom_deps = []
903 for k in sorted(entries.keys()):
904 if entries[k]:
905 # Quotes aren't escaped...
906 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
907 else:
908 custom_deps.append(' \"%s\": None,\n' % k)
909 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
910 'solution_name': d.name,
911 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000912 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000913 'safesync_url' : d.safesync_url or '',
914 'solution_deps': ''.join(custom_deps),
915 }
916 # Print the snapshot configuration file
917 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000918 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000919 entries = {}
920 for d in self.tree(False):
921 if self._options.actual:
922 entries[d.name] = GetURLAndRev(d)
923 else:
924 entries[d.name] = d.parsed_url
925 keys = sorted(entries.keys())
926 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000927 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000928 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000929
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000930 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000931 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000932 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000933
maruel@chromium.org75a59272010-06-11 22:34:03 +0000934 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000935 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000936 return self._root_dir
937
maruel@chromium.org271375b2010-06-23 19:17:38 +0000938 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000939 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000940 return self._enforced_os
941
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000942 def recursion_limit(self):
943 """How recursive can each dependencies in DEPS file can load DEPS file."""
944 return self._recursion_limit
945
maruel@chromium.org0d812442010-08-10 12:41:08 +0000946 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000947 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000948 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000949
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000951#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952
953
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000954def CMDcleanup(parser, args):
955 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000956
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000958"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000959 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
960 help='override deps for the specified (comma-separated) '
961 'platform(s); \'all\' will process all deps_os '
962 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000963 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000964 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000965 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000966 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000967 if options.verbose:
968 # Print out the .gclient file. This is longer than if we just printed the
969 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000970 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000971 return client.RunOnDeps('cleanup', args)
972
973
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000974@attr('usage', '[command] [args ...]')
975def CMDrecurse(parser, args):
976 """Operates on all the entries.
977
978 Runs a shell command on all entries.
979 """
980 # Stop parsing at the first non-arg so that these go through to the command
981 parser.disable_interspersed_args()
982 parser.add_option('-s', '--scm', action='append', default=[],
983 help='choose scm types to operate upon')
984 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000985 if not args:
986 print >> sys.stderr, 'Need to supply a command!'
987 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000988 root_and_entries = gclient_utils.GetGClientRootAndEntries()
989 if not root_and_entries:
990 print >> sys.stderr, (
991 'You need to run gclient sync at least once to use \'recurse\'.\n'
992 'This is because .gclient_entries needs to exist and be up to date.')
993 return 1
994 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000995 scm_set = set()
996 for scm in options.scm:
997 scm_set.update(scm.split(','))
998
999 # Pass in the SCM type as an env variable
1000 env = os.environ.copy()
1001
1002 for path, url in entries.iteritems():
1003 scm = gclient_scm.GetScmName(url)
1004 if scm_set and scm not in scm_set:
1005 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001006 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001007 if scm:
1008 env['GCLIENT_SCM'] = scm
1009 if url:
1010 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001011 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001012 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001013
1014
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001015@attr('usage', '[url] [safesync url]')
1016def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001017 """Create a .gclient file in the current directory.
1018
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001019This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001020top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001021modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001022provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001023URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001024"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001025 parser.add_option('--spec',
1026 help='create a gclient file containing the provided '
1027 'string. Due to Cygwin/Python brokenness, it '
1028 'probably can\'t contain any newlines.')
1029 parser.add_option('--name',
1030 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001031 parser.add_option('--deps-file', default='DEPS',
1032 help='overrides the default name for the DEPS file for the'
1033 'main solutions and all sub-dependencies')
1034 parser.add_option('--git-deps', action='store_true',
1035 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001036 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001037 if ((options.spec and args) or len(args) > 2 or
1038 (not options.spec and not args)):
1039 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1040
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001041 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042 if options.spec:
1043 client.SetConfig(options.spec)
1044 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001045 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001046 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001047 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001048 if name.endswith('.git'):
1049 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001050 else:
1051 # specify an alternate relpath for the given URL.
1052 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001053 deps_file = options.deps_file
1054 if options.git_deps:
1055 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001056 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057 if len(args) > 1:
1058 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001059 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001060 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001061 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001062
1063
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001064@attr('epilog', """Example:
1065 gclient pack > patch.txt
1066 generate simple patch for configured client and dependences
1067""")
1068def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001069 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001070
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001071Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001072dependencies, and performs minimal postprocessing of the output. The
1073resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001074checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001075"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001076 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1077 help='override deps for the specified (comma-separated) '
1078 'platform(s); \'all\' will process all deps_os '
1079 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001080 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001081 client = GClient.LoadCurrentConfig(options)
1082 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001083 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001084 if options.verbose:
1085 # Print out the .gclient file. This is longer than if we just printed the
1086 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001087 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001088 return client.RunOnDeps('pack', args)
1089
1090
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001091def CMDstatus(parser, args):
1092 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001093 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1094 help='override deps for the specified (comma-separated) '
1095 'platform(s); \'all\' will process all deps_os '
1096 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001097 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001098 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001100 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001101 if options.verbose:
1102 # Print out the .gclient file. This is longer than if we just printed the
1103 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001104 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001105 return client.RunOnDeps('status', args)
1106
1107
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001108@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001109 gclient sync
1110 update files from SCM according to current configuration,
1111 *for modules which have changed since last update or sync*
1112 gclient sync --force
1113 update files from SCM according to current configuration, for
1114 all modules (useful for recovering files deleted from local copy)
1115 gclient sync --revision src@31000
1116 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001117""")
1118def CMDsync(parser, args):
1119 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001120 parser.add_option('-f', '--force', action='store_true',
1121 help='force update even for unchanged modules')
1122 parser.add_option('-n', '--nohooks', action='store_true',
1123 help='don\'t run hooks after the update is complete')
1124 parser.add_option('-r', '--revision', action='append',
1125 dest='revisions', metavar='REV', default=[],
1126 help='Enforces revision/hash for the solutions with the '
1127 'format src@rev. The src@ part is optional and can be '
1128 'skipped. -r can be used multiple times when .gclient '
1129 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001130 'if the src@ part is skipped. Note that specifying '
1131 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001132 parser.add_option('-t', '--transitive', action='store_true',
1133 help='When a revision is specified (in the DEPS file or '
1134 'with the command-line flag), transitively update '
1135 'the dependencies to the date of the given revision. '
1136 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001137 parser.add_option('-H', '--head', action='store_true',
1138 help='skips any safesync_urls specified in '
1139 'configured solutions and sync to head instead')
1140 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001141 help='delete any dependency that have been removed from '
1142 'last sync as long as there is no local modification. '
1143 'Coupled with --force, it will remove them even with '
1144 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001145 parser.add_option('-R', '--reset', action='store_true',
1146 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001147 parser.add_option('-M', '--merge', action='store_true',
1148 help='merge upstream changes instead of trying to '
1149 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001150 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1151 help='override deps for the specified (comma-separated) '
1152 'platform(s); \'all\' will process all deps_os '
1153 'references')
1154 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1155 help='Skip svn up whenever possible by requesting '
1156 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001157 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001158 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159
1160 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001161 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001162
maruel@chromium.org307d1792010-05-31 20:03:13 +00001163 if options.revisions and options.head:
1164 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001165 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001166
1167 if options.verbose:
1168 # Print out the .gclient file. This is longer than if we just printed the
1169 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001170 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001171 return client.RunOnDeps('update', args)
1172
1173
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001174def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001175 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001178def CMDdiff(parser, args):
1179 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001180 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1181 help='override deps for the specified (comma-separated) '
1182 'platform(s); \'all\' will process all deps_os '
1183 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001184 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001185 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001187 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 if options.verbose:
1189 # Print out the .gclient file. This is longer than if we just printed the
1190 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001191 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001192 return client.RunOnDeps('diff', args)
1193
1194
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001195def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001196 """Revert all modifications in every dependencies.
1197
1198 That's the nuclear option to get back to a 'clean' state. It removes anything
1199 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001200 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1201 help='override deps for the specified (comma-separated) '
1202 'platform(s); \'all\' will process all deps_os '
1203 'references')
1204 parser.add_option('-n', '--nohooks', action='store_true',
1205 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001206 (options, args) = parser.parse_args(args)
1207 # --force is implied.
1208 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001209 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001210 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001211 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001212 return client.RunOnDeps('revert', args)
1213
1214
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001215def CMDrunhooks(parser, args):
1216 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001217 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1218 help='override deps for the specified (comma-separated) '
1219 'platform(s); \'all\' will process all deps_os '
1220 'references')
1221 parser.add_option('-f', '--force', action='store_true', default=True,
1222 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001223 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001224 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001225 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001226 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001227 if options.verbose:
1228 # Print out the .gclient file. This is longer than if we just printed the
1229 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001230 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001231 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001232 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233 return client.RunOnDeps('runhooks', args)
1234
1235
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001237 """Output revision info mapping for the client and its dependencies.
1238
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001239 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001240 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001241 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1242 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001243 commit can change.
1244 """
1245 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1246 help='override deps for the specified (comma-separated) '
1247 'platform(s); \'all\' will process all deps_os '
1248 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001249 parser.add_option('-a', '--actual', action='store_true',
1250 help='gets the actual checked out revisions instead of the '
1251 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001252 parser.add_option('-s', '--snapshot', action='store_true',
1253 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001254 'version of all repositories to reproduce the tree, '
1255 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001256 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001257 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001258 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001259 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001260 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001261 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001262
1263
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001264def Command(name):
1265 return getattr(sys.modules[__name__], 'CMD' + name, None)
1266
1267
1268def CMDhelp(parser, args):
1269 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001270 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001271 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001272 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001273 parser.print_help()
1274 return 0
1275
1276
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001277def GenUsage(parser, command):
1278 """Modify an OptParse object with the function's documentation."""
1279 obj = Command(command)
1280 if command == 'help':
1281 command = '<command>'
1282 # OptParser.description prefer nicely non-formatted strings.
1283 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1284 usage = getattr(obj, 'usage', '')
1285 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1286 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287
1288
maruel@chromium.org0895b752011-08-26 20:40:33 +00001289def Parser():
1290 """Returns the default parser."""
1291 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001292 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001293 help='Specify how many SCM commands can run in parallel; '
1294 'default=%default')
1295 parser.add_option('-v', '--verbose', action='count', default=0,
1296 help='Produces additional output for diagnostics. Can be '
1297 'used up to three times for more logging info.')
1298 parser.add_option('--gclientfile', dest='config_filename',
1299 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1300 help='Specify an alternate %default file')
1301 # Integrate standard options processing.
1302 old_parser = parser.parse_args
1303 def Parse(args):
1304 (options, args) = old_parser(args)
1305 level = None
1306 if options.verbose == 2:
1307 level = logging.INFO
1308 elif options.verbose > 2:
1309 level = logging.DEBUG
1310 logging.basicConfig(level=level,
1311 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1312 options.entries_filename = options.config_filename + '_entries'
1313 if options.jobs < 1:
1314 parser.error('--jobs must be 1 or higher')
1315
1316 # These hacks need to die.
1317 if not hasattr(options, 'revisions'):
1318 # GClient.RunOnDeps expects it even if not applicable.
1319 options.revisions = []
1320 if not hasattr(options, 'head'):
1321 options.head = None
1322 if not hasattr(options, 'nohooks'):
1323 options.nohooks = True
1324 if not hasattr(options, 'deps_os'):
1325 options.deps_os = None
1326 if not hasattr(options, 'manually_grab_svn_rev'):
1327 options.manually_grab_svn_rev = None
1328 if not hasattr(options, 'force'):
1329 options.force = None
1330 return (options, args)
1331 parser.parse_args = Parse
1332 # We don't want wordwrapping in epilog (usually examples)
1333 parser.format_epilog = lambda _: parser.epilog or ''
1334 return parser
1335
1336
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001337def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001338 """Doesn't parse the arguments here, just find the right subcommand to
1339 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001340 if sys.hexversion < 0x02050000:
1341 print >> sys.stderr, (
1342 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001343 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001344 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1345 # operations. Python as a strong tendency to buffer sys.stdout.
1346 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001347 # Make stdout annotated with the thread ids.
1348 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001349 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001350 # Unused variable 'usage'
1351 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001352 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1353 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1354 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001355 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001356 if argv:
1357 command = Command(argv[0])
1358 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001359 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001360 GenUsage(parser, argv[0])
1361 return command(parser, argv[1:])
1362 # Not a known command. Default to help.
1363 GenUsage(parser, 'help')
1364 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001365 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001366 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001367 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001368
1369
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001370if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001371 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001372 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373
1374# vim: ts=2:sw=2:tw=80:et: