blob: 7bd53ea81f719f87a79619f004b899a0440bd920 [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.
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000166 self._managed = managed
167 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.orgf13a4182011-09-22 00:26:15 +0000229 for i in root_deps:
230 if i is self.parent:
231 break
232 if i.name:
233 self._requirements.add(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000234
235 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000236 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000237
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000238 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000239 def yield_full_tree(root):
240 """Depth-first recursion."""
241 yield root
242 for i in root.dependencies:
243 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000244 if j.should_process:
245 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000246
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000247 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000248 if obj is self or not obj.name:
249 continue
250 # Step 1: Find any requirements self may need.
251 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000252 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000253 # Step 2: Find any requirements self may impose.
254 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000255 try:
256 # Access to a protected member _requirements of a client class
257 # pylint: disable=W0212
258 obj.lock.acquire()
259 obj._requirements.add(self.name)
260 finally:
261 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000262
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000263 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000264 """Resolves the parsed url from url.
265
266 Manages From() keyword accordingly. Do not touch self.parsed_url nor
267 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000268 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000269 overriden_url = self.get_custom_deps(self.name, url)
270 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000271 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000272 overriden_url))
273 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000274 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000275 ref = [
276 dep for dep in self.root.subtree(True) if url.module_name == dep.name
277 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000278 if not ref:
279 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
280 url.module_name, ref))
281 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000282 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000283 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000284 # Make sure the referenced dependency DEPS file is loaded and file the
285 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000286 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000287 found_dep = None
288 for d in ref.dependencies:
289 if d.name == sub_target:
290 found_dep = d
291 break
292 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000293 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000294 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
295 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000296 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000297
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000298 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000299 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000300 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000301 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000302 elif isinstance(url, basestring):
303 parsed_url = urlparse.urlparse(url)
304 if not parsed_url[0]:
305 # A relative url. Fetch the real base.
306 path = parsed_url[2]
307 if not path.startswith('/'):
308 raise gclient_utils.Error(
309 'relative DEPS entry \'%s\' must begin with a slash' % url)
310 # Create a scm just to query the full url.
311 parent_url = self.parent.parsed_url
312 if isinstance(parent_url, self.FileImpl):
313 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000314 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000315 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000316 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000317 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000318 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000319 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000320 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000321 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000322 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000323 return parsed_url
324 elif url is None:
325 return None
326 else:
327 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000328
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000329 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000330 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000331 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000332 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000333 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000334 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000335 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000336 # One thing is unintuitive, vars= {} must happen before Var() use.
337 local_scope = {}
338 var = self.VarImpl(self.custom_vars, local_scope)
339 global_scope = {
340 'File': self.FileImpl,
341 'From': self.FromImpl,
342 'Var': var.Lookup,
343 'deps_os': {},
344 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000345 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000346 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000347 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
348 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000349 else:
350 deps_content = gclient_utils.FileRead(filepath)
351 logging.debug(deps_content)
352 # Eval the content.
353 try:
354 exec(deps_content, global_scope, local_scope)
355 except SyntaxError, e:
356 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000357 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000358 # load os specific dependencies if defined. these dependencies may
359 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000360 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000361 enforced_os = self.root.enforced_os
362 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000363 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000364 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000365 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000366 # platform, so we collect the broadest set of dependencies
367 # available. We may end up with the wrong revision of something for
368 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000369 deps.update([x for x in os_deps.items() if not x[0] in deps])
370 else:
371 deps.update(os_deps)
372
maruel@chromium.org271375b2010-06-23 19:17:38 +0000373 self.deps_hooks.extend(local_scope.get('hooks', []))
374
375 # If a line is in custom_deps, but not in the solution, we want to append
376 # this line to the solution.
377 for d in self.custom_deps:
378 if d not in deps:
379 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000380
381 # If use_relative_paths is set in the DEPS file, regenerate
382 # the dictionary using paths relative to the directory containing
383 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000384 use_relative_paths = local_scope.get('use_relative_paths', False)
385 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000386 rel_deps = {}
387 for d, url in deps.items():
388 # normpath is required to allow DEPS to use .. in their
389 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000390 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
391 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000392
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 # Convert the deps into real Dependency.
394 for name, url in deps.iteritems():
395 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000396 raise gclient_utils.Error(
397 'The same name "%s" appears multiple times in the deps section' %
398 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000399 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000400 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000401 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000402 if name in tree:
403 if url == tree[name].url:
404 logging.info('Won\'t process duplicate dependency %s' % tree[name])
405 # In theory we could keep it as a shadow of the other one. In
406 # practice, simply ignore it.
407 #should_process = False
408 continue
409 else:
410 raise gclient_utils.Error(
411 'Dependency %s specified more than once:\n %s\nvs\n %s' %
412 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000413 self.dependencies.append(Dependency(self, name, url, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000414 None, self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000415 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000416
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000417 # Arguments number differs from overridden method
418 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000419 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420 """Runs 'command' before parsing the DEPS in case it's a initial checkout
421 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000422
423 def maybeGetParentRevision(options):
424 """If we are performing an update and --transitive is set, set the
425 revision to the parent's revision. If we have an explicit revision
426 do nothing."""
427 if command == 'update' and options.transitive and not options.revision:
428 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
429 if not revision:
430 options.revision = revision_overrides.get(self.parent.name)
431 if options.verbose and options.revision:
432 print("Using parent's revision date: %s" % options.revision)
433 # If the parent has a revision override, then it must have been
434 # converted to date format.
435 assert (not options.revision or
436 gclient_utils.IsDateRevision(options.revision))
437
438 def maybeConvertToDateRevision(options):
439 """If we are performing an update and --transitive is set, convert the
440 revision to a date-revision (if necessary). Instead of having
441 -r 101 replace the revision with the time stamp of 101 (e.g.
442 "{2011-18-04}").
443 This way dependencies are upgraded to the revision they had at the
444 check-in of revision 101."""
445 if (command == 'update' and
446 options.transitive and
447 options.revision and
448 not gclient_utils.IsDateRevision(options.revision)):
449 revision_date = scm.GetRevisionDate(options.revision)
450 revision = gclient_utils.MakeDateRevision(revision_date)
451 if options.verbose:
452 print("Updating revision override from %s to %s." %
453 (options.revision, revision))
454 revision_overrides[self.name] = revision
455
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000456 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000457 if not self.should_process:
458 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000459 # When running runhooks, there's no need to consult the SCM.
460 # All known hooks are expected to run unconditionally regardless of working
461 # copy state, so skip the SCM status check.
462 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000463 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000464 if run_scm and self.parsed_url:
465 if isinstance(self.parsed_url, self.FileImpl):
466 # Special support for single-file checkout.
467 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
468 options.revision = self.parsed_url.GetRevision()
469 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000470 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 self.name)
472 scm.RunCommand('updatesingle', options,
473 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000474 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000475 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000476 # Create a shallow copy to mutate revision.
477 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000478 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000479 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000480 scm = gclient_scm.CreateSCM(
481 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000482 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000483 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000484 self._file_list = [os.path.join(self.name, f.strip())
485 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000486
487 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
488 # Convert all absolute paths to relative.
489 for i in range(len(self._file_list)):
490 # It depends on the command being executed (like runhooks vs sync).
491 if not os.path.isabs(self._file_list[i]):
492 continue
493 prefix = os.path.commonprefix(
494 [self.root.root_dir.lower(), self._file_list[i].lower()])
495 self._file_list[i] = self._file_list[i][len(prefix):]
496 # Strip any leading path separators.
497 while (self._file_list[i].startswith('\\') or
498 self._file_list[i].startswith('/')):
499 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000500 self.processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000501 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000503 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000504
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505 # Parse the dependencies of this dependency.
506 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000507 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000508
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000510 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000511 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000512 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000513 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000514 # Don't run the hook when it is above recursion_limit.
515 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000516 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000518 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 # TODO(maruel): If the user is using git or git-svn, then we don't know
520 # what files have changed so we always run all hooks. It'd be nice to fix
521 # that.
522 if (options.force or
523 isinstance(self.parsed_url, self.FileImpl) or
524 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000525 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 for hook_dict in self.deps_hooks:
527 self._RunHookAction(hook_dict, [])
528 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000529 # Run hooks on the basis of whether the files from the gclient operation
530 # match each hook's pattern.
531 for hook_dict in self.deps_hooks:
532 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000533 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000534 if matching_file_list:
535 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000536 for s in self.dependencies:
537 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000538
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000539 def _RunHookAction(self, hook_dict, matching_file_list):
540 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000541 # A single DEPS file can specify multiple hooks so this function can be
542 # called multiple times on a single Dependency.
543 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000544 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000545 logging.debug(hook_dict)
546 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000547 command = hook_dict['action'][:]
548 if command[0] == 'python':
549 # If the hook specified "python" as the first item, the action is a
550 # Python script. Run it by starting a new copy of the same
551 # interpreter.
552 command[0] = sys.executable
553
554 if '$matching_files' in command:
555 splice_index = command.index('$matching_files')
556 command[splice_index:splice_index + 1] = matching_file_list
557
maruel@chromium.org17d01792010-09-01 18:07:10 +0000558 try:
559 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000560 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000561 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000562 # Use a discrete exit status code of 2 to indicate that a hook action
563 # failed. Users of this script may wish to treat hook action failures
564 # differently from VC failures.
565 print >> sys.stderr, 'Error: %s' % str(e)
566 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000567
maruel@chromium.org0d812442010-08-10 12:41:08 +0000568 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000569 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000570 result = []
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000571 dependencies = self.dependencies
572 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000573 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000574 result.append(d)
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000575 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000576 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000577 return result
578
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000579 def get_custom_deps(self, name, url):
580 """Returns a custom deps if applicable."""
581 if self.parent:
582 url = self.parent.get_custom_deps(name, url)
583 # None is a valid return value to disable a dependency.
584 return self.custom_deps.get(name, url)
585
maruel@chromium.org68988972011-09-20 14:11:42 +0000586 @property
587 def recursion_limit(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000588 """Returns > 0 if this dependency is not too recursed to be processed.
589
590 Immutable so no need to lock."""
maruel@chromium.org68988972011-09-20 14:11:42 +0000591 return max(self.parent.recursion_limit - 1, 0)
592
593 @property
594 def deps_file(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000595 """Immutable so no need to lock."""
maruel@chromium.org68988972011-09-20 14:11:42 +0000596 return self._deps_file
597
598 @property
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000599 def managed(self):
600 """Immutable so no need to lock."""
601 return self._managed
602
603 @property
maruel@chromium.org68988972011-09-20 14:11:42 +0000604 def safesync_url(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000605 """Immutable so no need to lock."""
maruel@chromium.org68988972011-09-20 14:11:42 +0000606 return self._safesync_url
607
608 @property
609 def should_process(self):
610 """True if this dependency should be processed, i.e. checked out."""
611 return self._should_process
612
613 @property
614 def parent(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000615 """Immutable so no need to lock."""
maruel@chromium.org68988972011-09-20 14:11:42 +0000616 return self._parent
617
618 @property
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000619 def custom_vars(self):
620 """Immutable so no need to lock."""
621 return self._custom_vars.copy()
622
623 @property
624 def custom_deps(self):
625 """Immutable so no need to lock."""
626 return self._custom_deps.copy()
627
628 @property
629 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000630 def file_list(self):
631 result = self._file_list[:]
632 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000633 result.extend(d.file_list)
634 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000635
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000636 def __str__(self):
637 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000638 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000639 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000640 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000641 # First try the native property if it exists.
642 if hasattr(self, '_' + i):
643 value = getattr(self, '_' + i, False)
644 else:
645 value = getattr(self, i, False)
646 if value:
647 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000648
649 for d in self.dependencies:
650 out.extend([' ' + x for x in str(d).splitlines()])
651 out.append('')
652 return '\n'.join(out)
653
654 def __repr__(self):
655 return '%s: %s' % (self.name, self.url)
656
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000657 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000658 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000659 out = '%s(%s)' % (self.name, self.url)
660 i = self.parent
661 while i and i.name:
662 out = '%s(%s) -> %s' % (i.name, i.url, out)
663 i = i.parent
664 return out
665
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000666 @property
667 def root(self):
668 """Returns the root node, a GClient object."""
669 if not self.parent:
670 # This line is to signal pylint that it could be a GClient instance.
671 return self or GClient(None, None)
672 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000673
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000674
675class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000676 """Object that represent a gclient checkout. A tree of Dependency(), one per
677 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000678
679 DEPS_OS_CHOICES = {
680 "win32": "win",
681 "win": "win",
682 "cygwin": "win",
683 "darwin": "mac",
684 "mac": "mac",
685 "unix": "unix",
686 "linux": "unix",
687 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000688 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000689 }
690
691 DEFAULT_CLIENT_FILE_TEXT = ("""\
692solutions = [
693 { "name" : "%(solution_name)s",
694 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000695 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000696 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000697 "custom_deps" : {
698 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000699 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000700 },
701]
702""")
703
704 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
705 { "name" : "%(solution_name)s",
706 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000707 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000708 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000709 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000710%(solution_deps)s },
711 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000712 },
713""")
714
715 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
716# Snapshot generated with gclient revinfo --snapshot
717solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000718%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000719""")
720
721 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000722 # Do not change previous behavior. Only solution level and immediate DEPS
723 # are processed.
724 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000725 Dependency.__init__(self, None, None, None, None, True, None, None,
726 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000727 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000728 if options.deps_os:
729 enforced_os = options.deps_os.split(',')
730 else:
731 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
732 if 'all' in enforced_os:
733 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000734 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000735 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000736 self.config_content = None
737
738 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000739 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000740 config_dict = {}
741 self.config_content = content
742 try:
743 exec(content, config_dict)
744 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000745 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000746 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000747 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000748 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000749 if s['name'] in tree:
750 raise gclient_utils.Error(
751 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000752 self.dependencies.append(Dependency(
753 self, s['name'], s['url'],
754 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000755 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000756 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000757 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000758 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000759 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000760 except KeyError:
761 raise gclient_utils.Error('Invalid .gclient file. Solution is '
762 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000763 # .gclient can have hooks.
764 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000765 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000766
767 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000768 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000769 self._options.config_filename),
770 self.config_content)
771
772 @staticmethod
773 def LoadCurrentConfig(options):
774 """Searches for and loads a .gclient file relative to the current working
775 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000776 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000777 if not path:
778 return None
779 client = GClient(path, options)
780 client.SetConfig(gclient_utils.FileRead(
781 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000782 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000783
nsylvain@google.comefc80932011-05-31 21:27:56 +0000784 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000785 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000786 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
787 'solution_name': solution_name,
788 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000789 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000790 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000791 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000792 })
793
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000794 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000795 """Creates a .gclient_entries file to record the list of unique checkouts.
796
797 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000798 """
799 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
800 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000801 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000802 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000803 # Skip over File() dependencies as we can't version them.
804 if not isinstance(entry.parsed_url, self.FileImpl):
805 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
806 pprint.pformat(entry.parsed_url))
807 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000808 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000809 logging.info(result)
810 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000811
812 def _ReadEntries(self):
813 """Read the .gclient_entries file for the given client.
814
815 Returns:
816 A sequence of solution names, which will be empty if there is the
817 entries file hasn't been created yet.
818 """
819 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000820 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000821 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000822 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000823 try:
824 exec(gclient_utils.FileRead(filename), scope)
825 except SyntaxError, e:
826 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000827 return scope['entries']
828
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000829 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000830 """Checks for revision overrides."""
831 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000832 if self._options.head:
833 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000834 # Do not check safesync_url if one or more --revision flag is specified.
835 if not self._options.revisions:
836 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000837 if not s.managed:
838 self._options.revisions.append('%s@unmanaged' % s.name)
839 elif s.safesync_url:
840 handle = urllib.urlopen(s.safesync_url)
841 rev = handle.read().strip()
842 handle.close()
843 if len(rev):
844 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000845 if not self._options.revisions:
846 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000847 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000848 index = 0
849 for revision in self._options.revisions:
850 if not '@' in revision:
851 # Support for --revision 123
852 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000853 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000854 if not sol in solutions_names:
855 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
856 print >> sys.stderr, ('Please fix your script, having invalid '
857 '--revision flags will soon considered an error.')
858 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000859 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000860 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000861 return revision_overrides
862
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863 def RunOnDeps(self, command, args):
864 """Runs a command on each dependency in a client and its dependencies.
865
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866 Args:
867 command: The command to use (e.g., 'status' or 'diff')
868 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000869 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000870 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000871 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000872 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000873 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000874 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000875 if (command in ('update', 'revert') and sys.stdout.isatty() and not
876 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000877 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000878 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000879 for s in self.dependencies:
880 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000881 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000882
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 # Once all the dependencies have been processed, it's now safe to run the
884 # hooks.
885 if not self._options.nohooks:
886 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000887
888 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000889 # Notify the user if there is an orphaned entry in their working copy.
890 # Only delete the directory if there are no changes in it, and
891 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000892 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000893 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000894 if not prev_url:
895 # entry must have been overridden via .gclient custom_deps
896 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000897 # Fix path separator on Windows.
898 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000899 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000900 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000901 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000902 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000903 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000904 scm.status(self._options, [], file_list)
905 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000906 if (not self._options.delete_unversioned_trees or
907 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000908 # There are modified files in this entry. Keep warning until
909 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000910 print(('\nWARNING: \'%s\' is no longer part of this client. '
911 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000912 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000913 else:
914 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000915 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000916 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000917 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000918 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000919 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000920 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000921
922 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000923 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000924 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000925 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000926 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000927 for s in self.dependencies:
928 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000929 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000931 def GetURLAndRev(dep):
932 """Returns the revision-qualified SCM url for a Dependency."""
933 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000934 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000935 if isinstance(dep.parsed_url, self.FileImpl):
936 original_url = dep.parsed_url.file_location
937 else:
938 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000939 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000940 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000941 if not os.path.isdir(scm.checkout_path):
942 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000943 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000945 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000946 new_gclient = ''
947 # First level at .gclient
948 for d in self.dependencies:
949 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000950 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000951 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000952 for d in dep.dependencies:
953 entries[d.name] = GetURLAndRev(d)
954 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000955 GrabDeps(d)
956 custom_deps = []
957 for k in sorted(entries.keys()):
958 if entries[k]:
959 # Quotes aren't escaped...
960 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
961 else:
962 custom_deps.append(' \"%s\": None,\n' % k)
963 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
964 'solution_name': d.name,
965 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000966 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000967 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000968 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000969 'solution_deps': ''.join(custom_deps),
970 }
971 # Print the snapshot configuration file
972 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000973 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000974 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000975 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000976 if self._options.actual:
977 entries[d.name] = GetURLAndRev(d)
978 else:
979 entries[d.name] = d.parsed_url
980 keys = sorted(entries.keys())
981 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000982 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000983 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000984
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000985 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000986 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000987 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000988
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000989 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +0000990 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000991 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000992 return self._root_dir
993
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000994 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +0000995 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000996 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000997 return self._enforced_os
998
maruel@chromium.org68988972011-09-20 14:11:42 +0000999 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001000 def recursion_limit(self):
1001 """How recursive can each dependencies in DEPS file can load DEPS file."""
1002 return self._recursion_limit
1003
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001004
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001005#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006
1007
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001008def CMDcleanup(parser, args):
1009 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001010
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001011Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001012"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001013 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1014 help='override deps for the specified (comma-separated) '
1015 'platform(s); \'all\' will process all deps_os '
1016 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001018 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001019 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001020 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001021 if options.verbose:
1022 # Print out the .gclient file. This is longer than if we just printed the
1023 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001024 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001025 return client.RunOnDeps('cleanup', args)
1026
1027
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001028@attr('usage', '[command] [args ...]')
1029def CMDrecurse(parser, args):
1030 """Operates on all the entries.
1031
1032 Runs a shell command on all entries.
1033 """
1034 # Stop parsing at the first non-arg so that these go through to the command
1035 parser.disable_interspersed_args()
1036 parser.add_option('-s', '--scm', action='append', default=[],
1037 help='choose scm types to operate upon')
1038 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001039 if not args:
1040 print >> sys.stderr, 'Need to supply a command!'
1041 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001042 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1043 if not root_and_entries:
1044 print >> sys.stderr, (
1045 'You need to run gclient sync at least once to use \'recurse\'.\n'
1046 'This is because .gclient_entries needs to exist and be up to date.')
1047 return 1
1048 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001049 scm_set = set()
1050 for scm in options.scm:
1051 scm_set.update(scm.split(','))
1052
1053 # Pass in the SCM type as an env variable
1054 env = os.environ.copy()
1055
1056 for path, url in entries.iteritems():
1057 scm = gclient_scm.GetScmName(url)
1058 if scm_set and scm not in scm_set:
1059 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001060 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001061 if scm:
1062 env['GCLIENT_SCM'] = scm
1063 if url:
1064 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001065 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001066 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001067
1068
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001069@attr('usage', '[url] [safesync url]')
1070def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001071 """Create a .gclient file in the current directory.
1072
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001074top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001075modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001076provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001077URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001078"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001079 parser.add_option('--spec',
1080 help='create a gclient file containing the provided '
1081 'string. Due to Cygwin/Python brokenness, it '
1082 'probably can\'t contain any newlines.')
1083 parser.add_option('--name',
1084 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001085 parser.add_option('--deps-file', default='DEPS',
1086 help='overrides the default name for the DEPS file for the'
1087 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001088 parser.add_option('--unmanaged', action='store_true', default=False,
1089 help='overrides the default behavior to make it possible '
1090 'to have the main solution untouched by gclient '
1091 '(gclient will check out unmanaged dependencies but '
1092 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001093 parser.add_option('--git-deps', action='store_true',
1094 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001095 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001096 if ((options.spec and args) or len(args) > 2 or
1097 (not options.spec and not args)):
1098 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1099
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001100 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001101 if options.spec:
1102 client.SetConfig(options.spec)
1103 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001104 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001105 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001106 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001107 if name.endswith('.git'):
1108 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001109 else:
1110 # specify an alternate relpath for the given URL.
1111 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001112 deps_file = options.deps_file
1113 if options.git_deps:
1114 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001115 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 if len(args) > 1:
1117 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001118 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1119 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001120 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001121 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001122
1123
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001124@attr('epilog', """Example:
1125 gclient pack > patch.txt
1126 generate simple patch for configured client and dependences
1127""")
1128def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001129 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001130
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001132dependencies, and performs minimal postprocessing of the output. The
1133resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001134checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001135"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001136 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1137 help='override deps for the specified (comma-separated) '
1138 'platform(s); \'all\' will process all deps_os '
1139 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001140 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001141 client = GClient.LoadCurrentConfig(options)
1142 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001144 if options.verbose:
1145 # Print out the .gclient file. This is longer than if we just printed the
1146 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001147 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001148 return client.RunOnDeps('pack', args)
1149
1150
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001151def CMDstatus(parser, args):
1152 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001153 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1154 help='override deps for the specified (comma-separated) '
1155 'platform(s); \'all\' will process all deps_os '
1156 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001157 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001158 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001160 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161 if options.verbose:
1162 # Print out the .gclient file. This is longer than if we just printed the
1163 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001164 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001165 return client.RunOnDeps('status', args)
1166
1167
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001168@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001169 gclient sync
1170 update files from SCM according to current configuration,
1171 *for modules which have changed since last update or sync*
1172 gclient sync --force
1173 update files from SCM according to current configuration, for
1174 all modules (useful for recovering files deleted from local copy)
1175 gclient sync --revision src@31000
1176 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177""")
1178def CMDsync(parser, args):
1179 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001180 parser.add_option('-f', '--force', action='store_true',
1181 help='force update even for unchanged modules')
1182 parser.add_option('-n', '--nohooks', action='store_true',
1183 help='don\'t run hooks after the update is complete')
1184 parser.add_option('-r', '--revision', action='append',
1185 dest='revisions', metavar='REV', default=[],
1186 help='Enforces revision/hash for the solutions with the '
1187 'format src@rev. The src@ part is optional and can be '
1188 'skipped. -r can be used multiple times when .gclient '
1189 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001190 'if the src@ part is skipped. Note that specifying '
1191 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001192 parser.add_option('-t', '--transitive', action='store_true',
1193 help='When a revision is specified (in the DEPS file or '
1194 'with the command-line flag), transitively update '
1195 'the dependencies to the date of the given revision. '
1196 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001197 parser.add_option('-H', '--head', action='store_true',
1198 help='skips any safesync_urls specified in '
1199 'configured solutions and sync to head instead')
1200 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001201 help='delete any dependency that have been removed from '
1202 'last sync as long as there is no local modification. '
1203 'Coupled with --force, it will remove them even with '
1204 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001205 parser.add_option('-R', '--reset', action='store_true',
1206 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001207 parser.add_option('-M', '--merge', action='store_true',
1208 help='merge upstream changes instead of trying to '
1209 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001210 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1211 help='override deps for the specified (comma-separated) '
1212 'platform(s); \'all\' will process all deps_os '
1213 'references')
1214 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1215 help='Skip svn up whenever possible by requesting '
1216 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001217 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001218 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001219
1220 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001221 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001222
maruel@chromium.org307d1792010-05-31 20:03:13 +00001223 if options.revisions and options.head:
1224 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001225 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001226
1227 if options.verbose:
1228 # Print out the .gclient file. This is longer than if we just printed the
1229 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001230 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001231 return client.RunOnDeps('update', args)
1232
1233
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001234def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001235 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001237
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001238def CMDdiff(parser, args):
1239 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001240 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1241 help='override deps for the specified (comma-separated) '
1242 'platform(s); \'all\' will process all deps_os '
1243 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001244 (options, args) = parser.parse_args(args)
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 if options.verbose:
1249 # Print out the .gclient file. This is longer than if we just printed the
1250 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001251 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252 return client.RunOnDeps('diff', args)
1253
1254
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001255def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001256 """Revert all modifications in every dependencies.
1257
1258 That's the nuclear option to get back to a 'clean' state. It removes anything
1259 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001260 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1261 help='override deps for the specified (comma-separated) '
1262 'platform(s); \'all\' will process all deps_os '
1263 'references')
1264 parser.add_option('-n', '--nohooks', action='store_true',
1265 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001266 (options, args) = parser.parse_args(args)
1267 # --force is implied.
1268 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001269 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001270 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001271 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272 return client.RunOnDeps('revert', args)
1273
1274
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001275def CMDrunhooks(parser, args):
1276 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001277 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1278 help='override deps for the specified (comma-separated) '
1279 'platform(s); \'all\' will process all deps_os '
1280 'references')
1281 parser.add_option('-f', '--force', action='store_true', default=True,
1282 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001283 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001284 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001285 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001286 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287 if options.verbose:
1288 # Print out the .gclient file. This is longer than if we just printed the
1289 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001290 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001291 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001292 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293 return client.RunOnDeps('runhooks', args)
1294
1295
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001296def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001297 """Output revision info mapping for the client and its dependencies.
1298
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001299 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001300 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001301 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1302 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001303 commit can change.
1304 """
1305 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1306 help='override deps for the specified (comma-separated) '
1307 'platform(s); \'all\' will process all deps_os '
1308 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001309 parser.add_option('-a', '--actual', action='store_true',
1310 help='gets the actual checked out revisions instead of the '
1311 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001312 parser.add_option('-s', '--snapshot', action='store_true',
1313 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001314 'version of all repositories to reproduce the tree, '
1315 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001316 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001317 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001319 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001320 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001321 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001322
1323
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001324def Command(name):
1325 return getattr(sys.modules[__name__], 'CMD' + name, None)
1326
1327
1328def CMDhelp(parser, args):
1329 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001330 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001331 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001332 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001333 parser.print_help()
1334 return 0
1335
1336
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001337def GenUsage(parser, command):
1338 """Modify an OptParse object with the function's documentation."""
1339 obj = Command(command)
1340 if command == 'help':
1341 command = '<command>'
1342 # OptParser.description prefer nicely non-formatted strings.
1343 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1344 usage = getattr(obj, 'usage', '')
1345 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1346 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001347
1348
maruel@chromium.org0895b752011-08-26 20:40:33 +00001349def Parser():
1350 """Returns the default parser."""
1351 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001352 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001353 help='Specify how many SCM commands can run in parallel; '
1354 'default=%default')
1355 parser.add_option('-v', '--verbose', action='count', default=0,
1356 help='Produces additional output for diagnostics. Can be '
1357 'used up to three times for more logging info.')
1358 parser.add_option('--gclientfile', dest='config_filename',
1359 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1360 help='Specify an alternate %default file')
1361 # Integrate standard options processing.
1362 old_parser = parser.parse_args
1363 def Parse(args):
1364 (options, args) = old_parser(args)
1365 level = None
1366 if options.verbose == 2:
1367 level = logging.INFO
1368 elif options.verbose > 2:
1369 level = logging.DEBUG
1370 logging.basicConfig(level=level,
1371 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1372 options.entries_filename = options.config_filename + '_entries'
1373 if options.jobs < 1:
1374 parser.error('--jobs must be 1 or higher')
1375
1376 # These hacks need to die.
1377 if not hasattr(options, 'revisions'):
1378 # GClient.RunOnDeps expects it even if not applicable.
1379 options.revisions = []
1380 if not hasattr(options, 'head'):
1381 options.head = None
1382 if not hasattr(options, 'nohooks'):
1383 options.nohooks = True
1384 if not hasattr(options, 'deps_os'):
1385 options.deps_os = None
1386 if not hasattr(options, 'manually_grab_svn_rev'):
1387 options.manually_grab_svn_rev = None
1388 if not hasattr(options, 'force'):
1389 options.force = None
1390 return (options, args)
1391 parser.parse_args = Parse
1392 # We don't want wordwrapping in epilog (usually examples)
1393 parser.format_epilog = lambda _: parser.epilog or ''
1394 return parser
1395
1396
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001397def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001398 """Doesn't parse the arguments here, just find the right subcommand to
1399 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001400 if sys.hexversion < 0x02050000:
1401 print >> sys.stderr, (
1402 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001403 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001404 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1405 # operations. Python as a strong tendency to buffer sys.stdout.
1406 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001407 # Make stdout annotated with the thread ids.
1408 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001409 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001410 # Unused variable 'usage'
1411 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001412 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1413 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1414 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001415 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001416 if argv:
1417 command = Command(argv[0])
1418 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001419 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001420 GenUsage(parser, argv[0])
1421 return command(parser, argv[1:])
1422 # Not a known command. Default to help.
1423 GenUsage(parser, 'help')
1424 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001425 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001426 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001427 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001428
1429
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001430if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001431 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001432 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001433
1434# vim: ts=2:sw=2:tw=80:et: