blob: a53626ba6d3384c729a08790645ee1c4acd13054 [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.org118fb1c2011-09-01 20:04:24 +0000278 def yield_full_tree(root):
279 """Depth-first recursion."""
280 yield root
281 for i in root.dependencies:
282 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000283 if j.should_process:
284 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000285
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000286 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287 if obj is self or not obj.name:
288 continue
289 # Step 1: Find any requirements self may need.
290 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000291 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000292 # Step 2: Find any requirements self may impose.
293 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000294 try:
295 # Access to a protected member _requirements of a client class
296 # pylint: disable=W0212
297 obj.lock.acquire()
298 obj._requirements.add(self.name)
299 finally:
300 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000301
maruel@chromium.org064186c2011-09-27 23:53:33 +0000302 if not self.name and self.parent:
303 raise gclient_utils.Error('Dependency without name')
304
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000305 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000306 """Resolves the parsed url from url.
307
308 Manages From() keyword accordingly. Do not touch self.parsed_url nor
309 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000310 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000311 overriden_url = self.get_custom_deps(self.name, url)
312 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000313 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000314 overriden_url))
315 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000316 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000317 ref = [
318 dep for dep in self.root.subtree(True) if url.module_name == dep.name
319 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000320 if not ref:
321 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
322 url.module_name, ref))
323 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000324 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000325 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000326 # Make sure the referenced dependency DEPS file is loaded and file the
327 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000328 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000329 found_dep = None
330 for d in ref.dependencies:
331 if d.name == sub_target:
332 found_dep = d
333 break
334 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000335 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000336 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
337 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000338 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000339
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000340 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000341 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000342 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000343 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000344 elif isinstance(url, basestring):
345 parsed_url = urlparse.urlparse(url)
346 if not parsed_url[0]:
347 # A relative url. Fetch the real base.
348 path = parsed_url[2]
349 if not path.startswith('/'):
350 raise gclient_utils.Error(
351 'relative DEPS entry \'%s\' must begin with a slash' % url)
352 # Create a scm just to query the full url.
353 parent_url = self.parent.parsed_url
354 if isinstance(parent_url, self.FileImpl):
355 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000356 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000357 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000358 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000359 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000360 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000361 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000363 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000364 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 return parsed_url
366 elif url is None:
367 return None
368 else:
369 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000370
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000371 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000372 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000373 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000374 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000375 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000377 # One thing is unintuitive, vars= {} must happen before Var() use.
378 local_scope = {}
379 var = self.VarImpl(self.custom_vars, local_scope)
380 global_scope = {
381 'File': self.FileImpl,
382 'From': self.FromImpl,
383 'Var': var.Lookup,
384 'deps_os': {},
385 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000386 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000387 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000388 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
389 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000390 else:
391 deps_content = gclient_utils.FileRead(filepath)
392 logging.debug(deps_content)
393 # Eval the content.
394 try:
395 exec(deps_content, global_scope, local_scope)
396 except SyntaxError, e:
397 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000398 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000399 # load os specific dependencies if defined. these dependencies may
400 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000401 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000402 enforced_os = self.root.enforced_os
403 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000404 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000405 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000406 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000407 # platform, so we collect the broadest set of dependencies
408 # available. We may end up with the wrong revision of something for
409 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000410 deps.update([x for x in os_deps.items() if not x[0] in deps])
411 else:
412 deps.update(os_deps)
413
maruel@chromium.org064186c2011-09-27 23:53:33 +0000414 self._deps_hooks.extend(local_scope.get('hooks', []))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000415
416 # If a line is in custom_deps, but not in the solution, we want to append
417 # this line to the solution.
418 for d in self.custom_deps:
419 if d not in deps:
420 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000421
422 # If use_relative_paths is set in the DEPS file, regenerate
423 # the dictionary using paths relative to the directory containing
424 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000425 use_relative_paths = local_scope.get('use_relative_paths', False)
426 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000427 rel_deps = {}
428 for d, url in deps.items():
429 # normpath is required to allow DEPS to use .. in their
430 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000431 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
432 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000433
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000434 # Convert the deps into real Dependency.
435 for name, url in deps.iteritems():
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000436 if name in [s.name for s in self._dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000437 raise gclient_utils.Error(
438 'The same name "%s" appears multiple times in the deps section' %
439 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000440 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000441 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000442 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000443 if name in tree:
444 if url == tree[name].url:
445 logging.info('Won\'t process duplicate dependency %s' % tree[name])
446 # In theory we could keep it as a shadow of the other one. In
447 # practice, simply ignore it.
448 #should_process = False
449 continue
450 else:
451 raise gclient_utils.Error(
452 'Dependency %s specified more than once:\n %s\nvs\n %s' %
453 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000454 self._dependencies.append(
455 Dependency(
456 self, name, url, None, None, None, None,
457 self.deps_file, should_process))
maruel@chromium.org064186c2011-09-27 23:53:33 +0000458 self._deps_parsed = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000459 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000460
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000461 # Arguments number differs from overridden method
462 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000463 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000464 """Runs 'command' before parsing the DEPS in case it's a initial checkout
465 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000466
467 def maybeGetParentRevision(options):
468 """If we are performing an update and --transitive is set, set the
469 revision to the parent's revision. If we have an explicit revision
470 do nothing."""
471 if command == 'update' and options.transitive and not options.revision:
472 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
473 if not revision:
474 options.revision = revision_overrides.get(self.parent.name)
475 if options.verbose and options.revision:
476 print("Using parent's revision date: %s" % options.revision)
477 # If the parent has a revision override, then it must have been
478 # converted to date format.
479 assert (not options.revision or
480 gclient_utils.IsDateRevision(options.revision))
481
482 def maybeConvertToDateRevision(options):
483 """If we are performing an update and --transitive is set, convert the
484 revision to a date-revision (if necessary). Instead of having
485 -r 101 replace the revision with the time stamp of 101 (e.g.
486 "{2011-18-04}").
487 This way dependencies are upgraded to the revision they had at the
488 check-in of revision 101."""
489 if (command == 'update' and
490 options.transitive and
491 options.revision and
492 not gclient_utils.IsDateRevision(options.revision)):
493 revision_date = scm.GetRevisionDate(options.revision)
494 revision = gclient_utils.MakeDateRevision(revision_date)
495 if options.verbose:
496 print("Updating revision override from %s to %s." %
497 (options.revision, revision))
498 revision_overrides[self.name] = revision
499
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000500 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000501 if not self.should_process:
502 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503 # When running runhooks, there's no need to consult the SCM.
504 # All known hooks are expected to run unconditionally regardless of working
505 # copy state, so skip the SCM status check.
506 run_scm = command not in ('runhooks', None)
maruel@chromium.org064186c2011-09-27 23:53:33 +0000507 self._parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508 if run_scm and self.parsed_url:
509 if isinstance(self.parsed_url, self.FileImpl):
510 # Special support for single-file checkout.
511 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
512 options.revision = self.parsed_url.GetRevision()
513 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000514 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000515 self.name)
516 scm.RunCommand('updatesingle', options,
517 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000518 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000520 # Create a shallow copy to mutate revision.
521 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000522 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000523 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000524 scm = gclient_scm.CreateSCM(
525 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000526 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000527 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000528 self._file_list = [os.path.join(self.name, f.strip())
529 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000530
531 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
532 # Convert all absolute paths to relative.
533 for i in range(len(self._file_list)):
534 # It depends on the command being executed (like runhooks vs sync).
535 if not os.path.isabs(self._file_list[i]):
536 continue
537 prefix = os.path.commonprefix(
538 [self.root.root_dir.lower(), self._file_list[i].lower()])
539 self._file_list[i] = self._file_list[i][len(prefix):]
540 # Strip any leading path separators.
541 while (self._file_list[i].startswith('\\') or
542 self._file_list[i].startswith('/')):
543 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.org064186c2011-09-27 23:53:33 +0000544 self._processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000545 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000546 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000547 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000548
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000549 # Parse the dependencies of this dependency.
550 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000551 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000552
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000553 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000554 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000555 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000556 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000557 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000558 # Don't run the hook when it is above recursion_limit.
559 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000560 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000561 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000562 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000563 # TODO(maruel): If the user is using git or git-svn, then we don't know
564 # what files have changed so we always run all hooks. It'd be nice to fix
565 # that.
566 if (options.force or
567 isinstance(self.parsed_url, self.FileImpl) or
568 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000569 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000570 for hook_dict in self.deps_hooks:
571 self._RunHookAction(hook_dict, [])
572 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000573 # Run hooks on the basis of whether the files from the gclient operation
574 # match each hook's pattern.
575 for hook_dict in self.deps_hooks:
576 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000577 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000578 if matching_file_list:
579 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000580 for s in self.dependencies:
581 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000582
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000583 def _RunHookAction(self, hook_dict, matching_file_list):
584 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000585 # A single DEPS file can specify multiple hooks so this function can be
586 # called multiple times on a single Dependency.
587 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000588 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000589 logging.debug(hook_dict)
590 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000591 command = hook_dict['action'][:]
592 if command[0] == 'python':
593 # If the hook specified "python" as the first item, the action is a
594 # Python script. Run it by starting a new copy of the same
595 # interpreter.
596 command[0] = sys.executable
597
598 if '$matching_files' in command:
599 splice_index = command.index('$matching_files')
600 command[splice_index:splice_index + 1] = matching_file_list
601
maruel@chromium.org17d01792010-09-01 18:07:10 +0000602 try:
603 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000604 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000605 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000606 # Use a discrete exit status code of 2 to indicate that a hook action
607 # failed. Users of this script may wish to treat hook action failures
608 # differently from VC failures.
609 print >> sys.stderr, 'Error: %s' % str(e)
610 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000611
maruel@chromium.org0d812442010-08-10 12:41:08 +0000612 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000613 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000614 result = []
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000615 dependencies = self.dependencies
616 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000617 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000618 result.append(d)
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000619 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000620 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000621 return result
622
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000623 def get_custom_deps(self, name, url):
624 """Returns a custom deps if applicable."""
625 if self.parent:
626 url = self.parent.get_custom_deps(name, url)
627 # None is a valid return value to disable a dependency.
628 return self.custom_deps.get(name, url)
629
maruel@chromium.org68988972011-09-20 14:11:42 +0000630 @property
631 def recursion_limit(self):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000632 """Returns > 0 if this dependency is not too recursed to be processed.
633
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000634 Immutable so no need to lock.
635 """
maruel@chromium.org68988972011-09-20 14:11:42 +0000636 return max(self.parent.recursion_limit - 1, 0)
637
638 @property
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000639 def dependencies(self):
640 return tuple(self._dependencies)
641
642 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000643 def deps_hooks(self):
644 return tuple(self._deps_hooks)
645
646 @property
647 def parsed_url(self):
648 return self._parsed_url
649
650 @property
651 def deps_parsed(self):
652 return self._deps_parsed
653
654 @property
655 def processed(self):
656 return self._processed
657
658 @property
659 def hooks_ran(self):
660 return self._hooks_ran
661
662 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000663 def file_list(self):
664 result = self._file_list[:]
665 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000666 result.extend(d.file_list)
667 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000668
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000669 def __str__(self):
670 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000671 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000672 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000673 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000674 # First try the native property if it exists.
675 if hasattr(self, '_' + i):
676 value = getattr(self, '_' + i, False)
677 else:
678 value = getattr(self, i, False)
679 if value:
680 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000681
682 for d in self.dependencies:
683 out.extend([' ' + x for x in str(d).splitlines()])
684 out.append('')
685 return '\n'.join(out)
686
687 def __repr__(self):
688 return '%s: %s' % (self.name, self.url)
689
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000690 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000691 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000692 out = '%s(%s)' % (self.name, self.url)
693 i = self.parent
694 while i and i.name:
695 out = '%s(%s) -> %s' % (i.name, i.url, out)
696 i = i.parent
697 return out
698
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000699 @property
700 def root(self):
701 """Returns the root node, a GClient object."""
702 if not self.parent:
703 # This line is to signal pylint that it could be a GClient instance.
704 return self or GClient(None, None)
705 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000706
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000707
708class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000709 """Object that represent a gclient checkout. A tree of Dependency(), one per
710 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000711
712 DEPS_OS_CHOICES = {
713 "win32": "win",
714 "win": "win",
715 "cygwin": "win",
716 "darwin": "mac",
717 "mac": "mac",
718 "unix": "unix",
719 "linux": "unix",
720 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000721 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000722 }
723
724 DEFAULT_CLIENT_FILE_TEXT = ("""\
725solutions = [
726 { "name" : "%(solution_name)s",
727 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000728 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000729 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000730 "custom_deps" : {
731 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000732 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000733 },
734]
735""")
736
737 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
738 { "name" : "%(solution_name)s",
739 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000740 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000741 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000743%(solution_deps)s },
744 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000745 },
746""")
747
748 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
749# Snapshot generated with gclient revinfo --snapshot
750solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000751%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000752""")
753
754 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000755 # Do not change previous behavior. Only solution level and immediate DEPS
756 # are processed.
757 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000758 Dependency.__init__(self, None, None, None, None, True, None, None,
759 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000760 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000761 if options.deps_os:
762 enforced_os = options.deps_os.split(',')
763 else:
764 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
765 if 'all' in enforced_os:
766 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000767 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000768 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000769 self.config_content = None
770
771 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000772 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000773 config_dict = {}
774 self.config_content = content
775 try:
776 exec(content, config_dict)
777 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000778 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000779 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000780 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000781 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000782 if s['name'] in tree:
783 raise gclient_utils.Error(
784 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000785 self._dependencies.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000786 self, s['name'], s['url'],
787 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000788 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000789 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000790 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000791 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000792 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000793 except KeyError:
794 raise gclient_utils.Error('Invalid .gclient file. Solution is '
795 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000796 # .gclient can have hooks.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000797 self._deps_hooks = config_dict.get('hooks', [])
798 self._deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000799
800 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000801 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000802 self._options.config_filename),
803 self.config_content)
804
805 @staticmethod
806 def LoadCurrentConfig(options):
807 """Searches for and loads a .gclient file relative to the current working
808 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000809 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000810 if not path:
811 return None
812 client = GClient(path, options)
813 client.SetConfig(gclient_utils.FileRead(
814 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000815 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000816
nsylvain@google.comefc80932011-05-31 21:27:56 +0000817 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000818 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000819 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
820 'solution_name': solution_name,
821 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000822 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000823 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000824 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000825 })
826
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000827 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000828 """Creates a .gclient_entries file to record the list of unique checkouts.
829
830 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000831 """
832 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
833 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000835 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000836 # Skip over File() dependencies as we can't version them.
837 if not isinstance(entry.parsed_url, self.FileImpl):
838 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
839 pprint.pformat(entry.parsed_url))
840 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000841 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000842 logging.info(result)
843 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000844
845 def _ReadEntries(self):
846 """Read the .gclient_entries file for the given client.
847
848 Returns:
849 A sequence of solution names, which will be empty if there is the
850 entries file hasn't been created yet.
851 """
852 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000853 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000854 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000855 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000856 try:
857 exec(gclient_utils.FileRead(filename), scope)
858 except SyntaxError, e:
859 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000860 return scope['entries']
861
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000862 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000863 """Checks for revision overrides."""
864 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000865 if self._options.head:
866 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000867 # Do not check safesync_url if one or more --revision flag is specified.
868 if not self._options.revisions:
869 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000870 if not s.managed:
871 self._options.revisions.append('%s@unmanaged' % s.name)
872 elif s.safesync_url:
873 handle = urllib.urlopen(s.safesync_url)
874 rev = handle.read().strip()
875 handle.close()
876 if len(rev):
877 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000878 if not self._options.revisions:
879 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000880 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000881 index = 0
882 for revision in self._options.revisions:
883 if not '@' in revision:
884 # Support for --revision 123
885 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000886 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000887 if not sol in solutions_names:
888 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
889 print >> sys.stderr, ('Please fix your script, having invalid '
890 '--revision flags will soon considered an error.')
891 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000892 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000893 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000894 return revision_overrides
895
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000896 def RunOnDeps(self, command, args):
897 """Runs a command on each dependency in a client and its dependencies.
898
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000899 Args:
900 command: The command to use (e.g., 'status' or 'diff')
901 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000902 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000903 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000904 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000905 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000906 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000907 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000908 if (command in ('update', 'revert') and sys.stdout.isatty() and not
909 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000910 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000911 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000912 for s in self.dependencies:
913 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000914 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000915
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000916 # Once all the dependencies have been processed, it's now safe to run the
917 # hooks.
918 if not self._options.nohooks:
919 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000920
921 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000922 # Notify the user if there is an orphaned entry in their working copy.
923 # Only delete the directory if there are no changes in it, and
924 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000925 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000926 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000927 if not prev_url:
928 # entry must have been overridden via .gclient custom_deps
929 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000930 # Fix path separator on Windows.
931 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000932 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000933 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000934 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000935 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000936 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000937 scm.status(self._options, [], file_list)
938 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000939 if (not self._options.delete_unversioned_trees or
940 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000941 # There are modified files in this entry. Keep warning until
942 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000943 print(('\nWARNING: \'%s\' is no longer part of this client. '
944 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000945 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000946 else:
947 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000948 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000949 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000950 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000951 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000952 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000953 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000954
955 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000956 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000957 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000958 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000959 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000960 for s in self.dependencies:
961 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000962 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000963
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000964 def GetURLAndRev(dep):
965 """Returns the revision-qualified SCM url for a Dependency."""
966 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000967 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000968 if isinstance(dep.parsed_url, self.FileImpl):
969 original_url = dep.parsed_url.file_location
970 else:
971 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000972 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000973 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000974 if not os.path.isdir(scm.checkout_path):
975 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000976 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000978 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000979 new_gclient = ''
980 # First level at .gclient
981 for d in self.dependencies:
982 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000983 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000984 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000985 for d in dep.dependencies:
986 entries[d.name] = GetURLAndRev(d)
987 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000988 GrabDeps(d)
989 custom_deps = []
990 for k in sorted(entries.keys()):
991 if entries[k]:
992 # Quotes aren't escaped...
993 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
994 else:
995 custom_deps.append(' \"%s\": None,\n' % k)
996 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
997 'solution_name': d.name,
998 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000999 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001000 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001001 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001002 'solution_deps': ''.join(custom_deps),
1003 }
1004 # Print the snapshot configuration file
1005 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001006 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001007 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001008 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001009 if self._options.actual:
1010 entries[d.name] = GetURLAndRev(d)
1011 else:
1012 entries[d.name] = d.parsed_url
1013 keys = sorted(entries.keys())
1014 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001015 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001016 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001017
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001018 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001019 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001020 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001021
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001022 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001023 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001024 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001025 return self._root_dir
1026
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001027 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001028 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001029 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001030 return self._enforced_os
1031
maruel@chromium.org68988972011-09-20 14:11:42 +00001032 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001033 def recursion_limit(self):
1034 """How recursive can each dependencies in DEPS file can load DEPS file."""
1035 return self._recursion_limit
1036
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001038#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039
1040
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001041def CMDcleanup(parser, args):
1042 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001043
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001044Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001045"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001046 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1047 help='override deps for the specified (comma-separated) '
1048 'platform(s); \'all\' will process all deps_os '
1049 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001050 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001051 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001053 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001054 if options.verbose:
1055 # Print out the .gclient file. This is longer than if we just printed the
1056 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001057 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058 return client.RunOnDeps('cleanup', args)
1059
1060
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001061@attr('usage', '[command] [args ...]')
1062def CMDrecurse(parser, args):
1063 """Operates on all the entries.
1064
1065 Runs a shell command on all entries.
1066 """
1067 # Stop parsing at the first non-arg so that these go through to the command
1068 parser.disable_interspersed_args()
1069 parser.add_option('-s', '--scm', action='append', default=[],
1070 help='choose scm types to operate upon')
1071 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001072 if not args:
1073 print >> sys.stderr, 'Need to supply a command!'
1074 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001075 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1076 if not root_and_entries:
1077 print >> sys.stderr, (
1078 'You need to run gclient sync at least once to use \'recurse\'.\n'
1079 'This is because .gclient_entries needs to exist and be up to date.')
1080 return 1
1081 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001082 scm_set = set()
1083 for scm in options.scm:
1084 scm_set.update(scm.split(','))
1085
1086 # Pass in the SCM type as an env variable
1087 env = os.environ.copy()
1088
1089 for path, url in entries.iteritems():
1090 scm = gclient_scm.GetScmName(url)
1091 if scm_set and scm not in scm_set:
1092 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001093 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001094 if scm:
1095 env['GCLIENT_SCM'] = scm
1096 if url:
1097 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001098 if os.path.isdir(cwd):
1099 subprocess2.call(args, cwd=cwd, env=env)
1100 else:
1101 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001102 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001103
1104
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001105@attr('usage', '[url] [safesync url]')
1106def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001107 """Create a .gclient file in the current directory.
1108
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001110top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001111modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001112provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001113URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001114"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001115 parser.add_option('--spec',
1116 help='create a gclient file containing the provided '
1117 'string. Due to Cygwin/Python brokenness, it '
1118 'probably can\'t contain any newlines.')
1119 parser.add_option('--name',
1120 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001121 parser.add_option('--deps-file', default='DEPS',
1122 help='overrides the default name for the DEPS file for the'
1123 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001124 parser.add_option('--unmanaged', action='store_true', default=False,
1125 help='overrides the default behavior to make it possible '
1126 'to have the main solution untouched by gclient '
1127 '(gclient will check out unmanaged dependencies but '
1128 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001129 parser.add_option('--git-deps', action='store_true',
1130 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001132 if ((options.spec and args) or len(args) > 2 or
1133 (not options.spec and not args)):
1134 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1135
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001136 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001137 if options.spec:
1138 client.SetConfig(options.spec)
1139 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001140 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001141 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001142 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001143 if name.endswith('.git'):
1144 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001145 else:
1146 # specify an alternate relpath for the given URL.
1147 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001148 deps_file = options.deps_file
1149 if options.git_deps:
1150 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001151 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 if len(args) > 1:
1153 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001154 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1155 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001157 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158
1159
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001160@attr('epilog', """Example:
1161 gclient pack > patch.txt
1162 generate simple patch for configured client and dependences
1163""")
1164def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001165 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001166
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001167Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001168dependencies, and performs minimal postprocessing of the output. The
1169resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001170checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001171"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001172 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1173 help='override deps for the specified (comma-separated) '
1174 'platform(s); \'all\' will process all deps_os '
1175 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001177 client = GClient.LoadCurrentConfig(options)
1178 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001179 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001180 if options.verbose:
1181 # Print out the .gclient file. This is longer than if we just printed the
1182 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001183 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001184 return client.RunOnDeps('pack', args)
1185
1186
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001187def CMDstatus(parser, args):
1188 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001189 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1190 help='override deps for the specified (comma-separated) '
1191 'platform(s); \'all\' will process all deps_os '
1192 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001194 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001195 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001196 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001197 if options.verbose:
1198 # Print out the .gclient file. This is longer than if we just printed the
1199 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001200 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001201 return client.RunOnDeps('status', args)
1202
1203
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001204@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001205 gclient sync
1206 update files from SCM according to current configuration,
1207 *for modules which have changed since last update or sync*
1208 gclient sync --force
1209 update files from SCM according to current configuration, for
1210 all modules (useful for recovering files deleted from local copy)
1211 gclient sync --revision src@31000
1212 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001213""")
1214def CMDsync(parser, args):
1215 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001216 parser.add_option('-f', '--force', action='store_true',
1217 help='force update even for unchanged modules')
1218 parser.add_option('-n', '--nohooks', action='store_true',
1219 help='don\'t run hooks after the update is complete')
1220 parser.add_option('-r', '--revision', action='append',
1221 dest='revisions', metavar='REV', default=[],
1222 help='Enforces revision/hash for the solutions with the '
1223 'format src@rev. The src@ part is optional and can be '
1224 'skipped. -r can be used multiple times when .gclient '
1225 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001226 'if the src@ part is skipped. Note that specifying '
1227 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001228 parser.add_option('-t', '--transitive', action='store_true',
1229 help='When a revision is specified (in the DEPS file or '
1230 'with the command-line flag), transitively update '
1231 'the dependencies to the date of the given revision. '
1232 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001233 parser.add_option('-H', '--head', action='store_true',
1234 help='skips any safesync_urls specified in '
1235 'configured solutions and sync to head instead')
1236 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001237 help='delete any dependency that have been removed from '
1238 'last sync as long as there is no local modification. '
1239 'Coupled with --force, it will remove them even with '
1240 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001241 parser.add_option('-R', '--reset', action='store_true',
1242 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001243 parser.add_option('-M', '--merge', action='store_true',
1244 help='merge upstream changes instead of trying to '
1245 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001246 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1247 help='override deps for the specified (comma-separated) '
1248 'platform(s); \'all\' will process all deps_os '
1249 'references')
1250 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1251 help='Skip svn up whenever possible by requesting '
1252 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001253 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001254 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001255
1256 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001257 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001258
maruel@chromium.org307d1792010-05-31 20:03:13 +00001259 if options.revisions and options.head:
1260 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001261 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001262
1263 if options.verbose:
1264 # Print out the .gclient file. This is longer than if we just printed the
1265 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001266 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001267 return client.RunOnDeps('update', args)
1268
1269
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001270def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001271 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001272 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001273
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001274def CMDdiff(parser, args):
1275 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001276 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1277 help='override deps for the specified (comma-separated) '
1278 'platform(s); \'all\' will process all deps_os '
1279 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001280 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001281 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001282 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001283 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284 if options.verbose:
1285 # Print out the .gclient file. This is longer than if we just printed the
1286 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001287 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001288 return client.RunOnDeps('diff', args)
1289
1290
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001291def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001292 """Revert all modifications in every dependencies.
1293
1294 That's the nuclear option to get back to a 'clean' state. It removes anything
1295 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001296 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1297 help='override deps for the specified (comma-separated) '
1298 'platform(s); \'all\' will process all deps_os '
1299 'references')
1300 parser.add_option('-n', '--nohooks', action='store_true',
1301 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001302 (options, args) = parser.parse_args(args)
1303 # --force is implied.
1304 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001305 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001306 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001307 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001308 return client.RunOnDeps('revert', args)
1309
1310
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001311def CMDrunhooks(parser, args):
1312 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1314 help='override deps for the specified (comma-separated) '
1315 'platform(s); \'all\' will process all deps_os '
1316 'references')
1317 parser.add_option('-f', '--force', action='store_true', default=True,
1318 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001319 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001320 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001321 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001322 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001323 if options.verbose:
1324 # Print out the .gclient file. This is longer than if we just printed the
1325 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001326 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001327 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001328 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329 return client.RunOnDeps('runhooks', args)
1330
1331
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001332def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001333 """Output revision info mapping for the client and its dependencies.
1334
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001335 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001336 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001337 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1338 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001339 commit can change.
1340 """
1341 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1342 help='override deps for the specified (comma-separated) '
1343 'platform(s); \'all\' will process all deps_os '
1344 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001345 parser.add_option('-a', '--actual', action='store_true',
1346 help='gets the actual checked out revisions instead of the '
1347 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001348 parser.add_option('-s', '--snapshot', action='store_true',
1349 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001350 'version of all repositories to reproduce the tree, '
1351 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001352 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001353 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001354 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001355 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001356 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001357 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001358
1359
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001360def Command(name):
1361 return getattr(sys.modules[__name__], 'CMD' + name, None)
1362
1363
1364def CMDhelp(parser, args):
1365 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001366 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001367 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001368 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001369 parser.print_help()
1370 return 0
1371
1372
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001373def GenUsage(parser, command):
1374 """Modify an OptParse object with the function's documentation."""
1375 obj = Command(command)
1376 if command == 'help':
1377 command = '<command>'
1378 # OptParser.description prefer nicely non-formatted strings.
1379 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1380 usage = getattr(obj, 'usage', '')
1381 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1382 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001383
1384
maruel@chromium.org0895b752011-08-26 20:40:33 +00001385def Parser():
1386 """Returns the default parser."""
1387 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001388 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001389 help='Specify how many SCM commands can run in parallel; '
1390 'default=%default')
1391 parser.add_option('-v', '--verbose', action='count', default=0,
1392 help='Produces additional output for diagnostics. Can be '
1393 'used up to three times for more logging info.')
1394 parser.add_option('--gclientfile', dest='config_filename',
1395 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1396 help='Specify an alternate %default file')
1397 # Integrate standard options processing.
1398 old_parser = parser.parse_args
1399 def Parse(args):
1400 (options, args) = old_parser(args)
1401 level = None
1402 if options.verbose == 2:
1403 level = logging.INFO
1404 elif options.verbose > 2:
1405 level = logging.DEBUG
1406 logging.basicConfig(level=level,
1407 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1408 options.entries_filename = options.config_filename + '_entries'
1409 if options.jobs < 1:
1410 parser.error('--jobs must be 1 or higher')
1411
1412 # These hacks need to die.
1413 if not hasattr(options, 'revisions'):
1414 # GClient.RunOnDeps expects it even if not applicable.
1415 options.revisions = []
1416 if not hasattr(options, 'head'):
1417 options.head = None
1418 if not hasattr(options, 'nohooks'):
1419 options.nohooks = True
1420 if not hasattr(options, 'deps_os'):
1421 options.deps_os = None
1422 if not hasattr(options, 'manually_grab_svn_rev'):
1423 options.manually_grab_svn_rev = None
1424 if not hasattr(options, 'force'):
1425 options.force = None
1426 return (options, args)
1427 parser.parse_args = Parse
1428 # We don't want wordwrapping in epilog (usually examples)
1429 parser.format_epilog = lambda _: parser.epilog or ''
1430 return parser
1431
1432
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001433def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001434 """Doesn't parse the arguments here, just find the right subcommand to
1435 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001436 if sys.hexversion < 0x02050000:
1437 print >> sys.stderr, (
1438 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001439 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001440 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1441 # operations. Python as a strong tendency to buffer sys.stdout.
1442 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001443 # Make stdout annotated with the thread ids.
1444 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001445 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001446 # Unused variable 'usage'
1447 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001448 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1449 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1450 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001451 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001452 if argv:
1453 command = Command(argv[0])
1454 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001455 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001456 GenUsage(parser, argv[0])
1457 return command(parser, argv[1:])
1458 # Not a known command. Default to help.
1459 GenUsage(parser, 'help')
1460 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001461 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001462 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001463 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001464
1465
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001466if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001467 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001468 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001469
1470# vim: ts=2:sw=2:tw=80:et: