blob: 671250c7eae55dbf3e4db2537b79644b9ab29f99 [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):
182 """Immutable so no need to lock."""
183 return self._deps_file
184
185 @property
186 def managed(self):
187 """Immutable so no need to lock."""
188 return self._managed
189
190 @property
191 def parent(self):
192 """Immutable so no need to lock."""
193 return self._parent
194
195 @property
196 def safesync_url(self):
197 """Immutable so no need to lock."""
198 return self._safesync_url
199
200 @property
201 def should_process(self):
202 """True if this dependency should be processed, i.e. checked out."""
203 return self._should_process
204
205 @property
206 def custom_vars(self):
207 """Immutable so no need to lock."""
208 return self._custom_vars.copy()
209
210 @property
211 def custom_deps(self):
212 """Immutable so no need to lock."""
213 return self._custom_deps.copy()
214
maruel@chromium.org064186c2011-09-27 23:53:33 +0000215 @property
216 def url(self):
217 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218
maruel@chromium.org064186c2011-09-27 23:53:33 +0000219
220class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000221 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000222
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000223 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000224 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000225 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000227 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000228 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000229
230 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000231 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000232
233 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000234 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000235 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000236 # A cache of the files affected by the current operation, necessary for
237 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000238 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000239 # If it is not set to True, the dependency wasn't processed for its child
240 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000242 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000243 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000244 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000246
maruel@chromium.org064186c2011-09-27 23:53:33 +0000247 # Setup self.requirements and find any other dependency who would have self
248 # as a requirement.
maruel@chromium.org98023df2011-09-07 18:44:47 +0000249
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000250 # self.parent is implicitly a requirement. This will be recursive by
251 # definition.
252 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000253 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000254
255 # For a tree with at least 2 levels*, the leaf node needs to depend
256 # on the level higher up in an orderly way.
257 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
258 # thus unsorted, while the .gclient format is a list thus sorted.
259 #
260 # * _recursion_limit is hard coded 2 and there is no hope to change this
261 # value.
262 #
263 # Interestingly enough, the following condition only works in the case we
264 # want: self is a 2nd level node. 3nd level node wouldn't need this since
265 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000266 root_deps = self.root.dependencies
267 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000268 for i in root_deps:
269 if i is self.parent:
270 break
271 if i.name:
272 self._requirements.add(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000273
274 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000275 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000276
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000277 if self.name and self.should_process:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000278 for obj in self.root.depth_first_tree():
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000279 if obj is self or not obj.name:
280 continue
281 # Step 1: Find any requirements self may need.
282 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000283 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000284 # Step 2: Find any requirements self may impose.
285 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000286 try:
287 # Access to a protected member _requirements of a client class
288 # pylint: disable=W0212
289 obj.lock.acquire()
290 obj._requirements.add(self.name)
291 finally:
292 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000293
maruel@chromium.org064186c2011-09-27 23:53:33 +0000294 if not self.name and self.parent:
295 raise gclient_utils.Error('Dependency without name')
296
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000297 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000298 """Resolves the parsed url from url.
299
300 Manages From() keyword accordingly. Do not touch self.parsed_url nor
301 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000302 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000303 overriden_url = self.get_custom_deps(self.name, url)
304 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000305 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000306 overriden_url))
307 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000308 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000309 ref = [
310 dep for dep in self.root.subtree(True) if url.module_name == dep.name
311 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000312 if not ref:
313 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
314 url.module_name, ref))
315 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000316 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000317 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000318 # Make sure the referenced dependency DEPS file is loaded and file the
319 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000320 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000321 found_dep = None
322 for d in ref.dependencies:
323 if d.name == sub_target:
324 found_dep = d
325 break
326 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000327 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000328 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
329 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000330 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000331
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000332 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000333 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000334 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000335 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000336 elif isinstance(url, basestring):
337 parsed_url = urlparse.urlparse(url)
338 if not parsed_url[0]:
339 # A relative url. Fetch the real base.
340 path = parsed_url[2]
341 if not path.startswith('/'):
342 raise gclient_utils.Error(
343 'relative DEPS entry \'%s\' must begin with a slash' % url)
344 # Create a scm just to query the full url.
345 parent_url = self.parent.parsed_url
346 if isinstance(parent_url, self.FileImpl):
347 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000348 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000349 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000350 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000351 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000352 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000353 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000354 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000355 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000356 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000357 return parsed_url
358 elif url is None:
359 return None
360 else:
361 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000363 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000364 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000365 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000366 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000367 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000369 # One thing is unintuitive, vars= {} must happen before Var() use.
370 local_scope = {}
371 var = self.VarImpl(self.custom_vars, local_scope)
372 global_scope = {
373 'File': self.FileImpl,
374 'From': self.FromImpl,
375 'Var': var.Lookup,
376 'deps_os': {},
377 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000378 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000379 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000380 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
381 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000382 else:
383 deps_content = gclient_utils.FileRead(filepath)
384 logging.debug(deps_content)
385 # Eval the content.
386 try:
387 exec(deps_content, global_scope, local_scope)
388 except SyntaxError, e:
389 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000390 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000391 # load os specific dependencies if defined. these dependencies may
392 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000393 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000394 enforced_os = self.root.enforced_os
395 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000396 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000397 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000398 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000399 # platform, so we collect the broadest set of dependencies
400 # available. We may end up with the wrong revision of something for
401 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000402 deps.update([x for x in os_deps.items() if not x[0] in deps])
403 else:
404 deps.update(os_deps)
405
maruel@chromium.org064186c2011-09-27 23:53:33 +0000406 self._deps_hooks.extend(local_scope.get('hooks', []))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000407
408 # If a line is in custom_deps, but not in the solution, we want to append
409 # this line to the solution.
410 for d in self.custom_deps:
411 if d not in deps:
412 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000413
414 # If use_relative_paths is set in the DEPS file, regenerate
415 # the dictionary using paths relative to the directory containing
416 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000417 use_relative_paths = local_scope.get('use_relative_paths', False)
418 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000419 rel_deps = {}
420 for d, url in deps.items():
421 # normpath is required to allow DEPS to use .. in their
422 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000423 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
424 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000425
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000426 # Convert the deps into real Dependency.
427 for name, url in deps.iteritems():
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000428 if name in [s.name for s in self._dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000429 raise gclient_utils.Error(
430 'The same name "%s" appears multiple times in the deps section' %
431 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000432 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000433 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000434 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000435 if name in tree:
436 if url == tree[name].url:
437 logging.info('Won\'t process duplicate dependency %s' % tree[name])
438 # In theory we could keep it as a shadow of the other one. In
439 # practice, simply ignore it.
440 #should_process = False
441 continue
442 else:
443 raise gclient_utils.Error(
444 'Dependency %s specified more than once:\n %s\nvs\n %s' %
445 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000446 self._dependencies.append(
447 Dependency(
448 self, name, url, None, None, None, None,
449 self.deps_file, should_process))
maruel@chromium.org064186c2011-09-27 23:53:33 +0000450 self._deps_parsed = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000451 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000452
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000453 # Arguments number differs from overridden method
454 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000455 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 """Runs 'command' before parsing the DEPS in case it's a initial checkout
457 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000458
459 def maybeGetParentRevision(options):
460 """If we are performing an update and --transitive is set, set the
461 revision to the parent's revision. If we have an explicit revision
462 do nothing."""
463 if command == 'update' and options.transitive and not options.revision:
464 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
465 if not revision:
466 options.revision = revision_overrides.get(self.parent.name)
467 if options.verbose and options.revision:
468 print("Using parent's revision date: %s" % options.revision)
469 # If the parent has a revision override, then it must have been
470 # converted to date format.
471 assert (not options.revision or
472 gclient_utils.IsDateRevision(options.revision))
473
474 def maybeConvertToDateRevision(options):
475 """If we are performing an update and --transitive is set, convert the
476 revision to a date-revision (if necessary). Instead of having
477 -r 101 replace the revision with the time stamp of 101 (e.g.
478 "{2011-18-04}").
479 This way dependencies are upgraded to the revision they had at the
480 check-in of revision 101."""
481 if (command == 'update' and
482 options.transitive and
483 options.revision and
484 not gclient_utils.IsDateRevision(options.revision)):
485 revision_date = scm.GetRevisionDate(options.revision)
486 revision = gclient_utils.MakeDateRevision(revision_date)
487 if options.verbose:
488 print("Updating revision override from %s to %s." %
489 (options.revision, revision))
490 revision_overrides[self.name] = revision
491
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000492 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000493 if not self.should_process:
494 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000495 # When running runhooks, there's no need to consult the SCM.
496 # All known hooks are expected to run unconditionally regardless of working
497 # copy state, so skip the SCM status check.
498 run_scm = command not in ('runhooks', None)
maruel@chromium.org064186c2011-09-27 23:53:33 +0000499 self._parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000500 if run_scm and self.parsed_url:
501 if isinstance(self.parsed_url, self.FileImpl):
502 # Special support for single-file checkout.
503 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
504 options.revision = self.parsed_url.GetRevision()
505 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000506 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507 self.name)
508 scm.RunCommand('updatesingle', options,
509 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000510 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000511 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000512 # Create a shallow copy to mutate revision.
513 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000515 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000516 scm = gclient_scm.CreateSCM(
517 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000518 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000519 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000520 self._file_list = [os.path.join(self.name, f.strip())
521 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000522
523 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
524 # Convert all absolute paths to relative.
525 for i in range(len(self._file_list)):
526 # It depends on the command being executed (like runhooks vs sync).
527 if not os.path.isabs(self._file_list[i]):
528 continue
529 prefix = os.path.commonprefix(
530 [self.root.root_dir.lower(), self._file_list[i].lower()])
531 self._file_list[i] = self._file_list[i][len(prefix):]
532 # Strip any leading path separators.
533 while (self._file_list[i].startswith('\\') or
534 self._file_list[i].startswith('/')):
535 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.org064186c2011-09-27 23:53:33 +0000536 self._processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000537 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000538 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000539 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000540
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000541 # Parse the dependencies of this dependency.
542 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000543 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000544
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000545 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000546 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000547 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000548 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000549 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000550 # Don't run the hook when it is above recursion_limit.
551 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000552 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000553 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000554 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000555 # TODO(maruel): If the user is using git or git-svn, then we don't know
556 # what files have changed so we always run all hooks. It'd be nice to fix
557 # that.
558 if (options.force or
559 isinstance(self.parsed_url, self.FileImpl) or
560 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000561 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000562 for hook_dict in self.deps_hooks:
563 self._RunHookAction(hook_dict, [])
564 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000565 # Run hooks on the basis of whether the files from the gclient operation
566 # match each hook's pattern.
567 for hook_dict in self.deps_hooks:
568 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000569 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000570 if matching_file_list:
571 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000572 for s in self.dependencies:
573 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000574
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000575 def _RunHookAction(self, hook_dict, matching_file_list):
576 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000577 # A single DEPS file can specify multiple hooks so this function can be
578 # called multiple times on a single Dependency.
579 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000580 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000581 logging.debug(hook_dict)
582 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000583 command = hook_dict['action'][:]
584 if command[0] == 'python':
585 # If the hook specified "python" as the first item, the action is a
586 # Python script. Run it by starting a new copy of the same
587 # interpreter.
588 command[0] = sys.executable
589
590 if '$matching_files' in command:
591 splice_index = command.index('$matching_files')
592 command[splice_index:splice_index + 1] = matching_file_list
593
maruel@chromium.org17d01792010-09-01 18:07:10 +0000594 try:
595 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000596 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000597 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000598 # Use a discrete exit status code of 2 to indicate that a hook action
599 # failed. Users of this script may wish to treat hook action failures
600 # differently from VC failures.
601 print >> sys.stderr, 'Error: %s' % str(e)
602 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000603
maruel@chromium.org0d812442010-08-10 12:41:08 +0000604 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000605 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000606 dependencies = self.dependencies
607 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000608 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000609 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000610 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000611 for i in d.subtree(include_all):
612 yield i
613
614 def depth_first_tree(self):
615 """Depth-first recursion including the root node."""
616 yield self
617 for i in self.dependencies:
618 for j in i.depth_first_tree():
619 if j.should_process:
620 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000621
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000622 def get_custom_deps(self, name, url):
623 """Returns a custom deps if applicable."""
624 if self.parent:
625 url = self.parent.get_custom_deps(name, url)
626 # None is a valid return value to disable a dependency.
627 return self.custom_deps.get(name, url)
628
maruel@chromium.org68988972011-09-20 14:11:42 +0000629 @property
630 def recursion_limit(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000631 """Returns > 0 if this dependency is not too recursed to be processed.
632
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000633 Immutable so no need to lock.
634 """
maruel@chromium.org68988972011-09-20 14:11:42 +0000635 return max(self.parent.recursion_limit - 1, 0)
636
637 @property
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000638 def dependencies(self):
639 return tuple(self._dependencies)
640
641 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000642 def deps_hooks(self):
643 return tuple(self._deps_hooks)
644
645 @property
646 def parsed_url(self):
647 return self._parsed_url
648
649 @property
650 def deps_parsed(self):
651 return self._deps_parsed
652
653 @property
654 def processed(self):
655 return self._processed
656
657 @property
658 def hooks_ran(self):
659 return self._hooks_ran
660
661 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000662 def file_list(self):
663 result = self._file_list[:]
664 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000665 result.extend(d.file_list)
666 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000667
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000668 def __str__(self):
669 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000670 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000671 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000672 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000673 # First try the native property if it exists.
674 if hasattr(self, '_' + i):
675 value = getattr(self, '_' + i, False)
676 else:
677 value = getattr(self, i, False)
678 if value:
679 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000680
681 for d in self.dependencies:
682 out.extend([' ' + x for x in str(d).splitlines()])
683 out.append('')
684 return '\n'.join(out)
685
686 def __repr__(self):
687 return '%s: %s' % (self.name, self.url)
688
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000689 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000690 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000691 out = '%s(%s)' % (self.name, self.url)
692 i = self.parent
693 while i and i.name:
694 out = '%s(%s) -> %s' % (i.name, i.url, out)
695 i = i.parent
696 return out
697
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000698 @property
699 def root(self):
700 """Returns the root node, a GClient object."""
701 if not self.parent:
702 # This line is to signal pylint that it could be a GClient instance.
703 return self or GClient(None, None)
704 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000705
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000706
707class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000708 """Object that represent a gclient checkout. A tree of Dependency(), one per
709 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000710
711 DEPS_OS_CHOICES = {
712 "win32": "win",
713 "win": "win",
714 "cygwin": "win",
715 "darwin": "mac",
716 "mac": "mac",
717 "unix": "unix",
718 "linux": "unix",
719 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000720 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000721 }
722
723 DEFAULT_CLIENT_FILE_TEXT = ("""\
724solutions = [
725 { "name" : "%(solution_name)s",
726 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000727 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000728 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000729 "custom_deps" : {
730 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000731 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000732 },
733]
734""")
735
736 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
737 { "name" : "%(solution_name)s",
738 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000739 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000740 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000741 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000742%(solution_deps)s },
743 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000744 },
745""")
746
747 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
748# Snapshot generated with gclient revinfo --snapshot
749solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000750%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000751""")
752
753 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000754 # Do not change previous behavior. Only solution level and immediate DEPS
755 # are processed.
756 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000757 Dependency.__init__(self, None, None, None, None, True, None, None,
758 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000759 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000760 if options.deps_os:
761 enforced_os = options.deps_os.split(',')
762 else:
763 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
764 if 'all' in enforced_os:
765 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000766 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000767 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768 self.config_content = None
769
770 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000771 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000772 config_dict = {}
773 self.config_content = content
774 try:
775 exec(content, config_dict)
776 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000777 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000778 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000779 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000780 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000781 if s['name'] in tree:
782 raise gclient_utils.Error(
783 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000784 self._dependencies.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000785 self, s['name'], s['url'],
786 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000787 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000788 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000789 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000790 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000791 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000792 except KeyError:
793 raise gclient_utils.Error('Invalid .gclient file. Solution is '
794 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000795 # .gclient can have hooks.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000796 self._deps_hooks = config_dict.get('hooks', [])
797 self._deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000798
799 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000800 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000801 self._options.config_filename),
802 self.config_content)
803
804 @staticmethod
805 def LoadCurrentConfig(options):
806 """Searches for and loads a .gclient file relative to the current working
807 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000808 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000809 if not path:
810 return None
811 client = GClient(path, options)
812 client.SetConfig(gclient_utils.FileRead(
813 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000814 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000815
nsylvain@google.comefc80932011-05-31 21:27:56 +0000816 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000817 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000818 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
819 'solution_name': solution_name,
820 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000821 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000822 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000823 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000824 })
825
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000826 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000827 """Creates a .gclient_entries file to record the list of unique checkouts.
828
829 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000830 """
831 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
832 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000833 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000834 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000835 # Skip over File() dependencies as we can't version them.
836 if not isinstance(entry.parsed_url, self.FileImpl):
837 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
838 pprint.pformat(entry.parsed_url))
839 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000840 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000841 logging.info(result)
842 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000843
844 def _ReadEntries(self):
845 """Read the .gclient_entries file for the given client.
846
847 Returns:
848 A sequence of solution names, which will be empty if there is the
849 entries file hasn't been created yet.
850 """
851 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000852 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000853 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000854 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000855 try:
856 exec(gclient_utils.FileRead(filename), scope)
857 except SyntaxError, e:
858 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000859 return scope['entries']
860
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000861 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000862 """Checks for revision overrides."""
863 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000864 if self._options.head:
865 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000866 # Do not check safesync_url if one or more --revision flag is specified.
867 if not self._options.revisions:
868 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000869 if not s.managed:
870 self._options.revisions.append('%s@unmanaged' % s.name)
871 elif s.safesync_url:
872 handle = urllib.urlopen(s.safesync_url)
873 rev = handle.read().strip()
874 handle.close()
875 if len(rev):
876 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000877 if not self._options.revisions:
878 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000879 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000880 index = 0
881 for revision in self._options.revisions:
882 if not '@' in revision:
883 # Support for --revision 123
884 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000885 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000886 if not sol in solutions_names:
887 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
888 print >> sys.stderr, ('Please fix your script, having invalid '
889 '--revision flags will soon considered an error.')
890 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000891 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000892 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000893 return revision_overrides
894
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000895 def RunOnDeps(self, command, args):
896 """Runs a command on each dependency in a client and its dependencies.
897
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000898 Args:
899 command: The command to use (e.g., 'status' or 'diff')
900 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000901 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000902 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000903 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000904 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000906 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000907 if (command in ('update', 'revert') and sys.stdout.isatty() and not
908 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000909 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000910 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000911 for s in self.dependencies:
912 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000913 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000914
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000915 # Once all the dependencies have been processed, it's now safe to run the
916 # hooks.
917 if not self._options.nohooks:
918 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000919
920 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000921 # Notify the user if there is an orphaned entry in their working copy.
922 # Only delete the directory if there are no changes in it, and
923 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000924 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000925 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000926 if not prev_url:
927 # entry must have been overridden via .gclient custom_deps
928 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000929 # Fix path separator on Windows.
930 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000931 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000932 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000933 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000934 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000935 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000936 scm.status(self._options, [], file_list)
937 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000938 if (not self._options.delete_unversioned_trees or
939 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000940 # There are modified files in this entry. Keep warning until
941 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000942 print(('\nWARNING: \'%s\' is no longer part of this client. '
943 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000944 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000945 else:
946 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000947 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000948 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000949 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000951 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000952 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000953
954 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000955 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000956 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000957 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000958 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000959 for s in self.dependencies:
960 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000961 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000962
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000963 def GetURLAndRev(dep):
964 """Returns the revision-qualified SCM url for a Dependency."""
965 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000966 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000967 if isinstance(dep.parsed_url, self.FileImpl):
968 original_url = dep.parsed_url.file_location
969 else:
970 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000971 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000972 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 if not os.path.isdir(scm.checkout_path):
974 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000975 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000977 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000978 new_gclient = ''
979 # First level at .gclient
980 for d in self.dependencies:
981 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000982 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000984 for d in dep.dependencies:
985 entries[d.name] = GetURLAndRev(d)
986 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000987 GrabDeps(d)
988 custom_deps = []
989 for k in sorted(entries.keys()):
990 if entries[k]:
991 # Quotes aren't escaped...
992 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
993 else:
994 custom_deps.append(' \"%s\": None,\n' % k)
995 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
996 'solution_name': d.name,
997 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000998 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000999 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001000 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001001 'solution_deps': ''.join(custom_deps),
1002 }
1003 # Print the snapshot configuration file
1004 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001005 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001006 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001007 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001008 if self._options.actual:
1009 entries[d.name] = GetURLAndRev(d)
1010 else:
1011 entries[d.name] = d.parsed_url
1012 keys = sorted(entries.keys())
1013 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001014 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001015 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001016
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001017 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001018 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001019 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001020
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001021 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001022 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001023 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001024 return self._root_dir
1025
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001026 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001027 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001028 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001029 return self._enforced_os
1030
maruel@chromium.org68988972011-09-20 14:11:42 +00001031 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001032 def recursion_limit(self):
1033 """How recursive can each dependencies in DEPS file can load DEPS file."""
1034 return self._recursion_limit
1035
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001036
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001037#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038
1039
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001040def CMDcleanup(parser, args):
1041 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001042
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001043Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001044"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001045 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1046 help='override deps for the specified (comma-separated) '
1047 'platform(s); \'all\' will process all deps_os '
1048 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001049 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001050 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001052 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 if options.verbose:
1054 # Print out the .gclient file. This is longer than if we just printed the
1055 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001056 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057 return client.RunOnDeps('cleanup', args)
1058
1059
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001060@attr('usage', '[command] [args ...]')
1061def CMDrecurse(parser, args):
1062 """Operates on all the entries.
1063
1064 Runs a shell command on all entries.
1065 """
1066 # Stop parsing at the first non-arg so that these go through to the command
1067 parser.disable_interspersed_args()
1068 parser.add_option('-s', '--scm', action='append', default=[],
1069 help='choose scm types to operate upon')
1070 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001071 if not args:
1072 print >> sys.stderr, 'Need to supply a command!'
1073 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001074 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1075 if not root_and_entries:
1076 print >> sys.stderr, (
1077 'You need to run gclient sync at least once to use \'recurse\'.\n'
1078 'This is because .gclient_entries needs to exist and be up to date.')
1079 return 1
1080 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001081 scm_set = set()
1082 for scm in options.scm:
1083 scm_set.update(scm.split(','))
1084
1085 # Pass in the SCM type as an env variable
1086 env = os.environ.copy()
1087
1088 for path, url in entries.iteritems():
1089 scm = gclient_scm.GetScmName(url)
1090 if scm_set and scm not in scm_set:
1091 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001092 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001093 if scm:
1094 env['GCLIENT_SCM'] = scm
1095 if url:
1096 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001097 if os.path.isdir(cwd):
1098 subprocess2.call(args, cwd=cwd, env=env)
1099 else:
1100 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001101 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001102
1103
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001104@attr('usage', '[url] [safesync url]')
1105def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001106 """Create a .gclient file in the current directory.
1107
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001108This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001109top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001111provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001112URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001113"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001114 parser.add_option('--spec',
1115 help='create a gclient file containing the provided '
1116 'string. Due to Cygwin/Python brokenness, it '
1117 'probably can\'t contain any newlines.')
1118 parser.add_option('--name',
1119 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001120 parser.add_option('--deps-file', default='DEPS',
1121 help='overrides the default name for the DEPS file for the'
1122 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001123 parser.add_option('--unmanaged', action='store_true', default=False,
1124 help='overrides the default behavior to make it possible '
1125 'to have the main solution untouched by gclient '
1126 '(gclient will check out unmanaged dependencies but '
1127 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001128 parser.add_option('--git-deps', action='store_true',
1129 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001130 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001131 if ((options.spec and args) or len(args) > 2 or
1132 (not options.spec and not args)):
1133 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1134
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001135 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136 if options.spec:
1137 client.SetConfig(options.spec)
1138 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001139 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001140 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001141 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001142 if name.endswith('.git'):
1143 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001144 else:
1145 # specify an alternate relpath for the given URL.
1146 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001147 deps_file = options.deps_file
1148 if options.git_deps:
1149 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001150 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151 if len(args) > 1:
1152 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001153 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1154 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001155 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001156 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001157
1158
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159@attr('epilog', """Example:
1160 gclient pack > patch.txt
1161 generate simple patch for configured client and dependences
1162""")
1163def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001164 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001165
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001167dependencies, and performs minimal postprocessing of the output. The
1168resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001169checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001170"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001171 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1172 help='override deps for the specified (comma-separated) '
1173 'platform(s); \'all\' will process all deps_os '
1174 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001175 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001176 client = GClient.LoadCurrentConfig(options)
1177 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001178 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001179 if options.verbose:
1180 # Print out the .gclient file. This is longer than if we just printed the
1181 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001182 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001183 return client.RunOnDeps('pack', args)
1184
1185
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001186def CMDstatus(parser, args):
1187 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001188 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1189 help='override deps for the specified (comma-separated) '
1190 'platform(s); \'all\' will process all deps_os '
1191 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001192 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001193 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001194 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001195 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001196 if options.verbose:
1197 # Print out the .gclient file. This is longer than if we just printed the
1198 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001199 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 return client.RunOnDeps('status', args)
1201
1202
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001204 gclient sync
1205 update files from SCM according to current configuration,
1206 *for modules which have changed since last update or sync*
1207 gclient sync --force
1208 update files from SCM according to current configuration, for
1209 all modules (useful for recovering files deleted from local copy)
1210 gclient sync --revision src@31000
1211 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001212""")
1213def CMDsync(parser, args):
1214 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001215 parser.add_option('-f', '--force', action='store_true',
1216 help='force update even for unchanged modules')
1217 parser.add_option('-n', '--nohooks', action='store_true',
1218 help='don\'t run hooks after the update is complete')
1219 parser.add_option('-r', '--revision', action='append',
1220 dest='revisions', metavar='REV', default=[],
1221 help='Enforces revision/hash for the solutions with the '
1222 'format src@rev. The src@ part is optional and can be '
1223 'skipped. -r can be used multiple times when .gclient '
1224 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001225 'if the src@ part is skipped. Note that specifying '
1226 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001227 parser.add_option('-t', '--transitive', action='store_true',
1228 help='When a revision is specified (in the DEPS file or '
1229 'with the command-line flag), transitively update '
1230 'the dependencies to the date of the given revision. '
1231 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001232 parser.add_option('-H', '--head', action='store_true',
1233 help='skips any safesync_urls specified in '
1234 'configured solutions and sync to head instead')
1235 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001236 help='delete any dependency that have been removed from '
1237 'last sync as long as there is no local modification. '
1238 'Coupled with --force, it will remove them even with '
1239 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001240 parser.add_option('-R', '--reset', action='store_true',
1241 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001242 parser.add_option('-M', '--merge', action='store_true',
1243 help='merge upstream changes instead of trying to '
1244 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001245 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1246 help='override deps for the specified (comma-separated) '
1247 'platform(s); \'all\' will process all deps_os '
1248 'references')
1249 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1250 help='Skip svn up whenever possible by requesting '
1251 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001252 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001253 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001254
1255 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001256 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001257
maruel@chromium.org307d1792010-05-31 20:03:13 +00001258 if options.revisions and options.head:
1259 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001260 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001261
1262 if options.verbose:
1263 # Print out the .gclient file. This is longer than if we just printed the
1264 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001265 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001266 return client.RunOnDeps('update', args)
1267
1268
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001269def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001270 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001271 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001273def CMDdiff(parser, args):
1274 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001275 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1276 help='override deps for the specified (comma-separated) '
1277 'platform(s); \'all\' will process all deps_os '
1278 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001279 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001280 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001281 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001282 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001283 if options.verbose:
1284 # Print out the .gclient file. This is longer than if we just printed the
1285 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001286 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287 return client.RunOnDeps('diff', args)
1288
1289
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001290def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001291 """Revert all modifications in every dependencies.
1292
1293 That's the nuclear option to get back to a 'clean' state. It removes anything
1294 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001295 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1296 help='override deps for the specified (comma-separated) '
1297 'platform(s); \'all\' will process all deps_os '
1298 'references')
1299 parser.add_option('-n', '--nohooks', action='store_true',
1300 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001301 (options, args) = parser.parse_args(args)
1302 # --force is implied.
1303 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001304 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001305 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001306 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001307 return client.RunOnDeps('revert', args)
1308
1309
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001310def CMDrunhooks(parser, args):
1311 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001312 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1313 help='override deps for the specified (comma-separated) '
1314 'platform(s); \'all\' will process all deps_os '
1315 'references')
1316 parser.add_option('-f', '--force', action='store_true', default=True,
1317 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001318 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001319 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001320 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001321 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001322 if options.verbose:
1323 # Print out the .gclient file. This is longer than if we just printed the
1324 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001325 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001326 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001327 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001328 return client.RunOnDeps('runhooks', args)
1329
1330
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001331def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001332 """Output revision info mapping for the client and its dependencies.
1333
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001334 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001335 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001336 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1337 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001338 commit can change.
1339 """
1340 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1341 help='override deps for the specified (comma-separated) '
1342 'platform(s); \'all\' will process all deps_os '
1343 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001344 parser.add_option('-a', '--actual', action='store_true',
1345 help='gets the actual checked out revisions instead of the '
1346 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001347 parser.add_option('-s', '--snapshot', action='store_true',
1348 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001349 'version of all repositories to reproduce the tree, '
1350 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001351 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001352 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001353 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001354 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001355 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001356 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001357
1358
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001359def Command(name):
1360 return getattr(sys.modules[__name__], 'CMD' + name, None)
1361
1362
1363def CMDhelp(parser, args):
1364 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001365 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001366 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001367 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001368 parser.print_help()
1369 return 0
1370
1371
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001372def GenUsage(parser, command):
1373 """Modify an OptParse object with the function's documentation."""
1374 obj = Command(command)
1375 if command == 'help':
1376 command = '<command>'
1377 # OptParser.description prefer nicely non-formatted strings.
1378 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1379 usage = getattr(obj, 'usage', '')
1380 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1381 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001382
1383
maruel@chromium.org0895b752011-08-26 20:40:33 +00001384def Parser():
1385 """Returns the default parser."""
1386 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001387 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001388 help='Specify how many SCM commands can run in parallel; '
1389 'default=%default')
1390 parser.add_option('-v', '--verbose', action='count', default=0,
1391 help='Produces additional output for diagnostics. Can be '
1392 'used up to three times for more logging info.')
1393 parser.add_option('--gclientfile', dest='config_filename',
1394 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1395 help='Specify an alternate %default file')
1396 # Integrate standard options processing.
1397 old_parser = parser.parse_args
1398 def Parse(args):
1399 (options, args) = old_parser(args)
1400 level = None
1401 if options.verbose == 2:
1402 level = logging.INFO
1403 elif options.verbose > 2:
1404 level = logging.DEBUG
1405 logging.basicConfig(level=level,
1406 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1407 options.entries_filename = options.config_filename + '_entries'
1408 if options.jobs < 1:
1409 parser.error('--jobs must be 1 or higher')
1410
1411 # These hacks need to die.
1412 if not hasattr(options, 'revisions'):
1413 # GClient.RunOnDeps expects it even if not applicable.
1414 options.revisions = []
1415 if not hasattr(options, 'head'):
1416 options.head = None
1417 if not hasattr(options, 'nohooks'):
1418 options.nohooks = True
1419 if not hasattr(options, 'deps_os'):
1420 options.deps_os = None
1421 if not hasattr(options, 'manually_grab_svn_rev'):
1422 options.manually_grab_svn_rev = None
1423 if not hasattr(options, 'force'):
1424 options.force = None
1425 return (options, args)
1426 parser.parse_args = Parse
1427 # We don't want wordwrapping in epilog (usually examples)
1428 parser.format_epilog = lambda _: parser.epilog or ''
1429 return parser
1430
1431
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001432def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001433 """Doesn't parse the arguments here, just find the right subcommand to
1434 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001435 if sys.hexversion < 0x02050000:
1436 print >> sys.stderr, (
1437 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001438 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001439 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1440 # operations. Python as a strong tendency to buffer sys.stdout.
1441 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001442 # Make stdout annotated with the thread ids.
1443 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001444 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001445 # Unused variable 'usage'
1446 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001447 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1448 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1449 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001450 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001451 if argv:
1452 command = Command(argv[0])
1453 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001454 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001455 GenUsage(parser, argv[0])
1456 return command(parser, argv[1:])
1457 # Not a known command. Default to help.
1458 GenUsage(parser, 'help')
1459 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001460 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001461 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001462 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001463
1464
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001465if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001466 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001467 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001468
1469# vim: ts=2:sw=2:tw=80:et: