blob: 93bc8b4dcadd1679decb68f5a5b88274944a1246 [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.org0329e672009-05-13 18:41:04 +0000984 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000985 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000986 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000987 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988 if options.spec:
989 client.SetConfig(options.spec)
990 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000991 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000992 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000993 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +0000994 if name.endswith('.git'):
995 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000996 else:
997 # specify an alternate relpath for the given URL.
998 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +0000999 deps_file = options.deps_file
1000 if options.git_deps:
1001 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001002 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001003 if len(args) > 1:
1004 safesync_url = args[1]
nsylvain@google.comefc80932011-05-31 21:27:56 +00001005 client.SetDefaultConfig(name, deps_file, base_url, safesync_url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001007 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001008
1009
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001010@attr('epilog', """Example:
1011 gclient pack > patch.txt
1012 generate simple patch for configured client and dependences
1013""")
1014def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001015 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001016
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001018dependencies, and performs minimal postprocessing of the output. The
1019resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001020checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001021"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001022 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1023 help='override deps for the specified (comma-separated) '
1024 'platform(s); \'all\' will process all deps_os '
1025 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001026 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001027 client = GClient.LoadCurrentConfig(options)
1028 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001029 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001030 if options.verbose:
1031 # Print out the .gclient file. This is longer than if we just printed the
1032 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001033 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001034 return client.RunOnDeps('pack', args)
1035
1036
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001037def CMDstatus(parser, args):
1038 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001039 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1040 help='override deps for the specified (comma-separated) '
1041 'platform(s); \'all\' will process all deps_os '
1042 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001043 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001044 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001045 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001046 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 if options.verbose:
1048 # Print out the .gclient file. This is longer than if we just printed the
1049 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001050 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 return client.RunOnDeps('status', args)
1052
1053
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001054@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001055 gclient sync
1056 update files from SCM according to current configuration,
1057 *for modules which have changed since last update or sync*
1058 gclient sync --force
1059 update files from SCM according to current configuration, for
1060 all modules (useful for recovering files deleted from local copy)
1061 gclient sync --revision src@31000
1062 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001063""")
1064def CMDsync(parser, args):
1065 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001066 parser.add_option('-f', '--force', action='store_true',
1067 help='force update even for unchanged modules')
1068 parser.add_option('-n', '--nohooks', action='store_true',
1069 help='don\'t run hooks after the update is complete')
1070 parser.add_option('-r', '--revision', action='append',
1071 dest='revisions', metavar='REV', default=[],
1072 help='Enforces revision/hash for the solutions with the '
1073 'format src@rev. The src@ part is optional and can be '
1074 'skipped. -r can be used multiple times when .gclient '
1075 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001076 'if the src@ part is skipped. Note that specifying '
1077 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001078 parser.add_option('-t', '--transitive', action='store_true',
1079 help='When a revision is specified (in the DEPS file or '
1080 'with the command-line flag), transitively update '
1081 'the dependencies to the date of the given revision. '
1082 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001083 parser.add_option('-H', '--head', action='store_true',
1084 help='skips any safesync_urls specified in '
1085 'configured solutions and sync to head instead')
1086 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001087 help='delete any dependency that have been removed from '
1088 'last sync as long as there is no local modification. '
1089 'Coupled with --force, it will remove them even with '
1090 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 parser.add_option('-R', '--reset', action='store_true',
1092 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001093 parser.add_option('-M', '--merge', action='store_true',
1094 help='merge upstream changes instead of trying to '
1095 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001096 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1097 help='override deps for the specified (comma-separated) '
1098 'platform(s); \'all\' will process all deps_os '
1099 'references')
1100 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1101 help='Skip svn up whenever possible by requesting '
1102 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001103 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001104 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001105
1106 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001107 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108
maruel@chromium.org307d1792010-05-31 20:03:13 +00001109 if options.revisions and options.head:
1110 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001111 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001112
1113 if options.verbose:
1114 # Print out the .gclient file. This is longer than if we just printed the
1115 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001116 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117 return client.RunOnDeps('update', args)
1118
1119
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001120def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001121 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001122 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001124def CMDdiff(parser, args):
1125 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001126 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1127 help='override deps for the specified (comma-separated) '
1128 'platform(s); \'all\' will process all deps_os '
1129 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001130 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001131 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001133 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134 if options.verbose:
1135 # Print out the .gclient file. This is longer than if we just printed the
1136 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001137 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138 return client.RunOnDeps('diff', args)
1139
1140
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001141def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001142 """Revert all modifications in every dependencies.
1143
1144 That's the nuclear option to get back to a 'clean' state. It removes anything
1145 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001146 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1147 help='override deps for the specified (comma-separated) '
1148 'platform(s); \'all\' will process all deps_os '
1149 'references')
1150 parser.add_option('-n', '--nohooks', action='store_true',
1151 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001152 (options, args) = parser.parse_args(args)
1153 # --force is implied.
1154 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001155 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001157 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158 return client.RunOnDeps('revert', args)
1159
1160
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001161def CMDrunhooks(parser, args):
1162 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001163 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1164 help='override deps for the specified (comma-separated) '
1165 'platform(s); \'all\' will process all deps_os '
1166 'references')
1167 parser.add_option('-f', '--force', action='store_true', default=True,
1168 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001169 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001170 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001171 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001172 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001173 if options.verbose:
1174 # Print out the .gclient file. This is longer than if we just printed the
1175 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001176 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001177 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001178 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179 return client.RunOnDeps('runhooks', args)
1180
1181
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001183 """Output revision info mapping for the client and its dependencies.
1184
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001185 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001186 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001187 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1188 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001189 commit can change.
1190 """
1191 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1192 help='override deps for the specified (comma-separated) '
1193 'platform(s); \'all\' will process all deps_os '
1194 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001195 parser.add_option('-a', '--actual', action='store_true',
1196 help='gets the actual checked out revisions instead of the '
1197 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001198 parser.add_option('-s', '--snapshot', action='store_true',
1199 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001200 'version of all repositories to reproduce the tree, '
1201 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001202 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001203 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001204 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001205 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001207 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001208
1209
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001210def Command(name):
1211 return getattr(sys.modules[__name__], 'CMD' + name, None)
1212
1213
1214def CMDhelp(parser, args):
1215 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001216 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001217 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001218 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001219 parser.print_help()
1220 return 0
1221
1222
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001223def GenUsage(parser, command):
1224 """Modify an OptParse object with the function's documentation."""
1225 obj = Command(command)
1226 if command == 'help':
1227 command = '<command>'
1228 # OptParser.description prefer nicely non-formatted strings.
1229 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1230 usage = getattr(obj, 'usage', '')
1231 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1232 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233
1234
1235def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236 """Doesn't parse the arguments here, just find the right subcommand to
1237 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001238 if sys.hexversion < 0x02050000:
1239 print >> sys.stderr, (
1240 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001241 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001242 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1243 # operations. Python as a strong tendency to buffer sys.stdout.
1244 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001245 # Make stdout annotated with the thread ids.
1246 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001247 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001248 # Unused variable 'usage'
1249 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001250 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1251 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1252 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1253 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001254 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001255 help='Specify how many SCM commands can run in parallel; '
1256 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001257 parser.add_option('-v', '--verbose', action='count', default=0,
1258 help='Produces additional output for diagnostics. Can be '
1259 'used up to three times for more logging info.')
1260 parser.add_option('--gclientfile', dest='config_filename',
1261 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1262 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001263 # Integrate standard options processing.
1264 old_parser = parser.parse_args
1265 def Parse(args):
1266 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001267 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001268 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001269 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001270 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001271 level = logging.DEBUG
1272 logging.basicConfig(level=level,
1273 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1274 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001275 if options.jobs < 1:
1276 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001277
1278 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001279 if not hasattr(options, 'revisions'):
1280 # GClient.RunOnDeps expects it even if not applicable.
1281 options.revisions = []
1282 if not hasattr(options, 'head'):
1283 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001284 if not hasattr(options, 'nohooks'):
1285 options.nohooks = True
1286 if not hasattr(options, 'deps_os'):
1287 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001288 if not hasattr(options, 'manually_grab_svn_rev'):
1289 options.manually_grab_svn_rev = None
1290 if not hasattr(options, 'force'):
1291 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001292 return (options, args)
1293 parser.parse_args = Parse
1294 # We don't want wordwrapping in epilog (usually examples)
1295 parser.format_epilog = lambda _: parser.epilog or ''
1296 if argv:
1297 command = Command(argv[0])
1298 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001299 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001300 GenUsage(parser, argv[0])
1301 return command(parser, argv[1:])
1302 # Not a known command. Default to help.
1303 GenUsage(parser, 'help')
1304 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001305 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001306 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001307 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001308
1309
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001310if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001311 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001312 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001313
1314# vim: ts=2:sw=2:tw=80:et: