blob: c038738c10d2d7d45793f947651ebf7edb460970 [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.orgadcf5b72011-09-14 20:14:14 +0000148 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000149 self.parent = parent
maruel@chromium.orgadcf5b72011-09-14 20:14:14 +0000150 self.name = name
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000151 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000152 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000153 # These 2 are only set in .gclient and not in DEPS files.
154 self.safesync_url = safesync_url
155 self.custom_vars = custom_vars or {}
156 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000157 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000158 self.dependencies = []
nsylvain@google.comefc80932011-05-31 21:27:56 +0000159 self.deps_file = deps_file
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000160 # A cache of the files affected by the current operation, necessary for
161 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000162 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000163 # If it is not set to True, the dependency wasn't processed for its child
164 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000165 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000166 # This dependency should be processed, i.e. checked out
167 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000168 # This dependency has been processed, i.e. checked out
169 self.processed = False
170 # This dependency had its hook run
171 self.hooks_ran = False
maruel@chromium.orgadcf5b72011-09-14 20:14:14 +0000172 # Required dependencies to run before running this one:
173 self.requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000174
maruel@chromium.org98023df2011-09-07 18:44:47 +0000175 # Post process the url to remove trailing slashes.
176 if isinstance(self.url, basestring):
177 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
178 # it to proto://host/path@rev.
179 if self.url.count('@') > 1:
180 raise gclient_utils.Error('Invalid url "%s"' % self.url)
181 self.url = self.url.replace('/@', '@')
182
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000183 self._FindDependencies()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000184
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000185 # Sanity checks
186 if not self.name and self.parent:
187 raise gclient_utils.Error('Dependency without name')
188 if not isinstance(self.url,
189 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
190 raise gclient_utils.Error('dependency url must be either a string, None, '
191 'File() or From() instead of %s' %
192 self.url.__class__.__name__)
193 if '/' in self.deps_file or '\\' in self.deps_file:
194 raise gclient_utils.Error('deps_file name must not be a path, just a '
195 'filename. %s' % self.deps_file)
196
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000197 def _FindDependencies(self):
198 """Setup self.requirements and find any other dependency who would have self
199 as a requirement.
200 """
201 # self.parent is implicitly a requirement. This will be recursive by
202 # definition.
203 if self.parent and self.parent.name:
maruel@chromium.orgadcf5b72011-09-14 20:14:14 +0000204 self.requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000205
206 # For a tree with at least 2 levels*, the leaf node needs to depend
207 # on the level higher up in an orderly way.
208 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
209 # thus unsorted, while the .gclient format is a list thus sorted.
210 #
211 # * _recursion_limit is hard coded 2 and there is no hope to change this
212 # value.
213 #
214 # Interestingly enough, the following condition only works in the case we
215 # want: self is a 2nd level node. 3nd level node wouldn't need this since
216 # they already have their parent as a requirement.
217 if self.parent in self.root_parent().dependencies:
218 root_deps = self.root_parent().dependencies
219 for i in range(0, root_deps.index(self.parent)):
220 value = root_deps[i]
221 if value.name:
maruel@chromium.orgadcf5b72011-09-14 20:14:14 +0000222 self.requirements.add(value.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000223
224 if isinstance(self.url, self.FromImpl):
maruel@chromium.orgadcf5b72011-09-14 20:14:14 +0000225 self.requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000226
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000227 if self.name and self.should_process:
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000228 def yield_full_tree(root):
229 """Depth-first recursion."""
230 yield root
231 for i in root.dependencies:
232 for j in yield_full_tree(i):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000233 if j.should_process:
234 yield j
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000235
236 for obj in yield_full_tree(self.root_parent()):
237 if obj is self or not obj.name:
238 continue
239 # Step 1: Find any requirements self may need.
240 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.orgadcf5b72011-09-14 20:14:14 +0000241 self.requirements.add(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000242 # Step 2: Find any requirements self may impose.
243 if obj.name.startswith(posixpath.join(self.name, '')):
244 obj.requirements.add(self.name)
245
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000246 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000247 """Resolves the parsed url from url.
248
249 Manages From() keyword accordingly. Do not touch self.parsed_url nor
250 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000251 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000252 overriden_url = self.get_custom_deps(self.name, url)
253 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000254 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000255 overriden_url))
256 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000257 elif isinstance(url, self.FromImpl):
258 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000259 if not ref:
260 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
261 url.module_name, ref))
262 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000263 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000264 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000265 # Make sure the referenced dependency DEPS file is loaded and file the
266 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000267 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000268 found_dep = None
269 for d in ref.dependencies:
270 if d.name == sub_target:
271 found_dep = d
272 break
273 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000274 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000275 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
276 sub_target, ref.name, self.name, self.parent.name,
277 str(self.root_parent())))
278
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000279 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000280 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000281 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000282 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000283 elif isinstance(url, basestring):
284 parsed_url = urlparse.urlparse(url)
285 if not parsed_url[0]:
286 # A relative url. Fetch the real base.
287 path = parsed_url[2]
288 if not path.startswith('/'):
289 raise gclient_utils.Error(
290 'relative DEPS entry \'%s\' must begin with a slash' % url)
291 # Create a scm just to query the full url.
292 parent_url = self.parent.parsed_url
293 if isinstance(parent_url, self.FileImpl):
294 parent_url = parent_url.file_location
295 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000296 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000297 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000298 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000299 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000300 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000301 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000302 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000303 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000304 return parsed_url
305 elif url is None:
306 return None
307 else:
308 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000309
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000310 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000311 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000312 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000313 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000314 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000315 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000316 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000317 # One thing is unintuitive, vars= {} must happen before Var() use.
318 local_scope = {}
319 var = self.VarImpl(self.custom_vars, local_scope)
320 global_scope = {
321 'File': self.FileImpl,
322 'From': self.FromImpl,
323 'Var': var.Lookup,
324 'deps_os': {},
325 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000326 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
327 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000328 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
329 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000330 else:
331 deps_content = gclient_utils.FileRead(filepath)
332 logging.debug(deps_content)
333 # Eval the content.
334 try:
335 exec(deps_content, global_scope, local_scope)
336 except SyntaxError, e:
337 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000338 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000339 # load os specific dependencies if defined. these dependencies may
340 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000341 if 'deps_os' in local_scope:
342 for deps_os_key in self.enforced_os():
343 os_deps = local_scope['deps_os'].get(deps_os_key, {})
344 if len(self.enforced_os()) > 1:
345 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000346 # platform, so we collect the broadest set of dependencies
347 # available. We may end up with the wrong revision of something for
348 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349 deps.update([x for x in os_deps.items() if not x[0] in deps])
350 else:
351 deps.update(os_deps)
352
maruel@chromium.org271375b2010-06-23 19:17:38 +0000353 self.deps_hooks.extend(local_scope.get('hooks', []))
354
355 # If a line is in custom_deps, but not in the solution, we want to append
356 # this line to the solution.
357 for d in self.custom_deps:
358 if d not in deps:
359 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000360
361 # If use_relative_paths is set in the DEPS file, regenerate
362 # the dictionary using paths relative to the directory containing
363 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000364 use_relative_paths = local_scope.get('use_relative_paths', False)
365 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000366 rel_deps = {}
367 for d, url in deps.items():
368 # normpath is required to allow DEPS to use .. in their
369 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000370 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
371 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000372
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000373 # Convert the deps into real Dependency.
374 for name, url in deps.iteritems():
375 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000376 raise gclient_utils.Error(
377 'The same name "%s" appears multiple times in the deps section' %
378 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000379 should_process = self.recursion_limit() > 0 and self.should_process
380 if should_process:
381 tree = dict((d.name, d) for d in self.tree(False))
382 if name in tree:
383 if url == tree[name].url:
384 logging.info('Won\'t process duplicate dependency %s' % tree[name])
385 # In theory we could keep it as a shadow of the other one. In
386 # practice, simply ignore it.
387 #should_process = False
388 continue
389 else:
390 raise gclient_utils.Error(
391 'Dependency %s specified more than once:\n %s\nvs\n %s' %
392 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000393 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000394 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000395 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000396
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000397 # Arguments number differs from overridden method
398 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000399 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000400 """Runs 'command' before parsing the DEPS in case it's a initial checkout
401 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000402
403 def maybeGetParentRevision(options):
404 """If we are performing an update and --transitive is set, set the
405 revision to the parent's revision. If we have an explicit revision
406 do nothing."""
407 if command == 'update' and options.transitive and not options.revision:
408 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
409 if not revision:
410 options.revision = revision_overrides.get(self.parent.name)
411 if options.verbose and options.revision:
412 print("Using parent's revision date: %s" % options.revision)
413 # If the parent has a revision override, then it must have been
414 # converted to date format.
415 assert (not options.revision or
416 gclient_utils.IsDateRevision(options.revision))
417
418 def maybeConvertToDateRevision(options):
419 """If we are performing an update and --transitive is set, convert the
420 revision to a date-revision (if necessary). Instead of having
421 -r 101 replace the revision with the time stamp of 101 (e.g.
422 "{2011-18-04}").
423 This way dependencies are upgraded to the revision they had at the
424 check-in of revision 101."""
425 if (command == 'update' and
426 options.transitive and
427 options.revision and
428 not gclient_utils.IsDateRevision(options.revision)):
429 revision_date = scm.GetRevisionDate(options.revision)
430 revision = gclient_utils.MakeDateRevision(revision_date)
431 if options.verbose:
432 print("Updating revision override from %s to %s." %
433 (options.revision, revision))
434 revision_overrides[self.name] = revision
435
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000436 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000437 if not self.should_process:
438 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000439 # When running runhooks, there's no need to consult the SCM.
440 # All known hooks are expected to run unconditionally regardless of working
441 # copy state, so skip the SCM status check.
442 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000443 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000444 if run_scm and self.parsed_url:
445 if isinstance(self.parsed_url, self.FileImpl):
446 # Special support for single-file checkout.
447 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
448 options.revision = self.parsed_url.GetRevision()
449 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
450 self.root_dir(),
451 self.name)
452 scm.RunCommand('updatesingle', options,
453 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000454 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000456 # Create a shallow copy to mutate revision.
457 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000459 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000460 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000461 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000462 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000463 self._file_list = [os.path.join(self.name, f.strip())
464 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000465 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000466 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000467 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000468 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000469
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000470 # Parse the dependencies of this dependency.
471 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000472 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000473
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000474 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000475 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000477 assert self.hooks_ran == False
478 if not self.should_process or self.recursion_limit() <= 0:
479 # Don't run the hook when it is above recursion_limit.
480 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000481 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000482 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000483 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000484 # TODO(maruel): If the user is using git or git-svn, then we don't know
485 # what files have changed so we always run all hooks. It'd be nice to fix
486 # that.
487 if (options.force or
488 isinstance(self.parsed_url, self.FileImpl) or
489 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
490 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
491 for hook_dict in self.deps_hooks:
492 self._RunHookAction(hook_dict, [])
493 else:
494 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
495 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000496 file_list = self.file_list()
497 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000499 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000500 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000501
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000503 file_list[i].lower()])
504 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505
506 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000507 while (file_list[i].startswith('\\') or
508 file_list[i].startswith('/')):
509 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000510
511 # Run hooks on the basis of whether the files from the gclient operation
512 # match each hook's pattern.
513 for hook_dict in self.deps_hooks:
514 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000515 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 if matching_file_list:
517 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000518 for s in self.dependencies:
519 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000520
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000521 def _RunHookAction(self, hook_dict, matching_file_list):
522 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000523 # A single DEPS file can specify multiple hooks so this function can be
524 # called multiple times on a single Dependency.
525 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000526 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000527 logging.debug(hook_dict)
528 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000529 command = hook_dict['action'][:]
530 if command[0] == 'python':
531 # If the hook specified "python" as the first item, the action is a
532 # Python script. Run it by starting a new copy of the same
533 # interpreter.
534 command[0] = sys.executable
535
536 if '$matching_files' in command:
537 splice_index = command.index('$matching_files')
538 command[splice_index:splice_index + 1] = matching_file_list
539
maruel@chromium.org17d01792010-09-01 18:07:10 +0000540 try:
541 gclient_utils.CheckCallAndFilterAndHeader(
542 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000543 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000544 # Use a discrete exit status code of 2 to indicate that a hook action
545 # failed. Users of this script may wish to treat hook action failures
546 # differently from VC failures.
547 print >> sys.stderr, 'Error: %s' % str(e)
548 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000549
maruel@chromium.org271375b2010-06-23 19:17:38 +0000550 def root_dir(self):
551 return self.parent.root_dir()
552
553 def enforced_os(self):
554 return self.parent.enforced_os()
555
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000556 def recursion_limit(self):
557 return self.parent.recursion_limit() - 1
558
maruel@chromium.org0d812442010-08-10 12:41:08 +0000559 def tree(self, include_all):
560 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000561
maruel@chromium.org0d812442010-08-10 12:41:08 +0000562 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000563 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000564 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000565 for d in self.dependencies:
566 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000567 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000568 for d in self.dependencies:
569 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000570 return result
571
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000572 def get_custom_deps(self, name, url):
573 """Returns a custom deps if applicable."""
574 if self.parent:
575 url = self.parent.get_custom_deps(name, url)
576 # None is a valid return value to disable a dependency.
577 return self.custom_deps.get(name, url)
578
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000579 def file_list(self):
580 result = self._file_list[:]
581 for d in self.dependencies:
582 result.extend(d.file_list())
583 return result
584
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000585 def __str__(self):
586 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000587 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000588 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000589 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000590 # First try the native property if it exists.
591 if hasattr(self, '_' + i):
592 value = getattr(self, '_' + i, False)
593 else:
594 value = getattr(self, i, False)
595 if value:
596 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000597
598 for d in self.dependencies:
599 out.extend([' ' + x for x in str(d).splitlines()])
600 out.append('')
601 return '\n'.join(out)
602
603 def __repr__(self):
604 return '%s: %s' % (self.name, self.url)
605
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000606 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000607 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000608 out = '%s(%s)' % (self.name, self.url)
609 i = self.parent
610 while i and i.name:
611 out = '%s(%s) -> %s' % (i.name, i.url, out)
612 i = i.parent
613 return out
614
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000615 def root_parent(self):
616 """Returns the root object, normally a GClient object."""
617 d = self
618 while d.parent:
619 d = d.parent
620 return d
621
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000622
623class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000624 """Object that represent a gclient checkout. A tree of Dependency(), one per
625 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000626
627 DEPS_OS_CHOICES = {
628 "win32": "win",
629 "win": "win",
630 "cygwin": "win",
631 "darwin": "mac",
632 "mac": "mac",
633 "unix": "unix",
634 "linux": "unix",
635 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000636 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000637 }
638
639 DEFAULT_CLIENT_FILE_TEXT = ("""\
640solutions = [
641 { "name" : "%(solution_name)s",
642 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000643 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000644 "custom_deps" : {
645 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000646 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000647 },
648]
649""")
650
651 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
652 { "name" : "%(solution_name)s",
653 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000654 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000655 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000656%(solution_deps)s },
657 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000658 },
659""")
660
661 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
662# Snapshot generated with gclient revinfo --snapshot
663solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000664%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000665""")
666
667 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000668 # Do not change previous behavior. Only solution level and immediate DEPS
669 # are processed.
670 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000671 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
672 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000673 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000674 if options.deps_os:
675 enforced_os = options.deps_os.split(',')
676 else:
677 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
678 if 'all' in enforced_os:
679 enforced_os = self.DEPS_OS_CHOICES.itervalues()
680 self._enforced_os = list(set(enforced_os))
681 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000682 self.config_content = None
683
684 def SetConfig(self, content):
685 assert self.dependencies == []
686 config_dict = {}
687 self.config_content = content
688 try:
689 exec(content, config_dict)
690 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000691 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000692 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000693 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000694 tree = dict((d.name, d) for d in self.tree(False))
695 if s['name'] in tree:
696 raise gclient_utils.Error(
697 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000698 self.dependencies.append(Dependency(
699 self, s['name'], s['url'],
700 s.get('safesync_url', None),
701 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000702 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000703 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000704 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000705 except KeyError:
706 raise gclient_utils.Error('Invalid .gclient file. Solution is '
707 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000708 # .gclient can have hooks.
709 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000710 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000711
712 def SaveConfig(self):
713 gclient_utils.FileWrite(os.path.join(self.root_dir(),
714 self._options.config_filename),
715 self.config_content)
716
717 @staticmethod
718 def LoadCurrentConfig(options):
719 """Searches for and loads a .gclient file relative to the current working
720 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000721 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000722 if not path:
723 return None
724 client = GClient(path, options)
725 client.SetConfig(gclient_utils.FileRead(
726 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000727 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000728
nsylvain@google.comefc80932011-05-31 21:27:56 +0000729 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
730 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000731 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
732 'solution_name': solution_name,
733 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000734 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000735 'safesync_url' : safesync_url,
736 })
737
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000739 """Creates a .gclient_entries file to record the list of unique checkouts.
740
741 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742 """
743 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
744 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000745 result = 'entries = {\n'
746 for entry in self.tree(False):
747 # Skip over File() dependencies as we can't version them.
748 if not isinstance(entry.parsed_url, self.FileImpl):
749 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
750 pprint.pformat(entry.parsed_url))
751 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000752 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000753 logging.info(result)
754 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000755
756 def _ReadEntries(self):
757 """Read the .gclient_entries file for the given client.
758
759 Returns:
760 A sequence of solution names, which will be empty if there is the
761 entries file hasn't been created yet.
762 """
763 scope = {}
764 filename = os.path.join(self.root_dir(), self._options.entries_filename)
765 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000766 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000767 try:
768 exec(gclient_utils.FileRead(filename), scope)
769 except SyntaxError, e:
770 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000771 return scope['entries']
772
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000773 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000774 """Checks for revision overrides."""
775 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000776 if self._options.head:
777 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000778 # Do not check safesync_url if one or more --revision flag is specified.
779 if not self._options.revisions:
780 for s in self.dependencies:
781 if not s.safesync_url:
782 continue
783 handle = urllib.urlopen(s.safesync_url)
784 rev = handle.read().strip()
785 handle.close()
786 if len(rev):
787 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000788 if not self._options.revisions:
789 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000790 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000791 index = 0
792 for revision in self._options.revisions:
793 if not '@' in revision:
794 # Support for --revision 123
795 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000796 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000797 if not sol in solutions_names:
798 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
799 print >> sys.stderr, ('Please fix your script, having invalid '
800 '--revision flags will soon considered an error.')
801 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000802 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000803 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000804 return revision_overrides
805
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000806 def RunOnDeps(self, command, args):
807 """Runs a command on each dependency in a client and its dependencies.
808
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809 Args:
810 command: The command to use (e.g., 'status' or 'diff')
811 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000812 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000813 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000814 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000815 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000816 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000817 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000818 if (command in ('update', 'revert') and sys.stdout.isatty() and not
819 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000820 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000821 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000822 for s in self.dependencies:
823 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000824 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000825
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000826 # Once all the dependencies have been processed, it's now safe to run the
827 # hooks.
828 if not self._options.nohooks:
829 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830
831 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000832 # Notify the user if there is an orphaned entry in their working copy.
833 # Only delete the directory if there are no changes in it, and
834 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000835 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000836 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000837 if not prev_url:
838 # entry must have been overridden via .gclient custom_deps
839 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000840 # Fix path separator on Windows.
841 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000842 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000843 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000844 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000845 file_list = []
846 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
847 scm.status(self._options, [], file_list)
848 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000849 if (not self._options.delete_unversioned_trees or
850 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000851 # There are modified files in this entry. Keep warning until
852 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000853 print(('\nWARNING: \'%s\' is no longer part of this client. '
854 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000855 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000856 else:
857 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000858 print('\n________ deleting \'%s\' in \'%s\'' % (
859 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000860 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000862 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000863 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000864
865 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000866 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000867 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000869 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000870 for s in self.dependencies:
871 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000872 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000873
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000874 def GetURLAndRev(dep):
875 """Returns the revision-qualified SCM url for a Dependency."""
876 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000877 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000878 if isinstance(dep.parsed_url, self.FileImpl):
879 original_url = dep.parsed_url.file_location
880 else:
881 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000882 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000883 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000884 if not os.path.isdir(scm.checkout_path):
885 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000886 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000887
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000888 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000889 new_gclient = ''
890 # First level at .gclient
891 for d in self.dependencies:
892 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000893 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000894 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000895 for d in dep.dependencies:
896 entries[d.name] = GetURLAndRev(d)
897 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000898 GrabDeps(d)
899 custom_deps = []
900 for k in sorted(entries.keys()):
901 if entries[k]:
902 # Quotes aren't escaped...
903 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
904 else:
905 custom_deps.append(' \"%s\": None,\n' % k)
906 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
907 'solution_name': d.name,
908 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000909 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000910 'safesync_url' : d.safesync_url or '',
911 'solution_deps': ''.join(custom_deps),
912 }
913 # Print the snapshot configuration file
914 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000915 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000916 entries = {}
917 for d in self.tree(False):
918 if self._options.actual:
919 entries[d.name] = GetURLAndRev(d)
920 else:
921 entries[d.name] = d.parsed_url
922 keys = sorted(entries.keys())
923 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000924 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000925 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000926
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000927 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000928 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000929 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000930
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.org271375b2010-06-23 19:17:38 +0000935 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000936 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000937 return self._enforced_os
938
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000939 def recursion_limit(self):
940 """How recursive can each dependencies in DEPS file can load DEPS file."""
941 return self._recursion_limit
942
maruel@chromium.org0d812442010-08-10 12:41:08 +0000943 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000944 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000945 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000946
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000947
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000948#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949
950
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000951def CMDcleanup(parser, args):
952 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000953
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000954Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000955"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000956 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
957 help='override deps for the specified (comma-separated) '
958 'platform(s); \'all\' will process all deps_os '
959 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000960 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000961 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000962 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000963 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000964 if options.verbose:
965 # Print out the .gclient file. This is longer than if we just printed the
966 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000967 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000968 return client.RunOnDeps('cleanup', args)
969
970
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000971@attr('usage', '[command] [args ...]')
972def CMDrecurse(parser, args):
973 """Operates on all the entries.
974
975 Runs a shell command on all entries.
976 """
977 # Stop parsing at the first non-arg so that these go through to the command
978 parser.disable_interspersed_args()
979 parser.add_option('-s', '--scm', action='append', default=[],
980 help='choose scm types to operate upon')
981 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000982 if not args:
983 print >> sys.stderr, 'Need to supply a command!'
984 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000985 root_and_entries = gclient_utils.GetGClientRootAndEntries()
986 if not root_and_entries:
987 print >> sys.stderr, (
988 'You need to run gclient sync at least once to use \'recurse\'.\n'
989 'This is because .gclient_entries needs to exist and be up to date.')
990 return 1
991 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000992 scm_set = set()
993 for scm in options.scm:
994 scm_set.update(scm.split(','))
995
996 # Pass in the SCM type as an env variable
997 env = os.environ.copy()
998
999 for path, url in entries.iteritems():
1000 scm = gclient_scm.GetScmName(url)
1001 if scm_set and scm not in scm_set:
1002 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001003 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001004 if scm:
1005 env['GCLIENT_SCM'] = scm
1006 if url:
1007 env['GCLIENT_URL'] = url
maruel@chromium.org1ba646f2011-09-08 17:11:53 +00001008 subprocess2.call(args, cwd=cwd, env=env)
maruel@chromium.orgac610232010-10-13 14:01:31 +00001009 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001010
1011
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001012@attr('usage', '[url] [safesync url]')
1013def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001014 """Create a .gclient file in the current directory.
1015
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001016This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001017top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001018modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001019provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001020URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001021"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001022 parser.add_option('--spec',
1023 help='create a gclient file containing the provided '
1024 'string. Due to Cygwin/Python brokenness, it '
1025 'probably can\'t contain any newlines.')
1026 parser.add_option('--name',
1027 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001028 parser.add_option('--deps-file', default='DEPS',
1029 help='overrides the default name for the DEPS file for the'
1030 'main solutions and all sub-dependencies')
1031 parser.add_option('--git-deps', action='store_true',
1032 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001033 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001034 if ((options.spec and args) or len(args) > 2 or
1035 (not options.spec and not args)):
1036 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1037
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001038 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 if options.spec:
1040 client.SetConfig(options.spec)
1041 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001042 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001043 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001044 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001045 if name.endswith('.git'):
1046 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001047 else:
1048 # specify an alternate relpath for the given URL.
1049 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001050 deps_file = options.deps_file
1051 if options.git_deps:
1052 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001053 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001054 if len(args) > 1:
1055 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001056 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001058 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001059
1060
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001061@attr('epilog', """Example:
1062 gclient pack > patch.txt
1063 generate simple patch for configured client and dependences
1064""")
1065def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001066 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001067
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001068Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001069dependencies, and performs minimal postprocessing of the output. The
1070resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001071checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001072"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001073 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1074 help='override deps for the specified (comma-separated) '
1075 'platform(s); \'all\' will process all deps_os '
1076 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001077 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001078 client = GClient.LoadCurrentConfig(options)
1079 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001080 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001081 if options.verbose:
1082 # Print out the .gclient file. This is longer than if we just printed the
1083 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001084 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001085 return client.RunOnDeps('pack', args)
1086
1087
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001088def CMDstatus(parser, args):
1089 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001090 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1091 help='override deps for the specified (comma-separated) '
1092 'platform(s); \'all\' will process all deps_os '
1093 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001094 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001095 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001097 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098 if options.verbose:
1099 # Print out the .gclient file. This is longer than if we just printed the
1100 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001101 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102 return client.RunOnDeps('status', args)
1103
1104
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001105@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001106 gclient sync
1107 update files from SCM according to current configuration,
1108 *for modules which have changed since last update or sync*
1109 gclient sync --force
1110 update files from SCM according to current configuration, for
1111 all modules (useful for recovering files deleted from local copy)
1112 gclient sync --revision src@31000
1113 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001114""")
1115def CMDsync(parser, args):
1116 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001117 parser.add_option('-f', '--force', action='store_true',
1118 help='force update even for unchanged modules')
1119 parser.add_option('-n', '--nohooks', action='store_true',
1120 help='don\'t run hooks after the update is complete')
1121 parser.add_option('-r', '--revision', action='append',
1122 dest='revisions', metavar='REV', default=[],
1123 help='Enforces revision/hash for the solutions with the '
1124 'format src@rev. The src@ part is optional and can be '
1125 'skipped. -r can be used multiple times when .gclient '
1126 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001127 'if the src@ part is skipped. Note that specifying '
1128 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001129 parser.add_option('-t', '--transitive', action='store_true',
1130 help='When a revision is specified (in the DEPS file or '
1131 'with the command-line flag), transitively update '
1132 'the dependencies to the date of the given revision. '
1133 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001134 parser.add_option('-H', '--head', action='store_true',
1135 help='skips any safesync_urls specified in '
1136 'configured solutions and sync to head instead')
1137 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001138 help='delete any dependency that have been removed from '
1139 'last sync as long as there is no local modification. '
1140 'Coupled with --force, it will remove them even with '
1141 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001142 parser.add_option('-R', '--reset', action='store_true',
1143 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001144 parser.add_option('-M', '--merge', action='store_true',
1145 help='merge upstream changes instead of trying to '
1146 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1148 help='override deps for the specified (comma-separated) '
1149 'platform(s); \'all\' will process all deps_os '
1150 'references')
1151 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1152 help='Skip svn up whenever possible by requesting '
1153 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001154 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001155 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156
1157 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001158 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159
maruel@chromium.org307d1792010-05-31 20:03:13 +00001160 if options.revisions and options.head:
1161 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001162 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001163
1164 if options.verbose:
1165 # Print out the .gclient file. This is longer than if we just printed the
1166 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001167 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168 return client.RunOnDeps('update', args)
1169
1170
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001171def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001172 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001173 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001174
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001175def CMDdiff(parser, args):
1176 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001177 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1178 help='override deps for the specified (comma-separated) '
1179 'platform(s); \'all\' will process all deps_os '
1180 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001181 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001182 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001183 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001184 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 if options.verbose:
1186 # Print out the .gclient file. This is longer than if we just printed the
1187 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001188 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001189 return client.RunOnDeps('diff', args)
1190
1191
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001192def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001193 """Revert all modifications in every dependencies.
1194
1195 That's the nuclear option to get back to a 'clean' state. It removes anything
1196 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001197 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1198 help='override deps for the specified (comma-separated) '
1199 'platform(s); \'all\' will process all deps_os '
1200 'references')
1201 parser.add_option('-n', '--nohooks', action='store_true',
1202 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203 (options, args) = parser.parse_args(args)
1204 # --force is implied.
1205 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001206 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001207 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001208 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001209 return client.RunOnDeps('revert', args)
1210
1211
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001212def CMDrunhooks(parser, args):
1213 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001214 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1215 help='override deps for the specified (comma-separated) '
1216 'platform(s); \'all\' will process all deps_os '
1217 'references')
1218 parser.add_option('-f', '--force', action='store_true', default=True,
1219 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001220 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001221 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001222 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001223 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224 if options.verbose:
1225 # Print out the .gclient file. This is longer than if we just printed the
1226 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001227 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001228 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001229 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001230 return client.RunOnDeps('runhooks', args)
1231
1232
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001233def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001234 """Output revision info mapping for the client and its dependencies.
1235
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001236 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001237 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001238 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1239 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001240 commit can change.
1241 """
1242 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1243 help='override deps for the specified (comma-separated) '
1244 'platform(s); \'all\' will process all deps_os '
1245 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001246 parser.add_option('-a', '--actual', action='store_true',
1247 help='gets the actual checked out revisions instead of the '
1248 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001249 parser.add_option('-s', '--snapshot', action='store_true',
1250 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001251 'version of all repositories to reproduce the tree, '
1252 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001253 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001254 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001255 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001256 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001257 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001258 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001259
1260
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001261def Command(name):
1262 return getattr(sys.modules[__name__], 'CMD' + name, None)
1263
1264
1265def CMDhelp(parser, args):
1266 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001267 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001268 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001269 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001270 parser.print_help()
1271 return 0
1272
1273
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001274def GenUsage(parser, command):
1275 """Modify an OptParse object with the function's documentation."""
1276 obj = Command(command)
1277 if command == 'help':
1278 command = '<command>'
1279 # OptParser.description prefer nicely non-formatted strings.
1280 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1281 usage = getattr(obj, 'usage', '')
1282 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1283 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284
1285
maruel@chromium.org0895b752011-08-26 20:40:33 +00001286def Parser():
1287 """Returns the default parser."""
1288 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001289 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001290 help='Specify how many SCM commands can run in parallel; '
1291 'default=%default')
1292 parser.add_option('-v', '--verbose', action='count', default=0,
1293 help='Produces additional output for diagnostics. Can be '
1294 'used up to three times for more logging info.')
1295 parser.add_option('--gclientfile', dest='config_filename',
1296 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1297 help='Specify an alternate %default file')
1298 # Integrate standard options processing.
1299 old_parser = parser.parse_args
1300 def Parse(args):
1301 (options, args) = old_parser(args)
1302 level = None
1303 if options.verbose == 2:
1304 level = logging.INFO
1305 elif options.verbose > 2:
1306 level = logging.DEBUG
1307 logging.basicConfig(level=level,
1308 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1309 options.entries_filename = options.config_filename + '_entries'
1310 if options.jobs < 1:
1311 parser.error('--jobs must be 1 or higher')
1312
1313 # These hacks need to die.
1314 if not hasattr(options, 'revisions'):
1315 # GClient.RunOnDeps expects it even if not applicable.
1316 options.revisions = []
1317 if not hasattr(options, 'head'):
1318 options.head = None
1319 if not hasattr(options, 'nohooks'):
1320 options.nohooks = True
1321 if not hasattr(options, 'deps_os'):
1322 options.deps_os = None
1323 if not hasattr(options, 'manually_grab_svn_rev'):
1324 options.manually_grab_svn_rev = None
1325 if not hasattr(options, 'force'):
1326 options.force = None
1327 return (options, args)
1328 parser.parse_args = Parse
1329 # We don't want wordwrapping in epilog (usually examples)
1330 parser.format_epilog = lambda _: parser.epilog or ''
1331 return parser
1332
1333
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001335 """Doesn't parse the arguments here, just find the right subcommand to
1336 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001337 if sys.hexversion < 0x02050000:
1338 print >> sys.stderr, (
1339 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001340 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001341 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1342 # operations. Python as a strong tendency to buffer sys.stdout.
1343 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001344 # Make stdout annotated with the thread ids.
1345 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001346 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001347 # Unused variable 'usage'
1348 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001349 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1350 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1351 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001352 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001353 if argv:
1354 command = Command(argv[0])
1355 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001356 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001357 GenUsage(parser, argv[0])
1358 return command(parser, argv[1:])
1359 # Not a known command. Default to help.
1360 GenUsage(parser, 'help')
1361 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001362 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001363 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001364 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001365
1366
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001367if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001368 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001369 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001370
1371# vim: ts=2:sw=2:tw=80:et: