blob: d86c3663e2d17a50f9b2c8e2b11bb698eeebce4e [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",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000582 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000583 }
584
585 DEFAULT_CLIENT_FILE_TEXT = ("""\
586solutions = [
587 { "name" : "%(solution_name)s",
588 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000589 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000590 "custom_deps" : {
591 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000592 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000593 },
594]
595""")
596
597 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
598 { "name" : "%(solution_name)s",
599 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000600 "deps_file" : "%(deps_file)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000601 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000602%(solution_deps)s },
603 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000604 },
605""")
606
607 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
608# Snapshot generated with gclient revinfo --snapshot
609solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000610%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000611""")
612
613 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000614 # Do not change previous behavior. Only solution level and immediate DEPS
615 # are processed.
616 self._recursion_limit = 2
nsylvain@google.comefc80932011-05-31 21:27:56 +0000617 Dependency.__init__(self, None, None, None, None, None, None, 'unused',
618 True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000619 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000620 if options.deps_os:
621 enforced_os = options.deps_os.split(',')
622 else:
623 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
624 if 'all' in enforced_os:
625 enforced_os = self.DEPS_OS_CHOICES.itervalues()
626 self._enforced_os = list(set(enforced_os))
627 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000628 self.config_content = None
629
630 def SetConfig(self, content):
631 assert self.dependencies == []
632 config_dict = {}
633 self.config_content = content
634 try:
635 exec(content, config_dict)
636 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000637 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000638 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000639 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000640 tree = dict((d.name, d) for d in self.tree(False))
641 if s['name'] in tree:
642 raise gclient_utils.Error(
643 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000644 self.dependencies.append(Dependency(
645 self, s['name'], s['url'],
646 s.get('safesync_url', None),
647 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000648 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000649 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000650 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000651 except KeyError:
652 raise gclient_utils.Error('Invalid .gclient file. Solution is '
653 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000654 # .gclient can have hooks.
655 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000656 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000657
658 def SaveConfig(self):
659 gclient_utils.FileWrite(os.path.join(self.root_dir(),
660 self._options.config_filename),
661 self.config_content)
662
663 @staticmethod
664 def LoadCurrentConfig(options):
665 """Searches for and loads a .gclient file relative to the current working
666 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000667 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000668 if not path:
669 return None
670 client = GClient(path, options)
671 client.SetConfig(gclient_utils.FileRead(
672 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000673 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000674
nsylvain@google.comefc80932011-05-31 21:27:56 +0000675 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
676 safesync_url):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000677 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
678 'solution_name': solution_name,
679 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000680 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000681 'safesync_url' : safesync_url,
682 })
683
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000684 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000685 """Creates a .gclient_entries file to record the list of unique checkouts.
686
687 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000688 """
689 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
690 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000691 result = 'entries = {\n'
692 for entry in self.tree(False):
693 # Skip over File() dependencies as we can't version them.
694 if not isinstance(entry.parsed_url, self.FileImpl):
695 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
696 pprint.pformat(entry.parsed_url))
697 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000698 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000699 logging.info(result)
700 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000701
702 def _ReadEntries(self):
703 """Read the .gclient_entries file for the given client.
704
705 Returns:
706 A sequence of solution names, which will be empty if there is the
707 entries file hasn't been created yet.
708 """
709 scope = {}
710 filename = os.path.join(self.root_dir(), self._options.entries_filename)
711 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000712 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000713 try:
714 exec(gclient_utils.FileRead(filename), scope)
715 except SyntaxError, e:
716 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000717 return scope['entries']
718
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000719 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000720 """Checks for revision overrides."""
721 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000722 if self._options.head:
723 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000724 # Do not check safesync_url if one or more --revision flag is specified.
725 if not self._options.revisions:
726 for s in self.dependencies:
727 if not s.safesync_url:
728 continue
729 handle = urllib.urlopen(s.safesync_url)
730 rev = handle.read().strip()
731 handle.close()
732 if len(rev):
733 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000734 if not self._options.revisions:
735 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000736 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000737 index = 0
738 for revision in self._options.revisions:
739 if not '@' in revision:
740 # Support for --revision 123
741 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000742 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000743 if not sol in solutions_names:
744 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
745 print >> sys.stderr, ('Please fix your script, having invalid '
746 '--revision flags will soon considered an error.')
747 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000748 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000749 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000750 return revision_overrides
751
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000752 def RunOnDeps(self, command, args):
753 """Runs a command on each dependency in a client and its dependencies.
754
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000755 Args:
756 command: The command to use (e.g., 'status' or 'diff')
757 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000758 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000759 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000760 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000761 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000762 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000763 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000764 if (command in ('update', 'revert') and sys.stdout.isatty() and not
765 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000766 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000767 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000768 for s in self.dependencies:
769 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000770 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000771
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000772 # Once all the dependencies have been processed, it's now safe to run the
773 # hooks.
774 if not self._options.nohooks:
775 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000776
777 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000778 # Notify the user if there is an orphaned entry in their working copy.
779 # Only delete the directory if there are no changes in it, and
780 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000781 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000782 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000783 if not prev_url:
784 # entry must have been overridden via .gclient custom_deps
785 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000786 # Fix path separator on Windows.
787 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000788 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000789 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000790 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000791 file_list = []
792 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
793 scm.status(self._options, [], file_list)
794 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000795 if (not self._options.delete_unversioned_trees or
796 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000797 # There are modified files in this entry. Keep warning until
798 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000799 print(('\nWARNING: \'%s\' is no longer part of this client. '
800 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000801 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802 else:
803 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000804 print('\n________ deleting \'%s\' in \'%s\'' % (
805 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000806 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000807 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000808 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000809 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000810
811 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000812 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000813 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000814 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000815 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000816 for s in self.dependencies:
817 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000818 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000820 def GetURLAndRev(dep):
821 """Returns the revision-qualified SCM url for a Dependency."""
822 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000823 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000824 if isinstance(dep.parsed_url, self.FileImpl):
825 original_url = dep.parsed_url.file_location
826 else:
827 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000828 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000829 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000830 if not os.path.isdir(scm.checkout_path):
831 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000832 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000834 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000835 new_gclient = ''
836 # First level at .gclient
837 for d in self.dependencies:
838 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000839 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000840 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000841 for d in dep.dependencies:
842 entries[d.name] = GetURLAndRev(d)
843 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000844 GrabDeps(d)
845 custom_deps = []
846 for k in sorted(entries.keys()):
847 if entries[k]:
848 # Quotes aren't escaped...
849 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
850 else:
851 custom_deps.append(' \"%s\": None,\n' % k)
852 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
853 'solution_name': d.name,
854 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000855 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000856 'safesync_url' : d.safesync_url or '',
857 'solution_deps': ''.join(custom_deps),
858 }
859 # Print the snapshot configuration file
860 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000861 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000862 entries = {}
863 for d in self.tree(False):
864 if self._options.actual:
865 entries[d.name] = GetURLAndRev(d)
866 else:
867 entries[d.name] = d.parsed_url
868 keys = sorted(entries.keys())
869 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000870 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000871 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000872
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000873 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000874 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000875 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000876
maruel@chromium.org75a59272010-06-11 22:34:03 +0000877 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000878 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000879 return self._root_dir
880
maruel@chromium.org271375b2010-06-23 19:17:38 +0000881 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000882 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000883 return self._enforced_os
884
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000885 def recursion_limit(self):
886 """How recursive can each dependencies in DEPS file can load DEPS file."""
887 return self._recursion_limit
888
maruel@chromium.org0d812442010-08-10 12:41:08 +0000889 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000890 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000891 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000892
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000893
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000894#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000895
896
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000897def CMDcleanup(parser, args):
898 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000899
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000900Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000901"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000902 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
903 help='override deps for the specified (comma-separated) '
904 'platform(s); \'all\' will process all deps_os '
905 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000906 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000907 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000908 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000909 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000910 if options.verbose:
911 # Print out the .gclient file. This is longer than if we just printed the
912 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000913 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914 return client.RunOnDeps('cleanup', args)
915
916
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000917@attr('usage', '[command] [args ...]')
918def CMDrecurse(parser, args):
919 """Operates on all the entries.
920
921 Runs a shell command on all entries.
922 """
923 # Stop parsing at the first non-arg so that these go through to the command
924 parser.disable_interspersed_args()
925 parser.add_option('-s', '--scm', action='append', default=[],
926 help='choose scm types to operate upon')
927 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000928 if not args:
929 print >> sys.stderr, 'Need to supply a command!'
930 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000931 root_and_entries = gclient_utils.GetGClientRootAndEntries()
932 if not root_and_entries:
933 print >> sys.stderr, (
934 'You need to run gclient sync at least once to use \'recurse\'.\n'
935 'This is because .gclient_entries needs to exist and be up to date.')
936 return 1
937 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000938 scm_set = set()
939 for scm in options.scm:
940 scm_set.update(scm.split(','))
941
942 # Pass in the SCM type as an env variable
943 env = os.environ.copy()
944
945 for path, url in entries.iteritems():
946 scm = gclient_scm.GetScmName(url)
947 if scm_set and scm not in scm_set:
948 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000949 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000950 if scm:
951 env['GCLIENT_SCM'] = scm
952 if url:
953 env['GCLIENT_URL'] = url
954 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
955 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000956
957
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000958@attr('usage', '[url] [safesync url]')
959def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000960 """Create a .gclient file in the current directory.
961
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000962This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000963top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000964modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000965provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000966URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000967"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000968 parser.add_option('--spec',
969 help='create a gclient file containing the provided '
970 'string. Due to Cygwin/Python brokenness, it '
971 'probably can\'t contain any newlines.')
972 parser.add_option('--name',
973 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +0000974 parser.add_option('--deps-file', default='DEPS',
975 help='overrides the default name for the DEPS file for the'
976 'main solutions and all sub-dependencies')
977 parser.add_option('--git-deps', action='store_true',
978 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000979 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000980 if ((options.spec and args) or len(args) > 2 or
981 (not options.spec and not args)):
982 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
983
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000984 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985 if options.spec:
986 client.SetConfig(options.spec)
987 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000988 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000989 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000990 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +0000991 if name.endswith('.git'):
992 name = name[:-4]
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)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001090 parser.add_option('-M', '--merge', action='store_true',
1091 help='merge upstream changes instead of trying to '
1092 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001093 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1094 help='override deps for the specified (comma-separated) '
1095 'platform(s); \'all\' will process all deps_os '
1096 'references')
1097 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1098 help='Skip svn up whenever possible by requesting '
1099 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001101 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102
1103 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001104 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001105
maruel@chromium.org307d1792010-05-31 20:03:13 +00001106 if options.revisions and options.head:
1107 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001108 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001109
1110 if options.verbose:
1111 # Print out the .gclient file. This is longer than if we just printed the
1112 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001113 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 return client.RunOnDeps('update', args)
1115
1116
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001117def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001118 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001119 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001120
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121def CMDdiff(parser, args):
1122 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001123 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1124 help='override deps for the specified (comma-separated) '
1125 'platform(s); \'all\' will process all deps_os '
1126 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001127 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001128 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001130 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 if options.verbose:
1132 # Print out the .gclient file. This is longer than if we just printed the
1133 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001134 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135 return client.RunOnDeps('diff', args)
1136
1137
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001138def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001139 """Revert all modifications in every dependencies.
1140
1141 That's the nuclear option to get back to a 'clean' state. It removes anything
1142 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1144 help='override deps for the specified (comma-separated) '
1145 'platform(s); \'all\' will process all deps_os '
1146 'references')
1147 parser.add_option('-n', '--nohooks', action='store_true',
1148 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001149 (options, args) = parser.parse_args(args)
1150 # --force is implied.
1151 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001152 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001154 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001155 return client.RunOnDeps('revert', args)
1156
1157
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001158def CMDrunhooks(parser, args):
1159 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001160 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1161 help='override deps for the specified (comma-separated) '
1162 'platform(s); \'all\' will process all deps_os '
1163 'references')
1164 parser.add_option('-f', '--force', action='store_true', default=True,
1165 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001167 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001169 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170 if options.verbose:
1171 # Print out the .gclient file. This is longer than if we just printed the
1172 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001173 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001174 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001175 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001176 return client.RunOnDeps('runhooks', args)
1177
1178
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001179def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001180 """Output revision info mapping for the client and its dependencies.
1181
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001182 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001183 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001184 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1185 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001186 commit can change.
1187 """
1188 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1189 help='override deps for the specified (comma-separated) '
1190 'platform(s); \'all\' will process all deps_os '
1191 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001192 parser.add_option('-a', '--actual', action='store_true',
1193 help='gets the actual checked out revisions instead of the '
1194 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001195 parser.add_option('-s', '--snapshot', action='store_true',
1196 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001197 'version of all repositories to reproduce the tree, '
1198 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001199 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001200 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001201 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001202 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001204 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001205
1206
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001207def Command(name):
1208 return getattr(sys.modules[__name__], 'CMD' + name, None)
1209
1210
1211def CMDhelp(parser, args):
1212 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001213 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001214 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001215 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001216 parser.print_help()
1217 return 0
1218
1219
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001220def GenUsage(parser, command):
1221 """Modify an OptParse object with the function's documentation."""
1222 obj = Command(command)
1223 if command == 'help':
1224 command = '<command>'
1225 # OptParser.description prefer nicely non-formatted strings.
1226 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1227 usage = getattr(obj, 'usage', '')
1228 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1229 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001230
1231
maruel@chromium.org0895b752011-08-26 20:40:33 +00001232def Parser():
1233 """Returns the default parser."""
1234 parser = optparse.OptionParser(version='%prog ' + __version__)
1235 parser.add_option('-j', '--jobs', default=1, type='int',
1236 help='Specify how many SCM commands can run in parallel; '
1237 'default=%default')
1238 parser.add_option('-v', '--verbose', action='count', default=0,
1239 help='Produces additional output for diagnostics. Can be '
1240 'used up to three times for more logging info.')
1241 parser.add_option('--gclientfile', dest='config_filename',
1242 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1243 help='Specify an alternate %default file')
1244 # Integrate standard options processing.
1245 old_parser = parser.parse_args
1246 def Parse(args):
1247 (options, args) = old_parser(args)
1248 level = None
1249 if options.verbose == 2:
1250 level = logging.INFO
1251 elif options.verbose > 2:
1252 level = logging.DEBUG
1253 logging.basicConfig(level=level,
1254 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1255 options.entries_filename = options.config_filename + '_entries'
1256 if options.jobs < 1:
1257 parser.error('--jobs must be 1 or higher')
1258
1259 # These hacks need to die.
1260 if not hasattr(options, 'revisions'):
1261 # GClient.RunOnDeps expects it even if not applicable.
1262 options.revisions = []
1263 if not hasattr(options, 'head'):
1264 options.head = None
1265 if not hasattr(options, 'nohooks'):
1266 options.nohooks = True
1267 if not hasattr(options, 'deps_os'):
1268 options.deps_os = None
1269 if not hasattr(options, 'manually_grab_svn_rev'):
1270 options.manually_grab_svn_rev = None
1271 if not hasattr(options, 'force'):
1272 options.force = None
1273 return (options, args)
1274 parser.parse_args = Parse
1275 # We don't want wordwrapping in epilog (usually examples)
1276 parser.format_epilog = lambda _: parser.epilog or ''
1277 return parser
1278
1279
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001280def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001281 """Doesn't parse the arguments here, just find the right subcommand to
1282 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001283 if sys.hexversion < 0x02050000:
1284 print >> sys.stderr, (
1285 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001286 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001287 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1288 # operations. Python as a strong tendency to buffer sys.stdout.
1289 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001290 # Make stdout annotated with the thread ids.
1291 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001292 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001293 # Unused variable 'usage'
1294 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001295 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1296 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1297 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001298 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001299 if argv:
1300 command = Command(argv[0])
1301 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001302 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001303 GenUsage(parser, argv[0])
1304 return command(parser, argv[1:])
1305 # Not a known command. Default to help.
1306 GenUsage(parser, 'help')
1307 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001308 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001309 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001310 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001311
1312
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001313if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001314 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001315 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001316
1317# vim: ts=2:sw=2:tw=80:et: