blob: 6962c9e26d3725fd93602534d946d8fad031fcbc [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
phajdan.jr@chromium.org6e043f72011-05-02 07:24:32 +000052__version__ = "0.6.2"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
maruel@chromium.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000075 """Sets an attribute on a function."""
76 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 return fn
79 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082## GClient implementation.
83
84
maruel@chromium.org116704f2010-06-11 17:34:38 +000085class GClientKeywords(object):
86 class FromImpl(object):
87 """Used to implement the From() syntax."""
88
89 def __init__(self, module_name, sub_target_name=None):
90 """module_name is the dep module we want to include from. It can also be
91 the name of a subdirectory to include from.
92
93 sub_target_name is an optional parameter if the module name in the other
94 DEPS file is different. E.g., you might want to map src/net to net."""
95 self.module_name = module_name
96 self.sub_target_name = sub_target_name
97
98 def __str__(self):
99 return 'From(%s, %s)' % (repr(self.module_name),
100 repr(self.sub_target_name))
101
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000104 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105
106 def __init__(self, file_location):
107 self.file_location = file_location
108
109 def __str__(self):
110 return 'File("%s")' % self.file_location
111
112 def GetPath(self):
113 return os.path.split(self.file_location)[0]
114
115 def GetFilename(self):
116 rev_tokens = self.file_location.split('@')
117 return os.path.split(rev_tokens[0])[1]
118
119 def GetRevision(self):
120 rev_tokens = self.file_location.split('@')
121 if len(rev_tokens) > 1:
122 return rev_tokens[1]
123 return None
124
125 class VarImpl(object):
126 def __init__(self, custom_vars, local_scope):
127 self._custom_vars = custom_vars
128 self._local_scope = local_scope
129
130 def Lookup(self, var_name):
131 """Implements the Var syntax."""
132 if var_name in self._custom_vars:
133 return self._custom_vars[var_name]
134 elif var_name in self._local_scope.get("vars", {}):
135 return self._local_scope["vars"][var_name]
136 raise gclient_utils.Error("Var is not defined: %s" % var_name)
137
138
maruel@chromium.org064186c2011-09-27 23:53:33 +0000139class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000140 """Immutable configuration settings."""
141 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000142 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000143 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000144 GClientKeywords.__init__(self)
145
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000146 # These are not mutable:
147 self._parent = parent
148 self._safesync_url = safesync_url
149 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000150 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000151 # 'managed' determines whether or not this dependency is synced/updated by
152 # gclient after gclient checks it out initially. The difference between
153 # 'managed' and 'should_process' is that the user specifies 'managed' via
154 # the --unmanaged command-line flag or a .gclient config, where
155 # 'should_process' is dynamically set by gclient if it goes over its
156 # recursion limit and controls gclient's behavior so it does not misbehave.
157 self._managed = managed
158 self._should_process = should_process
159
160 # These are only set in .gclient and not in DEPS files.
161 self._custom_vars = custom_vars or {}
162 self._custom_deps = custom_deps or {}
163
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 # Post process the url to remove trailing slashes.
165 if isinstance(self._url, basestring):
166 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
167 # it to proto://host/path@rev.
168 if self._url.count('@') > 1:
169 raise gclient_utils.Error('Invalid url "%s"' % self._url)
170 self._url = self._url.replace('/@', '@')
171 elif not isinstance(self._url,
172 (self.FromImpl, self.FileImpl, None.__class__)):
173 raise gclient_utils.Error(
174 ('dependency url must be either a string, None, '
175 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 if '/' in self._deps_file or '\\' in self._deps_file:
177 raise gclient_utils.Error('deps_file name must not be a path, just a '
178 'filename. %s' % self._deps_file)
179
180 @property
181 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 return self._deps_file
183
184 @property
185 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 return self._managed
187
188 @property
189 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 return self._parent
191
192 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000193 def root(self):
194 """Returns the root node, a GClient object."""
195 if not self.parent:
196 # This line is to signal pylint that it could be a GClient instance.
197 return self or GClient(None, None)
198 return self.parent.root
199
200 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000201 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 return self._safesync_url
203
204 @property
205 def should_process(self):
206 """True if this dependency should be processed, i.e. checked out."""
207 return self._should_process
208
209 @property
210 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 return self._custom_vars.copy()
212
213 @property
214 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 return self._custom_deps.copy()
216
maruel@chromium.org064186c2011-09-27 23:53:33 +0000217 @property
218 def url(self):
219 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000221 @property
222 def recursion_limit(self):
223 """Returns > 0 if this dependency is not too recursed to be processed."""
224 return max(self.parent.recursion_limit - 1, 0)
225
226 def get_custom_deps(self, name, url):
227 """Returns a custom deps if applicable."""
228 if self.parent:
229 url = self.parent.get_custom_deps(name, url)
230 # None is a valid return value to disable a dependency.
231 return self.custom_deps.get(name, url)
232
maruel@chromium.org064186c2011-09-27 23:53:33 +0000233
234class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000235 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000236
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000237 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000238 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000239 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000243
244 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000246
247 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000249 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250 # A cache of the files affected by the current operation, necessary for
251 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000252 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000253 # If it is not set to True, the dependency wasn't processed for its child
254 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000256 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000260
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 # Setup self.requirements and find any other dependency who would have self
262 # as a requirement.
maruel@chromium.org98023df2011-09-07 18:44:47 +0000263
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000264 # self.parent is implicitly a requirement. This will be recursive by
265 # definition.
266 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000267 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000268
269 # For a tree with at least 2 levels*, the leaf node needs to depend
270 # on the level higher up in an orderly way.
271 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
272 # thus unsorted, while the .gclient format is a list thus sorted.
273 #
274 # * _recursion_limit is hard coded 2 and there is no hope to change this
275 # value.
276 #
277 # Interestingly enough, the following condition only works in the case we
278 # want: self is a 2nd level node. 3nd level node wouldn't need this since
279 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000280 root_deps = self.root.dependencies
281 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000282 for i in root_deps:
283 if i is self.parent:
284 break
285 if i.name:
286 self._requirements.add(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287
288 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000289 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000290
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000291 if self.name and self.should_process:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000292 for obj in self.root.depth_first_tree():
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000293 if obj is self or not obj.name:
294 continue
295 # Step 1: Find any requirements self may need.
296 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000297 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000298 # Step 2: Find any requirements self may impose.
299 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000300 try:
301 # Access to a protected member _requirements of a client class
302 # pylint: disable=W0212
303 obj.lock.acquire()
304 obj._requirements.add(self.name)
305 finally:
306 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000307
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 if not self.name and self.parent:
309 raise gclient_utils.Error('Dependency without name')
310
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000311 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000312 """Resolves the parsed url from url.
313
314 Manages From() keyword accordingly. Do not touch self.parsed_url nor
315 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000316 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000317 overriden_url = self.get_custom_deps(self.name, url)
318 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000319 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000320 overriden_url))
321 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000322 elif isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000323 ref = [
324 dep for dep in self.root.subtree(True) if url.module_name == dep.name
325 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000326 if not ref:
327 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
328 url.module_name, ref))
329 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000330 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000331 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000332 # Make sure the referenced dependency DEPS file is loaded and file the
333 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000334 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000335 found_dep = None
336 for d in ref.dependencies:
337 if d.name == sub_target:
338 found_dep = d
339 break
340 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000341 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000342 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
343 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000344 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000345
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000346 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000347 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000348 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000349 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000350 elif isinstance(url, basestring):
351 parsed_url = urlparse.urlparse(url)
352 if not parsed_url[0]:
353 # A relative url. Fetch the real base.
354 path = parsed_url[2]
355 if not path.startswith('/'):
356 raise gclient_utils.Error(
357 'relative DEPS entry \'%s\' must begin with a slash' % url)
358 # Create a scm just to query the full url.
359 parent_url = self.parent.parsed_url
360 if isinstance(parent_url, self.FileImpl):
361 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000362 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000363 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000364 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000366 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000367 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000369 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000370 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000371 return parsed_url
372 elif url is None:
373 return None
374 else:
375 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000377 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000378 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000379 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000380 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000381 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000383 # One thing is unintuitive, vars= {} must happen before Var() use.
384 local_scope = {}
385 var = self.VarImpl(self.custom_vars, local_scope)
386 global_scope = {
387 'File': self.FileImpl,
388 'From': self.FromImpl,
389 'Var': var.Lookup,
390 'deps_os': {},
391 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000392 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000393 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000394 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
395 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000396 else:
397 deps_content = gclient_utils.FileRead(filepath)
398 logging.debug(deps_content)
399 # Eval the content.
400 try:
401 exec(deps_content, global_scope, local_scope)
402 except SyntaxError, e:
403 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000404 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000405 # load os specific dependencies if defined. these dependencies may
406 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000407 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000408 enforced_os = self.root.enforced_os
409 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000410 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000411 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000412 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000413 # platform, so we collect the broadest set of dependencies
414 # available. We may end up with the wrong revision of something for
415 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000416 deps.update([x for x in os_deps.items() if not x[0] in deps])
417 else:
418 deps.update(os_deps)
419
maruel@chromium.org064186c2011-09-27 23:53:33 +0000420 self._deps_hooks.extend(local_scope.get('hooks', []))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000421
422 # If a line is in custom_deps, but not in the solution, we want to append
423 # this line to the solution.
424 for d in self.custom_deps:
425 if d not in deps:
426 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000427
428 # If use_relative_paths is set in the DEPS file, regenerate
429 # the dictionary using paths relative to the directory containing
430 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000431 use_relative_paths = local_scope.get('use_relative_paths', False)
432 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000433 rel_deps = {}
434 for d, url in deps.items():
435 # normpath is required to allow DEPS to use .. in their
436 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000437 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
438 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000439
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000440 # Convert the deps into real Dependency.
441 for name, url in deps.iteritems():
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000442 if name in [s.name for s in self._dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000443 raise gclient_utils.Error(
444 'The same name "%s" appears multiple times in the deps section' %
445 name)
maruel@chromium.org68988972011-09-20 14:11:42 +0000446 should_process = self.recursion_limit and self.should_process
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000447 if should_process:
maruel@chromium.org68988972011-09-20 14:11:42 +0000448 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000449 if name in tree:
450 if url == tree[name].url:
451 logging.info('Won\'t process duplicate dependency %s' % tree[name])
452 # In theory we could keep it as a shadow of the other one. In
453 # practice, simply ignore it.
454 #should_process = False
455 continue
456 else:
457 raise gclient_utils.Error(
458 'Dependency %s specified more than once:\n %s\nvs\n %s' %
459 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000460 self._dependencies.append(
461 Dependency(
462 self, name, url, None, None, None, None,
463 self.deps_file, should_process))
maruel@chromium.org064186c2011-09-27 23:53:33 +0000464 self._deps_parsed = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000465 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000466
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000467 # Arguments number differs from overridden method
468 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000469 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000470 """Runs 'command' before parsing the DEPS in case it's a initial checkout
471 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000472
473 def maybeGetParentRevision(options):
474 """If we are performing an update and --transitive is set, set the
475 revision to the parent's revision. If we have an explicit revision
476 do nothing."""
477 if command == 'update' and options.transitive and not options.revision:
478 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
479 if not revision:
480 options.revision = revision_overrides.get(self.parent.name)
481 if options.verbose and options.revision:
482 print("Using parent's revision date: %s" % options.revision)
483 # If the parent has a revision override, then it must have been
484 # converted to date format.
485 assert (not options.revision or
486 gclient_utils.IsDateRevision(options.revision))
487
488 def maybeConvertToDateRevision(options):
489 """If we are performing an update and --transitive is set, convert the
490 revision to a date-revision (if necessary). Instead of having
491 -r 101 replace the revision with the time stamp of 101 (e.g.
492 "{2011-18-04}").
493 This way dependencies are upgraded to the revision they had at the
494 check-in of revision 101."""
495 if (command == 'update' and
496 options.transitive and
497 options.revision and
498 not gclient_utils.IsDateRevision(options.revision)):
499 revision_date = scm.GetRevisionDate(options.revision)
500 revision = gclient_utils.MakeDateRevision(revision_date)
501 if options.verbose:
502 print("Updating revision override from %s to %s." %
503 (options.revision, revision))
504 revision_overrides[self.name] = revision
505
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000506 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000507 if not self.should_process:
508 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509 # When running runhooks, there's no need to consult the SCM.
510 # All known hooks are expected to run unconditionally regardless of working
511 # copy state, so skip the SCM status check.
512 run_scm = command not in ('runhooks', None)
maruel@chromium.org064186c2011-09-27 23:53:33 +0000513 self._parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 if run_scm and self.parsed_url:
515 if isinstance(self.parsed_url, self.FileImpl):
516 # Special support for single-file checkout.
517 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
518 options.revision = self.parsed_url.GetRevision()
519 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000520 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000521 self.name)
522 scm.RunCommand('updatesingle', options,
523 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000524 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000525 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000526 # Create a shallow copy to mutate revision.
527 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000528 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000529 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000530 scm = gclient_scm.CreateSCM(
531 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000532 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000533 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000534 self._file_list = [os.path.join(self.name, f.strip())
535 for f in self._file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000536
537 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
538 # Convert all absolute paths to relative.
539 for i in range(len(self._file_list)):
540 # It depends on the command being executed (like runhooks vs sync).
541 if not os.path.isabs(self._file_list[i]):
542 continue
543 prefix = os.path.commonprefix(
544 [self.root.root_dir.lower(), self._file_list[i].lower()])
545 self._file_list[i] = self._file_list[i][len(prefix):]
546 # Strip any leading path separators.
547 while (self._file_list[i].startswith('\\') or
548 self._file_list[i].startswith('/')):
549 self._file_list[i] = self._file_list[i][1:]
maruel@chromium.org064186c2011-09-27 23:53:33 +0000550 self._processed = True
maruel@chromium.org68988972011-09-20 14:11:42 +0000551 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000552 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000553 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000554
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000555 # Parse the dependencies of this dependency.
556 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000557 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000559 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000560 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000561 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000562 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000563 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000564 # Don't run the hook when it is above recursion_limit.
565 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000566 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000567 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000568 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000569 # TODO(maruel): If the user is using git or git-svn, then we don't know
570 # what files have changed so we always run all hooks. It'd be nice to fix
571 # that.
572 if (options.force or
573 isinstance(self.parsed_url, self.FileImpl) or
574 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000575 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000576 for hook_dict in self.deps_hooks:
577 self._RunHookAction(hook_dict, [])
578 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000579 # Run hooks on the basis of whether the files from the gclient operation
580 # match each hook's pattern.
581 for hook_dict in self.deps_hooks:
582 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org68988972011-09-20 14:11:42 +0000583 matching_file_list = [f for f in self.file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000584 if matching_file_list:
585 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000586 for s in self.dependencies:
587 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000588
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000589 def _RunHookAction(self, hook_dict, matching_file_list):
590 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000591 # A single DEPS file can specify multiple hooks so this function can be
592 # called multiple times on a single Dependency.
593 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000594 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000595 logging.debug(hook_dict)
596 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000597 command = hook_dict['action'][:]
598 if command[0] == 'python':
599 # If the hook specified "python" as the first item, the action is a
600 # Python script. Run it by starting a new copy of the same
601 # interpreter.
602 command[0] = sys.executable
603
604 if '$matching_files' in command:
605 splice_index = command.index('$matching_files')
606 command[splice_index:splice_index + 1] = matching_file_list
607
maruel@chromium.org17d01792010-09-01 18:07:10 +0000608 try:
609 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000610 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000611 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000612 # Use a discrete exit status code of 2 to indicate that a hook action
613 # failed. Users of this script may wish to treat hook action failures
614 # differently from VC failures.
615 print >> sys.stderr, 'Error: %s' % str(e)
616 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000617
maruel@chromium.org0d812442010-08-10 12:41:08 +0000618 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000619 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000620 dependencies = self.dependencies
621 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000622 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000623 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000624 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000625 for i in d.subtree(include_all):
626 yield i
627
628 def depth_first_tree(self):
629 """Depth-first recursion including the root node."""
630 yield self
631 for i in self.dependencies:
632 for j in i.depth_first_tree():
633 if j.should_process:
634 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000635
maruel@chromium.org68988972011-09-20 14:11:42 +0000636 @property
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000637 def dependencies(self):
638 return tuple(self._dependencies)
639
640 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000641 def deps_hooks(self):
642 return tuple(self._deps_hooks)
643
644 @property
645 def parsed_url(self):
646 return self._parsed_url
647
648 @property
649 def deps_parsed(self):
650 return self._deps_parsed
651
652 @property
653 def processed(self):
654 return self._processed
655
656 @property
657 def hooks_ran(self):
658 return self._hooks_ran
659
660 @property
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000661 def file_list(self):
662 result = self._file_list[:]
663 for d in self.dependencies:
maruel@chromium.org68988972011-09-20 14:11:42 +0000664 result.extend(d.file_list)
665 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000666
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000667 def __str__(self):
668 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000669 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000670 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000671 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000672 # First try the native property if it exists.
673 if hasattr(self, '_' + i):
674 value = getattr(self, '_' + i, False)
675 else:
676 value = getattr(self, i, False)
677 if value:
678 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000679
680 for d in self.dependencies:
681 out.extend([' ' + x for x in str(d).splitlines()])
682 out.append('')
683 return '\n'.join(out)
684
685 def __repr__(self):
686 return '%s: %s' % (self.name, self.url)
687
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000688 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000689 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000690 out = '%s(%s)' % (self.name, self.url)
691 i = self.parent
692 while i and i.name:
693 out = '%s(%s) -> %s' % (i.name, i.url, out)
694 i = i.parent
695 return out
696
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000697
698class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000699 """Object that represent a gclient checkout. A tree of Dependency(), one per
700 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000701
702 DEPS_OS_CHOICES = {
703 "win32": "win",
704 "win": "win",
705 "cygwin": "win",
706 "darwin": "mac",
707 "mac": "mac",
708 "unix": "unix",
709 "linux": "unix",
710 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000711 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000712 }
713
714 DEFAULT_CLIENT_FILE_TEXT = ("""\
715solutions = [
716 { "name" : "%(solution_name)s",
717 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000718 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000719 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000720 "custom_deps" : {
721 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000722 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000723 },
724]
725""")
726
727 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
728 { "name" : "%(solution_name)s",
729 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000730 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000731 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000732 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000733%(solution_deps)s },
734 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000735 },
736""")
737
738 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
739# Snapshot generated with gclient revinfo --snapshot
740solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000741%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742""")
743
744 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000745 # Do not change previous behavior. Only solution level and immediate DEPS
746 # are processed.
747 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000748 Dependency.__init__(self, None, None, None, None, True, None, None,
749 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000750 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000751 if options.deps_os:
752 enforced_os = options.deps_os.split(',')
753 else:
754 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
755 if 'all' in enforced_os:
756 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000757 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000758 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000759 self.config_content = None
760
761 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000762 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000763 config_dict = {}
764 self.config_content = content
765 try:
766 exec(content, config_dict)
767 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000768 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000769 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000770 try:
maruel@chromium.org68988972011-09-20 14:11:42 +0000771 tree = dict((d.name, d) for d in self.root.subtree(False))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000772 if s['name'] in tree:
773 raise gclient_utils.Error(
774 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000775 self._dependencies.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000776 self, s['name'], s['url'],
777 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000778 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000779 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000780 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000781 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000782 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000783 except KeyError:
784 raise gclient_utils.Error('Invalid .gclient file. Solution is '
785 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000786 # .gclient can have hooks.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000787 self._deps_hooks = config_dict.get('hooks', [])
788 self._deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000789
790 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000791 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000792 self._options.config_filename),
793 self.config_content)
794
795 @staticmethod
796 def LoadCurrentConfig(options):
797 """Searches for and loads a .gclient file relative to the current working
798 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000799 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000800 if not path:
801 return None
802 client = GClient(path, options)
803 client.SetConfig(gclient_utils.FileRead(
804 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000805 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000806
nsylvain@google.comefc80932011-05-31 21:27:56 +0000807 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000808 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000809 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
810 'solution_name': solution_name,
811 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000812 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000813 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000814 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000815 })
816
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000817 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000818 """Creates a .gclient_entries file to record the list of unique checkouts.
819
820 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000821 """
822 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
823 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000825 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000826 # Skip over File() dependencies as we can't version them.
827 if not isinstance(entry.parsed_url, self.FileImpl):
828 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
829 pprint.pformat(entry.parsed_url))
830 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000831 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000832 logging.info(result)
833 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000834
835 def _ReadEntries(self):
836 """Read the .gclient_entries file for the given client.
837
838 Returns:
839 A sequence of solution names, which will be empty if there is the
840 entries file hasn't been created yet.
841 """
842 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000843 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000844 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000845 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000846 try:
847 exec(gclient_utils.FileRead(filename), scope)
848 except SyntaxError, e:
849 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000850 return scope['entries']
851
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000852 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000853 """Checks for revision overrides."""
854 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000855 if self._options.head:
856 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000857 # Do not check safesync_url if one or more --revision flag is specified.
858 if not self._options.revisions:
859 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000860 if not s.managed:
861 self._options.revisions.append('%s@unmanaged' % s.name)
862 elif s.safesync_url:
863 handle = urllib.urlopen(s.safesync_url)
864 rev = handle.read().strip()
865 handle.close()
866 if len(rev):
867 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000868 if not self._options.revisions:
869 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000870 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000871 index = 0
872 for revision in self._options.revisions:
873 if not '@' in revision:
874 # Support for --revision 123
875 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000876 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000877 if not sol in solutions_names:
878 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
879 print >> sys.stderr, ('Please fix your script, having invalid '
880 '--revision flags will soon considered an error.')
881 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000882 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000883 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000884 return revision_overrides
885
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886 def RunOnDeps(self, command, args):
887 """Runs a command on each dependency in a client and its dependencies.
888
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 Args:
890 command: The command to use (e.g., 'status' or 'diff')
891 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000892 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000893 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000894 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000895 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000896 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000897 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000898 if (command in ('update', 'revert') and sys.stdout.isatty() and not
899 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000900 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000901 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000902 for s in self.dependencies:
903 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000904 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000905
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000906 # Once all the dependencies have been processed, it's now safe to run the
907 # hooks.
908 if not self._options.nohooks:
909 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000910
911 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000912 # Notify the user if there is an orphaned entry in their working copy.
913 # Only delete the directory if there are no changes in it, and
914 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000915 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000916 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000917 if not prev_url:
918 # entry must have been overridden via .gclient custom_deps
919 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000920 # Fix path separator on Windows.
921 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000922 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000923 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000924 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000925 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000926 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000927 scm.status(self._options, [], file_list)
928 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000929 if (not self._options.delete_unversioned_trees or
930 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000931 # There are modified files in this entry. Keep warning until
932 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000933 print(('\nWARNING: \'%s\' is no longer part of this client. '
934 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000935 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000936 else:
937 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000938 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000939 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000940 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000941 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000942 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000943 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944
945 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000946 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000947 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000948 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000949 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000950 for s in self.dependencies:
951 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000952 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000953
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000954 def GetURLAndRev(dep):
955 """Returns the revision-qualified SCM url for a Dependency."""
956 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000957 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000958 if isinstance(dep.parsed_url, self.FileImpl):
959 original_url = dep.parsed_url.file_location
960 else:
961 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000962 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000963 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000964 if not os.path.isdir(scm.checkout_path):
965 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000966 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000967
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000968 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000969 new_gclient = ''
970 # First level at .gclient
971 for d in self.dependencies:
972 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000973 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000974 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000975 for d in dep.dependencies:
976 entries[d.name] = GetURLAndRev(d)
977 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000978 GrabDeps(d)
979 custom_deps = []
980 for k in sorted(entries.keys()):
981 if entries[k]:
982 # Quotes aren't escaped...
983 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
984 else:
985 custom_deps.append(' \"%s\": None,\n' % k)
986 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
987 'solution_name': d.name,
988 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000989 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000990 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000991 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000992 'solution_deps': ''.join(custom_deps),
993 }
994 # Print the snapshot configuration file
995 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000996 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000997 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +0000998 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000999 if self._options.actual:
1000 entries[d.name] = GetURLAndRev(d)
1001 else:
1002 entries[d.name] = d.parsed_url
1003 keys = sorted(entries.keys())
1004 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001005 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001006 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001007
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001008 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001009 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001010 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001011
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001012 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001013 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001014 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001015 return self._root_dir
1016
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001017 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001018 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001019 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001020 return self._enforced_os
1021
maruel@chromium.org68988972011-09-20 14:11:42 +00001022 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001023 def recursion_limit(self):
1024 """How recursive can each dependencies in DEPS file can load DEPS file."""
1025 return self._recursion_limit
1026
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001027
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001028#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001029
1030
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001031def CMDcleanup(parser, args):
1032 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001033
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001034Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001035"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001036 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1037 help='override deps for the specified (comma-separated) '
1038 'platform(s); \'all\' will process all deps_os '
1039 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001040 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001041 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001043 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044 if options.verbose:
1045 # Print out the .gclient file. This is longer than if we just printed the
1046 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001047 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048 return client.RunOnDeps('cleanup', args)
1049
1050
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001051@attr('usage', '[command] [args ...]')
1052def CMDrecurse(parser, args):
1053 """Operates on all the entries.
1054
1055 Runs a shell command on all entries.
1056 """
1057 # Stop parsing at the first non-arg so that these go through to the command
1058 parser.disable_interspersed_args()
1059 parser.add_option('-s', '--scm', action='append', default=[],
1060 help='choose scm types to operate upon')
1061 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001062 if not args:
1063 print >> sys.stderr, 'Need to supply a command!'
1064 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001065 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1066 if not root_and_entries:
1067 print >> sys.stderr, (
1068 'You need to run gclient sync at least once to use \'recurse\'.\n'
1069 'This is because .gclient_entries needs to exist and be up to date.')
1070 return 1
1071 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001072 scm_set = set()
1073 for scm in options.scm:
1074 scm_set.update(scm.split(','))
1075
1076 # Pass in the SCM type as an env variable
1077 env = os.environ.copy()
1078
1079 for path, url in entries.iteritems():
1080 scm = gclient_scm.GetScmName(url)
1081 if scm_set and scm not in scm_set:
1082 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001083 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001084 if scm:
1085 env['GCLIENT_SCM'] = scm
1086 if url:
1087 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001088 if os.path.isdir(cwd):
1089 subprocess2.call(args, cwd=cwd, env=env)
1090 else:
1091 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001092 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001093
1094
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001095@attr('usage', '[url] [safesync url]')
1096def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001097 """Create a .gclient file in the current directory.
1098
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001099This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001100top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001101modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001102provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001103URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001104"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001105 parser.add_option('--spec',
1106 help='create a gclient file containing the provided '
1107 'string. Due to Cygwin/Python brokenness, it '
1108 'probably can\'t contain any newlines.')
1109 parser.add_option('--name',
1110 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001111 parser.add_option('--deps-file', default='DEPS',
1112 help='overrides the default name for the DEPS file for the'
1113 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001114 parser.add_option('--unmanaged', action='store_true', default=False,
1115 help='overrides the default behavior to make it possible '
1116 'to have the main solution untouched by gclient '
1117 '(gclient will check out unmanaged dependencies but '
1118 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001119 parser.add_option('--git-deps', action='store_true',
1120 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001122 if ((options.spec and args) or len(args) > 2 or
1123 (not options.spec and not args)):
1124 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1125
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001126 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001127 if options.spec:
1128 client.SetConfig(options.spec)
1129 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001130 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001131 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001132 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001133 if name.endswith('.git'):
1134 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001135 else:
1136 # specify an alternate relpath for the given URL.
1137 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001138 deps_file = options.deps_file
1139 if options.git_deps:
1140 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001141 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001142 if len(args) > 1:
1143 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001144 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1145 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001146 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001147 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001148
1149
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001150@attr('epilog', """Example:
1151 gclient pack > patch.txt
1152 generate simple patch for configured client and dependences
1153""")
1154def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001155 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001156
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001157Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001158dependencies, and performs minimal postprocessing of the output. The
1159resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001160checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001161"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001162 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1163 help='override deps for the specified (comma-separated) '
1164 'platform(s); \'all\' will process all deps_os '
1165 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001167 client = GClient.LoadCurrentConfig(options)
1168 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001169 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001170 if options.verbose:
1171 # Print out the .gclient file. This is longer than if we just printed the
1172 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001173 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001174 return client.RunOnDeps('pack', args)
1175
1176
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177def CMDstatus(parser, args):
1178 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001179 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1180 help='override deps for the specified (comma-separated) '
1181 'platform(s); \'all\' will process all deps_os '
1182 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001183 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001184 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001186 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001187 if options.verbose:
1188 # Print out the .gclient file. This is longer than if we just printed the
1189 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001190 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001191 return client.RunOnDeps('status', args)
1192
1193
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001194@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001195 gclient sync
1196 update files from SCM according to current configuration,
1197 *for modules which have changed since last update or sync*
1198 gclient sync --force
1199 update files from SCM according to current configuration, for
1200 all modules (useful for recovering files deleted from local copy)
1201 gclient sync --revision src@31000
1202 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203""")
1204def CMDsync(parser, args):
1205 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001206 parser.add_option('-f', '--force', action='store_true',
1207 help='force update even for unchanged modules')
1208 parser.add_option('-n', '--nohooks', action='store_true',
1209 help='don\'t run hooks after the update is complete')
1210 parser.add_option('-r', '--revision', action='append',
1211 dest='revisions', metavar='REV', default=[],
1212 help='Enforces revision/hash for the solutions with the '
1213 'format src@rev. The src@ part is optional and can be '
1214 'skipped. -r can be used multiple times when .gclient '
1215 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001216 'if the src@ part is skipped. Note that specifying '
1217 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001218 parser.add_option('-t', '--transitive', action='store_true',
1219 help='When a revision is specified (in the DEPS file or '
1220 'with the command-line flag), transitively update '
1221 'the dependencies to the date of the given revision. '
1222 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001223 parser.add_option('-H', '--head', action='store_true',
1224 help='skips any safesync_urls specified in '
1225 'configured solutions and sync to head instead')
1226 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001227 help='delete any dependency that have been removed from '
1228 'last sync as long as there is no local modification. '
1229 'Coupled with --force, it will remove them even with '
1230 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001231 parser.add_option('-R', '--reset', action='store_true',
1232 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001233 parser.add_option('-M', '--merge', action='store_true',
1234 help='merge upstream changes instead of trying to '
1235 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001236 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1237 help='override deps for the specified (comma-separated) '
1238 'platform(s); \'all\' will process all deps_os '
1239 'references')
1240 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1241 help='Skip svn up whenever possible by requesting '
1242 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001243 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001244 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001245
1246 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001247 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248
maruel@chromium.org307d1792010-05-31 20:03:13 +00001249 if options.revisions and options.head:
1250 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001251 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252
1253 if options.verbose:
1254 # Print out the .gclient file. This is longer than if we just printed the
1255 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001256 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001257 return client.RunOnDeps('update', args)
1258
1259
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001260def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001261 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001263
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001264def CMDdiff(parser, args):
1265 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001266 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1267 help='override deps for the specified (comma-separated) '
1268 'platform(s); \'all\' will process all deps_os '
1269 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001270 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001271 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001273 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001274 if options.verbose:
1275 # Print out the .gclient file. This is longer than if we just printed the
1276 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001277 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001278 return client.RunOnDeps('diff', args)
1279
1280
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001281def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001282 """Revert all modifications in every dependencies.
1283
1284 That's the nuclear option to get back to a 'clean' state. It removes anything
1285 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001286 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1287 help='override deps for the specified (comma-separated) '
1288 'platform(s); \'all\' will process all deps_os '
1289 'references')
1290 parser.add_option('-n', '--nohooks', action='store_true',
1291 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001292 (options, args) = parser.parse_args(args)
1293 # --force is implied.
1294 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001295 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001296 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001297 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001298 return client.RunOnDeps('revert', args)
1299
1300
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001301def CMDrunhooks(parser, args):
1302 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001303 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1304 help='override deps for the specified (comma-separated) '
1305 'platform(s); \'all\' will process all deps_os '
1306 'references')
1307 parser.add_option('-f', '--force', action='store_true', default=True,
1308 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001309 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001310 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001311 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001312 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001313 if options.verbose:
1314 # Print out the .gclient file. This is longer than if we just printed the
1315 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001316 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001317 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001318 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001319 return client.RunOnDeps('runhooks', args)
1320
1321
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001322def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001323 """Output revision info mapping for the client and its dependencies.
1324
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001325 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001326 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001327 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1328 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001329 commit can change.
1330 """
1331 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1332 help='override deps for the specified (comma-separated) '
1333 'platform(s); \'all\' will process all deps_os '
1334 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001335 parser.add_option('-a', '--actual', action='store_true',
1336 help='gets the actual checked out revisions instead of the '
1337 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001338 parser.add_option('-s', '--snapshot', action='store_true',
1339 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001340 'version of all repositories to reproduce the tree, '
1341 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001342 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001343 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001344 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001345 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001346 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001347 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001348
1349
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001350def Command(name):
1351 return getattr(sys.modules[__name__], 'CMD' + name, None)
1352
1353
1354def CMDhelp(parser, args):
1355 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001356 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001357 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001358 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001359 parser.print_help()
1360 return 0
1361
1362
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001363def GenUsage(parser, command):
1364 """Modify an OptParse object with the function's documentation."""
1365 obj = Command(command)
1366 if command == 'help':
1367 command = '<command>'
1368 # OptParser.description prefer nicely non-formatted strings.
1369 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1370 usage = getattr(obj, 'usage', '')
1371 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1372 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373
1374
maruel@chromium.org0895b752011-08-26 20:40:33 +00001375def Parser():
1376 """Returns the default parser."""
1377 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001378 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001379 help='Specify how many SCM commands can run in parallel; '
1380 'default=%default')
1381 parser.add_option('-v', '--verbose', action='count', default=0,
1382 help='Produces additional output for diagnostics. Can be '
1383 'used up to three times for more logging info.')
1384 parser.add_option('--gclientfile', dest='config_filename',
1385 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1386 help='Specify an alternate %default file')
1387 # Integrate standard options processing.
1388 old_parser = parser.parse_args
1389 def Parse(args):
1390 (options, args) = old_parser(args)
1391 level = None
1392 if options.verbose == 2:
1393 level = logging.INFO
1394 elif options.verbose > 2:
1395 level = logging.DEBUG
1396 logging.basicConfig(level=level,
1397 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1398 options.entries_filename = options.config_filename + '_entries'
1399 if options.jobs < 1:
1400 parser.error('--jobs must be 1 or higher')
1401
1402 # These hacks need to die.
1403 if not hasattr(options, 'revisions'):
1404 # GClient.RunOnDeps expects it even if not applicable.
1405 options.revisions = []
1406 if not hasattr(options, 'head'):
1407 options.head = None
1408 if not hasattr(options, 'nohooks'):
1409 options.nohooks = True
1410 if not hasattr(options, 'deps_os'):
1411 options.deps_os = None
1412 if not hasattr(options, 'manually_grab_svn_rev'):
1413 options.manually_grab_svn_rev = None
1414 if not hasattr(options, 'force'):
1415 options.force = None
1416 return (options, args)
1417 parser.parse_args = Parse
1418 # We don't want wordwrapping in epilog (usually examples)
1419 parser.format_epilog = lambda _: parser.epilog or ''
1420 return parser
1421
1422
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001423def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001424 """Doesn't parse the arguments here, just find the right subcommand to
1425 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001426 if sys.hexversion < 0x02050000:
1427 print >> sys.stderr, (
1428 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001429 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001430 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1431 # operations. Python as a strong tendency to buffer sys.stdout.
1432 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001433 # Make stdout annotated with the thread ids.
1434 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001435 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001436 # Unused variable 'usage'
1437 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001438 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1439 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1440 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001441 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001442 if argv:
1443 command = Command(argv[0])
1444 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001445 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001446 GenUsage(parser, argv[0])
1447 return command(parser, argv[1:])
1448 # Not a known command. Default to help.
1449 GenUsage(parser, 'help')
1450 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001451 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001452 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001453 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001454
1455
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001456if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001457 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001458 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459
1460# vim: ts=2:sw=2:tw=80:et: