blob: 725642e478d9a9ada003773f3a5c60f538360f81 [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.org68988972011-09-20 14:11:42 +0000149
150 # These are not mutable:
151 self._parent = parent
152 self._safesync_url = safesync_url
153 self._deps_file = deps_file
154 self._should_process = should_process
155
156 # This is in both .gclient and DEPS files:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000157 self.url = url
maruel@chromium.org68988972011-09-20 14:11:42 +0000158
159 # These are only set in .gclient and not in DEPS files.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000160 self.custom_vars = custom_vars or {}
161 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000162 self.deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000163
164 # Calculates properties:
165 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000166 self.dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000167 # A cache of the files affected by the current operation, necessary for
168 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000169 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000170 # If it is not set to True, the dependency wasn't processed for its child
171 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000172 self.deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000173 # This dependency has been processed, i.e. checked out
174 self.processed = False
175 # This dependency had its hook run
176 self.hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000177
maruel@chromium.org98023df2011-09-07 18:44:47 +0000178 # Post process the url to remove trailing slashes.
179 if isinstance(self.url, basestring):
180 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
181 # it to proto://host/path@rev.
182 if self.url.count('@') > 1:
183 raise gclient_utils.Error('Invalid url "%s"' % self.url)
184 self.url = self.url.replace('/@', '@')
185
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000186 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000187
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000188 # Sanity checks
189 if not self.name and self.parent:
190 raise gclient_utils.Error('Dependency without name')
191 if not isinstance(self.url,
192 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
193 raise gclient_utils.Error('dependency url must be either a string, None, '
194 'File() or From() instead of %s' %
195 self.url.__class__.__name__)
196 if '/' in self.deps_file or '\\' in self.deps_file:
197 raise gclient_utils.Error('deps_file name must not be a path, just a '
198 'filename. %s' % self.deps_file)
199
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000200 def _FindDependencies(self):
201 """Setup self.requirements and find any other dependency who would have self
202 as a requirement.
203 """
204 # self.parent is implicitly a requirement. This will be recursive by
205 # definition.
206 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000207 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000208
209 # For a tree with at least 2 levels*, the leaf node needs to depend
210 # on the level higher up in an orderly way.
211 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
212 # thus unsorted, while the .gclient format is a list thus sorted.
213 #
214 # * _recursion_limit is hard coded 2 and there is no hope to change this
215 # value.
216 #
217 # Interestingly enough, the following condition only works in the case we
218 # want: self is a 2nd level node. 3nd level node wouldn't need this since
219 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000220 root_deps = self.root.dependencies
221 if self.parent in root_deps:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000222 for i in range(0, root_deps.index(self.parent)):
223 value = root_deps[i]
224 if value.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000225 self._requirements.add(value.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000226
227 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000228 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000229
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000230 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000231 def yield_full_tree(root):
232 """Depth-first recursion."""
233 yield root
234 for i in root.dependencies:
235 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000236 if j.should_process:
237 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000238
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000239 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000240 if obj is self or not obj.name:
241 continue
242 # Step 1: Find any requirements self may need.
243 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000244 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000245 # Step 2: Find any requirements self may impose.
246 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000247 try:
248 # Access to a protected member _requirements of a client class
249 # pylint: disable=W0212
250 obj.lock.acquire()
251 obj._requirements.add(self.name)
252 finally:
253 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000254
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000256 """Resolves the parsed url from url.
257
258 Manages From() keyword accordingly. Do not touch self.parsed_url nor
259 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000260 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000261 overriden_url = self.get_custom_deps(self.name, url)
262 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000263 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000264 overriden_url))
265 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000266 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000267 ref = [
268 dep for dep in self.root.subtree(True) if url.module_name == dep.name
269 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000270 if not ref:
271 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
272 url.module_name, ref))
273 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000274 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000275 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000276 # Make sure the referenced dependency DEPS file is loaded and file the
277 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000278 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000279 found_dep = None
280 for d in ref.dependencies:
281 if d.name == sub_target:
282 found_dep = d
283 break
284 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000285 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000286 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
287 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000288 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000289
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000290 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000291 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000292 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000293 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000294 elif isinstance(url, basestring):
295 parsed_url = urlparse.urlparse(url)
296 if not parsed_url[0]:
297 # A relative url. Fetch the real base.
298 path = parsed_url[2]
299 if not path.startswith('/'):
300 raise gclient_utils.Error(
301 'relative DEPS entry \'%s\' must begin with a slash' % url)
302 # Create a scm just to query the full url.
303 parent_url = self.parent.parsed_url
304 if isinstance(parent_url, self.FileImpl):
305 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000306 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000307 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000308 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000309 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000310 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000311 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000313 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000314 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000315 return parsed_url
316 elif url is None:
317 return None
318 else:
319 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000320
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000321 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000322 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000323 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000324 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000325 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000326 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000327 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000328 # One thing is unintuitive, vars= {} must happen before Var() use.
329 local_scope = {}
330 var = self.VarImpl(self.custom_vars, local_scope)
331 global_scope = {
332 'File': self.FileImpl,
333 'From': self.FromImpl,
334 'Var': var.Lookup,
335 'deps_os': {},
336 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000337 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000338 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000339 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
340 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000341 else:
342 deps_content = gclient_utils.FileRead(filepath)
343 logging.debug(deps_content)
344 # Eval the content.
345 try:
346 exec(deps_content, global_scope, local_scope)
347 except SyntaxError, e:
348 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000349 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000350 # load os specific dependencies if defined. these dependencies may
351 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000352 if 'deps_os' in local_scope:
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000353 for deps_os_key in self.root.enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000354 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000355 if len(self.root.enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000356 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000357 # platform, so we collect the broadest set of dependencies
358 # available. We may end up with the wrong revision of something for
359 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000360 deps.update([x for x in os_deps.items() if not x[0] in deps])
361 else:
362 deps.update(os_deps)
363
maruel@chromium.org271375b2010-06-23 19:17:38 +0000364 self.deps_hooks.extend(local_scope.get('hooks', []))
365
366 # If a line is in custom_deps, but not in the solution, we want to append
367 # this line to the solution.
368 for d in self.custom_deps:
369 if d not in deps:
370 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000371
372 # If use_relative_paths is set in the DEPS file, regenerate
373 # the dictionary using paths relative to the directory containing
374 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000375 use_relative_paths = local_scope.get('use_relative_paths', False)
376 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000377 rel_deps = {}
378 for d, url in deps.items():
379 # normpath is required to allow DEPS to use .. in their
380 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000381 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
382 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000383
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 # Convert the deps into real Dependency.
385 for name, url in deps.iteritems():
386 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000387 raise gclient_utils.Error(
388 'The same name "%s" appears multiple times in the deps section' %
389 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000390 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000391 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000392 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000393 if name in tree:
394 if url == tree[name].url:
395 logging.info('Won\'t process duplicate dependency %s' % tree[name])
396 # In theory we could keep it as a shadow of the other one. In
397 # practice, simply ignore it.
398 #should_process = False
399 continue
400 else:
401 raise gclient_utils.Error(
402 'Dependency %s specified more than once:\n %s\nvs\n %s' %
403 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000404 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000405 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000406 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000407
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000408 # Arguments number differs from overridden method
409 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000410 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 """Runs 'command' before parsing the DEPS in case it's a initial checkout
412 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000413
414 def maybeGetParentRevision(options):
415 """If we are performing an update and --transitive is set, set the
416 revision to the parent's revision. If we have an explicit revision
417 do nothing."""
418 if command == 'update' and options.transitive and not options.revision:
419 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
420 if not revision:
421 options.revision = revision_overrides.get(self.parent.name)
422 if options.verbose and options.revision:
423 print("Using parent's revision date: %s" % options.revision)
424 # If the parent has a revision override, then it must have been
425 # converted to date format.
426 assert (not options.revision or
427 gclient_utils.IsDateRevision(options.revision))
428
429 def maybeConvertToDateRevision(options):
430 """If we are performing an update and --transitive is set, convert the
431 revision to a date-revision (if necessary). Instead of having
432 -r 101 replace the revision with the time stamp of 101 (e.g.
433 "{2011-18-04}").
434 This way dependencies are upgraded to the revision they had at the
435 check-in of revision 101."""
436 if (command == 'update' and
437 options.transitive and
438 options.revision and
439 not gclient_utils.IsDateRevision(options.revision)):
440 revision_date = scm.GetRevisionDate(options.revision)
441 revision = gclient_utils.MakeDateRevision(revision_date)
442 if options.verbose:
443 print("Updating revision override from %s to %s." %
444 (options.revision, revision))
445 revision_overrides[self.name] = revision
446
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000447 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000448 if not self.should_process:
449 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 # When running runhooks, there's no need to consult the SCM.
451 # All known hooks are expected to run unconditionally regardless of working
452 # copy state, so skip the SCM status check.
453 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000454 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455 if run_scm and self.parsed_url:
456 if isinstance(self.parsed_url, self.FileImpl):
457 # Special support for single-file checkout.
458 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
459 options.revision = self.parsed_url.GetRevision()
460 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000461 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000462 self.name)
463 scm.RunCommand('updatesingle', options,
464 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000465 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000467 # Create a shallow copy to mutate revision.
468 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000470 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000471 scm = gclient_scm.CreateSCM(
472 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000473 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000474 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000475 self._file_list = [os.path.join(self.name, f.strip())
476 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000477
478 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
479 # Convert all absolute paths to relative.
480 for i in range(len(self._file_list)):
481 # It depends on the command being executed (like runhooks vs sync).
482 if not os.path.isabs(self._file_list[i]):
483 continue
484 prefix = os.path.commonprefix(
485 [self.root.root_dir.lower(), self._file_list[i].lower()])
486 self._file_list[i] = self._file_list[i][len(prefix):]
487 # Strip any leading path separators.
488 while (self._file_list[i].startswith('\\') or
489 self._file_list[i].startswith('/')):
490 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000491 self.processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000492 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000493 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000494 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000495
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000496 # Parse the dependencies of this dependency.
497 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000498 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000499
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000500 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000501 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000503 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000504 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000505 # Don't run the hook when it is above recursion_limit.
506 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000507 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000509 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000510 # TODO(maruel): If the user is using git or git-svn, then we don't know
511 # what files have changed so we always run all hooks. It'd be nice to fix
512 # that.
513 if (options.force or
514 isinstance(self.parsed_url, self.FileImpl) or
515 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000516 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 for hook_dict in self.deps_hooks:
518 self._RunHookAction(hook_dict, [])
519 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000520 # Run hooks on the basis of whether the files from the gclient operation
521 # match each hook's pattern.
522 for hook_dict in self.deps_hooks:
523 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000524 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000525 if matching_file_list:
526 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000527 for s in self.dependencies:
528 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000529
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000530 def _RunHookAction(self, hook_dict, matching_file_list):
531 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000532 # A single DEPS file can specify multiple hooks so this function can be
533 # called multiple times on a single Dependency.
534 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000535 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000536 logging.debug(hook_dict)
537 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000538 command = hook_dict['action'][:]
539 if command[0] == 'python':
540 # If the hook specified "python" as the first item, the action is a
541 # Python script. Run it by starting a new copy of the same
542 # interpreter.
543 command[0] = sys.executable
544
545 if '$matching_files' in command:
546 splice_index = command.index('$matching_files')
547 command[splice_index:splice_index + 1] = matching_file_list
548
maruel@chromium.org17d01792010-09-01 18:07:10 +0000549 try:
550 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000551 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000552 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000553 # Use a discrete exit status code of 2 to indicate that a hook action
554 # failed. Users of this script may wish to treat hook action failures
555 # differently from VC failures.
556 print >> sys.stderr, 'Error: %s' % str(e)
557 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +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.org68988972011-09-20 14:11:42 +0000576 @property
577 def recursion_limit(self):
578 """Returns > 0 if this dependency is not too recursed to be processed."""
579 return max(self.parent.recursion_limit - 1, 0)
580
581 @property
582 def deps_file(self):
583 return self._deps_file
584
585 @property
586 def safesync_url(self):
587 return self._safesync_url
588
589 @property
590 def should_process(self):
591 """True if this dependency should be processed, i.e. checked out."""
592 return self._should_process
593
594 @property
595 def parent(self):
596 return self._parent
597
598 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000599 def file_list(self):
600 result = self._file_list[:]
601 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000602 result.extend(d.file_list)
603 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000604
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000605 def __str__(self):
606 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000607 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000608 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000609 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000610 # First try the native property if it exists.
611 if hasattr(self, '_' + i):
612 value = getattr(self, '_' + i, False)
613 else:
614 value = getattr(self, i, False)
615 if value:
616 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000617
618 for d in self.dependencies:
619 out.extend([' ' + x for x in str(d).splitlines()])
620 out.append('')
621 return '\n'.join(out)
622
623 def __repr__(self):
624 return '%s: %s' % (self.name, self.url)
625
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000626 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000627 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000628 out = '%s(%s)' % (self.name, self.url)
629 i = self.parent
630 while i and i.name:
631 out = '%s(%s) -> %s' % (i.name, i.url, out)
632 i = i.parent
633 return out
634
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000635 @property
636 def root(self):
637 """Returns the root node, a GClient object."""
638 if not self.parent:
639 # This line is to signal pylint that it could be a GClient instance.
640 return self or GClient(None, None)
641 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000642
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643
644class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000645 """Object that represent a gclient checkout. A tree of Dependency(), one per
646 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000647
648 DEPS_OS_CHOICES = {
649 "win32": "win",
650 "win": "win",
651 "cygwin": "win",
652 "darwin": "mac",
653 "mac": "mac",
654 "unix": "unix",
655 "linux": "unix",
656 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000657 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000658 }
659
660 DEFAULT_CLIENT_FILE_TEXT = ("""\
661solutions = [
662 { "name" : "%(solution_name)s",
663 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000664 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000665 "custom_deps" : {
666 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000667 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000668 },
669]
670""")
671
672 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
673 { "name" : "%(solution_name)s",
674 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000675 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000676 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000677%(solution_deps)s },
678 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000679 },
680""")
681
682 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
683# Snapshot generated with gclient revinfo --snapshot
684solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000685%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000686""")
687
688 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000689 # Do not change previous behavior. Only solution level and immediate DEPS
690 # are processed.
691 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000692 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
693 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000694 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000695 if options.deps_os:
696 enforced_os = options.deps_os.split(',')
697 else:
698 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
699 if 'all' in enforced_os:
700 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000701 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000702 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000703 self.config_content = None
704
705 def SetConfig(self, content):
706 assert self.dependencies == []
707 config_dict = {}
708 self.config_content = content
709 try:
710 exec(content, config_dict)
711 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000712 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000713 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000714 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000715 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000716 if s['name'] in tree:
717 raise gclient_utils.Error(
718 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000719 self.dependencies.append(Dependency(
720 self, s['name'], s['url'],
721 s.get('safesync_url', None),
722 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000723 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000724 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000725 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000726 except KeyError:
727 raise gclient_utils.Error('Invalid .gclient file. Solution is '
728 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000729 # .gclient can have hooks.
730 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000731 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000732
733 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000734 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000735 self._options.config_filename),
736 self.config_content)
737
738 @staticmethod
739 def LoadCurrentConfig(options):
740 """Searches for and loads a .gclient file relative to the current working
741 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000742 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000743 if not path:
744 return None
745 client = GClient(path, options)
746 client.SetConfig(gclient_utils.FileRead(
747 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000748 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000749
nsylvain@google.comefc80932011-05-31 21:27:56 +0000750 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
751 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000752 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
753 'solution_name': solution_name,
754 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000755 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000756 'safesync_url' : safesync_url,
757 })
758
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000759 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000760 """Creates a .gclient_entries file to record the list of unique checkouts.
761
762 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000763 """
764 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
765 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000766 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000767 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000768 # Skip over File() dependencies as we can't version them.
769 if not isinstance(entry.parsed_url, self.FileImpl):
770 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
771 pprint.pformat(entry.parsed_url))
772 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000773 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000774 logging.info(result)
775 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000776
777 def _ReadEntries(self):
778 """Read the .gclient_entries file for the given client.
779
780 Returns:
781 A sequence of solution names, which will be empty if there is the
782 entries file hasn't been created yet.
783 """
784 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000785 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000786 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000787 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000788 try:
789 exec(gclient_utils.FileRead(filename), scope)
790 except SyntaxError, e:
791 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000792 return scope['entries']
793
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000794 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000795 """Checks for revision overrides."""
796 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000797 if self._options.head:
798 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000799 # Do not check safesync_url if one or more --revision flag is specified.
800 if not self._options.revisions:
801 for s in self.dependencies:
802 if not s.safesync_url:
803 continue
804 handle = urllib.urlopen(s.safesync_url)
805 rev = handle.read().strip()
806 handle.close()
807 if len(rev):
808 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000809 if not self._options.revisions:
810 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000811 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000812 index = 0
813 for revision in self._options.revisions:
814 if not '@' in revision:
815 # Support for --revision 123
816 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000817 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000818 if not sol in solutions_names:
819 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
820 print >> sys.stderr, ('Please fix your script, having invalid '
821 '--revision flags will soon considered an error.')
822 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000823 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000824 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000825 return revision_overrides
826
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827 def RunOnDeps(self, command, args):
828 """Runs a command on each dependency in a client and its dependencies.
829
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830 Args:
831 command: The command to use (e.g., 'status' or 'diff')
832 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000834 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000835 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000836 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000837 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000838 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000839 if (command in ('update', 'revert') and sys.stdout.isatty() and not
840 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000841 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000842 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000843 for s in self.dependencies:
844 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000845 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000846
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000847 # Once all the dependencies have been processed, it's now safe to run the
848 # hooks.
849 if not self._options.nohooks:
850 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851
852 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000853 # Notify the user if there is an orphaned entry in their working copy.
854 # Only delete the directory if there are no changes in it, and
855 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000856 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000857 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000858 if not prev_url:
859 # entry must have been overridden via .gclient custom_deps
860 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000861 # Fix path separator on Windows.
862 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000863 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000864 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000865 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000866 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000867 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 scm.status(self._options, [], file_list)
869 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000870 if (not self._options.delete_unversioned_trees or
871 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000872 # There are modified files in this entry. Keep warning until
873 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000874 print(('\nWARNING: \'%s\' is no longer part of this client. '
875 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000876 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000877 else:
878 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000879 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000880 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000881 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000882 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000884 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000885
886 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000887 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000888 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000889 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000890 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000891 for s in self.dependencies:
892 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000893 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000894
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000895 def GetURLAndRev(dep):
896 """Returns the revision-qualified SCM url for a Dependency."""
897 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000898 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000899 if isinstance(dep.parsed_url, self.FileImpl):
900 original_url = dep.parsed_url.file_location
901 else:
902 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000903 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000904 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 if not os.path.isdir(scm.checkout_path):
906 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000907 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000908
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000909 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000910 new_gclient = ''
911 # First level at .gclient
912 for d in self.dependencies:
913 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000914 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000915 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000916 for d in dep.dependencies:
917 entries[d.name] = GetURLAndRev(d)
918 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000919 GrabDeps(d)
920 custom_deps = []
921 for k in sorted(entries.keys()):
922 if entries[k]:
923 # Quotes aren't escaped...
924 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
925 else:
926 custom_deps.append(' \"%s\": None,\n' % k)
927 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
928 'solution_name': d.name,
929 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000930 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000931 'safesync_url' : d.safesync_url or '',
932 'solution_deps': ''.join(custom_deps),
933 }
934 # Print the snapshot configuration file
935 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000936 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000937 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000938 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000939 if self._options.actual:
940 entries[d.name] = GetURLAndRev(d)
941 else:
942 entries[d.name] = d.parsed_url
943 keys = sorted(entries.keys())
944 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000945 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000946 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000947
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000948 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000949 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000950 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000951
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000952 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +0000953 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000954 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000955 return self._root_dir
956
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000957 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +0000958 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000959 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000960 return self._enforced_os
961
maruel@chromium.org68988972011-09-20 14:11:42 +0000962 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000963 def recursion_limit(self):
964 """How recursive can each dependencies in DEPS file can load DEPS file."""
965 return self._recursion_limit
966
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000967
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000968#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000969
970
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000971def CMDcleanup(parser, args):
972 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000973
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000974Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000975"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000976 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
977 help='override deps for the specified (comma-separated) '
978 'platform(s); \'all\' will process all deps_os '
979 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000980 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000981 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000983 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000984 if options.verbose:
985 # Print out the .gclient file. This is longer than if we just printed the
986 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000987 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988 return client.RunOnDeps('cleanup', args)
989
990
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000991@attr('usage', '[command] [args ...]')
992def CMDrecurse(parser, args):
993 """Operates on all the entries.
994
995 Runs a shell command on all entries.
996 """
997 # Stop parsing at the first non-arg so that these go through to the command
998 parser.disable_interspersed_args()
999 parser.add_option('-s', '--scm', action='append', default=[],
1000 help='choose scm types to operate upon')
1001 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001002 if not args:
1003 print >> sys.stderr, 'Need to supply a command!'
1004 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001005 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1006 if not root_and_entries:
1007 print >> sys.stderr, (
1008 'You need to run gclient sync at least once to use \'recurse\'.\n'
1009 'This is because .gclient_entries needs to exist and be up to date.')
1010 return 1
1011 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001012 scm_set = set()
1013 for scm in options.scm:
1014 scm_set.update(scm.split(','))
1015
1016 # Pass in the SCM type as an env variable
1017 env = os.environ.copy()
1018
1019 for path, url in entries.iteritems():
1020 scm = gclient_scm.GetScmName(url)
1021 if scm_set and scm not in scm_set:
1022 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001023 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001024 if scm:
1025 env['GCLIENT_SCM'] = scm
1026 if url:
1027 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001028 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001029 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001030
1031
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001032@attr('usage', '[url] [safesync url]')
1033def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001034 """Create a .gclient file in the current directory.
1035
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001036This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001037top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001038modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001039provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001040URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001041"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001042 parser.add_option('--spec',
1043 help='create a gclient file containing the provided '
1044 'string. Due to Cygwin/Python brokenness, it '
1045 'probably can\'t contain any newlines.')
1046 parser.add_option('--name',
1047 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001048 parser.add_option('--deps-file', default='DEPS',
1049 help='overrides the default name for the DEPS file for the'
1050 'main solutions and all sub-dependencies')
1051 parser.add_option('--git-deps', action='store_true',
1052 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001053 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001054 if ((options.spec and args) or len(args) > 2 or
1055 (not options.spec and not args)):
1056 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1057
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001058 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001059 if options.spec:
1060 client.SetConfig(options.spec)
1061 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001062 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001063 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001064 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001065 if name.endswith('.git'):
1066 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001067 else:
1068 # specify an alternate relpath for the given URL.
1069 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001070 deps_file = options.deps_file
1071 if options.git_deps:
1072 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001073 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001074 if len(args) > 1:
1075 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001076 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001077 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001078 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079
1080
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001081@attr('epilog', """Example:
1082 gclient pack > patch.txt
1083 generate simple patch for configured client and dependences
1084""")
1085def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001086 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001087
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001088Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001089dependencies, and performs minimal postprocessing of the output. The
1090resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001091checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001092"""
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)
kbr@google.comab318592009-09-04 00:54:55 +00001098 client = GClient.LoadCurrentConfig(options)
1099 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001100 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +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)
kbr@google.comab318592009-09-04 00:54:55 +00001105 return client.RunOnDeps('pack', args)
1106
1107
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001108def CMDstatus(parser, args):
1109 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001110 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1111 help='override deps for the specified (comma-separated) '
1112 'platform(s); \'all\' will process all deps_os '
1113 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001114 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001115 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001117 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001118 if options.verbose:
1119 # Print out the .gclient file. This is longer than if we just printed the
1120 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001121 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001122 return client.RunOnDeps('status', args)
1123
1124
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001125@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001126 gclient sync
1127 update files from SCM according to current configuration,
1128 *for modules which have changed since last update or sync*
1129 gclient sync --force
1130 update files from SCM according to current configuration, for
1131 all modules (useful for recovering files deleted from local copy)
1132 gclient sync --revision src@31000
1133 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001134""")
1135def CMDsync(parser, args):
1136 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001137 parser.add_option('-f', '--force', action='store_true',
1138 help='force update even for unchanged modules')
1139 parser.add_option('-n', '--nohooks', action='store_true',
1140 help='don\'t run hooks after the update is complete')
1141 parser.add_option('-r', '--revision', action='append',
1142 dest='revisions', metavar='REV', default=[],
1143 help='Enforces revision/hash for the solutions with the '
1144 'format src@rev. The src@ part is optional and can be '
1145 'skipped. -r can be used multiple times when .gclient '
1146 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001147 'if the src@ part is skipped. Note that specifying '
1148 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001149 parser.add_option('-t', '--transitive', action='store_true',
1150 help='When a revision is specified (in the DEPS file or '
1151 'with the command-line flag), transitively update '
1152 'the dependencies to the date of the given revision. '
1153 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001154 parser.add_option('-H', '--head', action='store_true',
1155 help='skips any safesync_urls specified in '
1156 'configured solutions and sync to head instead')
1157 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001158 help='delete any dependency that have been removed from '
1159 'last sync as long as there is no local modification. '
1160 'Coupled with --force, it will remove them even with '
1161 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001162 parser.add_option('-R', '--reset', action='store_true',
1163 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001164 parser.add_option('-M', '--merge', action='store_true',
1165 help='merge upstream changes instead of trying to '
1166 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001167 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1168 help='override deps for the specified (comma-separated) '
1169 'platform(s); \'all\' will process all deps_os '
1170 'references')
1171 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1172 help='Skip svn up whenever possible by requesting '
1173 'actual HEAD revision from the repository')
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
1177 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001178 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179
maruel@chromium.org307d1792010-05-31 20:03:13 +00001180 if options.revisions and options.head:
1181 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001182 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001183
1184 if options.verbose:
1185 # Print out the .gclient file. This is longer than if we just printed the
1186 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001187 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 return client.RunOnDeps('update', args)
1189
1190
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001191def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001192 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001194
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001195def CMDdiff(parser, args):
1196 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001197 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1198 help='override deps for the specified (comma-separated) '
1199 'platform(s); \'all\' will process all deps_os '
1200 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001201 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001202 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001204 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001205 if options.verbose:
1206 # Print out the .gclient file. This is longer than if we just printed the
1207 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001208 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001209 return client.RunOnDeps('diff', args)
1210
1211
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001212def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001213 """Revert all modifications in every dependencies.
1214
1215 That's the nuclear option to get back to a 'clean' state. It removes anything
1216 that shows up in svn status."""
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('-n', '--nohooks', action='store_true',
1222 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001223 (options, args) = parser.parse_args(args)
1224 # --force is implied.
1225 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001226 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001227 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001228 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001229 return client.RunOnDeps('revert', args)
1230
1231
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001232def CMDrunhooks(parser, args):
1233 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001234 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1235 help='override deps for the specified (comma-separated) '
1236 'platform(s); \'all\' will process all deps_os '
1237 'references')
1238 parser.add_option('-f', '--force', action='store_true', default=True,
1239 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001240 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001241 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001242 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001243 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244 if options.verbose:
1245 # Print out the .gclient file. This is longer than if we just printed the
1246 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001247 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001248 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001249 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001250 return client.RunOnDeps('runhooks', args)
1251
1252
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001253def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001254 """Output revision info mapping for the client and its dependencies.
1255
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001256 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001257 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001258 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1259 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001260 commit can change.
1261 """
1262 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1263 help='override deps for the specified (comma-separated) '
1264 'platform(s); \'all\' will process all deps_os '
1265 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001266 parser.add_option('-a', '--actual', action='store_true',
1267 help='gets the actual checked out revisions instead of the '
1268 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001269 parser.add_option('-s', '--snapshot', action='store_true',
1270 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001271 'version of all repositories to reproduce the tree, '
1272 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001273 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001274 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001275 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001276 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001278 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001279
1280
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001281def Command(name):
1282 return getattr(sys.modules[__name__], 'CMD' + name, None)
1283
1284
1285def CMDhelp(parser, args):
1286 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001287 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001288 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001289 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001290 parser.print_help()
1291 return 0
1292
1293
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001294def GenUsage(parser, command):
1295 """Modify an OptParse object with the function's documentation."""
1296 obj = Command(command)
1297 if command == 'help':
1298 command = '<command>'
1299 # OptParser.description prefer nicely non-formatted strings.
1300 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1301 usage = getattr(obj, 'usage', '')
1302 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1303 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001304
1305
maruel@chromium.org0895b752011-08-26 20:40:33 +00001306def Parser():
1307 """Returns the default parser."""
1308 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001309 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001310 help='Specify how many SCM commands can run in parallel; '
1311 'default=%default')
1312 parser.add_option('-v', '--verbose', action='count', default=0,
1313 help='Produces additional output for diagnostics. Can be '
1314 'used up to three times for more logging info.')
1315 parser.add_option('--gclientfile', dest='config_filename',
1316 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1317 help='Specify an alternate %default file')
1318 # Integrate standard options processing.
1319 old_parser = parser.parse_args
1320 def Parse(args):
1321 (options, args) = old_parser(args)
1322 level = None
1323 if options.verbose == 2:
1324 level = logging.INFO
1325 elif options.verbose > 2:
1326 level = logging.DEBUG
1327 logging.basicConfig(level=level,
1328 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1329 options.entries_filename = options.config_filename + '_entries'
1330 if options.jobs < 1:
1331 parser.error('--jobs must be 1 or higher')
1332
1333 # These hacks need to die.
1334 if not hasattr(options, 'revisions'):
1335 # GClient.RunOnDeps expects it even if not applicable.
1336 options.revisions = []
1337 if not hasattr(options, 'head'):
1338 options.head = None
1339 if not hasattr(options, 'nohooks'):
1340 options.nohooks = True
1341 if not hasattr(options, 'deps_os'):
1342 options.deps_os = None
1343 if not hasattr(options, 'manually_grab_svn_rev'):
1344 options.manually_grab_svn_rev = None
1345 if not hasattr(options, 'force'):
1346 options.force = None
1347 return (options, args)
1348 parser.parse_args = Parse
1349 # We don't want wordwrapping in epilog (usually examples)
1350 parser.format_epilog = lambda _: parser.epilog or ''
1351 return parser
1352
1353
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001354def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001355 """Doesn't parse the arguments here, just find the right subcommand to
1356 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001357 if sys.hexversion < 0x02050000:
1358 print >> sys.stderr, (
1359 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001360 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001361 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1362 # operations. Python as a strong tendency to buffer sys.stdout.
1363 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001364 # Make stdout annotated with the thread ids.
1365 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001366 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001367 # Unused variable 'usage'
1368 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001369 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1370 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1371 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001372 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001373 if argv:
1374 command = Command(argv[0])
1375 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001376 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001377 GenUsage(parser, argv[0])
1378 return command(parser, argv[1:])
1379 # Not a known command. Default to help.
1380 GenUsage(parser, 'help')
1381 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001382 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001383 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001384 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001385
1386
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001387if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001388 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001389 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001390
1391# vim: ts=2:sw=2:tw=80:et: