blob: b7d8e77e7c19734494661431397d047f2023ffaf [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
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000142 def __init__(self, parent, name, url, safesync_url, managed, 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.
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000160 # 'managed' determines whether or not this dependency is synced/updated by
161 # gclient after gclient checks it out initially. The difference between
162 # 'managed' and 'should_process' (defined below) is that the user specifies
163 # 'managed' via the --unmanaged command-line flag or a .gclient config,
164 # where 'should_process' is dynamically set by gclient if it goes over its
165 # recursion limit and controls gclient's behavior so it does not misbehave.
166 self.managed = managed
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000167 self.custom_vars = custom_vars or {}
168 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000169 self.deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000170
171 # Calculates properties:
172 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000173 self.dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000174 # A cache of the files affected by the current operation, necessary for
175 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000176 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000177 # If it is not set to True, the dependency wasn't processed for its child
178 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000179 self.deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000180 # This dependency has been processed, i.e. checked out
181 self.processed = False
182 # This dependency had its hook run
183 self.hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000184
maruel@chromium.org98023df2011-09-07 18:44:47 +0000185 # Post process the url to remove trailing slashes.
186 if isinstance(self.url, basestring):
187 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
188 # it to proto://host/path@rev.
189 if self.url.count('@') > 1:
190 raise gclient_utils.Error('Invalid url "%s"' % self.url)
191 self.url = self.url.replace('/@', '@')
192
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000193 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000194
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000195 # Sanity checks
196 if not self.name and self.parent:
197 raise gclient_utils.Error('Dependency without name')
198 if not isinstance(self.url,
199 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
200 raise gclient_utils.Error('dependency url must be either a string, None, '
201 'File() or From() instead of %s' %
202 self.url.__class__.__name__)
203 if '/' in self.deps_file or '\\' in self.deps_file:
204 raise gclient_utils.Error('deps_file name must not be a path, just a '
205 'filename. %s' % self.deps_file)
206
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000207 def _FindDependencies(self):
208 """Setup self.requirements and find any other dependency who would have self
209 as a requirement.
210 """
211 # self.parent is implicitly a requirement. This will be recursive by
212 # definition.
213 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000214 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000215
216 # For a tree with at least 2 levels*, the leaf node needs to depend
217 # on the level higher up in an orderly way.
218 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
219 # thus unsorted, while the .gclient format is a list thus sorted.
220 #
221 # * _recursion_limit is hard coded 2 and there is no hope to change this
222 # value.
223 #
224 # Interestingly enough, the following condition only works in the case we
225 # want: self is a 2nd level node. 3nd level node wouldn't need this since
226 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000227 root_deps = self.root.dependencies
228 if self.parent in root_deps:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000229 for i in range(0, root_deps.index(self.parent)):
230 value = root_deps[i]
231 if value.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000232 self._requirements.add(value.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000233
234 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000235 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000236
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000237 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000238 def yield_full_tree(root):
239 """Depth-first recursion."""
240 yield root
241 for i in root.dependencies:
242 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000243 if j.should_process:
244 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000245
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000246 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000247 if obj is self or not obj.name:
248 continue
249 # Step 1: Find any requirements self may need.
250 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000251 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000252 # Step 2: Find any requirements self may impose.
253 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000254 try:
255 # Access to a protected member _requirements of a client class
256 # pylint: disable=W0212
257 obj.lock.acquire()
258 obj._requirements.add(self.name)
259 finally:
260 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000261
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000262 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000263 """Resolves the parsed url from url.
264
265 Manages From() keyword accordingly. Do not touch self.parsed_url nor
266 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000267 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000268 overriden_url = self.get_custom_deps(self.name, url)
269 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000270 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000271 overriden_url))
272 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000273 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000274 ref = [
275 dep for dep in self.root.subtree(True) if url.module_name == dep.name
276 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000277 if not ref:
278 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
279 url.module_name, ref))
280 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000281 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000282 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000283 # Make sure the referenced dependency DEPS file is loaded and file the
284 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000285 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000286 found_dep = None
287 for d in ref.dependencies:
288 if d.name == sub_target:
289 found_dep = d
290 break
291 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000292 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000293 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
294 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000295 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000296
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000297 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000298 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000299 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000300 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000301 elif isinstance(url, basestring):
302 parsed_url = urlparse.urlparse(url)
303 if not parsed_url[0]:
304 # A relative url. Fetch the real base.
305 path = parsed_url[2]
306 if not path.startswith('/'):
307 raise gclient_utils.Error(
308 'relative DEPS entry \'%s\' must begin with a slash' % url)
309 # Create a scm just to query the full url.
310 parent_url = self.parent.parsed_url
311 if isinstance(parent_url, self.FileImpl):
312 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000313 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000314 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000315 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000316 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000317 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000318 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000319 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000320 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000321 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000322 return parsed_url
323 elif url is None:
324 return None
325 else:
326 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000327
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000328 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000329 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000330 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000331 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000332 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000333 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000334 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000335 # One thing is unintuitive, vars= {} must happen before Var() use.
336 local_scope = {}
337 var = self.VarImpl(self.custom_vars, local_scope)
338 global_scope = {
339 'File': self.FileImpl,
340 'From': self.FromImpl,
341 'Var': var.Lookup,
342 'deps_os': {},
343 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000344 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000345 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000346 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
347 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000348 else:
349 deps_content = gclient_utils.FileRead(filepath)
350 logging.debug(deps_content)
351 # Eval the content.
352 try:
353 exec(deps_content, global_scope, local_scope)
354 except SyntaxError, e:
355 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000356 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000357 # load os specific dependencies if defined. these dependencies may
358 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000359 if 'deps_os' in local_scope:
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000360 for deps_os_key in self.root.enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000361 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000362 if len(self.root.enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000363 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000364 # platform, so we collect the broadest set of dependencies
365 # available. We may end up with the wrong revision of something for
366 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000367 deps.update([x for x in os_deps.items() if not x[0] in deps])
368 else:
369 deps.update(os_deps)
370
maruel@chromium.org271375b2010-06-23 19:17:38 +0000371 self.deps_hooks.extend(local_scope.get('hooks', []))
372
373 # If a line is in custom_deps, but not in the solution, we want to append
374 # this line to the solution.
375 for d in self.custom_deps:
376 if d not in deps:
377 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000378
379 # If use_relative_paths is set in the DEPS file, regenerate
380 # the dictionary using paths relative to the directory containing
381 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000382 use_relative_paths = local_scope.get('use_relative_paths', False)
383 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384 rel_deps = {}
385 for d, url in deps.items():
386 # normpath is required to allow DEPS to use .. in their
387 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000388 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
389 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000390
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000391 # Convert the deps into real Dependency.
392 for name, url in deps.iteritems():
393 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000394 raise gclient_utils.Error(
395 'The same name "%s" appears multiple times in the deps section' %
396 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000397 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000398 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000399 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000400 if name in tree:
401 if url == tree[name].url:
402 logging.info('Won\'t process duplicate dependency %s' % tree[name])
403 # In theory we could keep it as a shadow of the other one. In
404 # practice, simply ignore it.
405 #should_process = False
406 continue
407 else:
408 raise gclient_utils.Error(
409 'Dependency %s specified more than once:\n %s\nvs\n %s' %
410 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000411 self.dependencies.append(Dependency(self, name, url, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000412 None, self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000413 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000414
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000415 # Arguments number differs from overridden method
416 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000417 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000418 """Runs 'command' before parsing the DEPS in case it's a initial checkout
419 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000420
421 def maybeGetParentRevision(options):
422 """If we are performing an update and --transitive is set, set the
423 revision to the parent's revision. If we have an explicit revision
424 do nothing."""
425 if command == 'update' and options.transitive and not options.revision:
426 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
427 if not revision:
428 options.revision = revision_overrides.get(self.parent.name)
429 if options.verbose and options.revision:
430 print("Using parent's revision date: %s" % options.revision)
431 # If the parent has a revision override, then it must have been
432 # converted to date format.
433 assert (not options.revision or
434 gclient_utils.IsDateRevision(options.revision))
435
436 def maybeConvertToDateRevision(options):
437 """If we are performing an update and --transitive is set, convert the
438 revision to a date-revision (if necessary). Instead of having
439 -r 101 replace the revision with the time stamp of 101 (e.g.
440 "{2011-18-04}").
441 This way dependencies are upgraded to the revision they had at the
442 check-in of revision 101."""
443 if (command == 'update' and
444 options.transitive and
445 options.revision and
446 not gclient_utils.IsDateRevision(options.revision)):
447 revision_date = scm.GetRevisionDate(options.revision)
448 revision = gclient_utils.MakeDateRevision(revision_date)
449 if options.verbose:
450 print("Updating revision override from %s to %s." %
451 (options.revision, revision))
452 revision_overrides[self.name] = revision
453
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000454 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000455 if not self.should_process:
456 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000457 # When running runhooks, there's no need to consult the SCM.
458 # All known hooks are expected to run unconditionally regardless of working
459 # copy state, so skip the SCM status check.
460 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000461 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000462 if run_scm and self.parsed_url:
463 if isinstance(self.parsed_url, self.FileImpl):
464 # Special support for single-file checkout.
465 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
466 options.revision = self.parsed_url.GetRevision()
467 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000468 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469 self.name)
470 scm.RunCommand('updatesingle', options,
471 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000472 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000474 # Create a shallow copy to mutate revision.
475 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000477 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000478 scm = gclient_scm.CreateSCM(
479 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000480 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000481 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000482 self._file_list = [os.path.join(self.name, f.strip())
483 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000484
485 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
486 # Convert all absolute paths to relative.
487 for i in range(len(self._file_list)):
488 # It depends on the command being executed (like runhooks vs sync).
489 if not os.path.isabs(self._file_list[i]):
490 continue
491 prefix = os.path.commonprefix(
492 [self.root.root_dir.lower(), self._file_list[i].lower()])
493 self._file_list[i] = self._file_list[i][len(prefix):]
494 # Strip any leading path separators.
495 while (self._file_list[i].startswith('\\') or
496 self._file_list[i].startswith('/')):
497 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000498 self.processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000499 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000500 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000501 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000502
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503 # Parse the dependencies of this dependency.
504 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000505 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000506
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000508 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000510 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000511 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000512 # Don't run the hook when it is above recursion_limit.
513 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000514 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000515 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000516 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 # TODO(maruel): If the user is using git or git-svn, then we don't know
518 # what files have changed so we always run all hooks. It'd be nice to fix
519 # that.
520 if (options.force or
521 isinstance(self.parsed_url, self.FileImpl) or
522 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000523 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000524 for hook_dict in self.deps_hooks:
525 self._RunHookAction(hook_dict, [])
526 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000527 # Run hooks on the basis of whether the files from the gclient operation
528 # match each hook's pattern.
529 for hook_dict in self.deps_hooks:
530 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000531 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000532 if matching_file_list:
533 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000534 for s in self.dependencies:
535 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000536
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000537 def _RunHookAction(self, hook_dict, matching_file_list):
538 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000539 # A single DEPS file can specify multiple hooks so this function can be
540 # called multiple times on a single Dependency.
541 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000542 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000543 logging.debug(hook_dict)
544 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000545 command = hook_dict['action'][:]
546 if command[0] == 'python':
547 # If the hook specified "python" as the first item, the action is a
548 # Python script. Run it by starting a new copy of the same
549 # interpreter.
550 command[0] = sys.executable
551
552 if '$matching_files' in command:
553 splice_index = command.index('$matching_files')
554 command[splice_index:splice_index + 1] = matching_file_list
555
maruel@chromium.org17d01792010-09-01 18:07:10 +0000556 try:
557 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000558 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000559 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000560 # Use a discrete exit status code of 2 to indicate that a hook action
561 # failed. Users of this script may wish to treat hook action failures
562 # differently from VC failures.
563 print >> sys.stderr, 'Error: %s' % str(e)
564 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000565
maruel@chromium.org0d812442010-08-10 12:41:08 +0000566 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000567 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000568 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000569 for d in self.dependencies:
570 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000571 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000572 for d in self.dependencies:
573 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000574 return result
575
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000576 def get_custom_deps(self, name, url):
577 """Returns a custom deps if applicable."""
578 if self.parent:
579 url = self.parent.get_custom_deps(name, url)
580 # None is a valid return value to disable a dependency.
581 return self.custom_deps.get(name, url)
582
maruel@chromium.org68988972011-09-20 14:11:42 +0000583 @property
584 def recursion_limit(self):
585 """Returns > 0 if this dependency is not too recursed to be processed."""
586 return max(self.parent.recursion_limit - 1, 0)
587
588 @property
589 def deps_file(self):
590 return self._deps_file
591
592 @property
593 def safesync_url(self):
594 return self._safesync_url
595
596 @property
597 def should_process(self):
598 """True if this dependency should be processed, i.e. checked out."""
599 return self._should_process
600
601 @property
602 def parent(self):
603 return self._parent
604
605 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000606 def file_list(self):
607 result = self._file_list[:]
608 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000609 result.extend(d.file_list)
610 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000611
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000612 def __str__(self):
613 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000614 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000615 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000616 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000617 # First try the native property if it exists.
618 if hasattr(self, '_' + i):
619 value = getattr(self, '_' + i, False)
620 else:
621 value = getattr(self, i, False)
622 if value:
623 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000624
625 for d in self.dependencies:
626 out.extend([' ' + x for x in str(d).splitlines()])
627 out.append('')
628 return '\n'.join(out)
629
630 def __repr__(self):
631 return '%s: %s' % (self.name, self.url)
632
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000633 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000634 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000635 out = '%s(%s)' % (self.name, self.url)
636 i = self.parent
637 while i and i.name:
638 out = '%s(%s) -> %s' % (i.name, i.url, out)
639 i = i.parent
640 return out
641
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000642 @property
643 def root(self):
644 """Returns the root node, a GClient object."""
645 if not self.parent:
646 # This line is to signal pylint that it could be a GClient instance.
647 return self or GClient(None, None)
648 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000649
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650
651class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000652 """Object that represent a gclient checkout. A tree of Dependency(), one per
653 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000654
655 DEPS_OS_CHOICES = {
656 "win32": "win",
657 "win": "win",
658 "cygwin": "win",
659 "darwin": "mac",
660 "mac": "mac",
661 "unix": "unix",
662 "linux": "unix",
663 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000664 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000665 }
666
667 DEFAULT_CLIENT_FILE_TEXT = ("""\
668solutions = [
669 { "name" : "%(solution_name)s",
670 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000671 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000672 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000673 "custom_deps" : {
674 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000675 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000676 },
677]
678""")
679
680 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
681 { "name" : "%(solution_name)s",
682 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000683 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000684 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000685 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000686%(solution_deps)s },
687 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000688 },
689""")
690
691 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
692# Snapshot generated with gclient revinfo --snapshot
693solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000694%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000695""")
696
697 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000698 # Do not change previous behavior. Only solution level and immediate DEPS
699 # are processed.
700 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000701 Dependency.__init__(self, None, None, None, None, True, None, None,
702 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000703 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000704 if options.deps_os:
705 enforced_os = options.deps_os.split(',')
706 else:
707 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
708 if 'all' in enforced_os:
709 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000710 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000711 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000712 self.config_content = None
713
714 def SetConfig(self, content):
715 assert self.dependencies == []
716 config_dict = {}
717 self.config_content = content
718 try:
719 exec(content, config_dict)
720 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000721 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000722 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000723 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000724 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000725 if s['name'] in tree:
726 raise gclient_utils.Error(
727 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000728 self.dependencies.append(Dependency(
729 self, s['name'], s['url'],
730 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000731 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000732 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000733 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000734 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000735 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000736 except KeyError:
737 raise gclient_utils.Error('Invalid .gclient file. Solution is '
738 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000739 # .gclient can have hooks.
740 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000741 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742
743 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000744 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000745 self._options.config_filename),
746 self.config_content)
747
748 @staticmethod
749 def LoadCurrentConfig(options):
750 """Searches for and loads a .gclient file relative to the current working
751 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000752 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000753 if not path:
754 return None
755 client = GClient(path, options)
756 client.SetConfig(gclient_utils.FileRead(
757 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000758 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000759
nsylvain@google.comefc80932011-05-31 21:27:56 +0000760 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000761 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000762 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
763 'solution_name': solution_name,
764 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000765 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000766 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000767 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768 })
769
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000770 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000771 """Creates a .gclient_entries file to record the list of unique checkouts.
772
773 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000774 """
775 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
776 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000777 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000778 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000779 # Skip over File() dependencies as we can't version them.
780 if not isinstance(entry.parsed_url, self.FileImpl):
781 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
782 pprint.pformat(entry.parsed_url))
783 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000784 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000785 logging.info(result)
786 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000787
788 def _ReadEntries(self):
789 """Read the .gclient_entries file for the given client.
790
791 Returns:
792 A sequence of solution names, which will be empty if there is the
793 entries file hasn't been created yet.
794 """
795 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000796 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000797 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000798 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000799 try:
800 exec(gclient_utils.FileRead(filename), scope)
801 except SyntaxError, e:
802 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000803 return scope['entries']
804
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000805 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000806 """Checks for revision overrides."""
807 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000808 if self._options.head:
809 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000810 # Do not check safesync_url if one or more --revision flag is specified.
811 if not self._options.revisions:
812 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000813 if not s.managed:
814 self._options.revisions.append('%s@unmanaged' % s.name)
815 elif s.safesync_url:
816 handle = urllib.urlopen(s.safesync_url)
817 rev = handle.read().strip()
818 handle.close()
819 if len(rev):
820 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000821 if not self._options.revisions:
822 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000823 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000824 index = 0
825 for revision in self._options.revisions:
826 if not '@' in revision:
827 # Support for --revision 123
828 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000829 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000830 if not sol in solutions_names:
831 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
832 print >> sys.stderr, ('Please fix your script, having invalid '
833 '--revision flags will soon considered an error.')
834 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000835 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000836 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000837 return revision_overrides
838
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000839 def RunOnDeps(self, command, args):
840 """Runs a command on each dependency in a client and its dependencies.
841
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000842 Args:
843 command: The command to use (e.g., 'status' or 'diff')
844 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000845 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000846 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000847 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000848 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000849 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000850 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000851 if (command in ('update', 'revert') and sys.stdout.isatty() and not
852 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000853 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000854 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000855 for s in self.dependencies:
856 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000857 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000858
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000859 # Once all the dependencies have been processed, it's now safe to run the
860 # hooks.
861 if not self._options.nohooks:
862 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863
864 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000865 # Notify the user if there is an orphaned entry in their working copy.
866 # Only delete the directory if there are no changes in it, and
867 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000868 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000869 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000870 if not prev_url:
871 # entry must have been overridden via .gclient custom_deps
872 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000873 # Fix path separator on Windows.
874 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000875 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000876 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000877 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000878 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000879 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000880 scm.status(self._options, [], file_list)
881 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000882 if (not self._options.delete_unversioned_trees or
883 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000884 # There are modified files in this entry. Keep warning until
885 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000886 print(('\nWARNING: \'%s\' is no longer part of this client. '
887 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000888 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 else:
890 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000891 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000892 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000893 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000894 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000895 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000896 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000897
898 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000899 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000900 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000902 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000903 for s in self.dependencies:
904 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000905 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000906
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000907 def GetURLAndRev(dep):
908 """Returns the revision-qualified SCM url for a Dependency."""
909 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000910 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000911 if isinstance(dep.parsed_url, self.FileImpl):
912 original_url = dep.parsed_url.file_location
913 else:
914 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000915 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000916 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000917 if not os.path.isdir(scm.checkout_path):
918 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000919 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000920
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000921 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000922 new_gclient = ''
923 # First level at .gclient
924 for d in self.dependencies:
925 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000926 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000927 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000928 for d in dep.dependencies:
929 entries[d.name] = GetURLAndRev(d)
930 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000931 GrabDeps(d)
932 custom_deps = []
933 for k in sorted(entries.keys()):
934 if entries[k]:
935 # Quotes aren't escaped...
936 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
937 else:
938 custom_deps.append(' \"%s\": None,\n' % k)
939 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
940 'solution_name': d.name,
941 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000942 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000943 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000944 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000945 'solution_deps': ''.join(custom_deps),
946 }
947 # Print the snapshot configuration file
948 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000949 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000950 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000951 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000952 if self._options.actual:
953 entries[d.name] = GetURLAndRev(d)
954 else:
955 entries[d.name] = d.parsed_url
956 keys = sorted(entries.keys())
957 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000958 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000959 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000960
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000961 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000962 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000963 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000964
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000965 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +0000966 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000967 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000968 return self._root_dir
969
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000970 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +0000971 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000972 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000973 return self._enforced_os
974
maruel@chromium.org68988972011-09-20 14:11:42 +0000975 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000976 def recursion_limit(self):
977 """How recursive can each dependencies in DEPS file can load DEPS file."""
978 return self._recursion_limit
979
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000980
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000981#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982
983
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000984def CMDcleanup(parser, args):
985 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000986
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000987Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000988"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000989 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
990 help='override deps for the specified (comma-separated) '
991 'platform(s); \'all\' will process all deps_os '
992 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000993 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000994 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000996 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000997 if options.verbose:
998 # Print out the .gclient file. This is longer than if we just printed the
999 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001000 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001001 return client.RunOnDeps('cleanup', args)
1002
1003
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001004@attr('usage', '[command] [args ...]')
1005def CMDrecurse(parser, args):
1006 """Operates on all the entries.
1007
1008 Runs a shell command on all entries.
1009 """
1010 # Stop parsing at the first non-arg so that these go through to the command
1011 parser.disable_interspersed_args()
1012 parser.add_option('-s', '--scm', action='append', default=[],
1013 help='choose scm types to operate upon')
1014 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001015 if not args:
1016 print >> sys.stderr, 'Need to supply a command!'
1017 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001018 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1019 if not root_and_entries:
1020 print >> sys.stderr, (
1021 'You need to run gclient sync at least once to use \'recurse\'.\n'
1022 'This is because .gclient_entries needs to exist and be up to date.')
1023 return 1
1024 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001025 scm_set = set()
1026 for scm in options.scm:
1027 scm_set.update(scm.split(','))
1028
1029 # Pass in the SCM type as an env variable
1030 env = os.environ.copy()
1031
1032 for path, url in entries.iteritems():
1033 scm = gclient_scm.GetScmName(url)
1034 if scm_set and scm not in scm_set:
1035 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001036 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001037 if scm:
1038 env['GCLIENT_SCM'] = scm
1039 if url:
1040 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001041 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001042 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001043
1044
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001045@attr('usage', '[url] [safesync url]')
1046def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001047 """Create a .gclient file in the current directory.
1048
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001049This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001050top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001051modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001052provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001053URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001054"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001055 parser.add_option('--spec',
1056 help='create a gclient file containing the provided '
1057 'string. Due to Cygwin/Python brokenness, it '
1058 'probably can\'t contain any newlines.')
1059 parser.add_option('--name',
1060 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001061 parser.add_option('--deps-file', default='DEPS',
1062 help='overrides the default name for the DEPS file for the'
1063 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001064 parser.add_option('--unmanaged', action='store_true', default=False,
1065 help='overrides the default behavior to make it possible '
1066 'to have the main solution untouched by gclient '
1067 '(gclient will check out unmanaged dependencies but '
1068 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001069 parser.add_option('--git-deps', action='store_true',
1070 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001071 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001072 if ((options.spec and args) or len(args) > 2 or
1073 (not options.spec and not args)):
1074 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1075
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001076 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001077 if options.spec:
1078 client.SetConfig(options.spec)
1079 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001080 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001081 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001082 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001083 if name.endswith('.git'):
1084 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001085 else:
1086 # specify an alternate relpath for the given URL.
1087 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001088 deps_file = options.deps_file
1089 if options.git_deps:
1090 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 if len(args) > 1:
1093 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001094 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1095 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001097 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100@attr('epilog', """Example:
1101 gclient pack > patch.txt
1102 generate simple patch for configured client and dependences
1103""")
1104def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001105 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001106
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001107Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001108dependencies, and performs minimal postprocessing of the output. The
1109resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001111"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001112 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1113 help='override deps for the specified (comma-separated) '
1114 'platform(s); \'all\' will process all deps_os '
1115 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001116 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001117 client = GClient.LoadCurrentConfig(options)
1118 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001119 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001120 if options.verbose:
1121 # Print out the .gclient file. This is longer than if we just printed the
1122 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001123 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001124 return client.RunOnDeps('pack', args)
1125
1126
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001127def CMDstatus(parser, args):
1128 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001129 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1130 help='override deps for the specified (comma-separated) '
1131 'platform(s); \'all\' will process all deps_os '
1132 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001133 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001134 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001136 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001137 if options.verbose:
1138 # Print out the .gclient file. This is longer than if we just printed the
1139 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001140 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001141 return client.RunOnDeps('status', args)
1142
1143
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001144@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001145 gclient sync
1146 update files from SCM according to current configuration,
1147 *for modules which have changed since last update or sync*
1148 gclient sync --force
1149 update files from SCM according to current configuration, for
1150 all modules (useful for recovering files deleted from local copy)
1151 gclient sync --revision src@31000
1152 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001153""")
1154def CMDsync(parser, args):
1155 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001156 parser.add_option('-f', '--force', action='store_true',
1157 help='force update even for unchanged modules')
1158 parser.add_option('-n', '--nohooks', action='store_true',
1159 help='don\'t run hooks after the update is complete')
1160 parser.add_option('-r', '--revision', action='append',
1161 dest='revisions', metavar='REV', default=[],
1162 help='Enforces revision/hash for the solutions with the '
1163 'format src@rev. The src@ part is optional and can be '
1164 'skipped. -r can be used multiple times when .gclient '
1165 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001166 'if the src@ part is skipped. Note that specifying '
1167 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001168 parser.add_option('-t', '--transitive', action='store_true',
1169 help='When a revision is specified (in the DEPS file or '
1170 'with the command-line flag), transitively update '
1171 'the dependencies to the date of the given revision. '
1172 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001173 parser.add_option('-H', '--head', action='store_true',
1174 help='skips any safesync_urls specified in '
1175 'configured solutions and sync to head instead')
1176 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001177 help='delete any dependency that have been removed from '
1178 'last sync as long as there is no local modification. '
1179 'Coupled with --force, it will remove them even with '
1180 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001181 parser.add_option('-R', '--reset', action='store_true',
1182 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001183 parser.add_option('-M', '--merge', action='store_true',
1184 help='merge upstream changes instead of trying to '
1185 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001186 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1187 help='override deps for the specified (comma-separated) '
1188 'platform(s); \'all\' will process all deps_os '
1189 'references')
1190 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1191 help='Skip svn up whenever possible by requesting '
1192 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001194 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001195
1196 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001197 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001198
maruel@chromium.org307d1792010-05-31 20:03:13 +00001199 if options.revisions and options.head:
1200 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001201 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202
1203 if options.verbose:
1204 # Print out the .gclient file. This is longer than if we just printed the
1205 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001206 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001207 return client.RunOnDeps('update', args)
1208
1209
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001210def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001211 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001212 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001213
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001214def CMDdiff(parser, args):
1215 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001216 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1217 help='override deps for the specified (comma-separated) '
1218 'platform(s); \'all\' will process all deps_os '
1219 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001220 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001221 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001222 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001223 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224 if options.verbose:
1225 # Print out the .gclient file. This is longer than if we just printed the
1226 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001227 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001228 return client.RunOnDeps('diff', args)
1229
1230
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001231def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001232 """Revert all modifications in every dependencies.
1233
1234 That's the nuclear option to get back to a 'clean' state. It removes anything
1235 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001236 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1237 help='override deps for the specified (comma-separated) '
1238 'platform(s); \'all\' will process all deps_os '
1239 'references')
1240 parser.add_option('-n', '--nohooks', action='store_true',
1241 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242 (options, args) = parser.parse_args(args)
1243 # --force is implied.
1244 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001245 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001246 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001247 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248 return client.RunOnDeps('revert', args)
1249
1250
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001251def CMDrunhooks(parser, args):
1252 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001253 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1254 help='override deps for the specified (comma-separated) '
1255 'platform(s); \'all\' will process all deps_os '
1256 'references')
1257 parser.add_option('-f', '--force', action='store_true', default=True,
1258 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001259 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001260 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001261 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001262 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001263 if options.verbose:
1264 # Print out the .gclient file. This is longer than if we just printed the
1265 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001266 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001267 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001268 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001269 return client.RunOnDeps('runhooks', args)
1270
1271
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001272def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001273 """Output revision info mapping for the client and its dependencies.
1274
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001275 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001276 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001277 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1278 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001279 commit can change.
1280 """
1281 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1282 help='override deps for the specified (comma-separated) '
1283 'platform(s); \'all\' will process all deps_os '
1284 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001285 parser.add_option('-a', '--actual', action='store_true',
1286 help='gets the actual checked out revisions instead of the '
1287 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001288 parser.add_option('-s', '--snapshot', action='store_true',
1289 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001290 'version of all repositories to reproduce the tree, '
1291 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001292 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001293 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001294 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001295 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001296 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001297 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001298
1299
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001300def Command(name):
1301 return getattr(sys.modules[__name__], 'CMD' + name, None)
1302
1303
1304def CMDhelp(parser, args):
1305 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001306 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001307 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001309 parser.print_help()
1310 return 0
1311
1312
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001313def GenUsage(parser, command):
1314 """Modify an OptParse object with the function's documentation."""
1315 obj = Command(command)
1316 if command == 'help':
1317 command = '<command>'
1318 # OptParser.description prefer nicely non-formatted strings.
1319 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1320 usage = getattr(obj, 'usage', '')
1321 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1322 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001323
1324
maruel@chromium.org0895b752011-08-26 20:40:33 +00001325def Parser():
1326 """Returns the default parser."""
1327 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001328 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001329 help='Specify how many SCM commands can run in parallel; '
1330 'default=%default')
1331 parser.add_option('-v', '--verbose', action='count', default=0,
1332 help='Produces additional output for diagnostics. Can be '
1333 'used up to three times for more logging info.')
1334 parser.add_option('--gclientfile', dest='config_filename',
1335 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1336 help='Specify an alternate %default file')
1337 # Integrate standard options processing.
1338 old_parser = parser.parse_args
1339 def Parse(args):
1340 (options, args) = old_parser(args)
1341 level = None
1342 if options.verbose == 2:
1343 level = logging.INFO
1344 elif options.verbose > 2:
1345 level = logging.DEBUG
1346 logging.basicConfig(level=level,
1347 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1348 options.entries_filename = options.config_filename + '_entries'
1349 if options.jobs < 1:
1350 parser.error('--jobs must be 1 or higher')
1351
1352 # These hacks need to die.
1353 if not hasattr(options, 'revisions'):
1354 # GClient.RunOnDeps expects it even if not applicable.
1355 options.revisions = []
1356 if not hasattr(options, 'head'):
1357 options.head = None
1358 if not hasattr(options, 'nohooks'):
1359 options.nohooks = True
1360 if not hasattr(options, 'deps_os'):
1361 options.deps_os = None
1362 if not hasattr(options, 'manually_grab_svn_rev'):
1363 options.manually_grab_svn_rev = None
1364 if not hasattr(options, 'force'):
1365 options.force = None
1366 return (options, args)
1367 parser.parse_args = Parse
1368 # We don't want wordwrapping in epilog (usually examples)
1369 parser.format_epilog = lambda _: parser.epilog or ''
1370 return parser
1371
1372
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001374 """Doesn't parse the arguments here, just find the right subcommand to
1375 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001376 if sys.hexversion < 0x02050000:
1377 print >> sys.stderr, (
1378 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001379 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001380 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1381 # operations. Python as a strong tendency to buffer sys.stdout.
1382 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001383 # Make stdout annotated with the thread ids.
1384 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001385 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001386 # Unused variable 'usage'
1387 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001388 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1389 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1390 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001391 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001392 if argv:
1393 command = Command(argv[0])
1394 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001395 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001396 GenUsage(parser, argv[0])
1397 return command(parser, argv[1:])
1398 # Not a known command. Default to help.
1399 GenUsage(parser, 'help')
1400 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001401 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001402 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001403 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001404
1405
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001406if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001407 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001408 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001409
1410# vim: ts=2:sw=2:tw=80:et: