blob: ed64354f1b1043007f442bc4428b3e5e996e4e22 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
phajdan.jr@chromium.org6e043f72011-05-02 07:24:32 +000052__version__ = "0.6.2"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
maruel@chromium.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000075 """Sets an attribute on a function."""
76 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 return fn
79 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082## GClient implementation.
83
84
maruel@chromium.org116704f2010-06-11 17:34:38 +000085class GClientKeywords(object):
86 class FromImpl(object):
87 """Used to implement the From() syntax."""
88
89 def __init__(self, module_name, sub_target_name=None):
90 """module_name is the dep module we want to include from. It can also be
91 the name of a subdirectory to include from.
92
93 sub_target_name is an optional parameter if the module name in the other
94 DEPS file is different. E.g., you might want to map src/net to net."""
95 self.module_name = module_name
96 self.sub_target_name = sub_target_name
97
98 def __str__(self):
99 return 'From(%s, %s)' % (repr(self.module_name),
100 repr(self.sub_target_name))
101
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000104 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105
106 def __init__(self, file_location):
107 self.file_location = file_location
108
109 def __str__(self):
110 return 'File("%s")' % self.file_location
111
112 def GetPath(self):
113 return os.path.split(self.file_location)[0]
114
115 def GetFilename(self):
116 rev_tokens = self.file_location.split('@')
117 return os.path.split(rev_tokens[0])[1]
118
119 def GetRevision(self):
120 rev_tokens = self.file_location.split('@')
121 if len(rev_tokens) > 1:
122 return rev_tokens[1]
123 return None
124
125 class VarImpl(object):
126 def __init__(self, custom_vars, local_scope):
127 self._custom_vars = custom_vars
128 self._local_scope = local_scope
129
130 def Lookup(self, var_name):
131 """Implements the Var syntax."""
132 if var_name in self._custom_vars:
133 return self._custom_vars[var_name]
134 elif var_name in self._local_scope.get("vars", {}):
135 return self._local_scope["vars"][var_name]
136 raise gclient_utils.Error("Var is not defined: %s" % var_name)
137
138
maruel@chromium.org064186c2011-09-27 23:53:33 +0000139class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000140 """Immutable configuration settings."""
141 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000142 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000143 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000144 GClientKeywords.__init__(self)
145
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000146 # These are not mutable:
147 self._parent = parent
148 self._safesync_url = safesync_url
149 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000150 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000151 # 'managed' determines whether or not this dependency is synced/updated by
152 # gclient after gclient checks it out initially. The difference between
153 # 'managed' and 'should_process' is that the user specifies 'managed' via
154 # the --unmanaged command-line flag or a .gclient config, where
155 # 'should_process' is dynamically set by gclient if it goes over its
156 # recursion limit and controls gclient's behavior so it does not misbehave.
157 self._managed = managed
158 self._should_process = should_process
159
160 # These are only set in .gclient and not in DEPS files.
161 self._custom_vars = custom_vars or {}
162 self._custom_deps = custom_deps or {}
163
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 # Post process the url to remove trailing slashes.
165 if isinstance(self._url, basestring):
166 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
167 # it to proto://host/path@rev.
168 if self._url.count('@') > 1:
169 raise gclient_utils.Error('Invalid url "%s"' % self._url)
170 self._url = self._url.replace('/@', '@')
171 elif not isinstance(self._url,
172 (self.FromImpl, self.FileImpl, None.__class__)):
173 raise gclient_utils.Error(
174 ('dependency url must be either a string, None, '
175 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 if '/' in self._deps_file or '\\' in self._deps_file:
177 raise gclient_utils.Error('deps_file name must not be a path, just a '
178 'filename. %s' % self._deps_file)
179
180 @property
181 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 return self._deps_file
183
184 @property
185 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 return self._managed
187
188 @property
189 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 return self._parent
191
192 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000193 def root(self):
194 """Returns the root node, a GClient object."""
195 if not self.parent:
196 # This line is to signal pylint that it could be a GClient instance.
197 return self or GClient(None, None)
198 return self.parent.root
199
200 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000201 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 return self._safesync_url
203
204 @property
205 def should_process(self):
206 """True if this dependency should be processed, i.e. checked out."""
207 return self._should_process
208
209 @property
210 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 return self._custom_vars.copy()
212
213 @property
214 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 return self._custom_deps.copy()
216
maruel@chromium.org064186c2011-09-27 23:53:33 +0000217 @property
218 def url(self):
219 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000221 @property
222 def recursion_limit(self):
223 """Returns > 0 if this dependency is not too recursed to be processed."""
224 return max(self.parent.recursion_limit - 1, 0)
225
226 def get_custom_deps(self, name, url):
227 """Returns a custom deps if applicable."""
228 if self.parent:
229 url = self.parent.get_custom_deps(name, url)
230 # None is a valid return value to disable a dependency.
231 return self.custom_deps.get(name, url)
232
maruel@chromium.org064186c2011-09-27 23:53:33 +0000233
234class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000235 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000236
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000237 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000238 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000239 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000243
244 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000246
247 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000249 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250 # A cache of the files affected by the current operation, necessary for
251 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000252 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000253 # If it is not set to True, the dependency wasn't processed for its child
254 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000256 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000260
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 # Setup self.requirements and find any other dependency who would have self
262 # as a requirement.
maruel@chromium.org98023df2011-09-07 18:44:47 +0000263
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000264 # self.parent is implicitly a requirement. This will be recursive by
265 # definition.
266 if self.parent and self.parent.name:
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000267 self.add_requirement(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000268
269 # For a tree with at least 2 levels*, the leaf node needs to depend
270 # on the level higher up in an orderly way.
271 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
272 # thus unsorted, while the .gclient format is a list thus sorted.
273 #
274 # * _recursion_limit is hard coded 2 and there is no hope to change this
275 # value.
276 #
277 # Interestingly enough, the following condition only works in the case we
278 # want: self is a 2nd level node. 3nd level node wouldn't need this since
279 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000280 root_deps = self.root.dependencies
281 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000282 for i in root_deps:
283 if i is self.parent:
284 break
285 if i.name:
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000286 self.add_requirement(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287
288 if isinstance(self.url, self.FromImpl):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000289 self.add_requirement(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000290
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000291 if self.name and self.should_process:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000292 for obj in self.root.depth_first_tree():
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000293 if obj is self or not obj.name:
294 continue
295 # Step 1: Find any requirements self may need.
296 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000297 self.add_requirement(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000298 # Step 2: Find any requirements self may impose.
299 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000300 obj.add_requirement(self.name)
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.org1333cb32011-10-04 23:40:16 +0000311 parsed_url = self.get_custom_deps(self.name, url)
312 if parsed_url != url:
313 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
314 return parsed_url
315
316 if 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.org1333cb32011-10-04 23:40:16 +0000342 logging.info(
343 'LateOverride(%s, %s) -> %s (From)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000344 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000345
346 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000347 parsed_url = urlparse.urlparse(url)
348 if not parsed_url[0]:
349 # A relative url. Fetch the real base.
350 path = parsed_url[2]
351 if not path.startswith('/'):
352 raise gclient_utils.Error(
353 'relative DEPS entry \'%s\' must begin with a slash' % url)
354 # Create a scm just to query the full url.
355 parent_url = self.parent.parsed_url
356 if isinstance(parent_url, self.FileImpl):
357 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000358 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000359 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000361 parsed_url = url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000362 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000363 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000364
365 if isinstance(url, self.FileImpl):
366 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url))
367 return url
368
369 if url is None:
370 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url))
371 return url
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000372 else:
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000373 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000374
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000375 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000376 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000377 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000378 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000379 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000380 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000381 # One thing is unintuitive, vars= {} must happen before Var() use.
382 local_scope = {}
383 var = self.VarImpl(self.custom_vars, local_scope)
384 global_scope = {
385 'File': self.FileImpl,
386 'From': self.FromImpl,
387 'Var': var.Lookup,
388 'deps_os': {},
389 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000390 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000391 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000392 logging.info(
393 'ParseDepsFile(%s): No %s file found at %s' % (
394 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000395 else:
396 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000397 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000398 # Eval the content.
399 try:
400 exec(deps_content, global_scope, local_scope)
401 except SyntaxError, e:
402 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000403 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000404 # load os specific dependencies if defined. these dependencies may
405 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000406 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000407 enforced_os = self.root.enforced_os
408 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000409 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000410 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000411 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000412 # platform, so we collect the broadest set of dependencies
413 # available. We may end up with the wrong revision of something for
414 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000415 deps.update([x for x in os_deps.items() if not x[0] in deps])
416 else:
417 deps.update(os_deps)
418
maruel@chromium.org064186c2011-09-27 23:53:33 +0000419 self._deps_hooks.extend(local_scope.get('hooks', []))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000420
421 # If a line is in custom_deps, but not in the solution, we want to append
422 # this line to the solution.
423 for d in self.custom_deps:
424 if d not in deps:
425 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000426
427 # If use_relative_paths is set in the DEPS file, regenerate
428 # the dictionary using paths relative to the directory containing
429 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000430 use_relative_paths = local_scope.get('use_relative_paths', False)
431 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000432 rel_deps = {}
433 for d, url in deps.items():
434 # normpath is required to allow DEPS to use .. in their
435 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000436 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
437 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000438
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000439 # Convert the deps into real Dependency.
440 for name, url in deps.iteritems():
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000441 if name in [s.name for s in self._dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000442 raise gclient_utils.Error(
443 'The same name "%s" appears multiple times in the deps section' %
444 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000445 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000446 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000447 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000448 if name in tree:
449 if url == tree[name].url:
450 logging.info('Won\'t process duplicate dependency %s' % tree[name])
451 # In theory we could keep it as a shadow of the other one. In
452 # practice, simply ignore it.
453 #should_process = False
454 continue
455 else:
456 raise gclient_utils.Error(
457 'Dependency %s specified more than once:\n %s\nvs\n %s' %
458 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000459 self._dependencies.append(
460 Dependency(
461 self, name, url, None, None, None, None,
462 self.deps_file, should_process))
maruel@chromium.org064186c2011-09-27 23:53:33 +0000463 self._deps_parsed = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000464 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000465
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000466 # Arguments number differs from overridden method
467 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000468 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469 """Runs 'command' before parsing the DEPS in case it's a initial checkout
470 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000471
472 def maybeGetParentRevision(options):
473 """If we are performing an update and --transitive is set, set the
474 revision to the parent's revision. If we have an explicit revision
475 do nothing."""
476 if command == 'update' and options.transitive and not options.revision:
477 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
478 if not revision:
479 options.revision = revision_overrides.get(self.parent.name)
480 if options.verbose and options.revision:
481 print("Using parent's revision date: %s" % options.revision)
482 # If the parent has a revision override, then it must have been
483 # converted to date format.
484 assert (not options.revision or
485 gclient_utils.IsDateRevision(options.revision))
486
487 def maybeConvertToDateRevision(options):
488 """If we are performing an update and --transitive is set, convert the
489 revision to a date-revision (if necessary). Instead of having
490 -r 101 replace the revision with the time stamp of 101 (e.g.
491 "{2011-18-04}").
492 This way dependencies are upgraded to the revision they had at the
493 check-in of revision 101."""
494 if (command == 'update' and
495 options.transitive and
496 options.revision and
497 not gclient_utils.IsDateRevision(options.revision)):
498 revision_date = scm.GetRevisionDate(options.revision)
499 revision = gclient_utils.MakeDateRevision(revision_date)
500 if options.verbose:
501 print("Updating revision override from %s to %s." %
502 (options.revision, revision))
503 revision_overrides[self.name] = revision
504
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000505 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000506 if not self.should_process:
507 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508 # When running runhooks, there's no need to consult the SCM.
509 # All known hooks are expected to run unconditionally regardless of working
510 # copy state, so skip the SCM status check.
511 run_scm = command not in ('runhooks', None)
maruel@chromium.org064186c2011-09-27 23:53:33 +0000512 self._parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000513 if run_scm and self.parsed_url:
514 if isinstance(self.parsed_url, self.FileImpl):
515 # Special support for single-file checkout.
516 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
517 options.revision = self.parsed_url.GetRevision()
518 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000519 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000520 self.name)
521 scm.RunCommand('updatesingle', options,
522 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000523 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000524 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000525 # Create a shallow copy to mutate revision.
526 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000527 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000528 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000529 scm = gclient_scm.CreateSCM(
530 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000531 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000532 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000533 self._file_list = [os.path.join(self.name, f.strip())
534 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000535
536 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
537 # Convert all absolute paths to relative.
538 for i in range(len(self._file_list)):
539 # It depends on the command being executed (like runhooks vs sync).
540 if not os.path.isabs(self._file_list[i]):
541 continue
542 prefix = os.path.commonprefix(
543 [self.root.root_dir.lower(), self._file_list[i].lower()])
544 self._file_list[i] = self._file_list[i][len(prefix):]
545 # Strip any leading path separators.
546 while (self._file_list[i].startswith('\\') or
547 self._file_list[i].startswith('/')):
548 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.org064186c2011-09-27 23:53:33 +0000549 self._processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000550 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000551 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000552 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000553
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000554 # Parse the dependencies of this dependency.
555 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000556 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000557
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000558 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000559 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000560 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000561 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000562 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000563 # Don't run the hook when it is above recursion_limit.
564 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000565 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000566 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000567 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000568 # TODO(maruel): If the user is using git or git-svn, then we don't know
569 # what files have changed so we always run all hooks. It'd be nice to fix
570 # that.
571 if (options.force or
572 isinstance(self.parsed_url, self.FileImpl) or
573 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000574 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000575 for hook_dict in self.deps_hooks:
576 self._RunHookAction(hook_dict, [])
577 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000578 # Run hooks on the basis of whether the files from the gclient operation
579 # match each hook's pattern.
580 for hook_dict in self.deps_hooks:
581 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000582 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000583 if matching_file_list:
584 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000585 for s in self.dependencies:
586 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000587
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000588 def _RunHookAction(self, hook_dict, matching_file_list):
589 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000590 # A single DEPS file can specify multiple hooks so this function can be
591 # called multiple times on a single Dependency.
592 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000593 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000594 logging.debug(hook_dict)
595 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000596 command = hook_dict['action'][:]
597 if command[0] == 'python':
598 # If the hook specified "python" as the first item, the action is a
599 # Python script. Run it by starting a new copy of the same
600 # interpreter.
601 command[0] = sys.executable
602
603 if '$matching_files' in command:
604 splice_index = command.index('$matching_files')
605 command[splice_index:splice_index + 1] = matching_file_list
606
maruel@chromium.org17d01792010-09-01 18:07:10 +0000607 try:
608 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000609 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000610 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000611 # Use a discrete exit status code of 2 to indicate that a hook action
612 # failed. Users of this script may wish to treat hook action failures
613 # differently from VC failures.
614 print >> sys.stderr, 'Error: %s' % str(e)
615 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000616
maruel@chromium.org0d812442010-08-10 12:41:08 +0000617 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000618 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000619 dependencies = self.dependencies
620 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000621 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000622 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000623 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000624 for i in d.subtree(include_all):
625 yield i
626
627 def depth_first_tree(self):
628 """Depth-first recursion including the root node."""
629 yield self
630 for i in self.dependencies:
631 for j in i.depth_first_tree():
632 if j.should_process:
633 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000634
maruel@chromium.org68988972011-09-20 14:11:42 +0000635 @property
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000636 def dependencies(self):
637 return tuple(self._dependencies)
638
639 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000640 def deps_hooks(self):
641 return tuple(self._deps_hooks)
642
643 @property
644 def parsed_url(self):
645 return self._parsed_url
646
647 @property
648 def deps_parsed(self):
649 return self._deps_parsed
650
651 @property
652 def processed(self):
653 return self._processed
654
655 @property
656 def hooks_ran(self):
657 return self._hooks_ran
658
659 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000660 def file_list(self):
661 result = self._file_list[:]
662 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000663 result.extend(d.file_list)
664 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000665
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000666 def __str__(self):
667 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000668 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000669 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000670 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000671 # First try the native property if it exists.
672 if hasattr(self, '_' + i):
673 value = getattr(self, '_' + i, False)
674 else:
675 value = getattr(self, i, False)
676 if value:
677 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000678
679 for d in self.dependencies:
680 out.extend([' ' + x for x in str(d).splitlines()])
681 out.append('')
682 return '\n'.join(out)
683
684 def __repr__(self):
685 return '%s: %s' % (self.name, self.url)
686
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000687 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000688 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000689 out = '%s(%s)' % (self.name, self.url)
690 i = self.parent
691 while i and i.name:
692 out = '%s(%s) -> %s' % (i.name, i.url, out)
693 i = i.parent
694 return out
695
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000696
697class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000698 """Object that represent a gclient checkout. A tree of Dependency(), one per
699 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000700
701 DEPS_OS_CHOICES = {
702 "win32": "win",
703 "win": "win",
704 "cygwin": "win",
705 "darwin": "mac",
706 "mac": "mac",
707 "unix": "unix",
708 "linux": "unix",
709 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000710 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000711 }
712
713 DEFAULT_CLIENT_FILE_TEXT = ("""\
714solutions = [
715 { "name" : "%(solution_name)s",
716 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000717 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000718 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000719 "custom_deps" : {
720 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000721 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000722 },
723]
724""")
725
726 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
727 { "name" : "%(solution_name)s",
728 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000729 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000730 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000731 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000732%(solution_deps)s },
733 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000734 },
735""")
736
737 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
738# Snapshot generated with gclient revinfo --snapshot
739solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000740%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000741""")
742
743 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000744 # Do not change previous behavior. Only solution level and immediate DEPS
745 # are processed.
746 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000747 Dependency.__init__(self, None, None, None, None, True, None, None,
748 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000749 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000750 if options.deps_os:
751 enforced_os = options.deps_os.split(',')
752 else:
753 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
754 if 'all' in enforced_os:
755 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000756 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000757 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000758 self.config_content = None
759
760 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000761 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000762 config_dict = {}
763 self.config_content = content
764 try:
765 exec(content, config_dict)
766 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000767 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000769 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000770 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000771 if s['name'] in tree:
772 raise gclient_utils.Error(
773 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000774 self._dependencies.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000775 self, s['name'], s['url'],
776 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000777 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000778 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000779 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000780 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000781 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000782 except KeyError:
783 raise gclient_utils.Error('Invalid .gclient file. Solution is '
784 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000785 # .gclient can have hooks.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000786 self._deps_hooks = config_dict.get('hooks', [])
787 self._deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000788
789 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000790 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000791 self._options.config_filename),
792 self.config_content)
793
794 @staticmethod
795 def LoadCurrentConfig(options):
796 """Searches for and loads a .gclient file relative to the current working
797 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000798 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000799 if not path:
800 return None
801 client = GClient(path, options)
802 client.SetConfig(gclient_utils.FileRead(
803 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000804 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000805
nsylvain@google.comefc80932011-05-31 21:27:56 +0000806 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000807 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000808 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
809 'solution_name': solution_name,
810 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000811 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000812 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000813 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000814 })
815
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000816 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000817 """Creates a .gclient_entries file to record the list of unique checkouts.
818
819 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000820 """
821 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
822 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000823 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000824 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000825 # Skip over File() dependencies as we can't version them.
826 if not isinstance(entry.parsed_url, self.FileImpl):
827 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
828 pprint.pformat(entry.parsed_url))
829 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000830 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000831 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000832 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000833
834 def _ReadEntries(self):
835 """Read the .gclient_entries file for the given client.
836
837 Returns:
838 A sequence of solution names, which will be empty if there is the
839 entries file hasn't been created yet.
840 """
841 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000842 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000843 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000844 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000845 try:
846 exec(gclient_utils.FileRead(filename), scope)
847 except SyntaxError, e:
848 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000849 return scope['entries']
850
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000851 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000852 """Checks for revision overrides."""
853 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000854 if self._options.head:
855 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000856 # Do not check safesync_url if one or more --revision flag is specified.
857 if not self._options.revisions:
858 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000859 if not s.managed:
860 self._options.revisions.append('%s@unmanaged' % s.name)
861 elif s.safesync_url:
862 handle = urllib.urlopen(s.safesync_url)
863 rev = handle.read().strip()
864 handle.close()
865 if len(rev):
866 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000867 if not self._options.revisions:
868 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000869 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000870 index = 0
871 for revision in self._options.revisions:
872 if not '@' in revision:
873 # Support for --revision 123
874 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000875 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000876 if not sol in solutions_names:
877 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
878 print >> sys.stderr, ('Please fix your script, having invalid '
879 '--revision flags will soon considered an error.')
880 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000881 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000882 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000883 return revision_overrides
884
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000885 def RunOnDeps(self, command, args):
886 """Runs a command on each dependency in a client and its dependencies.
887
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888 Args:
889 command: The command to use (e.g., 'status' or 'diff')
890 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000891 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000892 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000893 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000894 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000895 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000896 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000897 if (command in ('update', 'revert') and sys.stdout.isatty() and not
898 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000899 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000900 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000901 for s in self.dependencies:
902 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000903 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000904
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 # Once all the dependencies have been processed, it's now safe to run the
906 # hooks.
907 if not self._options.nohooks:
908 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909
910 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000911 # Notify the user if there is an orphaned entry in their working copy.
912 # Only delete the directory if there are no changes in it, and
913 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000914 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000915 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000916 if not prev_url:
917 # entry must have been overridden via .gclient custom_deps
918 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000919 # Fix path separator on Windows.
920 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000921 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000922 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000923 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000924 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000925 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000926 scm.status(self._options, [], file_list)
927 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000928 if (not self._options.delete_unversioned_trees or
929 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000930 # There are modified files in this entry. Keep warning until
931 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000932 print(('\nWARNING: \'%s\' is no longer part of this client. '
933 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000934 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000935 else:
936 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000937 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000938 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000939 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000940 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000941 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000942 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943
944 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000945 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000946 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000947 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000948 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000949 for s in self.dependencies:
950 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000951 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000953 def GetURLAndRev(dep):
954 """Returns the revision-qualified SCM url for a Dependency."""
955 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000956 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000957 if isinstance(dep.parsed_url, self.FileImpl):
958 original_url = dep.parsed_url.file_location
959 else:
960 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000961 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000962 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000963 if not os.path.isdir(scm.checkout_path):
964 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000965 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000966
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000967 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000968 new_gclient = ''
969 # First level at .gclient
970 for d in self.dependencies:
971 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000972 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000974 for d in dep.dependencies:
975 entries[d.name] = GetURLAndRev(d)
976 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000977 GrabDeps(d)
978 custom_deps = []
979 for k in sorted(entries.keys()):
980 if entries[k]:
981 # Quotes aren't escaped...
982 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
983 else:
984 custom_deps.append(' \"%s\": None,\n' % k)
985 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
986 'solution_name': d.name,
987 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000988 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000989 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000990 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000991 'solution_deps': ''.join(custom_deps),
992 }
993 # Print the snapshot configuration file
994 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000995 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000996 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000997 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000998 if self._options.actual:
999 entries[d.name] = GetURLAndRev(d)
1000 else:
1001 entries[d.name] = d.parsed_url
1002 keys = sorted(entries.keys())
1003 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001004 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001005 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001007 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001008 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001009 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001010
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001011 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001012 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001013 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001014 return self._root_dir
1015
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001016 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001017 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001018 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001019 return self._enforced_os
1020
maruel@chromium.org68988972011-09-20 14:11:42 +00001021 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001022 def recursion_limit(self):
1023 """How recursive can each dependencies in DEPS file can load DEPS file."""
1024 return self._recursion_limit
1025
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001026
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001027#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028
1029
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001030def CMDcleanup(parser, args):
1031 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001032
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001033Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001034"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001035 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1036 help='override deps for the specified (comma-separated) '
1037 'platform(s); \'all\' will process all deps_os '
1038 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001039 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001040 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001041 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001042 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001043 if options.verbose:
1044 # Print out the .gclient file. This is longer than if we just printed the
1045 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001046 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 return client.RunOnDeps('cleanup', args)
1048
1049
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001050@attr('usage', '[command] [args ...]')
1051def CMDrecurse(parser, args):
1052 """Operates on all the entries.
1053
1054 Runs a shell command on all entries.
1055 """
1056 # Stop parsing at the first non-arg so that these go through to the command
1057 parser.disable_interspersed_args()
1058 parser.add_option('-s', '--scm', action='append', default=[],
1059 help='choose scm types to operate upon')
1060 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001061 if not args:
1062 print >> sys.stderr, 'Need to supply a command!'
1063 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001064 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1065 if not root_and_entries:
1066 print >> sys.stderr, (
1067 'You need to run gclient sync at least once to use \'recurse\'.\n'
1068 'This is because .gclient_entries needs to exist and be up to date.')
1069 return 1
1070 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001071 scm_set = set()
1072 for scm in options.scm:
1073 scm_set.update(scm.split(','))
1074
1075 # Pass in the SCM type as an env variable
1076 env = os.environ.copy()
1077
1078 for path, url in entries.iteritems():
1079 scm = gclient_scm.GetScmName(url)
1080 if scm_set and scm not in scm_set:
1081 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001082 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001083 if scm:
1084 env['GCLIENT_SCM'] = scm
1085 if url:
1086 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001087 if os.path.isdir(cwd):
1088 subprocess2.call(args, cwd=cwd, env=env)
1089 else:
1090 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001091 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001092
1093
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001094@attr('usage', '[url] [safesync url]')
1095def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001096 """Create a .gclient file in the current directory.
1097
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001098This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001099top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001101provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001102URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001103"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001104 parser.add_option('--spec',
1105 help='create a gclient file containing the provided '
1106 'string. Due to Cygwin/Python brokenness, it '
1107 'probably can\'t contain any newlines.')
1108 parser.add_option('--name',
1109 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001110 parser.add_option('--deps-file', default='DEPS',
1111 help='overrides the default name for the DEPS file for the'
1112 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001113 parser.add_option('--unmanaged', action='store_true', default=False,
1114 help='overrides the default behavior to make it possible '
1115 'to have the main solution untouched by gclient '
1116 '(gclient will check out unmanaged dependencies but '
1117 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001118 parser.add_option('--git-deps', action='store_true',
1119 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001120 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001121 if ((options.spec and args) or len(args) > 2 or
1122 (not options.spec and not args)):
1123 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1124
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001125 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001126 if options.spec:
1127 client.SetConfig(options.spec)
1128 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001129 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001130 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001131 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001132 if name.endswith('.git'):
1133 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001134 else:
1135 # specify an alternate relpath for the given URL.
1136 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001137 deps_file = options.deps_file
1138 if options.git_deps:
1139 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001140 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001141 if len(args) > 1:
1142 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001143 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1144 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001145 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001146 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001147
1148
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001149@attr('epilog', """Example:
1150 gclient pack > patch.txt
1151 generate simple patch for configured client and dependences
1152""")
1153def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001154 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001155
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001156Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001157dependencies, and performs minimal postprocessing of the output. The
1158resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001160"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001161 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1162 help='override deps for the specified (comma-separated) '
1163 'platform(s); \'all\' will process all deps_os '
1164 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001166 client = GClient.LoadCurrentConfig(options)
1167 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001168 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001169 if options.verbose:
1170 # Print out the .gclient file. This is longer than if we just printed the
1171 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001172 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001173 return client.RunOnDeps('pack', args)
1174
1175
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176def CMDstatus(parser, args):
1177 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001178 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1179 help='override deps for the specified (comma-separated) '
1180 'platform(s); \'all\' will process all deps_os '
1181 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001183 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001184 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001185 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 if options.verbose:
1187 # Print out the .gclient file. This is longer than if we just printed the
1188 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001189 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001190 return client.RunOnDeps('status', args)
1191
1192
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001194 gclient sync
1195 update files from SCM according to current configuration,
1196 *for modules which have changed since last update or sync*
1197 gclient sync --force
1198 update files from SCM according to current configuration, for
1199 all modules (useful for recovering files deleted from local copy)
1200 gclient sync --revision src@31000
1201 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001202""")
1203def CMDsync(parser, args):
1204 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001205 parser.add_option('-f', '--force', action='store_true',
1206 help='force update even for unchanged modules')
1207 parser.add_option('-n', '--nohooks', action='store_true',
1208 help='don\'t run hooks after the update is complete')
1209 parser.add_option('-r', '--revision', action='append',
1210 dest='revisions', metavar='REV', default=[],
1211 help='Enforces revision/hash for the solutions with the '
1212 'format src@rev. The src@ part is optional and can be '
1213 'skipped. -r can be used multiple times when .gclient '
1214 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001215 'if the src@ part is skipped. Note that specifying '
1216 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001217 parser.add_option('-t', '--transitive', action='store_true',
1218 help='When a revision is specified (in the DEPS file or '
1219 'with the command-line flag), transitively update '
1220 'the dependencies to the date of the given revision. '
1221 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001222 parser.add_option('-H', '--head', action='store_true',
1223 help='skips any safesync_urls specified in '
1224 'configured solutions and sync to head instead')
1225 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001226 help='delete any dependency that have been removed from '
1227 'last sync as long as there is no local modification. '
1228 'Coupled with --force, it will remove them even with '
1229 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001230 parser.add_option('-R', '--reset', action='store_true',
1231 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001232 parser.add_option('-M', '--merge', action='store_true',
1233 help='merge upstream changes instead of trying to '
1234 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001235 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1236 help='override deps for the specified (comma-separated) '
1237 'platform(s); \'all\' will process all deps_os '
1238 'references')
1239 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1240 help='Skip svn up whenever possible by requesting '
1241 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001243 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244
1245 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001246 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001247
maruel@chromium.org307d1792010-05-31 20:03:13 +00001248 if options.revisions and options.head:
1249 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001250 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251
1252 if options.verbose:
1253 # Print out the .gclient file. This is longer than if we just printed the
1254 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001255 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001256 return client.RunOnDeps('update', args)
1257
1258
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001259def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001260 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001261 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001262
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001263def CMDdiff(parser, args):
1264 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001265 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1266 help='override deps for the specified (comma-separated) '
1267 'platform(s); \'all\' will process all deps_os '
1268 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001269 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001270 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001271 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001272 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001273 if options.verbose:
1274 # Print out the .gclient file. This is longer than if we just printed the
1275 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001276 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277 return client.RunOnDeps('diff', args)
1278
1279
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001280def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001281 """Revert all modifications in every dependencies.
1282
1283 That's the nuclear option to get back to a 'clean' state. It removes anything
1284 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001285 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1286 help='override deps for the specified (comma-separated) '
1287 'platform(s); \'all\' will process all deps_os '
1288 'references')
1289 parser.add_option('-n', '--nohooks', action='store_true',
1290 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001291 (options, args) = parser.parse_args(args)
1292 # --force is implied.
1293 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001294 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001295 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001296 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001297 return client.RunOnDeps('revert', args)
1298
1299
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001300def CMDrunhooks(parser, args):
1301 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001302 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1303 help='override deps for the specified (comma-separated) '
1304 'platform(s); \'all\' will process all deps_os '
1305 'references')
1306 parser.add_option('-f', '--force', action='store_true', default=True,
1307 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001309 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001310 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001311 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001312 if options.verbose:
1313 # Print out the .gclient file. This is longer than if we just printed the
1314 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001315 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001316 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001317 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 return client.RunOnDeps('runhooks', args)
1319
1320
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001321def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001322 """Output revision info mapping for the client and its dependencies.
1323
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001324 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001325 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001326 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1327 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001328 commit can change.
1329 """
1330 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1331 help='override deps for the specified (comma-separated) '
1332 'platform(s); \'all\' will process all deps_os '
1333 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001334 parser.add_option('-a', '--actual', action='store_true',
1335 help='gets the actual checked out revisions instead of the '
1336 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001337 parser.add_option('-s', '--snapshot', action='store_true',
1338 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001339 'version of all repositories to reproduce the tree, '
1340 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001341 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001342 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001343 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001344 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001345 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001346 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001347
1348
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001349def Command(name):
1350 return getattr(sys.modules[__name__], 'CMD' + name, None)
1351
1352
1353def CMDhelp(parser, args):
1354 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001355 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001356 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001357 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001358 parser.print_help()
1359 return 0
1360
1361
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001362def GenUsage(parser, command):
1363 """Modify an OptParse object with the function's documentation."""
1364 obj = Command(command)
1365 if command == 'help':
1366 command = '<command>'
1367 # OptParser.description prefer nicely non-formatted strings.
1368 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1369 usage = getattr(obj, 'usage', '')
1370 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1371 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001372
1373
maruel@chromium.org0895b752011-08-26 20:40:33 +00001374def Parser():
1375 """Returns the default parser."""
1376 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001377 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001378 help='Specify how many SCM commands can run in parallel; '
1379 'default=%default')
1380 parser.add_option('-v', '--verbose', action='count', default=0,
1381 help='Produces additional output for diagnostics. Can be '
1382 'used up to three times for more logging info.')
1383 parser.add_option('--gclientfile', dest='config_filename',
1384 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1385 help='Specify an alternate %default file')
1386 # Integrate standard options processing.
1387 old_parser = parser.parse_args
1388 def Parse(args):
1389 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001390 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1391 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001392 logging.basicConfig(level=level,
1393 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1394 options.entries_filename = options.config_filename + '_entries'
1395 if options.jobs < 1:
1396 parser.error('--jobs must be 1 or higher')
1397
1398 # These hacks need to die.
1399 if not hasattr(options, 'revisions'):
1400 # GClient.RunOnDeps expects it even if not applicable.
1401 options.revisions = []
1402 if not hasattr(options, 'head'):
1403 options.head = None
1404 if not hasattr(options, 'nohooks'):
1405 options.nohooks = True
1406 if not hasattr(options, 'deps_os'):
1407 options.deps_os = None
1408 if not hasattr(options, 'manually_grab_svn_rev'):
1409 options.manually_grab_svn_rev = None
1410 if not hasattr(options, 'force'):
1411 options.force = None
1412 return (options, args)
1413 parser.parse_args = Parse
1414 # We don't want wordwrapping in epilog (usually examples)
1415 parser.format_epilog = lambda _: parser.epilog or ''
1416 return parser
1417
1418
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001419def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001420 """Doesn't parse the arguments here, just find the right subcommand to
1421 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001422 if sys.hexversion < 0x02050000:
1423 print >> sys.stderr, (
1424 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001425 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001426 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1427 # operations. Python as a strong tendency to buffer sys.stdout.
1428 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001429 # Make stdout annotated with the thread ids.
1430 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001431 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001432 # Unused variable 'usage'
1433 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001434 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1435 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1436 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001437 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001438 if argv:
1439 command = Command(argv[0])
1440 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001441 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001442 GenUsage(parser, argv[0])
1443 return command(parser, argv[1:])
1444 # Not a known command. Default to help.
1445 GenUsage(parser, 'help')
1446 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001447 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001448 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001449 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001450
1451
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001452if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001453 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001454 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001455
1456# vim: ts=2:sw=2:tw=80:et: