blob: 988581bc036875e0810c023f2da817c569cb7c29 [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.org064186c2011-09-27 23:53:33 +0000139class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000140 """Immutable configuration settings."""
141 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000142 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000143 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000144 GClientKeywords.__init__(self)
145
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000146 # These are not mutable:
147 self._parent = parent
148 self._safesync_url = safesync_url
149 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000150 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000151 # 'managed' determines whether or not this dependency is synced/updated by
152 # gclient after gclient checks it out initially. The difference between
153 # 'managed' and 'should_process' is that the user specifies 'managed' via
154 # the --unmanaged command-line flag or a .gclient config, where
155 # 'should_process' is dynamically set by gclient if it goes over its
156 # recursion limit and controls gclient's behavior so it does not misbehave.
157 self._managed = managed
158 self._should_process = should_process
159
160 # These are only set in .gclient and not in DEPS files.
161 self._custom_vars = custom_vars or {}
162 self._custom_deps = custom_deps or {}
163
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 # Post process the url to remove trailing slashes.
165 if isinstance(self._url, basestring):
166 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
167 # it to proto://host/path@rev.
168 if self._url.count('@') > 1:
169 raise gclient_utils.Error('Invalid url "%s"' % self._url)
170 self._url = self._url.replace('/@', '@')
171 elif not isinstance(self._url,
172 (self.FromImpl, self.FileImpl, None.__class__)):
173 raise gclient_utils.Error(
174 ('dependency url must be either a string, None, '
175 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 if '/' in self._deps_file or '\\' in self._deps_file:
177 raise gclient_utils.Error('deps_file name must not be a path, just a '
178 'filename. %s' % self._deps_file)
179
180 @property
181 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 return self._deps_file
183
184 @property
185 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 return self._managed
187
188 @property
189 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 return self._parent
191
192 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000193 def root(self):
194 """Returns the root node, a GClient object."""
195 if not self.parent:
196 # This line is to signal pylint that it could be a GClient instance.
197 return self or GClient(None, None)
198 return self.parent.root
199
200 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000201 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 return self._safesync_url
203
204 @property
205 def should_process(self):
206 """True if this dependency should be processed, i.e. checked out."""
207 return self._should_process
208
209 @property
210 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 return self._custom_vars.copy()
212
213 @property
214 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 return self._custom_deps.copy()
216
maruel@chromium.org064186c2011-09-27 23:53:33 +0000217 @property
218 def url(self):
219 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000221 @property
222 def recursion_limit(self):
223 """Returns > 0 if this dependency is not too recursed to be processed."""
224 return max(self.parent.recursion_limit - 1, 0)
225
226 def get_custom_deps(self, name, url):
227 """Returns a custom deps if applicable."""
228 if self.parent:
229 url = self.parent.get_custom_deps(name, url)
230 # None is a valid return value to disable a dependency.
231 return self.custom_deps.get(name, url)
232
maruel@chromium.org064186c2011-09-27 23:53:33 +0000233
234class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000235 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000236
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000237 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000238 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000239 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000243
244 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000246
247 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000249 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250 # A cache of the files affected by the current operation, necessary for
251 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000252 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000253 # If it is not set to True, the dependency wasn't processed for its child
254 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000256 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000260
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 # Setup self.requirements and find any other dependency who would have self
262 # as a requirement.
maruel@chromium.org98023df2011-09-07 18:44:47 +0000263
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000264 # self.parent is implicitly a requirement. This will be recursive by
265 # definition.
266 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000267 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000268
269 # For a tree with at least 2 levels*, the leaf node needs to depend
270 # on the level higher up in an orderly way.
271 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
272 # thus unsorted, while the .gclient format is a list thus sorted.
273 #
274 # * _recursion_limit is hard coded 2 and there is no hope to change this
275 # value.
276 #
277 # Interestingly enough, the following condition only works in the case we
278 # want: self is a 2nd level node. 3nd level node wouldn't need this since
279 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000280 root_deps = self.root.dependencies
281 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000282 for i in root_deps:
283 if i is self.parent:
284 break
285 if i.name:
286 self._requirements.add(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287
288 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000289 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000290
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000291 if self.name and self.should_process:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000292 for obj in self.root.depth_first_tree():
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000293 if obj is self or not obj.name:
294 continue
295 # Step 1: Find any requirements self may need.
296 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000297 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000298 # Step 2: Find any requirements self may impose.
299 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000300 try:
301 # Access to a protected member _requirements of a client class
302 # pylint: disable=W0212
303 obj.lock.acquire()
304 obj._requirements.add(self.name)
305 finally:
306 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000307
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 if not self.name and self.parent:
309 raise gclient_utils.Error('Dependency without name')
310
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000311 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000312 """Resolves the parsed url from url.
313
314 Manages From() keyword accordingly. Do not touch self.parsed_url nor
315 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000316 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000317 parsed_url = self.get_custom_deps(self.name, url)
318 if parsed_url != url:
319 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
320 return parsed_url
321
322 if isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000323 ref = [
324 dep for dep in self.root.subtree(True) if url.module_name == dep.name
325 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000326 if not ref:
327 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
328 url.module_name, ref))
329 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000330 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000331 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000332 # Make sure the referenced dependency DEPS file is loaded and file the
333 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000334 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000335 found_dep = None
336 for d in ref.dependencies:
337 if d.name == sub_target:
338 found_dep = d
339 break
340 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000341 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000342 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
343 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000344 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000345
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000346 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000347 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000348 logging.info(
349 'LateOverride(%s, %s) -> %s (From)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000350 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000351
352 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000353 parsed_url = urlparse.urlparse(url)
354 if not parsed_url[0]:
355 # A relative url. Fetch the real base.
356 path = parsed_url[2]
357 if not path.startswith('/'):
358 raise gclient_utils.Error(
359 'relative DEPS entry \'%s\' must begin with a slash' % url)
360 # Create a scm just to query the full url.
361 parent_url = self.parent.parsed_url
362 if isinstance(parent_url, self.FileImpl):
363 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000364 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000366 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000367 parsed_url = url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000368 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000369 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000370
371 if isinstance(url, self.FileImpl):
372 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url))
373 return url
374
375 if url is None:
376 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url))
377 return url
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000378 else:
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000379 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000380
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000381 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000382 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000383 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000385 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000386 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000387 # One thing is unintuitive, vars= {} must happen before Var() use.
388 local_scope = {}
389 var = self.VarImpl(self.custom_vars, local_scope)
390 global_scope = {
391 'File': self.FileImpl,
392 'From': self.FromImpl,
393 'Var': var.Lookup,
394 'deps_os': {},
395 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000396 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000397 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000398 logging.info(
399 'ParseDepsFile(%s): No %s file found at %s' % (
400 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000401 else:
402 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000403 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000404 # Eval the content.
405 try:
406 exec(deps_content, global_scope, local_scope)
407 except SyntaxError, e:
408 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000409 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000410 # load os specific dependencies if defined. these dependencies may
411 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000412 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000413 enforced_os = self.root.enforced_os
414 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000415 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000416 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000417 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000418 # platform, so we collect the broadest set of dependencies
419 # available. We may end up with the wrong revision of something for
420 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000421 deps.update([x for x in os_deps.items() if not x[0] in deps])
422 else:
423 deps.update(os_deps)
424
maruel@chromium.org064186c2011-09-27 23:53:33 +0000425 self._deps_hooks.extend(local_scope.get('hooks', []))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000426
427 # If a line is in custom_deps, but not in the solution, we want to append
428 # this line to the solution.
429 for d in self.custom_deps:
430 if d not in deps:
431 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000432
433 # If use_relative_paths is set in the DEPS file, regenerate
434 # the dictionary using paths relative to the directory containing
435 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000436 use_relative_paths = local_scope.get('use_relative_paths', False)
437 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000438 rel_deps = {}
439 for d, url in deps.items():
440 # normpath is required to allow DEPS to use .. in their
441 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000442 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
443 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000444
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000445 # Convert the deps into real Dependency.
446 for name, url in deps.iteritems():
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000447 if name in [s.name for s in self._dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000448 raise gclient_utils.Error(
449 'The same name "%s" appears multiple times in the deps section' %
450 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000451 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000452 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000453 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000454 if name in tree:
455 if url == tree[name].url:
456 logging.info('Won\'t process duplicate dependency %s' % tree[name])
457 # In theory we could keep it as a shadow of the other one. In
458 # practice, simply ignore it.
459 #should_process = False
460 continue
461 else:
462 raise gclient_utils.Error(
463 'Dependency %s specified more than once:\n %s\nvs\n %s' %
464 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000465 self._dependencies.append(
466 Dependency(
467 self, name, url, None, None, None, None,
468 self.deps_file, should_process))
maruel@chromium.org064186c2011-09-27 23:53:33 +0000469 self._deps_parsed = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000470 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000471
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000472 # Arguments number differs from overridden method
473 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000474 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000475 """Runs 'command' before parsing the DEPS in case it's a initial checkout
476 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000477
478 def maybeGetParentRevision(options):
479 """If we are performing an update and --transitive is set, set the
480 revision to the parent's revision. If we have an explicit revision
481 do nothing."""
482 if command == 'update' and options.transitive and not options.revision:
483 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
484 if not revision:
485 options.revision = revision_overrides.get(self.parent.name)
486 if options.verbose and options.revision:
487 print("Using parent's revision date: %s" % options.revision)
488 # If the parent has a revision override, then it must have been
489 # converted to date format.
490 assert (not options.revision or
491 gclient_utils.IsDateRevision(options.revision))
492
493 def maybeConvertToDateRevision(options):
494 """If we are performing an update and --transitive is set, convert the
495 revision to a date-revision (if necessary). Instead of having
496 -r 101 replace the revision with the time stamp of 101 (e.g.
497 "{2011-18-04}").
498 This way dependencies are upgraded to the revision they had at the
499 check-in of revision 101."""
500 if (command == 'update' and
501 options.transitive and
502 options.revision and
503 not gclient_utils.IsDateRevision(options.revision)):
504 revision_date = scm.GetRevisionDate(options.revision)
505 revision = gclient_utils.MakeDateRevision(revision_date)
506 if options.verbose:
507 print("Updating revision override from %s to %s." %
508 (options.revision, revision))
509 revision_overrides[self.name] = revision
510
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000511 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000512 if not self.should_process:
513 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 # When running runhooks, there's no need to consult the SCM.
515 # All known hooks are expected to run unconditionally regardless of working
516 # copy state, so skip the SCM status check.
517 run_scm = command not in ('runhooks', None)
maruel@chromium.org064186c2011-09-27 23:53:33 +0000518 self._parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 if run_scm and self.parsed_url:
520 if isinstance(self.parsed_url, self.FileImpl):
521 # Special support for single-file checkout.
522 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
523 options.revision = self.parsed_url.GetRevision()
524 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000525 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 self.name)
527 scm.RunCommand('updatesingle', options,
528 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000529 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000530 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000531 # Create a shallow copy to mutate revision.
532 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000533 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000534 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000535 scm = gclient_scm.CreateSCM(
536 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000537 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000538 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000539 self._file_list = [os.path.join(self.name, f.strip())
540 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000541
542 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
543 # Convert all absolute paths to relative.
544 for i in range(len(self._file_list)):
545 # It depends on the command being executed (like runhooks vs sync).
546 if not os.path.isabs(self._file_list[i]):
547 continue
548 prefix = os.path.commonprefix(
549 [self.root.root_dir.lower(), self._file_list[i].lower()])
550 self._file_list[i] = self._file_list[i][len(prefix):]
551 # Strip any leading path separators.
552 while (self._file_list[i].startswith('\\') or
553 self._file_list[i].startswith('/')):
554 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.org064186c2011-09-27 23:53:33 +0000555 self._processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000556 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000557 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000558 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000559
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000560 # Parse the dependencies of this dependency.
561 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000562 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000563
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000564 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000565 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000566 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000567 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000568 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000569 # Don't run the hook when it is above recursion_limit.
570 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000571 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000572 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000573 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000574 # TODO(maruel): If the user is using git or git-svn, then we don't know
575 # what files have changed so we always run all hooks. It'd be nice to fix
576 # that.
577 if (options.force or
578 isinstance(self.parsed_url, self.FileImpl) or
579 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000580 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000581 for hook_dict in self.deps_hooks:
582 self._RunHookAction(hook_dict, [])
583 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000584 # Run hooks on the basis of whether the files from the gclient operation
585 # match each hook's pattern.
586 for hook_dict in self.deps_hooks:
587 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000588 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000589 if matching_file_list:
590 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 for s in self.dependencies:
592 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000593
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000594 def _RunHookAction(self, hook_dict, matching_file_list):
595 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000596 # A single DEPS file can specify multiple hooks so this function can be
597 # called multiple times on a single Dependency.
598 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000599 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000600 logging.debug(hook_dict)
601 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000602 command = hook_dict['action'][:]
603 if command[0] == 'python':
604 # If the hook specified "python" as the first item, the action is a
605 # Python script. Run it by starting a new copy of the same
606 # interpreter.
607 command[0] = sys.executable
608
609 if '$matching_files' in command:
610 splice_index = command.index('$matching_files')
611 command[splice_index:splice_index + 1] = matching_file_list
612
maruel@chromium.org17d01792010-09-01 18:07:10 +0000613 try:
614 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000615 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000616 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000617 # Use a discrete exit status code of 2 to indicate that a hook action
618 # failed. Users of this script may wish to treat hook action failures
619 # differently from VC failures.
620 print >> sys.stderr, 'Error: %s' % str(e)
621 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000622
maruel@chromium.org0d812442010-08-10 12:41:08 +0000623 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000624 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000625 dependencies = self.dependencies
626 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000627 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000628 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000629 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000630 for i in d.subtree(include_all):
631 yield i
632
633 def depth_first_tree(self):
634 """Depth-first recursion including the root node."""
635 yield self
636 for i in self.dependencies:
637 for j in i.depth_first_tree():
638 if j.should_process:
639 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000640
maruel@chromium.org68988972011-09-20 14:11:42 +0000641 @property
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000642 def dependencies(self):
643 return tuple(self._dependencies)
644
645 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000646 def deps_hooks(self):
647 return tuple(self._deps_hooks)
648
649 @property
650 def parsed_url(self):
651 return self._parsed_url
652
653 @property
654 def deps_parsed(self):
655 return self._deps_parsed
656
657 @property
658 def processed(self):
659 return self._processed
660
661 @property
662 def hooks_ran(self):
663 return self._hooks_ran
664
665 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000666 def file_list(self):
667 result = self._file_list[:]
668 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000669 result.extend(d.file_list)
670 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000671
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000672 def __str__(self):
673 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000674 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000675 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000676 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000677 # First try the native property if it exists.
678 if hasattr(self, '_' + i):
679 value = getattr(self, '_' + i, False)
680 else:
681 value = getattr(self, i, False)
682 if value:
683 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000684
685 for d in self.dependencies:
686 out.extend([' ' + x for x in str(d).splitlines()])
687 out.append('')
688 return '\n'.join(out)
689
690 def __repr__(self):
691 return '%s: %s' % (self.name, self.url)
692
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000693 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000694 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000695 out = '%s(%s)' % (self.name, self.url)
696 i = self.parent
697 while i and i.name:
698 out = '%s(%s) -> %s' % (i.name, i.url, out)
699 i = i.parent
700 return out
701
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000702
703class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000704 """Object that represent a gclient checkout. A tree of Dependency(), one per
705 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000706
707 DEPS_OS_CHOICES = {
708 "win32": "win",
709 "win": "win",
710 "cygwin": "win",
711 "darwin": "mac",
712 "mac": "mac",
713 "unix": "unix",
714 "linux": "unix",
715 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000716 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000717 }
718
719 DEFAULT_CLIENT_FILE_TEXT = ("""\
720solutions = [
721 { "name" : "%(solution_name)s",
722 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000723 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000724 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000725 "custom_deps" : {
726 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000727 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000728 },
729]
730""")
731
732 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
733 { "name" : "%(solution_name)s",
734 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000735 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000736 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000737 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000738%(solution_deps)s },
739 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000740 },
741""")
742
743 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
744# Snapshot generated with gclient revinfo --snapshot
745solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000746%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000747""")
748
749 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000750 # Do not change previous behavior. Only solution level and immediate DEPS
751 # are processed.
752 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000753 Dependency.__init__(self, None, None, None, None, True, None, None,
754 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000755 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000756 if options.deps_os:
757 enforced_os = options.deps_os.split(',')
758 else:
759 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
760 if 'all' in enforced_os:
761 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000762 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000763 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000764 self.config_content = None
765
766 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000767 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768 config_dict = {}
769 self.config_content = content
770 try:
771 exec(content, config_dict)
772 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000773 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000774 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000775 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000776 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000777 if s['name'] in tree:
778 raise gclient_utils.Error(
779 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000780 self._dependencies.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000781 self, s['name'], s['url'],
782 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000783 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000784 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000785 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000786 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000787 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000788 except KeyError:
789 raise gclient_utils.Error('Invalid .gclient file. Solution is '
790 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000791 # .gclient can have hooks.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000792 self._deps_hooks = config_dict.get('hooks', [])
793 self._deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000794
795 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000796 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000797 self._options.config_filename),
798 self.config_content)
799
800 @staticmethod
801 def LoadCurrentConfig(options):
802 """Searches for and loads a .gclient file relative to the current working
803 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000804 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000805 if not path:
806 return None
807 client = GClient(path, options)
808 client.SetConfig(gclient_utils.FileRead(
809 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000810 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000811
nsylvain@google.comefc80932011-05-31 21:27:56 +0000812 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000813 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000814 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
815 'solution_name': solution_name,
816 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000817 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000818 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000819 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000820 })
821
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000822 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000823 """Creates a .gclient_entries file to record the list of unique checkouts.
824
825 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000826 """
827 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
828 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000830 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000831 # Skip over File() dependencies as we can't version them.
832 if not isinstance(entry.parsed_url, self.FileImpl):
833 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
834 pprint.pformat(entry.parsed_url))
835 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000836 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000837 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000838 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000839
840 def _ReadEntries(self):
841 """Read the .gclient_entries file for the given client.
842
843 Returns:
844 A sequence of solution names, which will be empty if there is the
845 entries file hasn't been created yet.
846 """
847 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000848 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000849 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000850 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000851 try:
852 exec(gclient_utils.FileRead(filename), scope)
853 except SyntaxError, e:
854 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000855 return scope['entries']
856
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000857 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000858 """Checks for revision overrides."""
859 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000860 if self._options.head:
861 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000862 # Do not check safesync_url if one or more --revision flag is specified.
863 if not self._options.revisions:
864 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000865 if not s.managed:
866 self._options.revisions.append('%s@unmanaged' % s.name)
867 elif s.safesync_url:
868 handle = urllib.urlopen(s.safesync_url)
869 rev = handle.read().strip()
870 handle.close()
871 if len(rev):
872 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000873 if not self._options.revisions:
874 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000875 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000876 index = 0
877 for revision in self._options.revisions:
878 if not '@' in revision:
879 # Support for --revision 123
880 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000881 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000882 if not sol in solutions_names:
883 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
884 print >> sys.stderr, ('Please fix your script, having invalid '
885 '--revision flags will soon considered an error.')
886 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000887 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000888 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000889 return revision_overrides
890
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000891 def RunOnDeps(self, command, args):
892 """Runs a command on each dependency in a client and its dependencies.
893
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000894 Args:
895 command: The command to use (e.g., 'status' or 'diff')
896 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000897 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000898 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000899 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000900 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000902 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000903 if (command in ('update', 'revert') and sys.stdout.isatty() and not
904 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000905 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000906 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000907 for s in self.dependencies:
908 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000909 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000910
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000911 # Once all the dependencies have been processed, it's now safe to run the
912 # hooks.
913 if not self._options.nohooks:
914 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000915
916 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000917 # Notify the user if there is an orphaned entry in their working copy.
918 # Only delete the directory if there are no changes in it, and
919 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000920 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000921 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000922 if not prev_url:
923 # entry must have been overridden via .gclient custom_deps
924 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000925 # Fix path separator on Windows.
926 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000927 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000928 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000929 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000930 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000931 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000932 scm.status(self._options, [], file_list)
933 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000934 if (not self._options.delete_unversioned_trees or
935 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000936 # There are modified files in this entry. Keep warning until
937 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000938 print(('\nWARNING: \'%s\' is no longer part of this client. '
939 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000940 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000941 else:
942 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000943 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000944 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000945 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000946 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000947 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000948 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949
950 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000951 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000952 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000953 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000954 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000955 for s in self.dependencies:
956 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000957 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000958
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000959 def GetURLAndRev(dep):
960 """Returns the revision-qualified SCM url for a Dependency."""
961 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000962 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000963 if isinstance(dep.parsed_url, self.FileImpl):
964 original_url = dep.parsed_url.file_location
965 else:
966 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000967 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000968 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000969 if not os.path.isdir(scm.checkout_path):
970 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000971 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000972
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000973 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000974 new_gclient = ''
975 # First level at .gclient
976 for d in self.dependencies:
977 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000978 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000979 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000980 for d in dep.dependencies:
981 entries[d.name] = GetURLAndRev(d)
982 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 GrabDeps(d)
984 custom_deps = []
985 for k in sorted(entries.keys()):
986 if entries[k]:
987 # Quotes aren't escaped...
988 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
989 else:
990 custom_deps.append(' \"%s\": None,\n' % k)
991 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
992 'solution_name': d.name,
993 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000994 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000995 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000996 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000997 'solution_deps': ''.join(custom_deps),
998 }
999 # Print the snapshot configuration file
1000 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001001 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001002 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001003 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001004 if self._options.actual:
1005 entries[d.name] = GetURLAndRev(d)
1006 else:
1007 entries[d.name] = d.parsed_url
1008 keys = sorted(entries.keys())
1009 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001010 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001011 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001012
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001013 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001014 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001015 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001016
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001017 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001018 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001019 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001020 return self._root_dir
1021
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001022 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001023 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001024 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001025 return self._enforced_os
1026
maruel@chromium.org68988972011-09-20 14:11:42 +00001027 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001028 def recursion_limit(self):
1029 """How recursive can each dependencies in DEPS file can load DEPS file."""
1030 return self._recursion_limit
1031
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001033#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034
1035
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001036def CMDcleanup(parser, args):
1037 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001038
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001039Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001040"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001041 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1042 help='override deps for the specified (comma-separated) '
1043 'platform(s); \'all\' will process all deps_os '
1044 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001045 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001046 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001048 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 if options.verbose:
1050 # Print out the .gclient file. This is longer than if we just printed the
1051 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001052 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 return client.RunOnDeps('cleanup', args)
1054
1055
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001056@attr('usage', '[command] [args ...]')
1057def CMDrecurse(parser, args):
1058 """Operates on all the entries.
1059
1060 Runs a shell command on all entries.
1061 """
1062 # Stop parsing at the first non-arg so that these go through to the command
1063 parser.disable_interspersed_args()
1064 parser.add_option('-s', '--scm', action='append', default=[],
1065 help='choose scm types to operate upon')
1066 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001067 if not args:
1068 print >> sys.stderr, 'Need to supply a command!'
1069 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001070 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1071 if not root_and_entries:
1072 print >> sys.stderr, (
1073 'You need to run gclient sync at least once to use \'recurse\'.\n'
1074 'This is because .gclient_entries needs to exist and be up to date.')
1075 return 1
1076 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001077 scm_set = set()
1078 for scm in options.scm:
1079 scm_set.update(scm.split(','))
1080
1081 # Pass in the SCM type as an env variable
1082 env = os.environ.copy()
1083
1084 for path, url in entries.iteritems():
1085 scm = gclient_scm.GetScmName(url)
1086 if scm_set and scm not in scm_set:
1087 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001088 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001089 if scm:
1090 env['GCLIENT_SCM'] = scm
1091 if url:
1092 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001093 if os.path.isdir(cwd):
1094 subprocess2.call(args, cwd=cwd, env=env)
1095 else:
1096 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001097 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100@attr('usage', '[url] [safesync url]')
1101def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001102 """Create a .gclient file in the current directory.
1103
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001104This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001105top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001106modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001107provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001108URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001109"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001110 parser.add_option('--spec',
1111 help='create a gclient file containing the provided '
1112 'string. Due to Cygwin/Python brokenness, it '
1113 'probably can\'t contain any newlines.')
1114 parser.add_option('--name',
1115 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001116 parser.add_option('--deps-file', default='DEPS',
1117 help='overrides the default name for the DEPS file for the'
1118 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001119 parser.add_option('--unmanaged', action='store_true', default=False,
1120 help='overrides the default behavior to make it possible '
1121 'to have the main solution untouched by gclient '
1122 '(gclient will check out unmanaged dependencies but '
1123 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001124 parser.add_option('--git-deps', action='store_true',
1125 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001126 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001127 if ((options.spec and args) or len(args) > 2 or
1128 (not options.spec and not args)):
1129 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1130
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001131 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132 if options.spec:
1133 client.SetConfig(options.spec)
1134 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001135 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001136 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001137 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001138 if name.endswith('.git'):
1139 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001140 else:
1141 # specify an alternate relpath for the given URL.
1142 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001143 deps_file = options.deps_file
1144 if options.git_deps:
1145 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001146 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001147 if len(args) > 1:
1148 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001149 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1150 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001152 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153
1154
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001155@attr('epilog', """Example:
1156 gclient pack > patch.txt
1157 generate simple patch for configured client and dependences
1158""")
1159def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001160 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001161
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001162Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001163dependencies, and performs minimal postprocessing of the output. The
1164resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001166"""
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)
kbr@google.comab318592009-09-04 00:54:55 +00001172 client = GClient.LoadCurrentConfig(options)
1173 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001174 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +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)
kbr@google.comab318592009-09-04 00:54:55 +00001179 return client.RunOnDeps('pack', args)
1180
1181
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182def CMDstatus(parser, args):
1183 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001184 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1185 help='override deps for the specified (comma-separated) '
1186 'platform(s); \'all\' will process all deps_os '
1187 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001188 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001189 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001190 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001191 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001192 if options.verbose:
1193 # Print out the .gclient file. This is longer than if we just printed the
1194 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001195 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001196 return client.RunOnDeps('status', args)
1197
1198
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001199@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001200 gclient sync
1201 update files from SCM according to current configuration,
1202 *for modules which have changed since last update or sync*
1203 gclient sync --force
1204 update files from SCM according to current configuration, for
1205 all modules (useful for recovering files deleted from local copy)
1206 gclient sync --revision src@31000
1207 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001208""")
1209def CMDsync(parser, args):
1210 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001211 parser.add_option('-f', '--force', action='store_true',
1212 help='force update even for unchanged modules')
1213 parser.add_option('-n', '--nohooks', action='store_true',
1214 help='don\'t run hooks after the update is complete')
1215 parser.add_option('-r', '--revision', action='append',
1216 dest='revisions', metavar='REV', default=[],
1217 help='Enforces revision/hash for the solutions with the '
1218 'format src@rev. The src@ part is optional and can be '
1219 'skipped. -r can be used multiple times when .gclient '
1220 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001221 'if the src@ part is skipped. Note that specifying '
1222 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001223 parser.add_option('-t', '--transitive', action='store_true',
1224 help='When a revision is specified (in the DEPS file or '
1225 'with the command-line flag), transitively update '
1226 'the dependencies to the date of the given revision. '
1227 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001228 parser.add_option('-H', '--head', action='store_true',
1229 help='skips any safesync_urls specified in '
1230 'configured solutions and sync to head instead')
1231 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001232 help='delete any dependency that have been removed from '
1233 'last sync as long as there is no local modification. '
1234 'Coupled with --force, it will remove them even with '
1235 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001236 parser.add_option('-R', '--reset', action='store_true',
1237 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001238 parser.add_option('-M', '--merge', action='store_true',
1239 help='merge upstream changes instead of trying to '
1240 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001241 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1242 help='override deps for the specified (comma-separated) '
1243 'platform(s); \'all\' will process all deps_os '
1244 'references')
1245 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1246 help='Skip svn up whenever possible by requesting '
1247 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001248 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001249 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001250
1251 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001252 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001253
maruel@chromium.org307d1792010-05-31 20:03:13 +00001254 if options.revisions and options.head:
1255 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001256 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001257
1258 if options.verbose:
1259 # Print out the .gclient file. This is longer than if we just printed the
1260 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001261 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001262 return client.RunOnDeps('update', args)
1263
1264
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001265def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001266 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001267 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001268
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001269def CMDdiff(parser, args):
1270 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001271 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1272 help='override deps for the specified (comma-separated) '
1273 'platform(s); \'all\' will process all deps_os '
1274 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001275 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001276 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001278 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001279 if options.verbose:
1280 # Print out the .gclient file. This is longer than if we just printed the
1281 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001282 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001283 return client.RunOnDeps('diff', args)
1284
1285
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001286def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001287 """Revert all modifications in every dependencies.
1288
1289 That's the nuclear option to get back to a 'clean' state. It removes anything
1290 that shows up in svn status."""
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('-n', '--nohooks', action='store_true',
1296 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001297 (options, args) = parser.parse_args(args)
1298 # --force is implied.
1299 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001300 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001301 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001302 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001303 return client.RunOnDeps('revert', args)
1304
1305
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001306def CMDrunhooks(parser, args):
1307 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001308 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1309 help='override deps for the specified (comma-separated) '
1310 'platform(s); \'all\' will process all deps_os '
1311 'references')
1312 parser.add_option('-f', '--force', action='store_true', default=True,
1313 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001314 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001315 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001316 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001317 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 if options.verbose:
1319 # Print out the .gclient file. This is longer than if we just printed the
1320 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001321 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001322 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001323 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001324 return client.RunOnDeps('runhooks', args)
1325
1326
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001327def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001328 """Output revision info mapping for the client and its dependencies.
1329
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001330 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001331 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001332 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1333 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001334 commit can change.
1335 """
1336 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1337 help='override deps for the specified (comma-separated) '
1338 'platform(s); \'all\' will process all deps_os '
1339 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001340 parser.add_option('-a', '--actual', action='store_true',
1341 help='gets the actual checked out revisions instead of the '
1342 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001343 parser.add_option('-s', '--snapshot', action='store_true',
1344 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001345 'version of all repositories to reproduce the tree, '
1346 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001347 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001348 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001349 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001350 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001351 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001352 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001353
1354
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001355def Command(name):
1356 return getattr(sys.modules[__name__], 'CMD' + name, None)
1357
1358
1359def CMDhelp(parser, args):
1360 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001361 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001362 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001363 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001364 parser.print_help()
1365 return 0
1366
1367
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001368def GenUsage(parser, command):
1369 """Modify an OptParse object with the function's documentation."""
1370 obj = Command(command)
1371 if command == 'help':
1372 command = '<command>'
1373 # OptParser.description prefer nicely non-formatted strings.
1374 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1375 usage = getattr(obj, 'usage', '')
1376 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1377 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001378
1379
maruel@chromium.org0895b752011-08-26 20:40:33 +00001380def Parser():
1381 """Returns the default parser."""
1382 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001383 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001384 help='Specify how many SCM commands can run in parallel; '
1385 'default=%default')
1386 parser.add_option('-v', '--verbose', action='count', default=0,
1387 help='Produces additional output for diagnostics. Can be '
1388 'used up to three times for more logging info.')
1389 parser.add_option('--gclientfile', dest='config_filename',
1390 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1391 help='Specify an alternate %default file')
1392 # Integrate standard options processing.
1393 old_parser = parser.parse_args
1394 def Parse(args):
1395 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001396 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1397 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001398 logging.basicConfig(level=level,
1399 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1400 options.entries_filename = options.config_filename + '_entries'
1401 if options.jobs < 1:
1402 parser.error('--jobs must be 1 or higher')
1403
1404 # These hacks need to die.
1405 if not hasattr(options, 'revisions'):
1406 # GClient.RunOnDeps expects it even if not applicable.
1407 options.revisions = []
1408 if not hasattr(options, 'head'):
1409 options.head = None
1410 if not hasattr(options, 'nohooks'):
1411 options.nohooks = True
1412 if not hasattr(options, 'deps_os'):
1413 options.deps_os = None
1414 if not hasattr(options, 'manually_grab_svn_rev'):
1415 options.manually_grab_svn_rev = None
1416 if not hasattr(options, 'force'):
1417 options.force = None
1418 return (options, args)
1419 parser.parse_args = Parse
1420 # We don't want wordwrapping in epilog (usually examples)
1421 parser.format_epilog = lambda _: parser.epilog or ''
1422 return parser
1423
1424
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001425def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001426 """Doesn't parse the arguments here, just find the right subcommand to
1427 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001428 if sys.hexversion < 0x02050000:
1429 print >> sys.stderr, (
1430 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001431 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001432 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1433 # operations. Python as a strong tendency to buffer sys.stdout.
1434 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001435 # Make stdout annotated with the thread ids.
1436 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001437 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001438 # Unused variable 'usage'
1439 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001440 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1441 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1442 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001443 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001444 if argv:
1445 command = Command(argv[0])
1446 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001447 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001448 GenUsage(parser, argv[0])
1449 return command(parser, argv[1:])
1450 # Not a known command. Default to help.
1451 GenUsage(parser, 'help')
1452 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001453 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001454 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001455 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456
1457
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001458if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001459 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001460 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001461
1462# vim: ts=2:sw=2:tw=80:et: