blob: 2d7a4a748ae6c812b54e7680cd4f73142bd2a218 [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.org118fb1c2011-09-01 20:04:24 +0000206 # Warning: this function can be called from any thread. Both
207 # self.dependencies and self.requirements are read and modified from
208 # multiple threads at the same time. Sad.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000209 GClientKeywords.__init__(self)
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000210 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 DependencySettings.__init__(
212 self, parent, safesync_url, managed, custom_deps, custom_vars,
213 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000214
215 # This is in both .gclient and DEPS files:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000216 self.url = url
maruel@chromium.org68988972011-09-20 14:11:42 +0000217
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000218 self.deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000219
220 # Calculates properties:
221 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000222 self.dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000223 # A cache of the files affected by the current operation, necessary for
224 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000225 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000226 # If it is not set to True, the dependency wasn't processed for its child
227 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000228 self.deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000229 # This dependency has been processed, i.e. checked out
230 self.processed = False
231 # This dependency had its hook run
232 self.hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000233
maruel@chromium.org98023df2011-09-07 18:44:47 +0000234 # Post process the url to remove trailing slashes.
235 if isinstance(self.url, basestring):
236 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
237 # it to proto://host/path@rev.
238 if self.url.count('@') > 1:
239 raise gclient_utils.Error('Invalid url "%s"' % self.url)
240 self.url = self.url.replace('/@', '@')
241
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000242 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000243
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000244 # Sanity checks
245 if not self.name and self.parent:
246 raise gclient_utils.Error('Dependency without name')
247 if not isinstance(self.url,
248 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
249 raise gclient_utils.Error('dependency url must be either a string, None, '
250 'File() or From() instead of %s' %
251 self.url.__class__.__name__)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000252
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000253 def _FindDependencies(self):
254 """Setup self.requirements and find any other dependency who would have self
255 as a requirement.
256 """
257 # self.parent is implicitly a requirement. This will be recursive by
258 # definition.
259 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000260 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000261
262 # For a tree with at least 2 levels*, the leaf node needs to depend
263 # on the level higher up in an orderly way.
264 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
265 # thus unsorted, while the .gclient format is a list thus sorted.
266 #
267 # * _recursion_limit is hard coded 2 and there is no hope to change this
268 # value.
269 #
270 # Interestingly enough, the following condition only works in the case we
271 # want: self is a 2nd level node. 3nd level node wouldn't need this since
272 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000273 root_deps = self.root.dependencies
274 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000275 for i in root_deps:
276 if i is self.parent:
277 break
278 if i.name:
279 self._requirements.add(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000280
281 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000282 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000283
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000284 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000285 def yield_full_tree(root):
286 """Depth-first recursion."""
287 yield root
288 for i in root.dependencies:
289 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000290 if j.should_process:
291 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000292
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000293 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000294 if obj is self or not obj.name:
295 continue
296 # Step 1: Find any requirements self may need.
297 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000298 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000299 # Step 2: Find any requirements self may impose.
300 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000301 try:
302 # Access to a protected member _requirements of a client class
303 # pylint: disable=W0212
304 obj.lock.acquire()
305 obj._requirements.add(self.name)
306 finally:
307 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000308
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000309 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000310 """Resolves the parsed url from url.
311
312 Manages From() keyword accordingly. Do not touch self.parsed_url nor
313 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000314 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000315 overriden_url = self.get_custom_deps(self.name, url)
316 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000317 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000318 overriden_url))
319 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000320 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000321 ref = [
322 dep for dep in self.root.subtree(True) if url.module_name == dep.name
323 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000324 if not ref:
325 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
326 url.module_name, ref))
327 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000328 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000329 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000330 # Make sure the referenced dependency DEPS file is loaded and file the
331 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000332 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000333 found_dep = None
334 for d in ref.dependencies:
335 if d.name == sub_target:
336 found_dep = d
337 break
338 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000339 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000340 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
341 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000342 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000343
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000344 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000345 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000346 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000347 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000348 elif isinstance(url, basestring):
349 parsed_url = urlparse.urlparse(url)
350 if not parsed_url[0]:
351 # A relative url. Fetch the real base.
352 path = parsed_url[2]
353 if not path.startswith('/'):
354 raise gclient_utils.Error(
355 'relative DEPS entry \'%s\' must begin with a slash' % url)
356 # Create a scm just to query the full url.
357 parent_url = self.parent.parsed_url
358 if isinstance(parent_url, self.FileImpl):
359 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000360 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000361 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000363 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000364 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000366 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000367 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000368 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000369 return parsed_url
370 elif url is None:
371 return None
372 else:
373 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000374
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000375 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000376 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000377 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000378 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000379 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000380 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000381 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000382 # One thing is unintuitive, vars= {} must happen before Var() use.
383 local_scope = {}
384 var = self.VarImpl(self.custom_vars, local_scope)
385 global_scope = {
386 'File': self.FileImpl,
387 'From': self.FromImpl,
388 'Var': var.Lookup,
389 'deps_os': {},
390 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000391 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000392 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000393 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
394 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000395 else:
396 deps_content = gclient_utils.FileRead(filepath)
397 logging.debug(deps_content)
398 # Eval the content.
399 try:
400 exec(deps_content, global_scope, local_scope)
401 except SyntaxError, e:
402 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000403 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000404 # load os specific dependencies if defined. these dependencies may
405 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000406 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000407 enforced_os = self.root.enforced_os
408 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000409 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000410 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000411 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000412 # platform, so we collect the broadest set of dependencies
413 # available. We may end up with the wrong revision of something for
414 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000415 deps.update([x for x in os_deps.items() if not x[0] in deps])
416 else:
417 deps.update(os_deps)
418
maruel@chromium.org271375b2010-06-23 19:17:38 +0000419 self.deps_hooks.extend(local_scope.get('hooks', []))
420
421 # If a line is in custom_deps, but not in the solution, we want to append
422 # this line to the solution.
423 for d in self.custom_deps:
424 if d not in deps:
425 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000426
427 # If use_relative_paths is set in the DEPS file, regenerate
428 # the dictionary using paths relative to the directory containing
429 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000430 use_relative_paths = local_scope.get('use_relative_paths', False)
431 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000432 rel_deps = {}
433 for d, url in deps.items():
434 # normpath is required to allow DEPS to use .. in their
435 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000436 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
437 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000438
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000439 # Convert the deps into real Dependency.
440 for name, url in deps.iteritems():
441 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000442 raise gclient_utils.Error(
443 'The same name "%s" appears multiple times in the deps section' %
444 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000445 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000446 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000447 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000448 if name in tree:
449 if url == tree[name].url:
450 logging.info('Won\'t process duplicate dependency %s' % tree[name])
451 # In theory we could keep it as a shadow of the other one. In
452 # practice, simply ignore it.
453 #should_process = False
454 continue
455 else:
456 raise gclient_utils.Error(
457 'Dependency %s specified more than once:\n %s\nvs\n %s' %
458 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000459 self.dependencies.append(Dependency(self, name, url, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000460 None, self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000461 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000462
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000463 # Arguments number differs from overridden method
464 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000465 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466 """Runs 'command' before parsing the DEPS in case it's a initial checkout
467 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000468
469 def maybeGetParentRevision(options):
470 """If we are performing an update and --transitive is set, set the
471 revision to the parent's revision. If we have an explicit revision
472 do nothing."""
473 if command == 'update' and options.transitive and not options.revision:
474 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
475 if not revision:
476 options.revision = revision_overrides.get(self.parent.name)
477 if options.verbose and options.revision:
478 print("Using parent's revision date: %s" % options.revision)
479 # If the parent has a revision override, then it must have been
480 # converted to date format.
481 assert (not options.revision or
482 gclient_utils.IsDateRevision(options.revision))
483
484 def maybeConvertToDateRevision(options):
485 """If we are performing an update and --transitive is set, convert the
486 revision to a date-revision (if necessary). Instead of having
487 -r 101 replace the revision with the time stamp of 101 (e.g.
488 "{2011-18-04}").
489 This way dependencies are upgraded to the revision they had at the
490 check-in of revision 101."""
491 if (command == 'update' and
492 options.transitive and
493 options.revision and
494 not gclient_utils.IsDateRevision(options.revision)):
495 revision_date = scm.GetRevisionDate(options.revision)
496 revision = gclient_utils.MakeDateRevision(revision_date)
497 if options.verbose:
498 print("Updating revision override from %s to %s." %
499 (options.revision, revision))
500 revision_overrides[self.name] = revision
501
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000502 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000503 if not self.should_process:
504 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505 # When running runhooks, there's no need to consult the SCM.
506 # All known hooks are expected to run unconditionally regardless of working
507 # copy state, so skip the SCM status check.
508 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000510 if run_scm and self.parsed_url:
511 if isinstance(self.parsed_url, self.FileImpl):
512 # Special support for single-file checkout.
513 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
514 options.revision = self.parsed_url.GetRevision()
515 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000516 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 self.name)
518 scm.RunCommand('updatesingle', options,
519 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000520 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000521 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000522 # Create a shallow copy to mutate revision.
523 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000524 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000525 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000526 scm = gclient_scm.CreateSCM(
527 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000528 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000529 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000530 self._file_list = [os.path.join(self.name, f.strip())
531 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000532
533 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
534 # Convert all absolute paths to relative.
535 for i in range(len(self._file_list)):
536 # It depends on the command being executed (like runhooks vs sync).
537 if not os.path.isabs(self._file_list[i]):
538 continue
539 prefix = os.path.commonprefix(
540 [self.root.root_dir.lower(), self._file_list[i].lower()])
541 self._file_list[i] = self._file_list[i][len(prefix):]
542 # Strip any leading path separators.
543 while (self._file_list[i].startswith('\\') or
544 self._file_list[i].startswith('/')):
545 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000546 self.processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000547 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000548 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000549 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000550
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000551 # Parse the dependencies of this dependency.
552 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000553 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000554
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000555 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000556 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000557 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000558 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000559 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000560 # Don't run the hook when it is above recursion_limit.
561 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000562 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000563 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000564 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000565 # TODO(maruel): If the user is using git or git-svn, then we don't know
566 # what files have changed so we always run all hooks. It'd be nice to fix
567 # that.
568 if (options.force or
569 isinstance(self.parsed_url, self.FileImpl) or
570 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000571 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000572 for hook_dict in self.deps_hooks:
573 self._RunHookAction(hook_dict, [])
574 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000575 # Run hooks on the basis of whether the files from the gclient operation
576 # match each hook's pattern.
577 for hook_dict in self.deps_hooks:
578 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000579 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000580 if matching_file_list:
581 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000582 for s in self.dependencies:
583 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000584
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000585 def _RunHookAction(self, hook_dict, matching_file_list):
586 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000587 # A single DEPS file can specify multiple hooks so this function can be
588 # called multiple times on a single Dependency.
589 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000590 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000591 logging.debug(hook_dict)
592 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000593 command = hook_dict['action'][:]
594 if command[0] == 'python':
595 # If the hook specified "python" as the first item, the action is a
596 # Python script. Run it by starting a new copy of the same
597 # interpreter.
598 command[0] = sys.executable
599
600 if '$matching_files' in command:
601 splice_index = command.index('$matching_files')
602 command[splice_index:splice_index + 1] = matching_file_list
603
maruel@chromium.org17d01792010-09-01 18:07:10 +0000604 try:
605 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000606 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000607 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000608 # Use a discrete exit status code of 2 to indicate that a hook action
609 # failed. Users of this script may wish to treat hook action failures
610 # differently from VC failures.
611 print >> sys.stderr, 'Error: %s' % str(e)
612 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000613
maruel@chromium.org0d812442010-08-10 12:41:08 +0000614 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000615 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000616 result = []
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000617 dependencies = self.dependencies
618 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000619 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000620 result.append(d)
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000621 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000622 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000623 return result
624
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000625 def get_custom_deps(self, name, url):
626 """Returns a custom deps if applicable."""
627 if self.parent:
628 url = self.parent.get_custom_deps(name, url)
629 # None is a valid return value to disable a dependency.
630 return self.custom_deps.get(name, url)
631
maruel@chromium.org68988972011-09-20 14:11:42 +0000632 @property
633 def recursion_limit(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000634 """Returns > 0 if this dependency is not too recursed to be processed.
635
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000636 Immutable so no need to lock.
637 """
maruel@chromium.org68988972011-09-20 14:11:42 +0000638 return max(self.parent.recursion_limit - 1, 0)
639
640 @property
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000641 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000642 def file_list(self):
643 result = self._file_list[:]
644 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000645 result.extend(d.file_list)
646 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000647
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000648 def __str__(self):
649 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000650 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000651 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000652 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000653 # First try the native property if it exists.
654 if hasattr(self, '_' + i):
655 value = getattr(self, '_' + i, False)
656 else:
657 value = getattr(self, i, False)
658 if value:
659 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000660
661 for d in self.dependencies:
662 out.extend([' ' + x for x in str(d).splitlines()])
663 out.append('')
664 return '\n'.join(out)
665
666 def __repr__(self):
667 return '%s: %s' % (self.name, self.url)
668
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000669 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000670 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000671 out = '%s(%s)' % (self.name, self.url)
672 i = self.parent
673 while i and i.name:
674 out = '%s(%s) -> %s' % (i.name, i.url, out)
675 i = i.parent
676 return out
677
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000678 @property
679 def root(self):
680 """Returns the root node, a GClient object."""
681 if not self.parent:
682 # This line is to signal pylint that it could be a GClient instance.
683 return self or GClient(None, None)
684 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000685
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000686
687class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000688 """Object that represent a gclient checkout. A tree of Dependency(), one per
689 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000690
691 DEPS_OS_CHOICES = {
692 "win32": "win",
693 "win": "win",
694 "cygwin": "win",
695 "darwin": "mac",
696 "mac": "mac",
697 "unix": "unix",
698 "linux": "unix",
699 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000700 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000701 }
702
703 DEFAULT_CLIENT_FILE_TEXT = ("""\
704solutions = [
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" : {
710 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000711 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000712 },
713]
714""")
715
716 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
717 { "name" : "%(solution_name)s",
718 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000719 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000720 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000721 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000722%(solution_deps)s },
723 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000724 },
725""")
726
727 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
728# Snapshot generated with gclient revinfo --snapshot
729solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000730%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000731""")
732
733 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000734 # Do not change previous behavior. Only solution level and immediate DEPS
735 # are processed.
736 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000737 Dependency.__init__(self, None, None, None, None, True, None, None,
738 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000739 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000740 if options.deps_os:
741 enforced_os = options.deps_os.split(',')
742 else:
743 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
744 if 'all' in enforced_os:
745 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000746 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000747 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000748 self.config_content = None
749
750 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000751 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000752 config_dict = {}
753 self.config_content = content
754 try:
755 exec(content, config_dict)
756 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000757 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000758 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000759 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000760 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000761 if s['name'] in tree:
762 raise gclient_utils.Error(
763 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000764 self.dependencies.append(Dependency(
765 self, s['name'], s['url'],
766 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000767 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000768 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000769 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000770 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000771 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000772 except KeyError:
773 raise gclient_utils.Error('Invalid .gclient file. Solution is '
774 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000775 # .gclient can have hooks.
776 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000777 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000778
779 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000780 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000781 self._options.config_filename),
782 self.config_content)
783
784 @staticmethod
785 def LoadCurrentConfig(options):
786 """Searches for and loads a .gclient file relative to the current working
787 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000788 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000789 if not path:
790 return None
791 client = GClient(path, options)
792 client.SetConfig(gclient_utils.FileRead(
793 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000794 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000795
nsylvain@google.comefc80932011-05-31 21:27:56 +0000796 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000797 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000798 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
799 'solution_name': solution_name,
800 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000801 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000802 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000803 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000804 })
805
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000806 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000807 """Creates a .gclient_entries file to record the list of unique checkouts.
808
809 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000810 """
811 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
812 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000813 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000814 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 # Skip over File() dependencies as we can't version them.
816 if not isinstance(entry.parsed_url, self.FileImpl):
817 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
818 pprint.pformat(entry.parsed_url))
819 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000820 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000821 logging.info(result)
822 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000823
824 def _ReadEntries(self):
825 """Read the .gclient_entries file for the given client.
826
827 Returns:
828 A sequence of solution names, which will be empty if there is the
829 entries file hasn't been created yet.
830 """
831 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000832 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000833 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000834 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000835 try:
836 exec(gclient_utils.FileRead(filename), scope)
837 except SyntaxError, e:
838 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000839 return scope['entries']
840
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000841 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000842 """Checks for revision overrides."""
843 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000844 if self._options.head:
845 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000846 # Do not check safesync_url if one or more --revision flag is specified.
847 if not self._options.revisions:
848 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000849 if not s.managed:
850 self._options.revisions.append('%s@unmanaged' % s.name)
851 elif s.safesync_url:
852 handle = urllib.urlopen(s.safesync_url)
853 rev = handle.read().strip()
854 handle.close()
855 if len(rev):
856 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000857 if not self._options.revisions:
858 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000859 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000860 index = 0
861 for revision in self._options.revisions:
862 if not '@' in revision:
863 # Support for --revision 123
864 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000865 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000866 if not sol in solutions_names:
867 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
868 print >> sys.stderr, ('Please fix your script, having invalid '
869 '--revision flags will soon considered an error.')
870 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000871 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000872 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000873 return revision_overrides
874
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000875 def RunOnDeps(self, command, args):
876 """Runs a command on each dependency in a client and its dependencies.
877
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000878 Args:
879 command: The command to use (e.g., 'status' or 'diff')
880 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000881 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000882 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000883 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000884 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000885 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000886 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000887 if (command in ('update', 'revert') and sys.stdout.isatty() and not
888 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000889 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000890 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000891 for s in self.dependencies:
892 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000893 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000894
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000895 # Once all the dependencies have been processed, it's now safe to run the
896 # hooks.
897 if not self._options.nohooks:
898 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000899
900 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000901 # Notify the user if there is an orphaned entry in their working copy.
902 # Only delete the directory if there are no changes in it, and
903 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000904 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000906 if not prev_url:
907 # entry must have been overridden via .gclient custom_deps
908 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000909 # Fix path separator on Windows.
910 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000911 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000912 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000913 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000914 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000915 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000916 scm.status(self._options, [], file_list)
917 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000918 if (not self._options.delete_unversioned_trees or
919 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000920 # There are modified files in this entry. Keep warning until
921 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000922 print(('\nWARNING: \'%s\' is no longer part of this client. '
923 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000924 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925 else:
926 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000927 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000928 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000929 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000931 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000932 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000933
934 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000935 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000936 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000937 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000938 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000939 for s in self.dependencies:
940 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000941 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000943 def GetURLAndRev(dep):
944 """Returns the revision-qualified SCM url for a Dependency."""
945 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000946 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000947 if isinstance(dep.parsed_url, self.FileImpl):
948 original_url = dep.parsed_url.file_location
949 else:
950 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000951 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000952 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000953 if not os.path.isdir(scm.checkout_path):
954 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000955 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000956
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000957 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000958 new_gclient = ''
959 # First level at .gclient
960 for d in self.dependencies:
961 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000962 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000963 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000964 for d in dep.dependencies:
965 entries[d.name] = GetURLAndRev(d)
966 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000967 GrabDeps(d)
968 custom_deps = []
969 for k in sorted(entries.keys()):
970 if entries[k]:
971 # Quotes aren't escaped...
972 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
973 else:
974 custom_deps.append(' \"%s\": None,\n' % k)
975 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
976 'solution_name': d.name,
977 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000978 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000979 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000980 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000981 'solution_deps': ''.join(custom_deps),
982 }
983 # Print the snapshot configuration file
984 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000985 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000986 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000987 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000988 if self._options.actual:
989 entries[d.name] = GetURLAndRev(d)
990 else:
991 entries[d.name] = d.parsed_url
992 keys = sorted(entries.keys())
993 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000994 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000995 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000997 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000998 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000999 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001000
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001001 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001002 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001003 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001004 return self._root_dir
1005
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001006 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001007 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001008 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001009 return self._enforced_os
1010
maruel@chromium.org68988972011-09-20 14:11:42 +00001011 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001012 def recursion_limit(self):
1013 """How recursive can each dependencies in DEPS file can load DEPS file."""
1014 return self._recursion_limit
1015
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001016
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001018
1019
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001020def CMDcleanup(parser, args):
1021 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001022
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001023Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001024"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001025 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1026 help='override deps for the specified (comma-separated) '
1027 'platform(s); \'all\' will process all deps_os '
1028 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001029 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001030 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001031 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001032 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033 if options.verbose:
1034 # Print out the .gclient file. This is longer than if we just printed the
1035 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001036 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037 return client.RunOnDeps('cleanup', args)
1038
1039
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001040@attr('usage', '[command] [args ...]')
1041def CMDrecurse(parser, args):
1042 """Operates on all the entries.
1043
1044 Runs a shell command on all entries.
1045 """
1046 # Stop parsing at the first non-arg so that these go through to the command
1047 parser.disable_interspersed_args()
1048 parser.add_option('-s', '--scm', action='append', default=[],
1049 help='choose scm types to operate upon')
1050 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001051 if not args:
1052 print >> sys.stderr, 'Need to supply a command!'
1053 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001054 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1055 if not root_and_entries:
1056 print >> sys.stderr, (
1057 'You need to run gclient sync at least once to use \'recurse\'.\n'
1058 'This is because .gclient_entries needs to exist and be up to date.')
1059 return 1
1060 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001061 scm_set = set()
1062 for scm in options.scm:
1063 scm_set.update(scm.split(','))
1064
1065 # Pass in the SCM type as an env variable
1066 env = os.environ.copy()
1067
1068 for path, url in entries.iteritems():
1069 scm = gclient_scm.GetScmName(url)
1070 if scm_set and scm not in scm_set:
1071 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001072 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001073 if scm:
1074 env['GCLIENT_SCM'] = scm
1075 if url:
1076 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001077 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001078 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001079
1080
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001081@attr('usage', '[url] [safesync url]')
1082def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001083 """Create a .gclient file in the current directory.
1084
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001085This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001086top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001087modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001088provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001090"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 parser.add_option('--spec',
1092 help='create a gclient file containing the provided '
1093 'string. Due to Cygwin/Python brokenness, it '
1094 'probably can\'t contain any newlines.')
1095 parser.add_option('--name',
1096 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001097 parser.add_option('--deps-file', default='DEPS',
1098 help='overrides the default name for the DEPS file for the'
1099 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001100 parser.add_option('--unmanaged', action='store_true', default=False,
1101 help='overrides the default behavior to make it possible '
1102 'to have the main solution untouched by gclient '
1103 '(gclient will check out unmanaged dependencies but '
1104 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001105 parser.add_option('--git-deps', action='store_true',
1106 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001107 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001108 if ((options.spec and args) or len(args) > 2 or
1109 (not options.spec and not args)):
1110 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1111
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001112 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001113 if options.spec:
1114 client.SetConfig(options.spec)
1115 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001116 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001117 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001118 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001119 if name.endswith('.git'):
1120 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001121 else:
1122 # specify an alternate relpath for the given URL.
1123 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001124 deps_file = options.deps_file
1125 if options.git_deps:
1126 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001127 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001128 if len(args) > 1:
1129 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001130 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1131 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001133 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134
1135
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136@attr('epilog', """Example:
1137 gclient pack > patch.txt
1138 generate simple patch for configured client and dependences
1139""")
1140def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001141 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001142
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001143Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001144dependencies, and performs minimal postprocessing of the output. The
1145resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001146checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001147"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001148 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1149 help='override deps for the specified (comma-separated) '
1150 'platform(s); \'all\' will process all deps_os '
1151 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001152 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001153 client = GClient.LoadCurrentConfig(options)
1154 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001155 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001156 if options.verbose:
1157 # Print out the .gclient file. This is longer than if we just printed the
1158 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001159 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001160 return client.RunOnDeps('pack', args)
1161
1162
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001163def CMDstatus(parser, args):
1164 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001165 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1166 help='override deps for the specified (comma-separated) '
1167 'platform(s); \'all\' will process all deps_os '
1168 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001169 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001170 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001171 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001172 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001173 if options.verbose:
1174 # Print out the .gclient file. This is longer than if we just printed the
1175 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001176 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177 return client.RunOnDeps('status', args)
1178
1179
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001180@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001181 gclient sync
1182 update files from SCM according to current configuration,
1183 *for modules which have changed since last update or sync*
1184 gclient sync --force
1185 update files from SCM according to current configuration, for
1186 all modules (useful for recovering files deleted from local copy)
1187 gclient sync --revision src@31000
1188 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001189""")
1190def CMDsync(parser, args):
1191 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001192 parser.add_option('-f', '--force', action='store_true',
1193 help='force update even for unchanged modules')
1194 parser.add_option('-n', '--nohooks', action='store_true',
1195 help='don\'t run hooks after the update is complete')
1196 parser.add_option('-r', '--revision', action='append',
1197 dest='revisions', metavar='REV', default=[],
1198 help='Enforces revision/hash for the solutions with the '
1199 'format src@rev. The src@ part is optional and can be '
1200 'skipped. -r can be used multiple times when .gclient '
1201 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001202 'if the src@ part is skipped. Note that specifying '
1203 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001204 parser.add_option('-t', '--transitive', action='store_true',
1205 help='When a revision is specified (in the DEPS file or '
1206 'with the command-line flag), transitively update '
1207 'the dependencies to the date of the given revision. '
1208 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001209 parser.add_option('-H', '--head', action='store_true',
1210 help='skips any safesync_urls specified in '
1211 'configured solutions and sync to head instead')
1212 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001213 help='delete any dependency that have been removed from '
1214 'last sync as long as there is no local modification. '
1215 'Coupled with --force, it will remove them even with '
1216 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001217 parser.add_option('-R', '--reset', action='store_true',
1218 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001219 parser.add_option('-M', '--merge', action='store_true',
1220 help='merge upstream changes instead of trying to '
1221 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001222 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1223 help='override deps for the specified (comma-separated) '
1224 'platform(s); \'all\' will process all deps_os '
1225 'references')
1226 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1227 help='Skip svn up whenever possible by requesting '
1228 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001229 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001230 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001231
1232 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001233 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001234
maruel@chromium.org307d1792010-05-31 20:03:13 +00001235 if options.revisions and options.head:
1236 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001237 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001238
1239 if options.verbose:
1240 # Print out the .gclient file. This is longer than if we just printed the
1241 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001242 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001243 return client.RunOnDeps('update', args)
1244
1245
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001246def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001247 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001248 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001249
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001250def CMDdiff(parser, args):
1251 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001252 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1253 help='override deps for the specified (comma-separated) '
1254 'platform(s); \'all\' will process all deps_os '
1255 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001256 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001257 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001258 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001259 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001260 if options.verbose:
1261 # Print out the .gclient file. This is longer than if we just printed the
1262 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001263 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001264 return client.RunOnDeps('diff', args)
1265
1266
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001267def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001268 """Revert all modifications in every dependencies.
1269
1270 That's the nuclear option to get back to a 'clean' state. It removes anything
1271 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001272 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1273 help='override deps for the specified (comma-separated) '
1274 'platform(s); \'all\' will process all deps_os '
1275 'references')
1276 parser.add_option('-n', '--nohooks', action='store_true',
1277 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001278 (options, args) = parser.parse_args(args)
1279 # --force is implied.
1280 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001281 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001282 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001283 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284 return client.RunOnDeps('revert', args)
1285
1286
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001287def CMDrunhooks(parser, args):
1288 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001289 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1290 help='override deps for the specified (comma-separated) '
1291 'platform(s); \'all\' will process all deps_os '
1292 'references')
1293 parser.add_option('-f', '--force', action='store_true', default=True,
1294 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001295 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001296 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001297 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001298 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001299 if options.verbose:
1300 # Print out the .gclient file. This is longer than if we just printed the
1301 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001302 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001303 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001304 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001305 return client.RunOnDeps('runhooks', args)
1306
1307
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001309 """Output revision info mapping for the client and its dependencies.
1310
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001311 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001312 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1314 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001315 commit can change.
1316 """
1317 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1318 help='override deps for the specified (comma-separated) '
1319 'platform(s); \'all\' will process all deps_os '
1320 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001321 parser.add_option('-a', '--actual', action='store_true',
1322 help='gets the actual checked out revisions instead of the '
1323 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001324 parser.add_option('-s', '--snapshot', action='store_true',
1325 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001326 'version of all repositories to reproduce the tree, '
1327 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001328 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001329 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001330 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001331 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001332 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001333 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334
1335
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001336def Command(name):
1337 return getattr(sys.modules[__name__], 'CMD' + name, None)
1338
1339
1340def CMDhelp(parser, args):
1341 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001342 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001343 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001344 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001345 parser.print_help()
1346 return 0
1347
1348
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001349def GenUsage(parser, command):
1350 """Modify an OptParse object with the function's documentation."""
1351 obj = Command(command)
1352 if command == 'help':
1353 command = '<command>'
1354 # OptParser.description prefer nicely non-formatted strings.
1355 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1356 usage = getattr(obj, 'usage', '')
1357 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1358 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001359
1360
maruel@chromium.org0895b752011-08-26 20:40:33 +00001361def Parser():
1362 """Returns the default parser."""
1363 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001364 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001365 help='Specify how many SCM commands can run in parallel; '
1366 'default=%default')
1367 parser.add_option('-v', '--verbose', action='count', default=0,
1368 help='Produces additional output for diagnostics. Can be '
1369 'used up to three times for more logging info.')
1370 parser.add_option('--gclientfile', dest='config_filename',
1371 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1372 help='Specify an alternate %default file')
1373 # Integrate standard options processing.
1374 old_parser = parser.parse_args
1375 def Parse(args):
1376 (options, args) = old_parser(args)
1377 level = None
1378 if options.verbose == 2:
1379 level = logging.INFO
1380 elif options.verbose > 2:
1381 level = logging.DEBUG
1382 logging.basicConfig(level=level,
1383 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1384 options.entries_filename = options.config_filename + '_entries'
1385 if options.jobs < 1:
1386 parser.error('--jobs must be 1 or higher')
1387
1388 # These hacks need to die.
1389 if not hasattr(options, 'revisions'):
1390 # GClient.RunOnDeps expects it even if not applicable.
1391 options.revisions = []
1392 if not hasattr(options, 'head'):
1393 options.head = None
1394 if not hasattr(options, 'nohooks'):
1395 options.nohooks = True
1396 if not hasattr(options, 'deps_os'):
1397 options.deps_os = None
1398 if not hasattr(options, 'manually_grab_svn_rev'):
1399 options.manually_grab_svn_rev = None
1400 if not hasattr(options, 'force'):
1401 options.force = None
1402 return (options, args)
1403 parser.parse_args = Parse
1404 # We don't want wordwrapping in epilog (usually examples)
1405 parser.format_epilog = lambda _: parser.epilog or ''
1406 return parser
1407
1408
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001409def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001410 """Doesn't parse the arguments here, just find the right subcommand to
1411 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001412 if sys.hexversion < 0x02050000:
1413 print >> sys.stderr, (
1414 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001415 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001416 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1417 # operations. Python as a strong tendency to buffer sys.stdout.
1418 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001419 # Make stdout annotated with the thread ids.
1420 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001421 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001422 # Unused variable 'usage'
1423 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001424 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1425 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1426 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001427 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001428 if argv:
1429 command = Command(argv[0])
1430 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001431 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001432 GenUsage(parser, argv[0])
1433 return command(parser, argv[1:])
1434 # Not a known command. Default to help.
1435 GenUsage(parser, 'help')
1436 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001437 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001438 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001439 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001440
1441
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001442if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001443 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001444 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001445
1446# vim: ts=2:sw=2:tw=80:et: