blob: 5f40f4ed997e851c1fd9bb85fb85a23082db6852 [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.org0bcfd182011-10-10 20:06:09 +0000261 if not self.name and self.parent:
262 raise gclient_utils.Error('Dependency without name')
263
264 def setup_requirements(self):
265 """Setup self.requirements and find any other dependency who would have self
266 as a requirement.
267
268 Returns True if this entry should be added, False if it is a duplicate of
269 another entry.
270 """
271 if self.name in [s.name for s in self.parent.dependencies]:
272 raise gclient_utils.Error(
273 'The same name "%s" appears multiple times in the deps section' %
274 self.name)
275 if self.should_process:
276 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
277 for sibling in siblings:
278 if self.url != sibling.url:
279 raise gclient_utils.Error(
280 'Dependency %s specified more than once:\n %s\nvs\n %s' %
281 (self.name, sibling.hierarchy(), self.hierarchy()))
282 # In theory we could keep it as a shadow of the other one. In
283 # practice, simply ignore it.
284 logging.warn('Won\'t process duplicate dependency %s' % sibling)
285 return False
maruel@chromium.org98023df2011-09-07 18:44:47 +0000286
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287 # self.parent is implicitly a requirement. This will be recursive by
288 # definition.
289 if self.parent and self.parent.name:
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000290 self.add_requirement(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000291
292 # For a tree with at least 2 levels*, the leaf node needs to depend
293 # on the level higher up in an orderly way.
294 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
295 # thus unsorted, while the .gclient format is a list thus sorted.
296 #
297 # * _recursion_limit is hard coded 2 and there is no hope to change this
298 # value.
299 #
300 # Interestingly enough, the following condition only works in the case we
301 # want: self is a 2nd level node. 3nd level node wouldn't need this since
302 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000303 root_deps = self.root.dependencies
304 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000305 for i in root_deps:
306 if i is self.parent:
307 break
308 if i.name:
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000309 self.add_requirement(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000310
311 if isinstance(self.url, self.FromImpl):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000312 self.add_requirement(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000313
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000314 if self.name and self.should_process:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000315 for obj in self.root.depth_first_tree():
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000316 if obj is self or not obj.name:
317 continue
318 # Step 1: Find any requirements self may need.
319 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000320 self.add_requirement(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000321 # Step 2: Find any requirements self may impose.
322 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000323 obj.add_requirement(self.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000324 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000325
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000326 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000327 """Resolves the parsed url from url.
328
329 Manages From() keyword accordingly. Do not touch self.parsed_url nor
330 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000331 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000332 parsed_url = self.get_custom_deps(self.name, url)
333 if parsed_url != url:
334 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
335 return parsed_url
336
337 if isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000338 ref = [
339 dep for dep in self.root.subtree(True) if url.module_name == dep.name
340 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000341 if not ref:
342 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
343 url.module_name, ref))
344 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000346 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000347 # Make sure the referenced dependency DEPS file is loaded and file the
348 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000349 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000350 found_dep = None
351 for d in ref.dependencies:
352 if d.name == sub_target:
353 found_dep = d
354 break
355 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000356 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000357 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
358 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000359 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000360
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000361 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000362 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000363 logging.info(
364 'LateOverride(%s, %s) -> %s (From)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000366
367 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 parsed_url = urlparse.urlparse(url)
369 if not parsed_url[0]:
370 # A relative url. Fetch the real base.
371 path = parsed_url[2]
372 if not path.startswith('/'):
373 raise gclient_utils.Error(
374 'relative DEPS entry \'%s\' must begin with a slash' % url)
375 # Create a scm just to query the full url.
376 parent_url = self.parent.parsed_url
377 if isinstance(parent_url, self.FileImpl):
378 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000379 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000380 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000381 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000382 parsed_url = url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000383 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000384 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000385
386 if isinstance(url, self.FileImpl):
387 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url))
388 return url
389
390 if url is None:
391 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url))
392 return url
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000393 else:
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000394 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000396 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000397 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000398 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000399 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000400 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000401 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000402 # One thing is unintuitive, vars= {} must happen before Var() use.
403 local_scope = {}
404 var = self.VarImpl(self.custom_vars, local_scope)
405 global_scope = {
406 'File': self.FileImpl,
407 'From': self.FromImpl,
408 'Var': var.Lookup,
409 'deps_os': {},
410 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000411 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000412 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000413 logging.info(
414 'ParseDepsFile(%s): No %s file found at %s' % (
415 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000416 else:
417 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000418 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000419 # Eval the content.
420 try:
421 exec(deps_content, global_scope, local_scope)
422 except SyntaxError, e:
423 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000424 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000425 # load os specific dependencies if defined. these dependencies may
426 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000427 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000428 enforced_os = self.root.enforced_os
429 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000430 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000431 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000432 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000433 # platform, so we collect the broadest set of dependencies
434 # available. We may end up with the wrong revision of something for
435 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000436 deps.update([x for x in os_deps.items() if not x[0] in deps])
437 else:
438 deps.update(os_deps)
439
maruel@chromium.org271375b2010-06-23 19:17:38 +0000440 # If a line is in custom_deps, but not in the solution, we want to append
441 # this line to the solution.
442 for d in self.custom_deps:
443 if d not in deps:
444 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000445
446 # If use_relative_paths is set in the DEPS file, regenerate
447 # the dictionary using paths relative to the directory containing
448 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000449 use_relative_paths = local_scope.get('use_relative_paths', False)
450 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000451 rel_deps = {}
452 for d, url in deps.items():
453 # normpath is required to allow DEPS to use .. in their
454 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000455 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
456 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000457
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000459 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000460 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000461 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000462 deps_to_add.append(Dependency(
463 self, name, url, None, None, None, None,
464 self.deps_file, should_process))
465 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
466 logging.info('ParseDepsFile(%s) done' % self.name)
467
468 def add_dependencies_and_close(self, deps_to_add, hooks):
469 """Adds the dependencies, hooks and mark the parsing as done."""
470 for dep in deps_to_add:
471 if dep.setup_requirements():
472 self.add_dependency(dep)
473 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000474
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000475 # Arguments number differs from overridden method
476 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000477 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000478 """Runs 'command' before parsing the DEPS in case it's a initial checkout
479 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000480
481 def maybeGetParentRevision(options):
482 """If we are performing an update and --transitive is set, set the
483 revision to the parent's revision. If we have an explicit revision
484 do nothing."""
485 if command == 'update' and options.transitive and not options.revision:
486 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
487 if not revision:
488 options.revision = revision_overrides.get(self.parent.name)
489 if options.verbose and options.revision:
490 print("Using parent's revision date: %s" % options.revision)
491 # If the parent has a revision override, then it must have been
492 # converted to date format.
493 assert (not options.revision or
494 gclient_utils.IsDateRevision(options.revision))
495
496 def maybeConvertToDateRevision(options):
497 """If we are performing an update and --transitive is set, convert the
498 revision to a date-revision (if necessary). Instead of having
499 -r 101 replace the revision with the time stamp of 101 (e.g.
500 "{2011-18-04}").
501 This way dependencies are upgraded to the revision they had at the
502 check-in of revision 101."""
503 if (command == 'update' and
504 options.transitive and
505 options.revision and
506 not gclient_utils.IsDateRevision(options.revision)):
507 revision_date = scm.GetRevisionDate(options.revision)
508 revision = gclient_utils.MakeDateRevision(revision_date)
509 if options.verbose:
510 print("Updating revision override from %s to %s." %
511 (options.revision, revision))
512 revision_overrides[self.name] = revision
513
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000514 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000515 if not self.should_process:
516 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 # When running runhooks, there's no need to consult the SCM.
518 # All known hooks are expected to run unconditionally regardless of working
519 # copy state, so skip the SCM status check.
520 run_scm = command not in ('runhooks', None)
maruel@chromium.org064186c2011-09-27 23:53:33 +0000521 self._parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000522 if run_scm and self.parsed_url:
523 if isinstance(self.parsed_url, self.FileImpl):
524 # Special support for single-file checkout.
525 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
526 options.revision = self.parsed_url.GetRevision()
527 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000528 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000529 self.name)
530 scm.RunCommand('updatesingle', options,
531 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000532 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000533 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000534 # Create a shallow copy to mutate revision.
535 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000536 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000537 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000538 scm = gclient_scm.CreateSCM(
539 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000540 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000541 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000542 self._file_list = [os.path.join(self.name, f.strip())
543 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000544
545 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
546 # Convert all absolute paths to relative.
547 for i in range(len(self._file_list)):
548 # It depends on the command being executed (like runhooks vs sync).
549 if not os.path.isabs(self._file_list[i]):
550 continue
551 prefix = os.path.commonprefix(
552 [self.root.root_dir.lower(), self._file_list[i].lower()])
553 self._file_list[i] = self._file_list[i][len(prefix):]
554 # Strip any leading path separators.
555 while (self._file_list[i].startswith('\\') or
556 self._file_list[i].startswith('/')):
557 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.org064186c2011-09-27 23:53:33 +0000558 self._processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000559 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000560 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000561 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000562
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000563 # Parse the dependencies of this dependency.
564 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000565 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000566
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000567 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000568 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000569 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000570 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000571 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000572 # Don't run the hook when it is above recursion_limit.
573 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000574 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000575 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000576 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000577 # TODO(maruel): If the user is using git or git-svn, then we don't know
578 # what files have changed so we always run all hooks. It'd be nice to fix
579 # that.
580 if (options.force or
581 isinstance(self.parsed_url, self.FileImpl) or
582 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000583 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000584 for hook_dict in self.deps_hooks:
585 self._RunHookAction(hook_dict, [])
586 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000587 # Run hooks on the basis of whether the files from the gclient operation
588 # match each hook's pattern.
589 for hook_dict in self.deps_hooks:
590 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000591 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000592 if matching_file_list:
593 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000594 for s in self.dependencies:
595 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000596
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000597 def _RunHookAction(self, hook_dict, matching_file_list):
598 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000599 # A single DEPS file can specify multiple hooks so this function can be
600 # called multiple times on a single Dependency.
601 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000602 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000603 logging.debug(hook_dict)
604 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000605 command = hook_dict['action'][:]
606 if command[0] == 'python':
607 # If the hook specified "python" as the first item, the action is a
608 # Python script. Run it by starting a new copy of the same
609 # interpreter.
610 command[0] = sys.executable
611
612 if '$matching_files' in command:
613 splice_index = command.index('$matching_files')
614 command[splice_index:splice_index + 1] = matching_file_list
615
maruel@chromium.org17d01792010-09-01 18:07:10 +0000616 try:
617 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000618 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000619 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000620 # Use a discrete exit status code of 2 to indicate that a hook action
621 # failed. Users of this script may wish to treat hook action failures
622 # differently from VC failures.
623 print >> sys.stderr, 'Error: %s' % str(e)
624 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000625
maruel@chromium.org0d812442010-08-10 12:41:08 +0000626 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000627 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000628 dependencies = self.dependencies
629 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000630 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000631 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000632 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000633 for i in d.subtree(include_all):
634 yield i
635
636 def depth_first_tree(self):
637 """Depth-first recursion including the root node."""
638 yield self
639 for i in self.dependencies:
640 for j in i.depth_first_tree():
641 if j.should_process:
642 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000643
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000644 @gclient_utils.lockedmethod
645 def add_dependency(self, new_dep):
646 self._dependencies.append(new_dep)
647
648 @gclient_utils.lockedmethod
649 def _mark_as_parsed(self, new_hooks):
650 self._deps_hooks.extend(new_hooks)
651 self._deps_parsed = True
652
maruel@chromium.org68988972011-09-20 14:11:42 +0000653 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000654 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000655 def dependencies(self):
656 return tuple(self._dependencies)
657
658 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000659 def deps_hooks(self):
660 return tuple(self._deps_hooks)
661
662 @property
663 def parsed_url(self):
664 return self._parsed_url
665
666 @property
667 def deps_parsed(self):
668 return self._deps_parsed
669
670 @property
671 def processed(self):
672 return self._processed
673
674 @property
675 def hooks_ran(self):
676 return self._hooks_ran
677
678 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000679 def file_list(self):
680 result = self._file_list[:]
681 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000682 result.extend(d.file_list)
683 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000684
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000685 def __str__(self):
686 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000687 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000688 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000689 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000690 # First try the native property if it exists.
691 if hasattr(self, '_' + i):
692 value = getattr(self, '_' + i, False)
693 else:
694 value = getattr(self, i, False)
695 if value:
696 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000697
698 for d in self.dependencies:
699 out.extend([' ' + x for x in str(d).splitlines()])
700 out.append('')
701 return '\n'.join(out)
702
703 def __repr__(self):
704 return '%s: %s' % (self.name, self.url)
705
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000706 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000707 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000708 out = '%s(%s)' % (self.name, self.url)
709 i = self.parent
710 while i and i.name:
711 out = '%s(%s) -> %s' % (i.name, i.url, out)
712 i = i.parent
713 return out
714
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000715
716class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000717 """Object that represent a gclient checkout. A tree of Dependency(), one per
718 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000719
720 DEPS_OS_CHOICES = {
721 "win32": "win",
722 "win": "win",
723 "cygwin": "win",
724 "darwin": "mac",
725 "mac": "mac",
726 "unix": "unix",
727 "linux": "unix",
728 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000729 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000730 }
731
732 DEFAULT_CLIENT_FILE_TEXT = ("""\
733solutions = [
734 { "name" : "%(solution_name)s",
735 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000736 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000737 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000738 "custom_deps" : {
739 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000740 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000741 },
742]
743""")
744
745 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
746 { "name" : "%(solution_name)s",
747 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000748 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000749 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000750 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000751%(solution_deps)s },
752 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000753 },
754""")
755
756 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
757# Snapshot generated with gclient revinfo --snapshot
758solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000759%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000760""")
761
762 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000763 # Do not change previous behavior. Only solution level and immediate DEPS
764 # are processed.
765 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000766 Dependency.__init__(self, None, None, None, None, True, None, None,
767 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000768 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000769 if options.deps_os:
770 enforced_os = options.deps_os.split(',')
771 else:
772 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
773 if 'all' in enforced_os:
774 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000775 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000776 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000777 self.config_content = None
778
779 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000780 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000781 config_dict = {}
782 self.config_content = content
783 try:
784 exec(content, config_dict)
785 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000786 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000787
788 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000789 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000790 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000791 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000792 self, s['name'], s['url'],
793 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000794 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000795 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000796 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000797 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000798 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000799 except KeyError:
800 raise gclient_utils.Error('Invalid .gclient file. Solution is '
801 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000802 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
803 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000804
805 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000806 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000807 self._options.config_filename),
808 self.config_content)
809
810 @staticmethod
811 def LoadCurrentConfig(options):
812 """Searches for and loads a .gclient file relative to the current working
813 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000814 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000815 if not path:
816 return None
817 client = GClient(path, options)
818 client.SetConfig(gclient_utils.FileRead(
819 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000820 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000821
nsylvain@google.comefc80932011-05-31 21:27:56 +0000822 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000823 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000824 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
825 'solution_name': solution_name,
826 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000827 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000828 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000829 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000830 })
831
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000832 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000833 """Creates a .gclient_entries file to record the list of unique checkouts.
834
835 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000836 """
837 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
838 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000839 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000840 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000841 # Skip over File() dependencies as we can't version them.
842 if not isinstance(entry.parsed_url, self.FileImpl):
843 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
844 pprint.pformat(entry.parsed_url))
845 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000846 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000847 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000848 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000849
850 def _ReadEntries(self):
851 """Read the .gclient_entries file for the given client.
852
853 Returns:
854 A sequence of solution names, which will be empty if there is the
855 entries file hasn't been created yet.
856 """
857 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000858 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000859 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000860 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000861 try:
862 exec(gclient_utils.FileRead(filename), scope)
863 except SyntaxError, e:
864 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000865 return scope['entries']
866
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000867 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000868 """Checks for revision overrides."""
869 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000870 if self._options.head:
871 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000872 # Do not check safesync_url if one or more --revision flag is specified.
873 if not self._options.revisions:
874 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000875 if not s.managed:
876 self._options.revisions.append('%s@unmanaged' % s.name)
877 elif s.safesync_url:
878 handle = urllib.urlopen(s.safesync_url)
879 rev = handle.read().strip()
880 handle.close()
881 if len(rev):
882 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000883 if not self._options.revisions:
884 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000885 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000886 index = 0
887 for revision in self._options.revisions:
888 if not '@' in revision:
889 # Support for --revision 123
890 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000891 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000892 if not sol in solutions_names:
893 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
894 print >> sys.stderr, ('Please fix your script, having invalid '
895 '--revision flags will soon considered an error.')
896 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000897 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000898 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000899 return revision_overrides
900
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000901 def RunOnDeps(self, command, args):
902 """Runs a command on each dependency in a client and its dependencies.
903
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000904 Args:
905 command: The command to use (e.g., 'status' or 'diff')
906 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000907 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000908 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000909 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000910 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000911 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000912 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000913 if (command in ('update', 'revert') and sys.stdout.isatty() and not
914 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000915 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000916 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000917 for s in self.dependencies:
918 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000919 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000920
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000921 # Once all the dependencies have been processed, it's now safe to run the
922 # hooks.
923 if not self._options.nohooks:
924 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925
926 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000927 # Notify the user if there is an orphaned entry in their working copy.
928 # Only delete the directory if there are no changes in it, and
929 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000930 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000931 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000932 if not prev_url:
933 # entry must have been overridden via .gclient custom_deps
934 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000935 # Fix path separator on Windows.
936 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000937 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000938 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000939 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000940 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000941 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000942 scm.status(self._options, [], file_list)
943 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000944 if (not self._options.delete_unversioned_trees or
945 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000946 # There are modified files in this entry. Keep warning until
947 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000948 print(('\nWARNING: \'%s\' is no longer part of this client. '
949 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000950 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000951 else:
952 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000953 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000954 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000955 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000956 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000957 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000958 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959
960 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000961 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000962 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000963 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000964 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000965 for s in self.dependencies:
966 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000967 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000968
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000969 def GetURLAndRev(dep):
970 """Returns the revision-qualified SCM url for a Dependency."""
971 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000972 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000973 if isinstance(dep.parsed_url, self.FileImpl):
974 original_url = dep.parsed_url.file_location
975 else:
976 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000977 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000978 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000979 if not os.path.isdir(scm.checkout_path):
980 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000981 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000983 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000984 new_gclient = ''
985 # First level at .gclient
986 for d in self.dependencies:
987 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000988 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000989 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000990 for d in dep.dependencies:
991 entries[d.name] = GetURLAndRev(d)
992 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000993 GrabDeps(d)
994 custom_deps = []
995 for k in sorted(entries.keys()):
996 if entries[k]:
997 # Quotes aren't escaped...
998 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
999 else:
1000 custom_deps.append(' \"%s\": None,\n' % k)
1001 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1002 'solution_name': d.name,
1003 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001004 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001005 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001006 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001007 'solution_deps': ''.join(custom_deps),
1008 }
1009 # Print the snapshot configuration file
1010 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001011 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001012 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001013 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001014 if self._options.actual:
1015 entries[d.name] = GetURLAndRev(d)
1016 else:
1017 entries[d.name] = d.parsed_url
1018 keys = sorted(entries.keys())
1019 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001020 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001021 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001022
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001023 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001024 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001025 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001026
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001027 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001028 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001029 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001030 return self._root_dir
1031
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001032 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001033 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001034 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001035 return self._enforced_os
1036
maruel@chromium.org68988972011-09-20 14:11:42 +00001037 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001038 def recursion_limit(self):
1039 """How recursive can each dependencies in DEPS file can load DEPS file."""
1040 return self._recursion_limit
1041
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001043#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044
1045
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001046def CMDcleanup(parser, args):
1047 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001048
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001049Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001050"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001051 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1052 help='override deps for the specified (comma-separated) '
1053 'platform(s); \'all\' will process all deps_os '
1054 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001055 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001056 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001058 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001059 if options.verbose:
1060 # Print out the .gclient file. This is longer than if we just printed the
1061 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001062 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063 return client.RunOnDeps('cleanup', args)
1064
1065
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001066@attr('usage', '[command] [args ...]')
1067def CMDrecurse(parser, args):
1068 """Operates on all the entries.
1069
1070 Runs a shell command on all entries.
1071 """
1072 # Stop parsing at the first non-arg so that these go through to the command
1073 parser.disable_interspersed_args()
1074 parser.add_option('-s', '--scm', action='append', default=[],
1075 help='choose scm types to operate upon')
1076 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001077 if not args:
1078 print >> sys.stderr, 'Need to supply a command!'
1079 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001080 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1081 if not root_and_entries:
1082 print >> sys.stderr, (
1083 'You need to run gclient sync at least once to use \'recurse\'.\n'
1084 'This is because .gclient_entries needs to exist and be up to date.')
1085 return 1
1086 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001087 scm_set = set()
1088 for scm in options.scm:
1089 scm_set.update(scm.split(','))
1090
1091 # Pass in the SCM type as an env variable
1092 env = os.environ.copy()
1093
1094 for path, url in entries.iteritems():
1095 scm = gclient_scm.GetScmName(url)
1096 if scm_set and scm not in scm_set:
1097 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001098 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001099 if scm:
1100 env['GCLIENT_SCM'] = scm
1101 if url:
1102 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001103 if os.path.isdir(cwd):
1104 subprocess2.call(args, cwd=cwd, env=env)
1105 else:
1106 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001107 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001108
1109
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110@attr('usage', '[url] [safesync url]')
1111def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001112 """Create a .gclient file in the current directory.
1113
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001114This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001115top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001116modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001117provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001118URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001119"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001120 parser.add_option('--spec',
1121 help='create a gclient file containing the provided '
1122 'string. Due to Cygwin/Python brokenness, it '
1123 'probably can\'t contain any newlines.')
1124 parser.add_option('--name',
1125 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001126 parser.add_option('--deps-file', default='DEPS',
1127 help='overrides the default name for the DEPS file for the'
1128 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001129 parser.add_option('--unmanaged', action='store_true', default=False,
1130 help='overrides the default behavior to make it possible '
1131 'to have the main solution untouched by gclient '
1132 '(gclient will check out unmanaged dependencies but '
1133 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001134 parser.add_option('--git-deps', action='store_true',
1135 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001137 if ((options.spec and args) or len(args) > 2 or
1138 (not options.spec and not args)):
1139 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1140
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001141 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001142 if options.spec:
1143 client.SetConfig(options.spec)
1144 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001145 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001146 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001148 if name.endswith('.git'):
1149 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001150 else:
1151 # specify an alternate relpath for the given URL.
1152 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001153 deps_file = options.deps_file
1154 if options.git_deps:
1155 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001156 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001157 if len(args) > 1:
1158 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001159 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1160 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001162 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001163
1164
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165@attr('epilog', """Example:
1166 gclient pack > patch.txt
1167 generate simple patch for configured client and dependences
1168""")
1169def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001170 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001171
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001172Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001173dependencies, and performs minimal postprocessing of the output. The
1174resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001175checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001176"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001177 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1178 help='override deps for the specified (comma-separated) '
1179 'platform(s); \'all\' will process all deps_os '
1180 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001181 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001182 client = GClient.LoadCurrentConfig(options)
1183 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001184 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001185 if options.verbose:
1186 # Print out the .gclient file. This is longer than if we just printed the
1187 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001188 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001189 return client.RunOnDeps('pack', args)
1190
1191
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001192def CMDstatus(parser, args):
1193 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001194 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1195 help='override deps for the specified (comma-separated) '
1196 'platform(s); \'all\' will process all deps_os '
1197 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001198 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001199 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001201 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202 if options.verbose:
1203 # Print out the .gclient file. This is longer than if we just printed the
1204 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001205 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206 return client.RunOnDeps('status', args)
1207
1208
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001209@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001210 gclient sync
1211 update files from SCM according to current configuration,
1212 *for modules which have changed since last update or sync*
1213 gclient sync --force
1214 update files from SCM according to current configuration, for
1215 all modules (useful for recovering files deleted from local copy)
1216 gclient sync --revision src@31000
1217 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001218""")
1219def CMDsync(parser, args):
1220 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001221 parser.add_option('-f', '--force', action='store_true',
1222 help='force update even for unchanged modules')
1223 parser.add_option('-n', '--nohooks', action='store_true',
1224 help='don\'t run hooks after the update is complete')
1225 parser.add_option('-r', '--revision', action='append',
1226 dest='revisions', metavar='REV', default=[],
1227 help='Enforces revision/hash for the solutions with the '
1228 'format src@rev. The src@ part is optional and can be '
1229 'skipped. -r can be used multiple times when .gclient '
1230 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001231 'if the src@ part is skipped. Note that specifying '
1232 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001233 parser.add_option('-t', '--transitive', action='store_true',
1234 help='When a revision is specified (in the DEPS file or '
1235 'with the command-line flag), transitively update '
1236 'the dependencies to the date of the given revision. '
1237 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001238 parser.add_option('-H', '--head', action='store_true',
1239 help='skips any safesync_urls specified in '
1240 'configured solutions and sync to head instead')
1241 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001242 help='delete any dependency that have been removed from '
1243 'last sync as long as there is no local modification. '
1244 'Coupled with --force, it will remove them even with '
1245 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001246 parser.add_option('-R', '--reset', action='store_true',
1247 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001248 parser.add_option('-M', '--merge', action='store_true',
1249 help='merge upstream changes instead of trying to '
1250 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001251 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1252 help='override deps for the specified (comma-separated) '
1253 'platform(s); \'all\' will process all deps_os '
1254 'references')
1255 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1256 help='Skip svn up whenever possible by requesting '
1257 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001258 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001259 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001260
1261 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001262 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001263
maruel@chromium.org307d1792010-05-31 20:03:13 +00001264 if options.revisions and options.head:
1265 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001266 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001267
1268 if options.verbose:
1269 # Print out the .gclient file. This is longer than if we just printed the
1270 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001271 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272 return client.RunOnDeps('update', args)
1273
1274
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001275def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001276 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001277 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001278
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001279def CMDdiff(parser, args):
1280 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001281 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1282 help='override deps for the specified (comma-separated) '
1283 'platform(s); \'all\' will process all deps_os '
1284 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001285 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001286 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001288 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001289 if options.verbose:
1290 # Print out the .gclient file. This is longer than if we just printed the
1291 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001292 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293 return client.RunOnDeps('diff', args)
1294
1295
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001296def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001297 """Revert all modifications in every dependencies.
1298
1299 That's the nuclear option to get back to a 'clean' state. It removes anything
1300 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001301 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1302 help='override deps for the specified (comma-separated) '
1303 'platform(s); \'all\' will process all deps_os '
1304 'references')
1305 parser.add_option('-n', '--nohooks', action='store_true',
1306 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001307 (options, args) = parser.parse_args(args)
1308 # --force is implied.
1309 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001310 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001311 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001312 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001313 return client.RunOnDeps('revert', args)
1314
1315
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001316def CMDrunhooks(parser, args):
1317 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001318 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1319 help='override deps for the specified (comma-separated) '
1320 'platform(s); \'all\' will process all deps_os '
1321 'references')
1322 parser.add_option('-f', '--force', action='store_true', default=True,
1323 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001324 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001325 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001326 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001327 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001328 if options.verbose:
1329 # Print out the .gclient file. This is longer than if we just printed the
1330 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001331 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001332 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001333 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334 return client.RunOnDeps('runhooks', args)
1335
1336
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001337def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001338 """Output revision info mapping for the client and its dependencies.
1339
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001340 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001341 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001342 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1343 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001344 commit can change.
1345 """
1346 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1347 help='override deps for the specified (comma-separated) '
1348 'platform(s); \'all\' will process all deps_os '
1349 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001350 parser.add_option('-a', '--actual', action='store_true',
1351 help='gets the actual checked out revisions instead of the '
1352 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001353 parser.add_option('-s', '--snapshot', action='store_true',
1354 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001355 'version of all repositories to reproduce the tree, '
1356 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001357 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001358 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001359 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001360 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001362 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001363
1364
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001365def Command(name):
1366 return getattr(sys.modules[__name__], 'CMD' + name, None)
1367
1368
1369def CMDhelp(parser, args):
1370 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001371 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001372 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001373 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001374 parser.print_help()
1375 return 0
1376
1377
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001378def GenUsage(parser, command):
1379 """Modify an OptParse object with the function's documentation."""
1380 obj = Command(command)
1381 if command == 'help':
1382 command = '<command>'
1383 # OptParser.description prefer nicely non-formatted strings.
1384 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1385 usage = getattr(obj, 'usage', '')
1386 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1387 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001388
1389
maruel@chromium.org0895b752011-08-26 20:40:33 +00001390def Parser():
1391 """Returns the default parser."""
1392 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001393 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001394 help='Specify how many SCM commands can run in parallel; '
1395 'default=%default')
1396 parser.add_option('-v', '--verbose', action='count', default=0,
1397 help='Produces additional output for diagnostics. Can be '
1398 'used up to three times for more logging info.')
1399 parser.add_option('--gclientfile', dest='config_filename',
1400 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1401 help='Specify an alternate %default file')
1402 # Integrate standard options processing.
1403 old_parser = parser.parse_args
1404 def Parse(args):
1405 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001406 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1407 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001408 logging.basicConfig(level=level,
1409 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1410 options.entries_filename = options.config_filename + '_entries'
1411 if options.jobs < 1:
1412 parser.error('--jobs must be 1 or higher')
1413
1414 # These hacks need to die.
1415 if not hasattr(options, 'revisions'):
1416 # GClient.RunOnDeps expects it even if not applicable.
1417 options.revisions = []
1418 if not hasattr(options, 'head'):
1419 options.head = None
1420 if not hasattr(options, 'nohooks'):
1421 options.nohooks = True
1422 if not hasattr(options, 'deps_os'):
1423 options.deps_os = None
1424 if not hasattr(options, 'manually_grab_svn_rev'):
1425 options.manually_grab_svn_rev = None
1426 if not hasattr(options, 'force'):
1427 options.force = None
1428 return (options, args)
1429 parser.parse_args = Parse
1430 # We don't want wordwrapping in epilog (usually examples)
1431 parser.format_epilog = lambda _: parser.epilog or ''
1432 return parser
1433
1434
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001435def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001436 """Doesn't parse the arguments here, just find the right subcommand to
1437 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001438 if sys.hexversion < 0x02050000:
1439 print >> sys.stderr, (
1440 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001441 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001442 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1443 # operations. Python as a strong tendency to buffer sys.stdout.
1444 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001445 # Make stdout annotated with the thread ids.
1446 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001447 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001448 # Unused variable 'usage'
1449 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001450 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1451 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1452 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001453 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001454 if argv:
1455 command = Command(argv[0])
1456 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001457 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001458 GenUsage(parser, argv[0])
1459 return command(parser, argv[1:])
1460 # Not a known command. Default to help.
1461 GenUsage(parser, 'help')
1462 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001463 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001464 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001465 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001466
1467
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001468if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001469 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001470 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001471
1472# vim: ts=2:sw=2:tw=80:et: