blob: 5d03c146c9601eb6a1c05843fde50dd9ca5360f7 [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.org80cbe8b2010-08-13 13:53:07 +0000139class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000140 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000141
maruel@chromium.org0d812442010-08-10 12:41:08 +0000142 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000143 custom_vars, deps_file, should_process):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000144 # Warning: this function can be called from any thread. Both
145 # self.dependencies and self.requirements are read and modified from
146 # multiple threads at the same time. Sad.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000147 GClientKeywords.__init__(self)
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000148 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000149 self.parent = parent
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000150 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000151 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000152 # These 2 are only set in .gclient and not in DEPS files.
153 self.safesync_url = safesync_url
154 self.custom_vars = custom_vars or {}
155 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000156 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000157 self.dependencies = []
nsylvain@google.comefc80932011-05-31 21:27:56 +0000158 self.deps_file = deps_file
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000159 # A cache of the files affected by the current operation, necessary for
160 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000161 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000162 # If it is not set to True, the dependency wasn't processed for its child
163 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000164 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000165 # This dependency should be processed, i.e. checked out
166 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000167 # This dependency has been processed, i.e. checked out
168 self.processed = False
169 # This dependency had its hook run
170 self.hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000171
maruel@chromium.org98023df2011-09-07 18:44:47 +0000172 # Post process the url to remove trailing slashes.
173 if isinstance(self.url, basestring):
174 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
175 # it to proto://host/path@rev.
176 if self.url.count('@') > 1:
177 raise gclient_utils.Error('Invalid url "%s"' % self.url)
178 self.url = self.url.replace('/@', '@')
179
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000180 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000181
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000182 # Sanity checks
183 if not self.name and self.parent:
184 raise gclient_utils.Error('Dependency without name')
185 if not isinstance(self.url,
186 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
187 raise gclient_utils.Error('dependency url must be either a string, None, '
188 'File() or From() instead of %s' %
189 self.url.__class__.__name__)
190 if '/' in self.deps_file or '\\' in self.deps_file:
191 raise gclient_utils.Error('deps_file name must not be a path, just a '
192 'filename. %s' % self.deps_file)
193
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000194 def _FindDependencies(self):
195 """Setup self.requirements and find any other dependency who would have self
196 as a requirement.
197 """
198 # self.parent is implicitly a requirement. This will be recursive by
199 # definition.
200 if self.parent and self.parent.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000201 self._requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000202
203 # For a tree with at least 2 levels*, the leaf node needs to depend
204 # on the level higher up in an orderly way.
205 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
206 # thus unsorted, while the .gclient format is a list thus sorted.
207 #
208 # * _recursion_limit is hard coded 2 and there is no hope to change this
209 # value.
210 #
211 # Interestingly enough, the following condition only works in the case we
212 # want: self is a 2nd level node. 3nd level node wouldn't need this since
213 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000214 root_deps = self.root.dependencies
215 if self.parent in root_deps:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000216 for i in range(0, root_deps.index(self.parent)):
217 value = root_deps[i]
218 if value.name:
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000219 self._requirements.add(value.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000220
221 if isinstance(self.url, self.FromImpl):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000222 self._requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000223
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000224 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000225 def yield_full_tree(root):
226 """Depth-first recursion."""
227 yield root
228 for i in root.dependencies:
229 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000230 if j.should_process:
231 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000232
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000233 for obj in yield_full_tree(self.root):
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000234 if obj is self or not obj.name:
235 continue
236 # Step 1: Find any requirements self may need.
237 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000238 self._requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000239 # Step 2: Find any requirements self may impose.
240 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000241 try:
242 # Access to a protected member _requirements of a client class
243 # pylint: disable=W0212
244 obj.lock.acquire()
245 obj._requirements.add(self.name)
246 finally:
247 obj.lock.release()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000248
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000249 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000250 """Resolves the parsed url from url.
251
252 Manages From() keyword accordingly. Do not touch self.parsed_url nor
253 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000254 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 overriden_url = self.get_custom_deps(self.name, url)
256 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000257 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000258 overriden_url))
259 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000260 elif isinstance(url, self.FromImpl):
261 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000262 if not ref:
263 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
264 url.module_name, ref))
265 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000266 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000267 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000268 # Make sure the referenced dependency DEPS file is loaded and file the
269 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000270 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000271 found_dep = None
272 for d in ref.dependencies:
273 if d.name == sub_target:
274 found_dep = d
275 break
276 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000277 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000278 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
279 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000280 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000281
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000282 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000283 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000284 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000285 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000286 elif isinstance(url, basestring):
287 parsed_url = urlparse.urlparse(url)
288 if not parsed_url[0]:
289 # A relative url. Fetch the real base.
290 path = parsed_url[2]
291 if not path.startswith('/'):
292 raise gclient_utils.Error(
293 'relative DEPS entry \'%s\' must begin with a slash' % url)
294 # Create a scm just to query the full url.
295 parent_url = self.parent.parsed_url
296 if isinstance(parent_url, self.FileImpl):
297 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000298 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000299 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000300 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000301 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000302 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000303 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000304 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000305 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000306 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000307 return parsed_url
308 elif url is None:
309 return None
310 else:
311 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000313 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000314 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000315 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000316 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000317 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000318 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000319 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000320 # One thing is unintuitive, vars= {} must happen before Var() use.
321 local_scope = {}
322 var = self.VarImpl(self.custom_vars, local_scope)
323 global_scope = {
324 'File': self.FileImpl,
325 'From': self.FromImpl,
326 'Var': var.Lookup,
327 'deps_os': {},
328 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000329 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000330 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000331 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
332 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000333 else:
334 deps_content = gclient_utils.FileRead(filepath)
335 logging.debug(deps_content)
336 # Eval the content.
337 try:
338 exec(deps_content, global_scope, local_scope)
339 except SyntaxError, e:
340 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000341 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000342 # load os specific dependencies if defined. these dependencies may
343 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000344 if 'deps_os' in local_scope:
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000345 for deps_os_key in self.root.enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000346 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000347 if len(self.root.enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000348 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000349 # platform, so we collect the broadest set of dependencies
350 # available. We may end up with the wrong revision of something for
351 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000352 deps.update([x for x in os_deps.items() if not x[0] in deps])
353 else:
354 deps.update(os_deps)
355
maruel@chromium.org271375b2010-06-23 19:17:38 +0000356 self.deps_hooks.extend(local_scope.get('hooks', []))
357
358 # If a line is in custom_deps, but not in the solution, we want to append
359 # this line to the solution.
360 for d in self.custom_deps:
361 if d not in deps:
362 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000363
364 # If use_relative_paths is set in the DEPS file, regenerate
365 # the dictionary using paths relative to the directory containing
366 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000367 use_relative_paths = local_scope.get('use_relative_paths', False)
368 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000369 rel_deps = {}
370 for d, url in deps.items():
371 # normpath is required to allow DEPS to use .. in their
372 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000373 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
374 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376 # Convert the deps into real Dependency.
377 for name, url in deps.iteritems():
378 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000379 raise gclient_utils.Error(
380 'The same name "%s" appears multiple times in the deps section' %
381 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000382 should_process = self.recursion_limit() > 0 and self.should_process
383 if should_process:
384 tree = dict((d.name, d) for d in self.tree(False))
385 if name in tree:
386 if url == tree[name].url:
387 logging.info('Won\'t process duplicate dependency %s' % tree[name])
388 # In theory we could keep it as a shadow of the other one. In
389 # practice, simply ignore it.
390 #should_process = False
391 continue
392 else:
393 raise gclient_utils.Error(
394 'Dependency %s specified more than once:\n %s\nvs\n %s' %
395 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000396 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000397 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000398 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000399
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000400 # Arguments number differs from overridden method
401 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000402 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000403 """Runs 'command' before parsing the DEPS in case it's a initial checkout
404 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000405
406 def maybeGetParentRevision(options):
407 """If we are performing an update and --transitive is set, set the
408 revision to the parent's revision. If we have an explicit revision
409 do nothing."""
410 if command == 'update' and options.transitive and not options.revision:
411 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
412 if not revision:
413 options.revision = revision_overrides.get(self.parent.name)
414 if options.verbose and options.revision:
415 print("Using parent's revision date: %s" % options.revision)
416 # If the parent has a revision override, then it must have been
417 # converted to date format.
418 assert (not options.revision or
419 gclient_utils.IsDateRevision(options.revision))
420
421 def maybeConvertToDateRevision(options):
422 """If we are performing an update and --transitive is set, convert the
423 revision to a date-revision (if necessary). Instead of having
424 -r 101 replace the revision with the time stamp of 101 (e.g.
425 "{2011-18-04}").
426 This way dependencies are upgraded to the revision they had at the
427 check-in of revision 101."""
428 if (command == 'update' and
429 options.transitive and
430 options.revision and
431 not gclient_utils.IsDateRevision(options.revision)):
432 revision_date = scm.GetRevisionDate(options.revision)
433 revision = gclient_utils.MakeDateRevision(revision_date)
434 if options.verbose:
435 print("Updating revision override from %s to %s." %
436 (options.revision, revision))
437 revision_overrides[self.name] = revision
438
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000439 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000440 if not self.should_process:
441 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000442 # When running runhooks, there's no need to consult the SCM.
443 # All known hooks are expected to run unconditionally regardless of working
444 # copy state, so skip the SCM status check.
445 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000446 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000447 if run_scm and self.parsed_url:
448 if isinstance(self.parsed_url, self.FileImpl):
449 # Special support for single-file checkout.
450 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
451 options.revision = self.parsed_url.GetRevision()
452 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000453 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000454 self.name)
455 scm.RunCommand('updatesingle', options,
456 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000457 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000459 # Create a shallow copy to mutate revision.
460 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000461 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000462 maybeGetParentRevision(options)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000463 scm = gclient_scm.CreateSCM(
464 self.parsed_url, self.root.root_dir, self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000465 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000466 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000467 self._file_list = [os.path.join(self.name, f.strip())
468 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000469 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000470 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000472 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000473
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000474 # Parse the dependencies of this dependency.
475 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000476 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000477
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000478 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000479 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000480 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000481 assert self.hooks_ran == False
482 if not self.should_process or self.recursion_limit() <= 0:
483 # Don't run the hook when it is above recursion_limit.
484 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000485 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000486 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000487 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000488 # TODO(maruel): If the user is using git or git-svn, then we don't know
489 # what files have changed so we always run all hooks. It'd be nice to fix
490 # that.
491 if (options.force or
492 isinstance(self.parsed_url, self.FileImpl) or
493 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000494 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000495 for hook_dict in self.deps_hooks:
496 self._RunHookAction(hook_dict, [])
497 else:
498 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
499 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000500 file_list = self.file_list()
501 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000503 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000504 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000505
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000506 prefix = os.path.commonprefix([self.root.root_dir.lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000507 file_list[i].lower()])
508 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509
510 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000511 while (file_list[i].startswith('\\') or
512 file_list[i].startswith('/')):
513 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514
515 # Run hooks on the basis of whether the files from the gclient operation
516 # match each hook's pattern.
517 for hook_dict in self.deps_hooks:
518 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000519 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000520 if matching_file_list:
521 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000522 for s in self.dependencies:
523 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000524
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000525 def _RunHookAction(self, hook_dict, matching_file_list):
526 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000527 # A single DEPS file can specify multiple hooks so this function can be
528 # called multiple times on a single Dependency.
529 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000530 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000531 logging.debug(hook_dict)
532 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000533 command = hook_dict['action'][:]
534 if command[0] == 'python':
535 # If the hook specified "python" as the first item, the action is a
536 # Python script. Run it by starting a new copy of the same
537 # interpreter.
538 command[0] = sys.executable
539
540 if '$matching_files' in command:
541 splice_index = command.index('$matching_files')
542 command[splice_index:splice_index + 1] = matching_file_list
543
maruel@chromium.org17d01792010-09-01 18:07:10 +0000544 try:
545 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000546 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000547 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000548 # Use a discrete exit status code of 2 to indicate that a hook action
549 # failed. Users of this script may wish to treat hook action failures
550 # differently from VC failures.
551 print >> sys.stderr, 'Error: %s' % str(e)
552 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000553
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000554 def recursion_limit(self):
555 return self.parent.recursion_limit() - 1
556
maruel@chromium.org0d812442010-08-10 12:41:08 +0000557 def tree(self, include_all):
558 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000559
maruel@chromium.org0d812442010-08-10 12:41:08 +0000560 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000561 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000562 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000563 for d in self.dependencies:
564 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000565 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000566 for d in self.dependencies:
567 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000568 return result
569
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000570 def get_custom_deps(self, name, url):
571 """Returns a custom deps if applicable."""
572 if self.parent:
573 url = self.parent.get_custom_deps(name, url)
574 # None is a valid return value to disable a dependency.
575 return self.custom_deps.get(name, url)
576
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000577 def file_list(self):
578 result = self._file_list[:]
579 for d in self.dependencies:
580 result.extend(d.file_list())
581 return result
582
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000583 def __str__(self):
584 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000585 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000586 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000587 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000588 # First try the native property if it exists.
589 if hasattr(self, '_' + i):
590 value = getattr(self, '_' + i, False)
591 else:
592 value = getattr(self, i, False)
593 if value:
594 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000595
596 for d in self.dependencies:
597 out.extend([' ' + x for x in str(d).splitlines()])
598 out.append('')
599 return '\n'.join(out)
600
601 def __repr__(self):
602 return '%s: %s' % (self.name, self.url)
603
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000604 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000605 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000606 out = '%s(%s)' % (self.name, self.url)
607 i = self.parent
608 while i and i.name:
609 out = '%s(%s) -> %s' % (i.name, i.url, out)
610 i = i.parent
611 return out
612
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000613 @property
614 def root(self):
615 """Returns the root node, a GClient object."""
616 if not self.parent:
617 # This line is to signal pylint that it could be a GClient instance.
618 return self or GClient(None, None)
619 return self.parent.root
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000620
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000621
622class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000623 """Object that represent a gclient checkout. A tree of Dependency(), one per
624 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000625
626 DEPS_OS_CHOICES = {
627 "win32": "win",
628 "win": "win",
629 "cygwin": "win",
630 "darwin": "mac",
631 "mac": "mac",
632 "unix": "unix",
633 "linux": "unix",
634 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000635 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000636 }
637
638 DEFAULT_CLIENT_FILE_TEXT = ("""\
639solutions = [
640 { "name" : "%(solution_name)s",
641 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000642 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643 "custom_deps" : {
644 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000645 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000646 },
647]
648""")
649
650 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
651 { "name" : "%(solution_name)s",
652 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000653 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000654 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000655%(solution_deps)s },
656 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000657 },
658""")
659
660 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
661# Snapshot generated with gclient revinfo --snapshot
662solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000663%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000664""")
665
666 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000667 # Do not change previous behavior. Only solution level and immediate DEPS
668 # are processed.
669 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000670 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
671 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000672 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000673 if options.deps_os:
674 enforced_os = options.deps_os.split(',')
675 else:
676 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
677 if 'all' in enforced_os:
678 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000679 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000680 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000681 self.config_content = None
682
683 def SetConfig(self, content):
684 assert self.dependencies == []
685 config_dict = {}
686 self.config_content = content
687 try:
688 exec(content, config_dict)
689 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000690 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000691 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000692 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000693 tree = dict((d.name, d) for d in self.tree(False))
694 if s['name'] in tree:
695 raise gclient_utils.Error(
696 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000697 self.dependencies.append(Dependency(
698 self, s['name'], s['url'],
699 s.get('safesync_url', None),
700 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000701 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000702 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000703 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000704 except KeyError:
705 raise gclient_utils.Error('Invalid .gclient file. Solution is '
706 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000707 # .gclient can have hooks.
708 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000709 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000710
711 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000712 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000713 self._options.config_filename),
714 self.config_content)
715
716 @staticmethod
717 def LoadCurrentConfig(options):
718 """Searches for and loads a .gclient file relative to the current working
719 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000720 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000721 if not path:
722 return None
723 client = GClient(path, options)
724 client.SetConfig(gclient_utils.FileRead(
725 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000726 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000727
nsylvain@google.comefc80932011-05-31 21:27:56 +0000728 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
729 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000730 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
731 'solution_name': solution_name,
732 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000733 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000734 'safesync_url' : safesync_url,
735 })
736
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000737 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000738 """Creates a .gclient_entries file to record the list of unique checkouts.
739
740 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000741 """
742 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
743 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000744 result = 'entries = {\n'
745 for entry in self.tree(False):
746 # Skip over File() dependencies as we can't version them.
747 if not isinstance(entry.parsed_url, self.FileImpl):
748 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
749 pprint.pformat(entry.parsed_url))
750 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000751 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000752 logging.info(result)
753 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000754
755 def _ReadEntries(self):
756 """Read the .gclient_entries file for the given client.
757
758 Returns:
759 A sequence of solution names, which will be empty if there is the
760 entries file hasn't been created yet.
761 """
762 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000763 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000764 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000765 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000766 try:
767 exec(gclient_utils.FileRead(filename), scope)
768 except SyntaxError, e:
769 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000770 return scope['entries']
771
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000772 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000773 """Checks for revision overrides."""
774 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000775 if self._options.head:
776 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000777 # Do not check safesync_url if one or more --revision flag is specified.
778 if not self._options.revisions:
779 for s in self.dependencies:
780 if not s.safesync_url:
781 continue
782 handle = urllib.urlopen(s.safesync_url)
783 rev = handle.read().strip()
784 handle.close()
785 if len(rev):
786 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000787 if not self._options.revisions:
788 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000789 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000790 index = 0
791 for revision in self._options.revisions:
792 if not '@' in revision:
793 # Support for --revision 123
794 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000795 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000796 if not sol in solutions_names:
797 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
798 print >> sys.stderr, ('Please fix your script, having invalid '
799 '--revision flags will soon considered an error.')
800 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000801 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000802 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000803 return revision_overrides
804
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805 def RunOnDeps(self, command, args):
806 """Runs a command on each dependency in a client and its dependencies.
807
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808 Args:
809 command: The command to use (e.g., 'status' or 'diff')
810 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000811 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000812 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000813 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000814 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000816 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000817 if (command in ('update', 'revert') and sys.stdout.isatty() and not
818 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000819 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000820 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000821 for s in self.dependencies:
822 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000823 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000824
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000825 # Once all the dependencies have been processed, it's now safe to run the
826 # hooks.
827 if not self._options.nohooks:
828 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000829
830 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000831 # Notify the user if there is an orphaned entry in their working copy.
832 # Only delete the directory if there are no changes in it, and
833 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000834 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000835 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000836 if not prev_url:
837 # entry must have been overridden via .gclient custom_deps
838 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000839 # Fix path separator on Windows.
840 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000841 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000842 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000843 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000844 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000845 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000846 scm.status(self._options, [], file_list)
847 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000848 if (not self._options.delete_unversioned_trees or
849 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000850 # There are modified files in this entry. Keep warning until
851 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000852 print(('\nWARNING: \'%s\' is no longer part of this client. '
853 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000854 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855 else:
856 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000857 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000858 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000859 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000860 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000861 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000862 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863
864 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000865 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000866 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000867 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000868 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000869 for s in self.dependencies:
870 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000871 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000872
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000873 def GetURLAndRev(dep):
874 """Returns the revision-qualified SCM url for a Dependency."""
875 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000876 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000877 if isinstance(dep.parsed_url, self.FileImpl):
878 original_url = dep.parsed_url.file_location
879 else:
880 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000881 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000882 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 if not os.path.isdir(scm.checkout_path):
884 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000885 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000887 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000888 new_gclient = ''
889 # First level at .gclient
890 for d in self.dependencies:
891 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000892 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000893 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000894 for d in dep.dependencies:
895 entries[d.name] = GetURLAndRev(d)
896 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000897 GrabDeps(d)
898 custom_deps = []
899 for k in sorted(entries.keys()):
900 if entries[k]:
901 # Quotes aren't escaped...
902 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
903 else:
904 custom_deps.append(' \"%s\": None,\n' % k)
905 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
906 'solution_name': d.name,
907 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000908 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000909 'safesync_url' : d.safesync_url or '',
910 'solution_deps': ''.join(custom_deps),
911 }
912 # Print the snapshot configuration file
913 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000914 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000915 entries = {}
916 for d in self.tree(False):
917 if self._options.actual:
918 entries[d.name] = GetURLAndRev(d)
919 else:
920 entries[d.name] = d.parsed_url
921 keys = sorted(entries.keys())
922 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000923 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000924 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000926 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000927 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000928 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000929
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000930 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +0000931 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000932 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000933 return self._root_dir
934
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000935 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +0000936 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000937 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000938 return self._enforced_os
939
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000940 def recursion_limit(self):
941 """How recursive can each dependencies in DEPS file can load DEPS file."""
942 return self._recursion_limit
943
maruel@chromium.org0d812442010-08-10 12:41:08 +0000944 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000945 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000946 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000947
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000948
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000949#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950
951
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000952def CMDcleanup(parser, args):
953 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000954
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000955Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000956"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000957 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
958 help='override deps for the specified (comma-separated) '
959 'platform(s); \'all\' will process all deps_os '
960 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000961 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000962 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000963 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000964 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000965 if options.verbose:
966 # Print out the .gclient file. This is longer than if we just printed the
967 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000968 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000969 return client.RunOnDeps('cleanup', args)
970
971
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000972@attr('usage', '[command] [args ...]')
973def CMDrecurse(parser, args):
974 """Operates on all the entries.
975
976 Runs a shell command on all entries.
977 """
978 # Stop parsing at the first non-arg so that these go through to the command
979 parser.disable_interspersed_args()
980 parser.add_option('-s', '--scm', action='append', default=[],
981 help='choose scm types to operate upon')
982 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000983 if not args:
984 print >> sys.stderr, 'Need to supply a command!'
985 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000986 root_and_entries = gclient_utils.GetGClientRootAndEntries()
987 if not root_and_entries:
988 print >> sys.stderr, (
989 'You need to run gclient sync at least once to use \'recurse\'.\n'
990 'This is because .gclient_entries needs to exist and be up to date.')
991 return 1
992 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000993 scm_set = set()
994 for scm in options.scm:
995 scm_set.update(scm.split(','))
996
997 # Pass in the SCM type as an env variable
998 env = os.environ.copy()
999
1000 for path, url in entries.iteritems():
1001 scm = gclient_scm.GetScmName(url)
1002 if scm_set and scm not in scm_set:
1003 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001004 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001005 if scm:
1006 env['GCLIENT_SCM'] = scm
1007 if url:
1008 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001009 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001010 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001011
1012
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001013@attr('usage', '[url] [safesync url]')
1014def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001015 """Create a .gclient file in the current directory.
1016
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001018top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001019modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001020provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001021URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001022"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001023 parser.add_option('--spec',
1024 help='create a gclient file containing the provided '
1025 'string. Due to Cygwin/Python brokenness, it '
1026 'probably can\'t contain any newlines.')
1027 parser.add_option('--name',
1028 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001029 parser.add_option('--deps-file', default='DEPS',
1030 help='overrides the default name for the DEPS file for the'
1031 'main solutions and all sub-dependencies')
1032 parser.add_option('--git-deps', action='store_true',
1033 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001034 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001035 if ((options.spec and args) or len(args) > 2 or
1036 (not options.spec and not args)):
1037 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1038
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001039 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 if options.spec:
1041 client.SetConfig(options.spec)
1042 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001043 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001044 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001045 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001046 if name.endswith('.git'):
1047 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001048 else:
1049 # specify an alternate relpath for the given URL.
1050 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001051 deps_file = options.deps_file
1052 if options.git_deps:
1053 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001054 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055 if len(args) > 1:
1056 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001057 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001059 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001060
1061
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001062@attr('epilog', """Example:
1063 gclient pack > patch.txt
1064 generate simple patch for configured client and dependences
1065""")
1066def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001067 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001068
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001069Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001070dependencies, and performs minimal postprocessing of the output. The
1071resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001073"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001074 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1075 help='override deps for the specified (comma-separated) '
1076 'platform(s); \'all\' will process all deps_os '
1077 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001078 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001079 client = GClient.LoadCurrentConfig(options)
1080 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001081 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001082 if options.verbose:
1083 # Print out the .gclient file. This is longer than if we just printed the
1084 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001085 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001086 return client.RunOnDeps('pack', args)
1087
1088
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089def CMDstatus(parser, args):
1090 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1092 help='override deps for the specified (comma-separated) '
1093 'platform(s); \'all\' will process all deps_os '
1094 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001095 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001096 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001098 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099 if options.verbose:
1100 # Print out the .gclient file. This is longer than if we just printed the
1101 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001102 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001103 return client.RunOnDeps('status', args)
1104
1105
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001106@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001107 gclient sync
1108 update files from SCM according to current configuration,
1109 *for modules which have changed since last update or sync*
1110 gclient sync --force
1111 update files from SCM according to current configuration, for
1112 all modules (useful for recovering files deleted from local copy)
1113 gclient sync --revision src@31000
1114 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001115""")
1116def CMDsync(parser, args):
1117 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001118 parser.add_option('-f', '--force', action='store_true',
1119 help='force update even for unchanged modules')
1120 parser.add_option('-n', '--nohooks', action='store_true',
1121 help='don\'t run hooks after the update is complete')
1122 parser.add_option('-r', '--revision', action='append',
1123 dest='revisions', metavar='REV', default=[],
1124 help='Enforces revision/hash for the solutions with the '
1125 'format src@rev. The src@ part is optional and can be '
1126 'skipped. -r can be used multiple times when .gclient '
1127 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001128 'if the src@ part is skipped. Note that specifying '
1129 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001130 parser.add_option('-t', '--transitive', action='store_true',
1131 help='When a revision is specified (in the DEPS file or '
1132 'with the command-line flag), transitively update '
1133 'the dependencies to the date of the given revision. '
1134 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001135 parser.add_option('-H', '--head', action='store_true',
1136 help='skips any safesync_urls specified in '
1137 'configured solutions and sync to head instead')
1138 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001139 help='delete any dependency that have been removed from '
1140 'last sync as long as there is no local modification. '
1141 'Coupled with --force, it will remove them even with '
1142 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 parser.add_option('-R', '--reset', action='store_true',
1144 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001145 parser.add_option('-M', '--merge', action='store_true',
1146 help='merge upstream changes instead of trying to '
1147 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001148 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1149 help='override deps for the specified (comma-separated) '
1150 'platform(s); \'all\' will process all deps_os '
1151 'references')
1152 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1153 help='Skip svn up whenever possible by requesting '
1154 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001155 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001156 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001157
1158 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001159 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001160
maruel@chromium.org307d1792010-05-31 20:03:13 +00001161 if options.revisions and options.head:
1162 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001163 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164
1165 if options.verbose:
1166 # Print out the .gclient file. This is longer than if we just printed the
1167 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001168 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001169 return client.RunOnDeps('update', args)
1170
1171
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001172def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001173 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001174 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176def CMDdiff(parser, args):
1177 """Displays local diff 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('diff', args)
1191
1192
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001194 """Revert all modifications in every dependencies.
1195
1196 That's the nuclear option to get back to a 'clean' state. It removes anything
1197 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001198 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1199 help='override deps for the specified (comma-separated) '
1200 'platform(s); \'all\' will process all deps_os '
1201 'references')
1202 parser.add_option('-n', '--nohooks', action='store_true',
1203 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001204 (options, args) = parser.parse_args(args)
1205 # --force is implied.
1206 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001207 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001208 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001209 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001210 return client.RunOnDeps('revert', args)
1211
1212
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001213def CMDrunhooks(parser, args):
1214 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001215 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1216 help='override deps for the specified (comma-separated) '
1217 'platform(s); \'all\' will process all deps_os '
1218 'references')
1219 parser.add_option('-f', '--force', action='store_true', default=True,
1220 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001221 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001222 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001223 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001224 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001225 if options.verbose:
1226 # Print out the .gclient file. This is longer than if we just printed the
1227 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001228 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001229 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001230 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001231 return client.RunOnDeps('runhooks', args)
1232
1233
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001234def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001235 """Output revision info mapping for the client and its dependencies.
1236
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001237 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001238 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001239 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1240 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001241 commit can change.
1242 """
1243 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1244 help='override deps for the specified (comma-separated) '
1245 'platform(s); \'all\' will process all deps_os '
1246 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001247 parser.add_option('-a', '--actual', action='store_true',
1248 help='gets the actual checked out revisions instead of the '
1249 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001250 parser.add_option('-s', '--snapshot', action='store_true',
1251 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001252 'version of all repositories to reproduce the tree, '
1253 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001254 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001255 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001256 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001257 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001258 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001259 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001260
1261
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262def Command(name):
1263 return getattr(sys.modules[__name__], 'CMD' + name, None)
1264
1265
1266def CMDhelp(parser, args):
1267 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001268 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001269 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001270 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001271 parser.print_help()
1272 return 0
1273
1274
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001275def GenUsage(parser, command):
1276 """Modify an OptParse object with the function's documentation."""
1277 obj = Command(command)
1278 if command == 'help':
1279 command = '<command>'
1280 # OptParser.description prefer nicely non-formatted strings.
1281 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1282 usage = getattr(obj, 'usage', '')
1283 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1284 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001285
1286
maruel@chromium.org0895b752011-08-26 20:40:33 +00001287def Parser():
1288 """Returns the default parser."""
1289 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001290 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001291 help='Specify how many SCM commands can run in parallel; '
1292 'default=%default')
1293 parser.add_option('-v', '--verbose', action='count', default=0,
1294 help='Produces additional output for diagnostics. Can be '
1295 'used up to three times for more logging info.')
1296 parser.add_option('--gclientfile', dest='config_filename',
1297 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1298 help='Specify an alternate %default file')
1299 # Integrate standard options processing.
1300 old_parser = parser.parse_args
1301 def Parse(args):
1302 (options, args) = old_parser(args)
1303 level = None
1304 if options.verbose == 2:
1305 level = logging.INFO
1306 elif options.verbose > 2:
1307 level = logging.DEBUG
1308 logging.basicConfig(level=level,
1309 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1310 options.entries_filename = options.config_filename + '_entries'
1311 if options.jobs < 1:
1312 parser.error('--jobs must be 1 or higher')
1313
1314 # These hacks need to die.
1315 if not hasattr(options, 'revisions'):
1316 # GClient.RunOnDeps expects it even if not applicable.
1317 options.revisions = []
1318 if not hasattr(options, 'head'):
1319 options.head = None
1320 if not hasattr(options, 'nohooks'):
1321 options.nohooks = True
1322 if not hasattr(options, 'deps_os'):
1323 options.deps_os = None
1324 if not hasattr(options, 'manually_grab_svn_rev'):
1325 options.manually_grab_svn_rev = None
1326 if not hasattr(options, 'force'):
1327 options.force = None
1328 return (options, args)
1329 parser.parse_args = Parse
1330 # We don't want wordwrapping in epilog (usually examples)
1331 parser.format_epilog = lambda _: parser.epilog or ''
1332 return parser
1333
1334
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001335def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001336 """Doesn't parse the arguments here, just find the right subcommand to
1337 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001338 if sys.hexversion < 0x02050000:
1339 print >> sys.stderr, (
1340 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001341 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001342 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1343 # operations. Python as a strong tendency to buffer sys.stdout.
1344 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001345 # Make stdout annotated with the thread ids.
1346 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001347 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001348 # Unused variable 'usage'
1349 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001350 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1351 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1352 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001353 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001354 if argv:
1355 command = Command(argv[0])
1356 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001357 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001358 GenUsage(parser, argv[0])
1359 return command(parser, argv[1:])
1360 # Not a known command. Default to help.
1361 GenUsage(parser, 'help')
1362 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001363 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001364 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001365 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001366
1367
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001368if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001369 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001370 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001371
1372# vim: ts=2:sw=2:tw=80:et: