blob: 9efc1c83fcb60aa79b8f6af3b43fe673c72642a0 [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.org8ac2b272011-09-26 18:49:49 +0000139class DependencySettings(object):
140 """Immutable configuration settings."""
141 def __init__(
142 self, parent, safesync_url, managed, custom_deps, custom_vars,
143 deps_file, should_process):
144 # These are not mutable:
145 self._parent = parent
146 self._safesync_url = safesync_url
147 self._deps_file = deps_file
148 # 'managed' determines whether or not this dependency is synced/updated by
149 # gclient after gclient checks it out initially. The difference between
150 # 'managed' and 'should_process' is that the user specifies 'managed' via
151 # the --unmanaged command-line flag or a .gclient config, where
152 # 'should_process' is dynamically set by gclient if it goes over its
153 # recursion limit and controls gclient's behavior so it does not misbehave.
154 self._managed = managed
155 self._should_process = should_process
156
157 # These are only set in .gclient and not in DEPS files.
158 self._custom_vars = custom_vars or {}
159 self._custom_deps = custom_deps or {}
160
161 if '/' in self._deps_file or '\\' in self._deps_file:
162 raise gclient_utils.Error('deps_file name must not be a path, just a '
163 'filename. %s' % self._deps_file)
164
165 @property
166 def deps_file(self):
167 """Immutable so no need to lock."""
168 return self._deps_file
169
170 @property
171 def managed(self):
172 """Immutable so no need to lock."""
173 return self._managed
174
175 @property
176 def parent(self):
177 """Immutable so no need to lock."""
178 return self._parent
179
180 @property
181 def safesync_url(self):
182 """Immutable so no need to lock."""
183 return self._safesync_url
184
185 @property
186 def should_process(self):
187 """True if this dependency should be processed, i.e. checked out."""
188 return self._should_process
189
190 @property
191 def custom_vars(self):
192 """Immutable so no need to lock."""
193 return self._custom_vars.copy()
194
195 @property
196 def custom_deps(self):
197 """Immutable so no need to lock."""
198 return self._custom_deps.copy()
199
200
201class Dependency(GClientKeywords, gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000202 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000203
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000204 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000205 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000206 GClientKeywords.__init__(self)
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000207 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000208 DependencySettings.__init__(
209 self, parent, safesync_url, managed, custom_deps, custom_vars,
210 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000211
212 # This is in both .gclient and DEPS files:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000213 self.url = url
maruel@chromium.org68988972011-09-20 14:11:42 +0000214
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000215 self.deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000216
217 # Calculates properties:
218 self.parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000219 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000220 # A cache of the files affected by the current operation, necessary for
221 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000222 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000223 # If it is not set to True, the dependency wasn't processed for its child
224 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000225 self.deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000226 # This dependency has been processed, i.e. checked out
227 self.processed = False
228 # This dependency had its hook run
229 self.hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000230
maruel@chromium.org98023df2011-09-07 18:44:47 +0000231 # Post process the url to remove trailing slashes.
232 if isinstance(self.url, basestring):
233 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
234 # it to proto://host/path@rev.
235 if self.url.count('@') > 1:
236 raise gclient_utils.Error('Invalid url "%s"' % self.url)
237 self.url = self.url.replace('/@', '@')
238
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000239 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000240
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000241 # Sanity checks
242 if not self.name and self.parent:
243 raise gclient_utils.Error('Dependency without name')
244 if not isinstance(self.url,
245 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
246 raise gclient_utils.Error('dependency url must be either a string, None, '
247 'File() or From() instead of %s' %
248 self.url.__class__.__name__)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000249
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000250 def _FindDependencies(self):
251 """Setup self.requirements and find any other dependency who would have self
252 as a requirement.
253 """
254 # self.parent is implicitly a requirement. This will be recursive by
255 # definition.
256 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000257 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000258
259 # For a tree with at least 2 levels*, the leaf node needs to depend
260 # on the level higher up in an orderly way.
261 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
262 # thus unsorted, while the .gclient format is a list thus sorted.
263 #
264 # * _recursion_limit is hard coded 2 and there is no hope to change this
265 # value.
266 #
267 # Interestingly enough, the following condition only works in the case we
268 # want: self is a 2nd level node. 3nd level node wouldn't need this since
269 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000270 root_deps = self.root.dependencies
271 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000272 for i in root_deps:
273 if i is self.parent:
274 break
275 if i.name:
276 self._requirements.add(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000277
278 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000279 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000280
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000281 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000282 def yield_full_tree(root):
283 """Depth-first recursion."""
284 yield root
285 for i in root.dependencies:
286 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000287 if j.should_process:
288 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000289
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000290 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000291 if obj is self or not obj.name:
292 continue
293 # Step 1: Find any requirements self may need.
294 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000295 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000296 # Step 2: Find any requirements self may impose.
297 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000298 try:
299 # Access to a protected member _requirements of a client class
300 # pylint: disable=W0212
301 obj.lock.acquire()
302 obj._requirements.add(self.name)
303 finally:
304 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000305
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000306 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000307 """Resolves the parsed url from url.
308
309 Manages From() keyword accordingly. Do not touch self.parsed_url nor
310 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000311 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 overriden_url = self.get_custom_deps(self.name, url)
313 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000314 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000315 overriden_url))
316 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000317 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000318 ref = [
319 dep for dep in self.root.subtree(True) if url.module_name == dep.name
320 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000321 if not ref:
322 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
323 url.module_name, ref))
324 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000325 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000326 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000327 # Make sure the referenced dependency DEPS file is loaded and file the
328 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000329 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000330 found_dep = None
331 for d in ref.dependencies:
332 if d.name == sub_target:
333 found_dep = d
334 break
335 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000336 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000337 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
338 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000339 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000340
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000341 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000342 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000343 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000344 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 elif isinstance(url, basestring):
346 parsed_url = urlparse.urlparse(url)
347 if not parsed_url[0]:
348 # A relative url. Fetch the real base.
349 path = parsed_url[2]
350 if not path.startswith('/'):
351 raise gclient_utils.Error(
352 'relative DEPS entry \'%s\' must begin with a slash' % url)
353 # Create a scm just to query the full url.
354 parent_url = self.parent.parsed_url
355 if isinstance(parent_url, self.FileImpl):
356 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000357 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000358 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000359 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000360 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000361 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000362 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000363 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000364 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000365 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000366 return parsed_url
367 elif url is None:
368 return None
369 else:
370 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000371
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000372 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000373 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000374 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000375 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000376 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000377 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000378 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000379 # One thing is unintuitive, vars= {} must happen before Var() use.
380 local_scope = {}
381 var = self.VarImpl(self.custom_vars, local_scope)
382 global_scope = {
383 'File': self.FileImpl,
384 'From': self.FromImpl,
385 'Var': var.Lookup,
386 'deps_os': {},
387 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000388 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000389 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000390 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
391 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000392 else:
393 deps_content = gclient_utils.FileRead(filepath)
394 logging.debug(deps_content)
395 # Eval the content.
396 try:
397 exec(deps_content, global_scope, local_scope)
398 except SyntaxError, e:
399 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000400 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000401 # load os specific dependencies if defined. these dependencies may
402 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000403 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000404 enforced_os = self.root.enforced_os
405 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000406 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000407 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000408 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000409 # platform, so we collect the broadest set of dependencies
410 # available. We may end up with the wrong revision of something for
411 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000412 deps.update([x for x in os_deps.items() if not x[0] in deps])
413 else:
414 deps.update(os_deps)
415
maruel@chromium.org271375b2010-06-23 19:17:38 +0000416 self.deps_hooks.extend(local_scope.get('hooks', []))
417
418 # If a line is in custom_deps, but not in the solution, we want to append
419 # this line to the solution.
420 for d in self.custom_deps:
421 if d not in deps:
422 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000423
424 # If use_relative_paths is set in the DEPS file, regenerate
425 # the dictionary using paths relative to the directory containing
426 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000427 use_relative_paths = local_scope.get('use_relative_paths', False)
428 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000429 rel_deps = {}
430 for d, url in deps.items():
431 # normpath is required to allow DEPS to use .. in their
432 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000433 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
434 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000435
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000436 # Convert the deps into real Dependency.
437 for name, url in deps.iteritems():
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000438 if name in [s.name for s in self._dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000439 raise gclient_utils.Error(
440 'The same name "%s" appears multiple times in the deps section' %
441 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000442 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000443 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000444 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000445 if name in tree:
446 if url == tree[name].url:
447 logging.info('Won\'t process duplicate dependency %s' % tree[name])
448 # In theory we could keep it as a shadow of the other one. In
449 # practice, simply ignore it.
450 #should_process = False
451 continue
452 else:
453 raise gclient_utils.Error(
454 'Dependency %s specified more than once:\n %s\nvs\n %s' %
455 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000456 self._dependencies.append(
457 Dependency(
458 self, name, url, None, None, None, None,
459 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000460 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000461
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000462 # Arguments number differs from overridden method
463 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000464 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000465 """Runs 'command' before parsing the DEPS in case it's a initial checkout
466 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000467
468 def maybeGetParentRevision(options):
469 """If we are performing an update and --transitive is set, set the
470 revision to the parent's revision. If we have an explicit revision
471 do nothing."""
472 if command == 'update' and options.transitive and not options.revision:
473 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
474 if not revision:
475 options.revision = revision_overrides.get(self.parent.name)
476 if options.verbose and options.revision:
477 print("Using parent's revision date: %s" % options.revision)
478 # If the parent has a revision override, then it must have been
479 # converted to date format.
480 assert (not options.revision or
481 gclient_utils.IsDateRevision(options.revision))
482
483 def maybeConvertToDateRevision(options):
484 """If we are performing an update and --transitive is set, convert the
485 revision to a date-revision (if necessary). Instead of having
486 -r 101 replace the revision with the time stamp of 101 (e.g.
487 "{2011-18-04}").
488 This way dependencies are upgraded to the revision they had at the
489 check-in of revision 101."""
490 if (command == 'update' and
491 options.transitive and
492 options.revision and
493 not gclient_utils.IsDateRevision(options.revision)):
494 revision_date = scm.GetRevisionDate(options.revision)
495 revision = gclient_utils.MakeDateRevision(revision_date)
496 if options.verbose:
497 print("Updating revision override from %s to %s." %
498 (options.revision, revision))
499 revision_overrides[self.name] = revision
500
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000501 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000502 if not self.should_process:
503 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000504 # When running runhooks, there's no need to consult the SCM.
505 # All known hooks are expected to run unconditionally regardless of working
506 # copy state, so skip the SCM status check.
507 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000508 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509 if run_scm and self.parsed_url:
510 if isinstance(self.parsed_url, self.FileImpl):
511 # Special support for single-file checkout.
512 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
513 options.revision = self.parsed_url.GetRevision()
514 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000515 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 self.name)
517 scm.RunCommand('updatesingle', options,
518 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000519 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000520 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000521 # Create a shallow copy to mutate revision.
522 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000523 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000524 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000525 scm = gclient_scm.CreateSCM(
526 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000527 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000528 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000529 self._file_list = [os.path.join(self.name, f.strip())
530 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000531
532 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
533 # Convert all absolute paths to relative.
534 for i in range(len(self._file_list)):
535 # It depends on the command being executed (like runhooks vs sync).
536 if not os.path.isabs(self._file_list[i]):
537 continue
538 prefix = os.path.commonprefix(
539 [self.root.root_dir.lower(), self._file_list[i].lower()])
540 self._file_list[i] = self._file_list[i][len(prefix):]
541 # Strip any leading path separators.
542 while (self._file_list[i].startswith('\\') or
543 self._file_list[i].startswith('/')):
544 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000545 self.processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000546 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000547 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000548 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000549
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000550 # Parse the dependencies of this dependency.
551 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000552 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000553
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000554 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000555 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000556 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000557 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000558 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000559 # Don't run the hook when it is above recursion_limit.
560 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000561 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000562 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000563 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000564 # TODO(maruel): If the user is using git or git-svn, then we don't know
565 # what files have changed so we always run all hooks. It'd be nice to fix
566 # that.
567 if (options.force or
568 isinstance(self.parsed_url, self.FileImpl) or
569 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000570 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000571 for hook_dict in self.deps_hooks:
572 self._RunHookAction(hook_dict, [])
573 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000574 # Run hooks on the basis of whether the files from the gclient operation
575 # match each hook's pattern.
576 for hook_dict in self.deps_hooks:
577 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000578 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000579 if matching_file_list:
580 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000581 for s in self.dependencies:
582 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000583
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000584 def _RunHookAction(self, hook_dict, matching_file_list):
585 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000586 # A single DEPS file can specify multiple hooks so this function can be
587 # called multiple times on a single Dependency.
588 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000589 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000590 logging.debug(hook_dict)
591 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000592 command = hook_dict['action'][:]
593 if command[0] == 'python':
594 # If the hook specified "python" as the first item, the action is a
595 # Python script. Run it by starting a new copy of the same
596 # interpreter.
597 command[0] = sys.executable
598
599 if '$matching_files' in command:
600 splice_index = command.index('$matching_files')
601 command[splice_index:splice_index + 1] = matching_file_list
602
maruel@chromium.org17d01792010-09-01 18:07:10 +0000603 try:
604 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000605 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000606 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000607 # Use a discrete exit status code of 2 to indicate that a hook action
608 # failed. Users of this script may wish to treat hook action failures
609 # differently from VC failures.
610 print >> sys.stderr, 'Error: %s' % str(e)
611 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000612
maruel@chromium.org0d812442010-08-10 12:41:08 +0000613 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000614 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000615 result = []
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000616 dependencies = self.dependencies
617 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000618 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000619 result.append(d)
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000620 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000621 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000622 return result
623
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000624 def get_custom_deps(self, name, url):
625 """Returns a custom deps if applicable."""
626 if self.parent:
627 url = self.parent.get_custom_deps(name, url)
628 # None is a valid return value to disable a dependency.
629 return self.custom_deps.get(name, url)
630
maruel@chromium.org68988972011-09-20 14:11:42 +0000631 @property
632 def recursion_limit(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000633 """Returns > 0 if this dependency is not too recursed to be processed.
634
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000635 Immutable so no need to lock.
636 """
maruel@chromium.org68988972011-09-20 14:11:42 +0000637 return max(self.parent.recursion_limit - 1, 0)
638
639 @property
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000640 def dependencies(self):
641 return tuple(self._dependencies)
642
643 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000644 def file_list(self):
645 result = self._file_list[:]
646 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000647 result.extend(d.file_list)
648 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000649
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000650 def __str__(self):
651 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000652 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000653 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000654 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000655 # First try the native property if it exists.
656 if hasattr(self, '_' + i):
657 value = getattr(self, '_' + i, False)
658 else:
659 value = getattr(self, i, False)
660 if value:
661 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000662
663 for d in self.dependencies:
664 out.extend([' ' + x for x in str(d).splitlines()])
665 out.append('')
666 return '\n'.join(out)
667
668 def __repr__(self):
669 return '%s: %s' % (self.name, self.url)
670
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000671 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000672 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000673 out = '%s(%s)' % (self.name, self.url)
674 i = self.parent
675 while i and i.name:
676 out = '%s(%s) -> %s' % (i.name, i.url, out)
677 i = i.parent
678 return out
679
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000680 @property
681 def root(self):
682 """Returns the root node, a GClient object."""
683 if not self.parent:
684 # This line is to signal pylint that it could be a GClient instance.
685 return self or GClient(None, None)
686 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000687
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000688
689class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000690 """Object that represent a gclient checkout. A tree of Dependency(), one per
691 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000692
693 DEPS_OS_CHOICES = {
694 "win32": "win",
695 "win": "win",
696 "cygwin": "win",
697 "darwin": "mac",
698 "mac": "mac",
699 "unix": "unix",
700 "linux": "unix",
701 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000702 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000703 }
704
705 DEFAULT_CLIENT_FILE_TEXT = ("""\
706solutions = [
707 { "name" : "%(solution_name)s",
708 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000709 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000710 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000711 "custom_deps" : {
712 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000713 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000714 },
715]
716""")
717
718 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
719 { "name" : "%(solution_name)s",
720 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000721 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000722 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000723 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000724%(solution_deps)s },
725 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000726 },
727""")
728
729 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
730# Snapshot generated with gclient revinfo --snapshot
731solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000732%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000733""")
734
735 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000736 # Do not change previous behavior. Only solution level and immediate DEPS
737 # are processed.
738 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000739 Dependency.__init__(self, None, None, None, None, True, None, None,
740 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000741 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000742 if options.deps_os:
743 enforced_os = options.deps_os.split(',')
744 else:
745 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
746 if 'all' in enforced_os:
747 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000748 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000749 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000750 self.config_content = None
751
752 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000753 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000754 config_dict = {}
755 self.config_content = content
756 try:
757 exec(content, config_dict)
758 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000759 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000760 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000761 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000762 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000763 if s['name'] in tree:
764 raise gclient_utils.Error(
765 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000766 self._dependencies.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000767 self, s['name'], s['url'],
768 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000769 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000770 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000771 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000772 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000773 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000774 except KeyError:
775 raise gclient_utils.Error('Invalid .gclient file. Solution is '
776 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000777 # .gclient can have hooks.
778 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000779 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000780
781 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000782 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000783 self._options.config_filename),
784 self.config_content)
785
786 @staticmethod
787 def LoadCurrentConfig(options):
788 """Searches for and loads a .gclient file relative to the current working
789 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000790 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000791 if not path:
792 return None
793 client = GClient(path, options)
794 client.SetConfig(gclient_utils.FileRead(
795 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000796 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000797
nsylvain@google.comefc80932011-05-31 21:27:56 +0000798 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000799 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000800 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
801 'solution_name': solution_name,
802 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000803 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000804 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000805 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000806 })
807
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000808 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000809 """Creates a .gclient_entries file to record the list of unique checkouts.
810
811 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000812 """
813 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
814 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000816 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000817 # Skip over File() dependencies as we can't version them.
818 if not isinstance(entry.parsed_url, self.FileImpl):
819 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
820 pprint.pformat(entry.parsed_url))
821 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000822 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000823 logging.info(result)
824 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000825
826 def _ReadEntries(self):
827 """Read the .gclient_entries file for the given client.
828
829 Returns:
830 A sequence of solution names, which will be empty if there is the
831 entries file hasn't been created yet.
832 """
833 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000834 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000835 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000836 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000837 try:
838 exec(gclient_utils.FileRead(filename), scope)
839 except SyntaxError, e:
840 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000841 return scope['entries']
842
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000843 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000844 """Checks for revision overrides."""
845 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000846 if self._options.head:
847 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000848 # Do not check safesync_url if one or more --revision flag is specified.
849 if not self._options.revisions:
850 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000851 if not s.managed:
852 self._options.revisions.append('%s@unmanaged' % s.name)
853 elif s.safesync_url:
854 handle = urllib.urlopen(s.safesync_url)
855 rev = handle.read().strip()
856 handle.close()
857 if len(rev):
858 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000859 if not self._options.revisions:
860 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000861 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000862 index = 0
863 for revision in self._options.revisions:
864 if not '@' in revision:
865 # Support for --revision 123
866 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000867 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000868 if not sol in solutions_names:
869 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
870 print >> sys.stderr, ('Please fix your script, having invalid '
871 '--revision flags will soon considered an error.')
872 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000873 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000874 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000875 return revision_overrides
876
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000877 def RunOnDeps(self, command, args):
878 """Runs a command on each dependency in a client and its dependencies.
879
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000880 Args:
881 command: The command to use (e.g., 'status' or 'diff')
882 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000884 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000885 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000886 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000887 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000888 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000889 if (command in ('update', 'revert') and sys.stdout.isatty() and not
890 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000891 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000892 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000893 for s in self.dependencies:
894 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000895 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000896
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000897 # Once all the dependencies have been processed, it's now safe to run the
898 # hooks.
899 if not self._options.nohooks:
900 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000901
902 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000903 # Notify the user if there is an orphaned entry in their working copy.
904 # Only delete the directory if there are no changes in it, and
905 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000906 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000907 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000908 if not prev_url:
909 # entry must have been overridden via .gclient custom_deps
910 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000911 # Fix path separator on Windows.
912 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000913 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000914 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000915 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000916 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000917 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000918 scm.status(self._options, [], file_list)
919 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000920 if (not self._options.delete_unversioned_trees or
921 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000922 # There are modified files in this entry. Keep warning until
923 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000924 print(('\nWARNING: \'%s\' is no longer part of this client. '
925 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000926 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000927 else:
928 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000929 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000930 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000931 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000932 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000933 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000934 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000935
936 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000937 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000938 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000939 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000940 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000941 for s in self.dependencies:
942 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000943 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000945 def GetURLAndRev(dep):
946 """Returns the revision-qualified SCM url for a Dependency."""
947 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000948 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000949 if isinstance(dep.parsed_url, self.FileImpl):
950 original_url = dep.parsed_url.file_location
951 else:
952 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000953 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000954 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000955 if not os.path.isdir(scm.checkout_path):
956 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000957 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000958
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000959 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000960 new_gclient = ''
961 # First level at .gclient
962 for d in self.dependencies:
963 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000964 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000965 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000966 for d in dep.dependencies:
967 entries[d.name] = GetURLAndRev(d)
968 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000969 GrabDeps(d)
970 custom_deps = []
971 for k in sorted(entries.keys()):
972 if entries[k]:
973 # Quotes aren't escaped...
974 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
975 else:
976 custom_deps.append(' \"%s\": None,\n' % k)
977 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
978 'solution_name': d.name,
979 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000980 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000981 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000982 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 'solution_deps': ''.join(custom_deps),
984 }
985 # Print the snapshot configuration file
986 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000987 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000988 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000989 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000990 if self._options.actual:
991 entries[d.name] = GetURLAndRev(d)
992 else:
993 entries[d.name] = d.parsed_url
994 keys = sorted(entries.keys())
995 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000996 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000997 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000998
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000999 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001000 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001001 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001002
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001003 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001004 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001005 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001006 return self._root_dir
1007
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001008 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001009 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001010 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001011 return self._enforced_os
1012
maruel@chromium.org68988972011-09-20 14:11:42 +00001013 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001014 def recursion_limit(self):
1015 """How recursive can each dependencies in DEPS file can load DEPS file."""
1016 return self._recursion_limit
1017
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001018
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001019#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001020
1021
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001022def CMDcleanup(parser, args):
1023 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001024
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001025Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001026"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001027 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1028 help='override deps for the specified (comma-separated) '
1029 'platform(s); \'all\' will process all deps_os '
1030 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001031 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001032 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001034 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001035 if options.verbose:
1036 # Print out the .gclient file. This is longer than if we just printed the
1037 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001038 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 return client.RunOnDeps('cleanup', args)
1040
1041
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001042@attr('usage', '[command] [args ...]')
1043def CMDrecurse(parser, args):
1044 """Operates on all the entries.
1045
1046 Runs a shell command on all entries.
1047 """
1048 # Stop parsing at the first non-arg so that these go through to the command
1049 parser.disable_interspersed_args()
1050 parser.add_option('-s', '--scm', action='append', default=[],
1051 help='choose scm types to operate upon')
1052 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001053 if not args:
1054 print >> sys.stderr, 'Need to supply a command!'
1055 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001056 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1057 if not root_and_entries:
1058 print >> sys.stderr, (
1059 'You need to run gclient sync at least once to use \'recurse\'.\n'
1060 'This is because .gclient_entries needs to exist and be up to date.')
1061 return 1
1062 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001063 scm_set = set()
1064 for scm in options.scm:
1065 scm_set.update(scm.split(','))
1066
1067 # Pass in the SCM type as an env variable
1068 env = os.environ.copy()
1069
1070 for path, url in entries.iteritems():
1071 scm = gclient_scm.GetScmName(url)
1072 if scm_set and scm not in scm_set:
1073 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001074 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001075 if scm:
1076 env['GCLIENT_SCM'] = scm
1077 if url:
1078 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001079 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001080 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001081
1082
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001083@attr('usage', '[url] [safesync url]')
1084def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001085 """Create a .gclient file in the current directory.
1086
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001087This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001088top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001090provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001091URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001092"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001093 parser.add_option('--spec',
1094 help='create a gclient file containing the provided '
1095 'string. Due to Cygwin/Python brokenness, it '
1096 'probably can\'t contain any newlines.')
1097 parser.add_option('--name',
1098 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001099 parser.add_option('--deps-file', default='DEPS',
1100 help='overrides the default name for the DEPS file for the'
1101 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001102 parser.add_option('--unmanaged', action='store_true', default=False,
1103 help='overrides the default behavior to make it possible '
1104 'to have the main solution untouched by gclient '
1105 '(gclient will check out unmanaged dependencies but '
1106 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001107 parser.add_option('--git-deps', action='store_true',
1108 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001110 if ((options.spec and args) or len(args) > 2 or
1111 (not options.spec and not args)):
1112 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1113
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001114 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001115 if options.spec:
1116 client.SetConfig(options.spec)
1117 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001118 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001119 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001120 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001121 if name.endswith('.git'):
1122 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001123 else:
1124 # specify an alternate relpath for the given URL.
1125 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001126 deps_file = options.deps_file
1127 if options.git_deps:
1128 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001129 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130 if len(args) > 1:
1131 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001132 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1133 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001135 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136
1137
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001138@attr('epilog', """Example:
1139 gclient pack > patch.txt
1140 generate simple patch for configured client and dependences
1141""")
1142def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001143 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001144
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001145Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001146dependencies, and performs minimal postprocessing of the output. The
1147resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001148checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001149"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001150 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1151 help='override deps for the specified (comma-separated) '
1152 'platform(s); \'all\' will process all deps_os '
1153 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001154 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001155 client = GClient.LoadCurrentConfig(options)
1156 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001157 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001158 if options.verbose:
1159 # Print out the .gclient file. This is longer than if we just printed the
1160 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001161 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001162 return client.RunOnDeps('pack', args)
1163
1164
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165def CMDstatus(parser, args):
1166 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001167 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1168 help='override deps for the specified (comma-separated) '
1169 'platform(s); \'all\' will process all deps_os '
1170 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001171 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001172 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001173 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001174 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175 if options.verbose:
1176 # Print out the .gclient file. This is longer than if we just printed the
1177 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001178 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179 return client.RunOnDeps('status', args)
1180
1181
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001183 gclient sync
1184 update files from SCM according to current configuration,
1185 *for modules which have changed since last update or sync*
1186 gclient sync --force
1187 update files from SCM according to current configuration, for
1188 all modules (useful for recovering files deleted from local copy)
1189 gclient sync --revision src@31000
1190 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001191""")
1192def CMDsync(parser, args):
1193 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001194 parser.add_option('-f', '--force', action='store_true',
1195 help='force update even for unchanged modules')
1196 parser.add_option('-n', '--nohooks', action='store_true',
1197 help='don\'t run hooks after the update is complete')
1198 parser.add_option('-r', '--revision', action='append',
1199 dest='revisions', metavar='REV', default=[],
1200 help='Enforces revision/hash for the solutions with the '
1201 'format src@rev. The src@ part is optional and can be '
1202 'skipped. -r can be used multiple times when .gclient '
1203 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001204 'if the src@ part is skipped. Note that specifying '
1205 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001206 parser.add_option('-t', '--transitive', action='store_true',
1207 help='When a revision is specified (in the DEPS file or '
1208 'with the command-line flag), transitively update '
1209 'the dependencies to the date of the given revision. '
1210 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001211 parser.add_option('-H', '--head', action='store_true',
1212 help='skips any safesync_urls specified in '
1213 'configured solutions and sync to head instead')
1214 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001215 help='delete any dependency that have been removed from '
1216 'last sync as long as there is no local modification. '
1217 'Coupled with --force, it will remove them even with '
1218 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001219 parser.add_option('-R', '--reset', action='store_true',
1220 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001221 parser.add_option('-M', '--merge', action='store_true',
1222 help='merge upstream changes instead of trying to '
1223 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001224 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1225 help='override deps for the specified (comma-separated) '
1226 'platform(s); \'all\' will process all deps_os '
1227 'references')
1228 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1229 help='Skip svn up whenever possible by requesting '
1230 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001231 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001232 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233
1234 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001235 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001236
maruel@chromium.org307d1792010-05-31 20:03:13 +00001237 if options.revisions and options.head:
1238 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001239 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001240
1241 if options.verbose:
1242 # Print out the .gclient file. This is longer than if we just printed the
1243 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001244 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001245 return client.RunOnDeps('update', args)
1246
1247
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001248def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001249 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001250 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001252def CMDdiff(parser, args):
1253 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001254 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1255 help='override deps for the specified (comma-separated) '
1256 'platform(s); \'all\' will process all deps_os '
1257 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001258 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001259 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001260 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001261 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001262 if options.verbose:
1263 # Print out the .gclient file. This is longer than if we just printed the
1264 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001265 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001266 return client.RunOnDeps('diff', args)
1267
1268
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001269def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001270 """Revert all modifications in every dependencies.
1271
1272 That's the nuclear option to get back to a 'clean' state. It removes anything
1273 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001274 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1275 help='override deps for the specified (comma-separated) '
1276 'platform(s); \'all\' will process all deps_os '
1277 'references')
1278 parser.add_option('-n', '--nohooks', action='store_true',
1279 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001280 (options, args) = parser.parse_args(args)
1281 # --force is implied.
1282 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001283 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001285 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001286 return client.RunOnDeps('revert', args)
1287
1288
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001289def CMDrunhooks(parser, args):
1290 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001291 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1292 help='override deps for the specified (comma-separated) '
1293 'platform(s); \'all\' will process all deps_os '
1294 'references')
1295 parser.add_option('-f', '--force', action='store_true', default=True,
1296 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001297 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001298 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001299 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001300 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001301 if options.verbose:
1302 # Print out the .gclient file. This is longer than if we just printed the
1303 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001304 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001305 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001306 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001307 return client.RunOnDeps('runhooks', args)
1308
1309
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001310def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001311 """Output revision info mapping for the client and its dependencies.
1312
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001314 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001315 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1316 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001317 commit can change.
1318 """
1319 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1320 help='override deps for the specified (comma-separated) '
1321 'platform(s); \'all\' will process all deps_os '
1322 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001323 parser.add_option('-a', '--actual', action='store_true',
1324 help='gets the actual checked out revisions instead of the '
1325 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001326 parser.add_option('-s', '--snapshot', action='store_true',
1327 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001328 'version of all repositories to reproduce the tree, '
1329 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001330 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001331 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001332 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001333 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001335 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001336
1337
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001338def Command(name):
1339 return getattr(sys.modules[__name__], 'CMD' + name, None)
1340
1341
1342def CMDhelp(parser, args):
1343 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001344 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001345 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001346 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001347 parser.print_help()
1348 return 0
1349
1350
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001351def GenUsage(parser, command):
1352 """Modify an OptParse object with the function's documentation."""
1353 obj = Command(command)
1354 if command == 'help':
1355 command = '<command>'
1356 # OptParser.description prefer nicely non-formatted strings.
1357 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1358 usage = getattr(obj, 'usage', '')
1359 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1360 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361
1362
maruel@chromium.org0895b752011-08-26 20:40:33 +00001363def Parser():
1364 """Returns the default parser."""
1365 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001366 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001367 help='Specify how many SCM commands can run in parallel; '
1368 'default=%default')
1369 parser.add_option('-v', '--verbose', action='count', default=0,
1370 help='Produces additional output for diagnostics. Can be '
1371 'used up to three times for more logging info.')
1372 parser.add_option('--gclientfile', dest='config_filename',
1373 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1374 help='Specify an alternate %default file')
1375 # Integrate standard options processing.
1376 old_parser = parser.parse_args
1377 def Parse(args):
1378 (options, args) = old_parser(args)
1379 level = None
1380 if options.verbose == 2:
1381 level = logging.INFO
1382 elif options.verbose > 2:
1383 level = logging.DEBUG
1384 logging.basicConfig(level=level,
1385 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1386 options.entries_filename = options.config_filename + '_entries'
1387 if options.jobs < 1:
1388 parser.error('--jobs must be 1 or higher')
1389
1390 # These hacks need to die.
1391 if not hasattr(options, 'revisions'):
1392 # GClient.RunOnDeps expects it even if not applicable.
1393 options.revisions = []
1394 if not hasattr(options, 'head'):
1395 options.head = None
1396 if not hasattr(options, 'nohooks'):
1397 options.nohooks = True
1398 if not hasattr(options, 'deps_os'):
1399 options.deps_os = None
1400 if not hasattr(options, 'manually_grab_svn_rev'):
1401 options.manually_grab_svn_rev = None
1402 if not hasattr(options, 'force'):
1403 options.force = None
1404 return (options, args)
1405 parser.parse_args = Parse
1406 # We don't want wordwrapping in epilog (usually examples)
1407 parser.format_epilog = lambda _: parser.epilog or ''
1408 return parser
1409
1410
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001411def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001412 """Doesn't parse the arguments here, just find the right subcommand to
1413 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001414 if sys.hexversion < 0x02050000:
1415 print >> sys.stderr, (
1416 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001417 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001418 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1419 # operations. Python as a strong tendency to buffer sys.stdout.
1420 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001421 # Make stdout annotated with the thread ids.
1422 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001423 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001424 # Unused variable 'usage'
1425 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001426 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1427 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1428 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001429 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001430 if argv:
1431 command = Command(argv[0])
1432 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001433 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001434 GenUsage(parser, argv[0])
1435 return command(parser, argv[1:])
1436 # Not a known command. Default to help.
1437 GenUsage(parser, 'help')
1438 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001439 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001440 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001441 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001442
1443
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001444if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001445 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001446 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001447
1448# vim: ts=2:sw=2:tw=80:et: