blob: 1b77aae13bc25ebfde93d46f144092a3ad0163fb [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.org54a07a22010-06-14 19:07:39 +0000144 GClientKeywords.__init__(self)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000145 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000146 self.parent = parent
147 self.name = name
148 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000149 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000150 # These 2 are only set in .gclient and not in DEPS files.
151 self.safesync_url = safesync_url
152 self.custom_vars = custom_vars or {}
153 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000154 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000155 self.dependencies = []
nsylvain@google.comefc80932011-05-31 21:27:56 +0000156 self.deps_file = deps_file
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000157 # A cache of the files affected by the current operation, necessary for
158 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000159 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000160 # If it is not set to True, the dependency wasn't processed for its child
161 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000162 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000163 # This dependency should be processed, i.e. checked out
164 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000165 # This dependency has been processed, i.e. checked out
166 self.processed = False
167 # This dependency had its hook run
168 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000169 # Required dependencies to run before running this one:
170 self.requirements = []
171 if self.parent and self.parent.name:
172 self.requirements.append(self.parent.name)
173 if isinstance(self.url, self.FromImpl):
174 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000175
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000176 # Sanity checks
177 if not self.name and self.parent:
178 raise gclient_utils.Error('Dependency without name')
179 if not isinstance(self.url,
180 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
181 raise gclient_utils.Error('dependency url must be either a string, None, '
182 'File() or From() instead of %s' %
183 self.url.__class__.__name__)
184 if '/' in self.deps_file or '\\' in self.deps_file:
185 raise gclient_utils.Error('deps_file name must not be a path, just a '
186 'filename. %s' % self.deps_file)
187
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000188 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000189 """Resolves the parsed url from url.
190
191 Manages From() keyword accordingly. Do not touch self.parsed_url nor
192 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000193 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000194 overriden_url = self.get_custom_deps(self.name, url)
195 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000196 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000197 overriden_url))
198 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000199 elif isinstance(url, self.FromImpl):
200 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000201 if not ref:
202 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
203 url.module_name, ref))
204 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000205 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000206 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000207 # Make sure the referenced dependency DEPS file is loaded and file the
208 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000209 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000210 found_dep = None
211 for d in ref.dependencies:
212 if d.name == sub_target:
213 found_dep = d
214 break
215 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000216 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000217 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
218 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000219 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000220 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000221 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000222 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000223 elif isinstance(url, basestring):
224 parsed_url = urlparse.urlparse(url)
225 if not parsed_url[0]:
226 # A relative url. Fetch the real base.
227 path = parsed_url[2]
228 if not path.startswith('/'):
229 raise gclient_utils.Error(
230 'relative DEPS entry \'%s\' must begin with a slash' % url)
231 # Create a scm just to query the full url.
232 parent_url = self.parent.parsed_url
233 if isinstance(parent_url, self.FileImpl):
234 parent_url = parent_url.file_location
235 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000236 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000237 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000238 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000239 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000240 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000241 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000242 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000243 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000244 return parsed_url
245 elif url is None:
246 return None
247 else:
248 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000249
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000250 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000251 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000252 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000253 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000254 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000256 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000257 # One thing is unintuitive, vars= {} must happen before Var() use.
258 local_scope = {}
259 var = self.VarImpl(self.custom_vars, local_scope)
260 global_scope = {
261 'File': self.FileImpl,
262 'From': self.FromImpl,
263 'Var': var.Lookup,
264 'deps_os': {},
265 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000266 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
267 if not os.path.isfile(filepath):
nsylvain@google.comefc80932011-05-31 21:27:56 +0000268 logging.info('%s: No %s file found at %s' % (self.name, self.deps_file,
269 filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000270 else:
271 deps_content = gclient_utils.FileRead(filepath)
272 logging.debug(deps_content)
273 # Eval the content.
274 try:
275 exec(deps_content, global_scope, local_scope)
276 except SyntaxError, e:
277 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000278 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 # load os specific dependencies if defined. these dependencies may
280 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000281 if 'deps_os' in local_scope:
282 for deps_os_key in self.enforced_os():
283 os_deps = local_scope['deps_os'].get(deps_os_key, {})
284 if len(self.enforced_os()) > 1:
285 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000286 # platform, so we collect the broadest set of dependencies
287 # available. We may end up with the wrong revision of something for
288 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000289 deps.update([x for x in os_deps.items() if not x[0] in deps])
290 else:
291 deps.update(os_deps)
292
maruel@chromium.org271375b2010-06-23 19:17:38 +0000293 self.deps_hooks.extend(local_scope.get('hooks', []))
294
295 # If a line is in custom_deps, but not in the solution, we want to append
296 # this line to the solution.
297 for d in self.custom_deps:
298 if d not in deps:
299 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000300
301 # If use_relative_paths is set in the DEPS file, regenerate
302 # the dictionary using paths relative to the directory containing
303 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000304 use_relative_paths = local_scope.get('use_relative_paths', False)
305 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000306 rel_deps = {}
307 for d, url in deps.items():
308 # normpath is required to allow DEPS to use .. in their
309 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000310 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
311 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000313 # Convert the deps into real Dependency.
314 for name, url in deps.iteritems():
315 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000316 raise gclient_utils.Error(
317 'The same name "%s" appears multiple times in the deps section' %
318 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000319 should_process = self.recursion_limit() > 0 and self.should_process
320 if should_process:
321 tree = dict((d.name, d) for d in self.tree(False))
322 if name in tree:
323 if url == tree[name].url:
324 logging.info('Won\'t process duplicate dependency %s' % tree[name])
325 # In theory we could keep it as a shadow of the other one. In
326 # practice, simply ignore it.
327 #should_process = False
328 continue
329 else:
330 raise gclient_utils.Error(
331 'Dependency %s specified more than once:\n %s\nvs\n %s' %
332 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000333 self.dependencies.append(Dependency(self, name, url, None, None, None,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000334 self.deps_file, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000335 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000336
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000337 # Arguments number differs from overridden method
338 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000339 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000340 """Runs 'command' before parsing the DEPS in case it's a initial checkout
341 or a revert."""
floitsch@google.comeaab7842011-04-28 09:07:58 +0000342
343 def maybeGetParentRevision(options):
344 """If we are performing an update and --transitive is set, set the
345 revision to the parent's revision. If we have an explicit revision
346 do nothing."""
347 if command == 'update' and options.transitive and not options.revision:
348 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url)
349 if not revision:
350 options.revision = revision_overrides.get(self.parent.name)
351 if options.verbose and options.revision:
352 print("Using parent's revision date: %s" % options.revision)
353 # If the parent has a revision override, then it must have been
354 # converted to date format.
355 assert (not options.revision or
356 gclient_utils.IsDateRevision(options.revision))
357
358 def maybeConvertToDateRevision(options):
359 """If we are performing an update and --transitive is set, convert the
360 revision to a date-revision (if necessary). Instead of having
361 -r 101 replace the revision with the time stamp of 101 (e.g.
362 "{2011-18-04}").
363 This way dependencies are upgraded to the revision they had at the
364 check-in of revision 101."""
365 if (command == 'update' and
366 options.transitive and
367 options.revision and
368 not gclient_utils.IsDateRevision(options.revision)):
369 revision_date = scm.GetRevisionDate(options.revision)
370 revision = gclient_utils.MakeDateRevision(revision_date)
371 if options.verbose:
372 print("Updating revision override from %s to %s." %
373 (options.revision, revision))
374 revision_overrides[self.name] = revision
375
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000376 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000377 if not self.should_process:
378 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000379 # When running runhooks, there's no need to consult the SCM.
380 # All known hooks are expected to run unconditionally regardless of working
381 # copy state, so skip the SCM status check.
382 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 if run_scm and self.parsed_url:
385 if isinstance(self.parsed_url, self.FileImpl):
386 # Special support for single-file checkout.
387 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
388 options.revision = self.parsed_url.GetRevision()
389 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
390 self.root_dir(),
391 self.name)
392 scm.RunCommand('updatesingle', options,
393 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000394 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000396 # Create a shallow copy to mutate revision.
397 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000398 options.revision = revision_overrides.get(self.name)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000399 maybeGetParentRevision(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000400 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000401 scm.RunCommand(command, options, args, self._file_list)
floitsch@google.comeaab7842011-04-28 09:07:58 +0000402 maybeConvertToDateRevision(options)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000403 self._file_list = [os.path.join(self.name, f.strip())
404 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000405 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000406 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000407 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000408 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000409 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
410 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000411 # src/foo. Yes, it's O(n^2)... It's important to do that before
412 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000413 for s in self.dependencies:
414 for s2 in self.dependencies:
415 if s is s2:
416 continue
417 if s.name.startswith(posixpath.join(s2.name, '')):
418 s.requirements.append(s2.name)
419
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420 # Parse the dependencies of this dependency.
421 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000422 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000423
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000424 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000425 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000426 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000427 assert self.hooks_ran == False
428 if not self.should_process or self.recursion_limit() <= 0:
429 # Don't run the hook when it is above recursion_limit.
430 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000431 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000432 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000433 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000434 # TODO(maruel): If the user is using git or git-svn, then we don't know
435 # what files have changed so we always run all hooks. It'd be nice to fix
436 # that.
437 if (options.force or
438 isinstance(self.parsed_url, self.FileImpl) or
439 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
440 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
441 for hook_dict in self.deps_hooks:
442 self._RunHookAction(hook_dict, [])
443 else:
444 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
445 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000446 file_list = self.file_list()
447 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000448 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000449 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000451
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000452 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000453 file_list[i].lower()])
454 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455
456 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000457 while (file_list[i].startswith('\\') or
458 file_list[i].startswith('/')):
459 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000460
461 # Run hooks on the basis of whether the files from the gclient operation
462 # match each hook's pattern.
463 for hook_dict in self.deps_hooks:
464 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000465 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466 if matching_file_list:
467 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000468 for s in self.dependencies:
469 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000470
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000471 def _RunHookAction(self, hook_dict, matching_file_list):
472 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000473 # A single DEPS file can specify multiple hooks so this function can be
474 # called multiple times on a single Dependency.
475 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000476 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000477 logging.debug(hook_dict)
478 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000479 command = hook_dict['action'][:]
480 if command[0] == 'python':
481 # If the hook specified "python" as the first item, the action is a
482 # Python script. Run it by starting a new copy of the same
483 # interpreter.
484 command[0] = sys.executable
485
486 if '$matching_files' in command:
487 splice_index = command.index('$matching_files')
488 command[splice_index:splice_index + 1] = matching_file_list
489
maruel@chromium.org17d01792010-09-01 18:07:10 +0000490 try:
491 gclient_utils.CheckCallAndFilterAndHeader(
492 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000493 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000494 # Use a discrete exit status code of 2 to indicate that a hook action
495 # failed. Users of this script may wish to treat hook action failures
496 # differently from VC failures.
497 print >> sys.stderr, 'Error: %s' % str(e)
498 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000499
maruel@chromium.org271375b2010-06-23 19:17:38 +0000500 def root_dir(self):
501 return self.parent.root_dir()
502
503 def enforced_os(self):
504 return self.parent.enforced_os()
505
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000506 def recursion_limit(self):
507 return self.parent.recursion_limit() - 1
508
maruel@chromium.org0d812442010-08-10 12:41:08 +0000509 def tree(self, include_all):
510 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000511
maruel@chromium.org0d812442010-08-10 12:41:08 +0000512 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000513 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000514 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000515 for d in self.dependencies:
516 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000517 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000518 for d in self.dependencies:
519 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000520 return result
521
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000522 def get_custom_deps(self, name, url):
523 """Returns a custom deps if applicable."""
524 if self.parent:
525 url = self.parent.get_custom_deps(name, url)
526 # None is a valid return value to disable a dependency.
527 return self.custom_deps.get(name, url)
528
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000529 def file_list(self):
530 result = self._file_list[:]
531 for d in self.dependencies:
532 result.extend(d.file_list())
533 return result
534
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000535 def __str__(self):
536 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000537 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000538 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
539 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000540 # 'deps_file'
541 if self.__dict__[i]:
542 out.append('%s: %s' % (i, self.__dict__[i]))
543
544 for d in self.dependencies:
545 out.extend([' ' + x for x in str(d).splitlines()])
546 out.append('')
547 return '\n'.join(out)
548
549 def __repr__(self):
550 return '%s: %s' % (self.name, self.url)
551
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000552 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000553 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000554 out = '%s(%s)' % (self.name, self.url)
555 i = self.parent
556 while i and i.name:
557 out = '%s(%s) -> %s' % (i.name, i.url, out)
558 i = i.parent
559 return out
560
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000561 def root_parent(self):
562 """Returns the root object, normally a GClient object."""
563 d = self
564 while d.parent:
565 d = d.parent
566 return d
567
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000568
569class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000570 """Object that represent a gclient checkout. A tree of Dependency(), one per
571 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000572
573 DEPS_OS_CHOICES = {
574 "win32": "win",
575 "win": "win",
576 "cygwin": "win",
577 "darwin": "mac",
578 "mac": "mac",
579 "unix": "unix",
580 "linux": "unix",
581 "linux2": "unix",
582 }
583
584 DEFAULT_CLIENT_FILE_TEXT = ("""\
585solutions = [
586 { "name" : "%(solution_name)s",
587 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000588 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000589 "custom_deps" : {
590 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000591 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000592 },
593]
594""")
595
596 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
597 { "name" : "%(solution_name)s",
598 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000599 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000600 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000601%(solution_deps)s },
602 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000603 },
604""")
605
606 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
607# Snapshot generated with gclient revinfo --snapshot
608solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000609%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000610""")
611
612 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000613 # Do not change previous behavior. Only solution level and immediate DEPS
614 # are processed.
615 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000616 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
617 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000618 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000619 if options.deps_os:
620 enforced_os = options.deps_os.split(',')
621 else:
622 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
623 if 'all' in enforced_os:
624 enforced_os = self.DEPS_OS_CHOICES.itervalues()
625 self._enforced_os = list(set(enforced_os))
626 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000627 self.config_content = None
628
629 def SetConfig(self, content):
630 assert self.dependencies == []
631 config_dict = {}
632 self.config_content = content
633 try:
634 exec(content, config_dict)
635 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000636 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000637 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000638 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000639 tree = dict((d.name, d) for d in self.tree(False))
640 if s['name'] in tree:
641 raise gclient_utils.Error(
642 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000643 self.dependencies.append(Dependency(
644 self, s['name'], s['url'],
645 s.get('safesync_url', None),
646 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000647 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000648 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000649 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000650 except KeyError:
651 raise gclient_utils.Error('Invalid .gclient file. Solution is '
652 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000653 # .gclient can have hooks.
654 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000655 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000656
657 def SaveConfig(self):
658 gclient_utils.FileWrite(os.path.join(self.root_dir(),
659 self._options.config_filename),
660 self.config_content)
661
662 @staticmethod
663 def LoadCurrentConfig(options):
664 """Searches for and loads a .gclient file relative to the current working
665 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000666 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000667 if not path:
668 return None
669 client = GClient(path, options)
670 client.SetConfig(gclient_utils.FileRead(
671 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000672 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000673
nsylvain@google.comefc80932011-05-31 21:27:56 +0000674 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
675 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000676 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
677 'solution_name': solution_name,
678 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000679 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000680 'safesync_url' : safesync_url,
681 })
682
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000683 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000684 """Creates a .gclient_entries file to record the list of unique checkouts.
685
686 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000687 """
688 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
689 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000690 result = 'entries = {\n'
691 for entry in self.tree(False):
692 # Skip over File() dependencies as we can't version them.
693 if not isinstance(entry.parsed_url, self.FileImpl):
694 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
695 pprint.pformat(entry.parsed_url))
696 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000697 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000698 logging.info(result)
699 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000700
701 def _ReadEntries(self):
702 """Read the .gclient_entries file for the given client.
703
704 Returns:
705 A sequence of solution names, which will be empty if there is the
706 entries file hasn't been created yet.
707 """
708 scope = {}
709 filename = os.path.join(self.root_dir(), self._options.entries_filename)
710 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000711 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000712 try:
713 exec(gclient_utils.FileRead(filename), scope)
714 except SyntaxError, e:
715 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000716 return scope['entries']
717
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000718 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000719 """Checks for revision overrides."""
720 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000721 if self._options.head:
722 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000723 # Do not check safesync_url if one or more --revision flag is specified.
724 if not self._options.revisions:
725 for s in self.dependencies:
726 if not s.safesync_url:
727 continue
728 handle = urllib.urlopen(s.safesync_url)
729 rev = handle.read().strip()
730 handle.close()
731 if len(rev):
732 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000733 if not self._options.revisions:
734 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000735 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000736 index = 0
737 for revision in self._options.revisions:
738 if not '@' in revision:
739 # Support for --revision 123
740 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000741 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000742 if not sol in solutions_names:
743 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
744 print >> sys.stderr, ('Please fix your script, having invalid '
745 '--revision flags will soon considered an error.')
746 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000747 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000748 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000749 return revision_overrides
750
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000751 def RunOnDeps(self, command, args):
752 """Runs a command on each dependency in a client and its dependencies.
753
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000754 Args:
755 command: The command to use (e.g., 'status' or 'diff')
756 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000757 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000758 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000759 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000760 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000761 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000762 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000763 if (command in ('update', 'revert') and sys.stdout.isatty() and not
764 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000765 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000766 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000767 for s in self.dependencies:
768 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000769 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000770
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000771 # Once all the dependencies have been processed, it's now safe to run the
772 # hooks.
773 if not self._options.nohooks:
774 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000775
776 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000777 # Notify the user if there is an orphaned entry in their working copy.
778 # Only delete the directory if there are no changes in it, and
779 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000780 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000781 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000782 if not prev_url:
783 # entry must have been overridden via .gclient custom_deps
784 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000785 # Fix path separator on Windows.
786 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000787 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000788 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000789 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000790 file_list = []
791 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
792 scm.status(self._options, [], file_list)
793 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000794 if (not self._options.delete_unversioned_trees or
795 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000796 # There are modified files in this entry. Keep warning until
797 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000798 print(('\nWARNING: \'%s\' is no longer part of this client. '
799 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000800 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000801 else:
802 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000803 print('\n________ deleting \'%s\' in \'%s\'' % (
804 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000805 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000806 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000807 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000808 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809
810 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000811 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000812 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000813 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000814 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000815 for s in self.dependencies:
816 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000817 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000818
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000819 def GetURLAndRev(dep):
820 """Returns the revision-qualified SCM url for a Dependency."""
821 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000822 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000823 if isinstance(dep.parsed_url, self.FileImpl):
824 original_url = dep.parsed_url.file_location
825 else:
826 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000827 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000828 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 if not os.path.isdir(scm.checkout_path):
830 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000831 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000832
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000833 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 new_gclient = ''
835 # First level at .gclient
836 for d in self.dependencies:
837 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000838 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000839 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000840 for d in dep.dependencies:
841 entries[d.name] = GetURLAndRev(d)
842 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000843 GrabDeps(d)
844 custom_deps = []
845 for k in sorted(entries.keys()):
846 if entries[k]:
847 # Quotes aren't escaped...
848 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
849 else:
850 custom_deps.append(' \"%s\": None,\n' % k)
851 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
852 'solution_name': d.name,
853 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000854 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000855 'safesync_url' : d.safesync_url or '',
856 'solution_deps': ''.join(custom_deps),
857 }
858 # Print the snapshot configuration file
859 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000860 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000861 entries = {}
862 for d in self.tree(False):
863 if self._options.actual:
864 entries[d.name] = GetURLAndRev(d)
865 else:
866 entries[d.name] = d.parsed_url
867 keys = sorted(entries.keys())
868 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000869 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000870 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000871
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000872 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000873 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000874 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000875
maruel@chromium.org75a59272010-06-11 22:34:03 +0000876 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000877 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000878 return self._root_dir
879
maruel@chromium.org271375b2010-06-23 19:17:38 +0000880 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000881 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000882 return self._enforced_os
883
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000884 def recursion_limit(self):
885 """How recursive can each dependencies in DEPS file can load DEPS file."""
886 return self._recursion_limit
887
maruel@chromium.org0d812442010-08-10 12:41:08 +0000888 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000889 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000890 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000891
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000892
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000893#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000894
895
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000896def CMDcleanup(parser, args):
897 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000898
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000899Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000900"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000901 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
902 help='override deps for the specified (comma-separated) '
903 'platform(s); \'all\' will process all deps_os '
904 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000905 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000906 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000907 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000908 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909 if options.verbose:
910 # Print out the .gclient file. This is longer than if we just printed the
911 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000912 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000913 return client.RunOnDeps('cleanup', args)
914
915
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000916@attr('usage', '[command] [args ...]')
917def CMDrecurse(parser, args):
918 """Operates on all the entries.
919
920 Runs a shell command on all entries.
921 """
922 # Stop parsing at the first non-arg so that these go through to the command
923 parser.disable_interspersed_args()
924 parser.add_option('-s', '--scm', action='append', default=[],
925 help='choose scm types to operate upon')
926 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000927 if not args:
928 print >> sys.stderr, 'Need to supply a command!'
929 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000930 root_and_entries = gclient_utils.GetGClientRootAndEntries()
931 if not root_and_entries:
932 print >> sys.stderr, (
933 'You need to run gclient sync at least once to use \'recurse\'.\n'
934 'This is because .gclient_entries needs to exist and be up to date.')
935 return 1
936 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000937 scm_set = set()
938 for scm in options.scm:
939 scm_set.update(scm.split(','))
940
941 # Pass in the SCM type as an env variable
942 env = os.environ.copy()
943
944 for path, url in entries.iteritems():
945 scm = gclient_scm.GetScmName(url)
946 if scm_set and scm not in scm_set:
947 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000948 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000949 if scm:
950 env['GCLIENT_SCM'] = scm
951 if url:
952 env['GCLIENT_URL'] = url
953 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
954 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000955
956
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957@attr('usage', '[url] [safesync url]')
958def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000959 """Create a .gclient file in the current directory.
960
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000961This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000962top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000963modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000964provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000965URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000966"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000967 parser.add_option('--spec',
968 help='create a gclient file containing the provided '
969 'string. Due to Cygwin/Python brokenness, it '
970 'probably can\'t contain any newlines.')
971 parser.add_option('--name',
972 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +0000973 parser.add_option('--deps-file', default='DEPS',
974 help='overrides the default name for the DEPS file for the'
975 'main solutions and all sub-dependencies')
976 parser.add_option('--git-deps', action='store_true',
977 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000978 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000979 if ((options.spec and args) or len(args) > 2 or
980 (not options.spec and not args)):
981 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
982
maruel@chromium.org0329e672009-05-13 18:41:04 +0000983 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000984 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000985 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000986 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000987 if options.spec:
988 client.SetConfig(options.spec)
989 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000990 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000991 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000992 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000993 else:
994 # specify an alternate relpath for the given URL.
995 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +0000996 deps_file = options.deps_file
997 if options.git_deps:
998 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000999 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001000 if len(args) > 1:
1001 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001002 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001003 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001004 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001005
1006
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001007@attr('epilog', """Example:
1008 gclient pack > patch.txt
1009 generate simple patch for configured client and dependences
1010""")
1011def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001012 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001013
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001014Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001015dependencies, and performs minimal postprocessing of the output. The
1016resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001018"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001019 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1020 help='override deps for the specified (comma-separated) '
1021 'platform(s); \'all\' will process all deps_os '
1022 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001023 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001024 client = GClient.LoadCurrentConfig(options)
1025 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001026 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001027 if options.verbose:
1028 # Print out the .gclient file. This is longer than if we just printed the
1029 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001030 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001031 return client.RunOnDeps('pack', args)
1032
1033
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001034def CMDstatus(parser, args):
1035 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001036 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1037 help='override deps for the specified (comma-separated) '
1038 'platform(s); \'all\' will process all deps_os '
1039 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001040 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001041 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001043 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044 if options.verbose:
1045 # Print out the .gclient file. This is longer than if we just printed the
1046 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001047 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048 return client.RunOnDeps('status', args)
1049
1050
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001051@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001052 gclient sync
1053 update files from SCM according to current configuration,
1054 *for modules which have changed since last update or sync*
1055 gclient sync --force
1056 update files from SCM according to current configuration, for
1057 all modules (useful for recovering files deleted from local copy)
1058 gclient sync --revision src@31000
1059 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001060""")
1061def CMDsync(parser, args):
1062 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001063 parser.add_option('-f', '--force', action='store_true',
1064 help='force update even for unchanged modules')
1065 parser.add_option('-n', '--nohooks', action='store_true',
1066 help='don\'t run hooks after the update is complete')
1067 parser.add_option('-r', '--revision', action='append',
1068 dest='revisions', metavar='REV', default=[],
1069 help='Enforces revision/hash for the solutions with the '
1070 'format src@rev. The src@ part is optional and can be '
1071 'skipped. -r can be used multiple times when .gclient '
1072 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001073 'if the src@ part is skipped. Note that specifying '
1074 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001075 parser.add_option('-t', '--transitive', action='store_true',
1076 help='When a revision is specified (in the DEPS file or '
1077 'with the command-line flag), transitively update '
1078 'the dependencies to the date of the given revision. '
1079 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001080 parser.add_option('-H', '--head', action='store_true',
1081 help='skips any safesync_urls specified in '
1082 'configured solutions and sync to head instead')
1083 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001084 help='delete any dependency that have been removed from '
1085 'last sync as long as there is no local modification. '
1086 'Coupled with --force, it will remove them even with '
1087 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001088 parser.add_option('-R', '--reset', action='store_true',
1089 help='resets any local changes before updating (git only)')
1090 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')
1094 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1095 help='Skip svn up whenever possible by requesting '
1096 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001097 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001098 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099
1100 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001101 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102
maruel@chromium.org307d1792010-05-31 20:03:13 +00001103 if options.revisions and options.head:
1104 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001105 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001106
1107 if options.verbose:
1108 # Print out the .gclient file. This is longer than if we just printed the
1109 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001110 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001111 return client.RunOnDeps('update', args)
1112
1113
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001114def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001115 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001116 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001118def CMDdiff(parser, args):
1119 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001120 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1121 help='override deps for the specified (comma-separated) '
1122 'platform(s); \'all\' will process all deps_os '
1123 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001124 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001125 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001126 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001127 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001128 if options.verbose:
1129 # Print out the .gclient file. This is longer than if we just printed the
1130 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001131 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132 return client.RunOnDeps('diff', args)
1133
1134
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001135def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001136 """Revert all modifications in every dependencies.
1137
1138 That's the nuclear option to get back to a 'clean' state. It removes anything
1139 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001140 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1141 help='override deps for the specified (comma-separated) '
1142 'platform(s); \'all\' will process all deps_os '
1143 'references')
1144 parser.add_option('-n', '--nohooks', action='store_true',
1145 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001146 (options, args) = parser.parse_args(args)
1147 # --force is implied.
1148 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001149 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001150 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001151 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 return client.RunOnDeps('revert', args)
1153
1154
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001155def CMDrunhooks(parser, args):
1156 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001157 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1158 help='override deps for the specified (comma-separated) '
1159 'platform(s); \'all\' will process all deps_os '
1160 'references')
1161 parser.add_option('-f', '--force', action='store_true', default=True,
1162 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001163 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001164 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001165 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001166 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001167 if options.verbose:
1168 # Print out the .gclient file. This is longer than if we just printed the
1169 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001170 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001171 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001172 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001173 return client.RunOnDeps('runhooks', args)
1174
1175
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001177 """Output revision info mapping for the client and its dependencies.
1178
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001179 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001180 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001181 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1182 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001183 commit can change.
1184 """
1185 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1186 help='override deps for the specified (comma-separated) '
1187 'platform(s); \'all\' will process all deps_os '
1188 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001189 parser.add_option('-a', '--actual', action='store_true',
1190 help='gets the actual checked out revisions instead of the '
1191 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001192 parser.add_option('-s', '--snapshot', action='store_true',
1193 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001194 'version of all repositories to reproduce the tree, '
1195 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001196 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001197 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001198 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001199 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001201 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202
1203
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001204def Command(name):
1205 return getattr(sys.modules[__name__], 'CMD' + name, None)
1206
1207
1208def CMDhelp(parser, args):
1209 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001210 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001211 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001212 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001213 parser.print_help()
1214 return 0
1215
1216
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001217def GenUsage(parser, command):
1218 """Modify an OptParse object with the function's documentation."""
1219 obj = Command(command)
1220 if command == 'help':
1221 command = '<command>'
1222 # OptParser.description prefer nicely non-formatted strings.
1223 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1224 usage = getattr(obj, 'usage', '')
1225 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1226 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001227
1228
1229def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001230 """Doesn't parse the arguments here, just find the right subcommand to
1231 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001232 if sys.hexversion < 0x02050000:
1233 print >> sys.stderr, (
1234 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001235 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001236 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1237 # operations. Python as a strong tendency to buffer sys.stdout.
1238 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001239 # Make stdout annotated with the thread ids.
1240 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001241 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001242 # Unused variable 'usage'
1243 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001244 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1245 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1246 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1247 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001248 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001249 help='Specify how many SCM commands can run in parallel; '
1250 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001251 parser.add_option('-v', '--verbose', action='count', default=0,
1252 help='Produces additional output for diagnostics. Can be '
1253 'used up to three times for more logging info.')
1254 parser.add_option('--gclientfile', dest='config_filename',
1255 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1256 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001257 # Integrate standard options processing.
1258 old_parser = parser.parse_args
1259 def Parse(args):
1260 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001261 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001262 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001263 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001264 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001265 level = logging.DEBUG
1266 logging.basicConfig(level=level,
1267 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1268 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001269 if options.jobs < 1:
1270 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001271
1272 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001273 if not hasattr(options, 'revisions'):
1274 # GClient.RunOnDeps expects it even if not applicable.
1275 options.revisions = []
1276 if not hasattr(options, 'head'):
1277 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001278 if not hasattr(options, 'nohooks'):
1279 options.nohooks = True
1280 if not hasattr(options, 'deps_os'):
1281 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001282 if not hasattr(options, 'manually_grab_svn_rev'):
1283 options.manually_grab_svn_rev = None
1284 if not hasattr(options, 'force'):
1285 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001286 return (options, args)
1287 parser.parse_args = Parse
1288 # We don't want wordwrapping in epilog (usually examples)
1289 parser.format_epilog = lambda _: parser.epilog or ''
1290 if argv:
1291 command = Command(argv[0])
1292 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001293 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001294 GenUsage(parser, argv[0])
1295 return command(parser, argv[1:])
1296 # Not a known command. Default to help.
1297 GenUsage(parser, 'help')
1298 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001299 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001300 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001301 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001302
1303
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001304if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001305 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001306 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001307
1308# vim: ts=2:sw=2:tw=80:et: