blob: 741d3ebfa2e071211c3b089638016d084a240155 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# 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
maruel@chromium.orgf50907b2010-08-12 17:05:48 +000052__version__ = "0.5.2"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org754960e2009-09-21 12:31:05 +000054import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055import optparse
56import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000057import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000058import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000060import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@chromium.org049bced2010-08-12 13:37:20 +000062import threading
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000064import urllib
65
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066import breakpad
67
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@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000073def attr(attr, data):
74 """Sets an attribute on a function."""
75 def hook(fn):
76 setattr(fn, attr, data)
77 return fn
78 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081## GClient implementation.
82
maruel@chromium.org049bced2010-08-12 13:37:20 +000083class WorkItem(object):
84 """One work item."""
85 requirements = []
86 name = None
87
88 def run(self):
89 pass
90
91
92class ExecutionQueue(object):
93 """Dependencies sometime needs to be run out of order due to From() keyword.
94
95 This class manages that all the required dependencies are run before running
96 each one.
97
98 Methods of this class are multithread safe.
99 """
100 def __init__(self, progress):
101 self.lock = threading.Lock()
102 # List of Dependency.
103 self.queued = []
104 # List of strings representing each Dependency.name that was run.
105 self.ran = []
106 # List of items currently running.
107 self.running = []
108 self.progress = progress
109 if self.progress:
110 self.progress.update()
111
112 def enqueue(self, d):
113 """Enqueue one Dependency to be executed later once its requirements are
114 satisfied.
115 """
116 assert isinstance(d, WorkItem)
117 try:
118 self.lock.acquire()
119 self.queued.append(d)
120 total = len(self.queued) + len(self.ran) + len(self.running)
121 finally:
122 self.lock.release()
123 if self.progress:
124 self.progress._total = total + 1
125 self.progress.update(0)
126
127 def flush(self, *args, **kwargs):
128 """Runs all enqueued items until all are executed."""
129 while self._run_one_item(*args, **kwargs):
130 pass
131 queued = []
132 running = []
133 try:
134 self.lock.acquire()
135 if self.queued:
136 queued = self.queued
137 self.queued = []
138 if self.running:
139 running = self.running
140 self.running = []
141 finally:
142 self.lock.release()
143 if self.progress:
144 self.progress.end()
145 if queued:
146 raise gclient_utils.Error('Entries still queued: %s' % str(queued))
147 if running:
148 raise gclient_utils.Error('Entries still queued: %s' % str(running))
149
150 def _run_one_item(self, *args, **kwargs):
151 """Removes one item from the queue that has all its requirements completed
152 and execute it.
153
154 Returns False if no item could be run.
155 """
156 i = 0
157 d = None
158 try:
159 self.lock.acquire()
160 while i != len(self.queued) and not d:
161 d = self.queued.pop(i)
162 for r in d.requirements:
163 if not r in self.ran:
164 self.queued.insert(i, d)
165 d = None
166 break
167 i += 1
168 if not d:
169 return False
170 self.running.append(d)
171 finally:
172 self.lock.release()
173 d.run(*args, **kwargs)
174 try:
175 self.lock.acquire()
176 # TODO(maruel): http://crbug.com/51711
177 #assert not d.name in self.ran
178 if not d.name in self.ran:
179 self.ran.append(d.name)
180 self.running.remove(d)
181 if self.progress:
182 self.progress.update(1)
183 finally:
184 self.lock.release()
185 return True
186
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000187
maruel@chromium.org116704f2010-06-11 17:34:38 +0000188class GClientKeywords(object):
189 class FromImpl(object):
190 """Used to implement the From() syntax."""
191
192 def __init__(self, module_name, sub_target_name=None):
193 """module_name is the dep module we want to include from. It can also be
194 the name of a subdirectory to include from.
195
196 sub_target_name is an optional parameter if the module name in the other
197 DEPS file is different. E.g., you might want to map src/net to net."""
198 self.module_name = module_name
199 self.sub_target_name = sub_target_name
200
201 def __str__(self):
202 return 'From(%s, %s)' % (repr(self.module_name),
203 repr(self.sub_target_name))
204
maruel@chromium.org116704f2010-06-11 17:34:38 +0000205 class FileImpl(object):
206 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000207 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000208
209 def __init__(self, file_location):
210 self.file_location = file_location
211
212 def __str__(self):
213 return 'File("%s")' % self.file_location
214
215 def GetPath(self):
216 return os.path.split(self.file_location)[0]
217
218 def GetFilename(self):
219 rev_tokens = self.file_location.split('@')
220 return os.path.split(rev_tokens[0])[1]
221
222 def GetRevision(self):
223 rev_tokens = self.file_location.split('@')
224 if len(rev_tokens) > 1:
225 return rev_tokens[1]
226 return None
227
228 class VarImpl(object):
229 def __init__(self, custom_vars, local_scope):
230 self._custom_vars = custom_vars
231 self._local_scope = local_scope
232
233 def Lookup(self, var_name):
234 """Implements the Var syntax."""
235 if var_name in self._custom_vars:
236 return self._custom_vars[var_name]
237 elif var_name in self._local_scope.get("vars", {}):
238 return self._local_scope["vars"][var_name]
239 raise gclient_utils.Error("Var is not defined: %s" % var_name)
240
241
maruel@chromium.org049bced2010-08-12 13:37:20 +0000242class Dependency(GClientKeywords, WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000243 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000244 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000245
maruel@chromium.org0d812442010-08-10 12:41:08 +0000246 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000247 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000248 GClientKeywords.__init__(self)
249 self.parent = parent
250 self.name = name
251 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000252 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000253 # These 2 are only set in .gclient and not in DEPS files.
254 self.safesync_url = safesync_url
255 self.custom_vars = custom_vars or {}
256 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000257 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000258 self.dependencies = []
259 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000260 # A cache of the files affected by the current operation, necessary for
261 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000262 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000263 # If it is not set to True, the dependency wasn't processed for its child
264 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000265 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000266 # This dependency should be processed, i.e. checked out
267 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000268 # This dependency has been processed, i.e. checked out
269 self.processed = False
270 # This dependency had its hook run
271 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000272 # Required dependencies to run before running this one:
273 self.requirements = []
274 if self.parent and self.parent.name:
275 self.requirements.append(self.parent.name)
276 if isinstance(self.url, self.FromImpl):
277 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000279 # Sanity checks
280 if not self.name and self.parent:
281 raise gclient_utils.Error('Dependency without name')
282 if not isinstance(self.url,
283 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
284 raise gclient_utils.Error('dependency url must be either a string, None, '
285 'File() or From() instead of %s' %
286 self.url.__class__.__name__)
287 if '/' in self.deps_file or '\\' in self.deps_file:
288 raise gclient_utils.Error('deps_file name must not be a path, just a '
289 'filename. %s' % self.deps_file)
290
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000291 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000292 """Resolves the parsed url from url.
293
294 Manages From() keyword accordingly. Do not touch self.parsed_url nor
295 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000296 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000297 overriden_url = self.get_custom_deps(self.name, url)
298 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000299 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000300 overriden_url))
301 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000302 elif isinstance(url, self.FromImpl):
303 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000304 if not ref:
305 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
306 url.module_name, ref))
307 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000308 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000309 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000310 # Make sure the referenced dependency DEPS file is loaded and file the
311 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000312 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000313 found_dep = None
314 for d in ref.dependencies:
315 if d.name == sub_target:
316 found_dep = d
317 break
318 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000319 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000320 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
321 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000322 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000323 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000324 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000325 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000326 elif isinstance(url, basestring):
327 parsed_url = urlparse.urlparse(url)
328 if not parsed_url[0]:
329 # A relative url. Fetch the real base.
330 path = parsed_url[2]
331 if not path.startswith('/'):
332 raise gclient_utils.Error(
333 'relative DEPS entry \'%s\' must begin with a slash' % url)
334 # Create a scm just to query the full url.
335 parent_url = self.parent.parsed_url
336 if isinstance(parent_url, self.FileImpl):
337 parent_url = parent_url.file_location
338 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000339 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000340 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000341 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000342 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000343 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000344 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000345 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000346 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000347 return parsed_url
348 elif url is None:
349 return None
350 else:
351 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000352
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000353 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000354 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000355 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000356 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000357 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000358 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000359 self.deps_parsed = True
360 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
361 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000362 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000363 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000364 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000365 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000366
maruel@chromium.org271375b2010-06-23 19:17:38 +0000367 # Eval the content.
368 # One thing is unintuitive, vars= {} must happen before Var() use.
369 local_scope = {}
370 var = self.VarImpl(self.custom_vars, local_scope)
371 global_scope = {
372 'File': self.FileImpl,
373 'From': self.FromImpl,
374 'Var': var.Lookup,
375 'deps_os': {},
376 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000377 try:
378 exec(deps_content, global_scope, local_scope)
379 except SyntaxError, e:
380 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000381 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000382 # load os specific dependencies if defined. these dependencies may
383 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000384 if 'deps_os' in local_scope:
385 for deps_os_key in self.enforced_os():
386 os_deps = local_scope['deps_os'].get(deps_os_key, {})
387 if len(self.enforced_os()) > 1:
388 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000389 # platform, so we collect the broadest set of dependencies available.
390 # We may end up with the wrong revision of something for our
391 # platform, but this is the best we can do.
392 deps.update([x for x in os_deps.items() if not x[0] in deps])
393 else:
394 deps.update(os_deps)
395
maruel@chromium.org271375b2010-06-23 19:17:38 +0000396 self.deps_hooks.extend(local_scope.get('hooks', []))
397
398 # If a line is in custom_deps, but not in the solution, we want to append
399 # this line to the solution.
400 for d in self.custom_deps:
401 if d not in deps:
402 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000403
404 # If use_relative_paths is set in the DEPS file, regenerate
405 # the dictionary using paths relative to the directory containing
406 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000407 use_relative_paths = local_scope.get('use_relative_paths', False)
408 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000409 rel_deps = {}
410 for d, url in deps.items():
411 # normpath is required to allow DEPS to use .. in their
412 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000413 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
414 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000415
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416 # Convert the deps into real Dependency.
417 for name, url in deps.iteritems():
418 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000419 raise gclient_utils.Error(
420 'The same name "%s" appears multiple times in the deps section' %
421 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000422 should_process = self.recursion_limit() > 0 and self.should_process
423 if should_process:
424 tree = dict((d.name, d) for d in self.tree(False))
425 if name in tree:
426 if url == tree[name].url:
427 logging.info('Won\'t process duplicate dependency %s' % tree[name])
428 # In theory we could keep it as a shadow of the other one. In
429 # practice, simply ignore it.
430 #should_process = False
431 continue
432 else:
433 raise gclient_utils.Error(
434 'Dependency %s specified more than once:\n %s\nvs\n %s' %
435 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000436 self.dependencies.append(Dependency(self, name, url, None, None, None,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000437 None, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000438 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000439
maruel@chromium.org049bced2010-08-12 13:37:20 +0000440 def run(self, options, revision_overrides, command, args, work_queue):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000441 """Runs 'command' before parsing the DEPS in case it's a initial checkout
442 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000443 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000444 if not self.should_process:
445 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000446 # When running runhooks, there's no need to consult the SCM.
447 # All known hooks are expected to run unconditionally regardless of working
448 # copy state, so skip the SCM status check.
449 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000450 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000451 if run_scm and self.parsed_url:
452 if isinstance(self.parsed_url, self.FileImpl):
453 # Special support for single-file checkout.
454 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
455 options.revision = self.parsed_url.GetRevision()
456 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
457 self.root_dir(),
458 self.name)
459 scm.RunCommand('updatesingle', options,
460 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000461 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000462 else:
463 options.revision = revision_overrides.get(self.name)
464 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000465 scm.RunCommand(command, options, args, self._file_list)
466 self._file_list = [os.path.join(self.name, f.strip())
467 for f in self._file_list]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000468 options.revision = None
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000469 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000470 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000472 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000473 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
474 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000475 # src/foo. Yes, it's O(n^2)... It's important to do that before
476 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000477 for s in self.dependencies:
478 for s2 in self.dependencies:
479 if s is s2:
480 continue
481 if s.name.startswith(posixpath.join(s2.name, '')):
482 s.requirements.append(s2.name)
483
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000484 # Parse the dependencies of this dependency.
485 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000486 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000487
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000488 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000489 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000490 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000491 assert self.hooks_ran == False
492 if not self.should_process or self.recursion_limit() <= 0:
493 # Don't run the hook when it is above recursion_limit.
494 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000495 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000496 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000497 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 # TODO(maruel): If the user is using git or git-svn, then we don't know
499 # what files have changed so we always run all hooks. It'd be nice to fix
500 # that.
501 if (options.force or
502 isinstance(self.parsed_url, self.FileImpl) or
503 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
504 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
505 for hook_dict in self.deps_hooks:
506 self._RunHookAction(hook_dict, [])
507 else:
508 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
509 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000510 file_list = self.file_list()
511 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000512 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000513 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000515
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000517 file_list[i].lower()])
518 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519
520 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000521 while (file_list[i].startswith('\\') or
522 file_list[i].startswith('/')):
523 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000524
525 # Run hooks on the basis of whether the files from the gclient operation
526 # match each hook's pattern.
527 for hook_dict in self.deps_hooks:
528 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000529 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000530 if matching_file_list:
531 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000532 for s in self.dependencies:
533 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000534
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000535 def _RunHookAction(self, hook_dict, matching_file_list):
536 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000537 # A single DEPS file can specify multiple hooks so this function can be
538 # called multiple times on a single Dependency.
539 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000540 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000541 logging.debug(hook_dict)
542 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000543 command = hook_dict['action'][:]
544 if command[0] == 'python':
545 # If the hook specified "python" as the first item, the action is a
546 # Python script. Run it by starting a new copy of the same
547 # interpreter.
548 command[0] = sys.executable
549
550 if '$matching_files' in command:
551 splice_index = command.index('$matching_files')
552 command[splice_index:splice_index + 1] = matching_file_list
553
554 # Use a discrete exit status code of 2 to indicate that a hook action
555 # failed. Users of this script may wish to treat hook action failures
556 # differently from VC failures.
557 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
558
maruel@chromium.org271375b2010-06-23 19:17:38 +0000559 def root_dir(self):
560 return self.parent.root_dir()
561
562 def enforced_os(self):
563 return self.parent.enforced_os()
564
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000565 def recursion_limit(self):
566 return self.parent.recursion_limit() - 1
567
maruel@chromium.org0d812442010-08-10 12:41:08 +0000568 def tree(self, include_all):
569 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000570
maruel@chromium.org0d812442010-08-10 12:41:08 +0000571 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000572 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000573 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000574 for d in self.dependencies:
575 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000576 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000577 for d in self.dependencies:
578 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000579 return result
580
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000581 def get_custom_deps(self, name, url):
582 """Returns a custom deps if applicable."""
583 if self.parent:
584 url = self.parent.get_custom_deps(name, url)
585 # None is a valid return value to disable a dependency.
586 return self.custom_deps.get(name, url)
587
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000588 def file_list(self):
589 result = self._file_list[:]
590 for d in self.dependencies:
591 result.extend(d.file_list())
592 return result
593
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000594 def __str__(self):
595 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000596 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000597 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
598 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000599 # 'deps_file'
600 if self.__dict__[i]:
601 out.append('%s: %s' % (i, self.__dict__[i]))
602
603 for d in self.dependencies:
604 out.extend([' ' + x for x in str(d).splitlines()])
605 out.append('')
606 return '\n'.join(out)
607
608 def __repr__(self):
609 return '%s: %s' % (self.name, self.url)
610
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000611 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000612 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000613 out = '%s(%s)' % (self.name, self.url)
614 i = self.parent
615 while i and i.name:
616 out = '%s(%s) -> %s' % (i.name, i.url, out)
617 i = i.parent
618 return out
619
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000620 def root_parent(self):
621 """Returns the root object, normally a GClient object."""
622 d = self
623 while d.parent:
624 d = d.parent
625 return d
626
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000627
628class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000629 """Object that represent a gclient checkout. A tree of Dependency(), one per
630 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000631
632 DEPS_OS_CHOICES = {
633 "win32": "win",
634 "win": "win",
635 "cygwin": "win",
636 "darwin": "mac",
637 "mac": "mac",
638 "unix": "unix",
639 "linux": "unix",
640 "linux2": "unix",
641 }
642
643 DEFAULT_CLIENT_FILE_TEXT = ("""\
644solutions = [
645 { "name" : "%(solution_name)s",
646 "url" : "%(solution_url)s",
647 "custom_deps" : {
648 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000649 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000650 },
651]
652""")
653
654 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
655 { "name" : "%(solution_name)s",
656 "url" : "%(solution_url)s",
657 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000658%(solution_deps)s },
659 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000660 },
661""")
662
663 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
664# Snapshot generated with gclient revinfo --snapshot
665solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000666%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000667""")
668
669 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000670 # Do not change previous behavior. Only solution level and immediate DEPS
671 # are processed.
672 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000673 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000674 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000675 if options.deps_os:
676 enforced_os = options.deps_os.split(',')
677 else:
678 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
679 if 'all' in enforced_os:
680 enforced_os = self.DEPS_OS_CHOICES.itervalues()
681 self._enforced_os = list(set(enforced_os))
682 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000683 self.config_content = None
684
685 def SetConfig(self, content):
686 assert self.dependencies == []
687 config_dict = {}
688 self.config_content = content
689 try:
690 exec(content, config_dict)
691 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000692 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000693 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000694 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000695 tree = dict((d.name, d) for d in self.tree(False))
696 if s['name'] in tree:
697 raise gclient_utils.Error(
698 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000699 self.dependencies.append(Dependency(
700 self, s['name'], s['url'],
701 s.get('safesync_url', None),
702 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000703 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000704 None,
705 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000706 except KeyError:
707 raise gclient_utils.Error('Invalid .gclient file. Solution is '
708 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000709 # .gclient can have hooks.
710 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000711 self.direct_reference = True
712 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000713
714 def SaveConfig(self):
715 gclient_utils.FileWrite(os.path.join(self.root_dir(),
716 self._options.config_filename),
717 self.config_content)
718
719 @staticmethod
720 def LoadCurrentConfig(options):
721 """Searches for and loads a .gclient file relative to the current working
722 dir. Returns a GClient object."""
723 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
724 if not path:
725 return None
726 client = GClient(path, options)
727 client.SetConfig(gclient_utils.FileRead(
728 os.path.join(path, options.config_filename)))
729 return client
730
731 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
732 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
733 'solution_name': solution_name,
734 'solution_url': solution_url,
735 'safesync_url' : safesync_url,
736 })
737
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000739 """Creates a .gclient_entries file to record the list of unique checkouts.
740
741 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742 """
743 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
744 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000745 result = 'entries = {\n'
746 for entry in self.tree(False):
747 # Skip over File() dependencies as we can't version them.
748 if not isinstance(entry.parsed_url, self.FileImpl):
749 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
750 pprint.pformat(entry.parsed_url))
751 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000752 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000753 logging.info(result)
754 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000755
756 def _ReadEntries(self):
757 """Read the .gclient_entries file for the given client.
758
759 Returns:
760 A sequence of solution names, which will be empty if there is the
761 entries file hasn't been created yet.
762 """
763 scope = {}
764 filename = os.path.join(self.root_dir(), self._options.entries_filename)
765 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000766 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000767 try:
768 exec(gclient_utils.FileRead(filename), scope)
769 except SyntaxError, e:
770 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000771 return scope['entries']
772
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000773 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000774 """Checks for revision overrides."""
775 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000776 if self._options.head:
777 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000778 for s in self.dependencies:
779 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000780 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000781 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000782 rev = handle.read().strip()
783 handle.close()
784 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000785 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000786 if not self._options.revisions:
787 return revision_overrides
788 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000789 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000790 index = 0
791 for revision in self._options.revisions:
792 if not '@' in revision:
793 # Support for --revision 123
794 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000795 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000796 if not sol in solutions_names:
797 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
798 print >> sys.stderr, ('Please fix your script, having invalid '
799 '--revision flags will soon considered an error.')
800 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000801 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000802 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000803 return revision_overrides
804
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805 def RunOnDeps(self, command, args):
806 """Runs a command on each dependency in a client and its dependencies.
807
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808 Args:
809 command: The command to use (e.g., 'status' or 'diff')
810 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000811 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000812 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000813 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000814 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000816 if command == 'update' and not self._options.verbose:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000817 pm = Progress('Syncing projects', 1)
818 work_queue = ExecutionQueue(pm)
819 for s in self.dependencies:
820 work_queue.enqueue(s)
821 work_queue.flush(self._options, revision_overrides, command, args,
822 work_queue)
piman@chromium.org6f363722010-04-27 00:41:09 +0000823
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 # Once all the dependencies have been processed, it's now safe to run the
825 # hooks.
826 if not self._options.nohooks:
827 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000828
829 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000830 # Notify the user if there is an orphaned entry in their working copy.
831 # Only delete the directory if there are no changes in it, and
832 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000833 entries = [i.name for i in self.tree(False)]
834 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000835 # Fix path separator on Windows.
836 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000837 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000838 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000839 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000840 file_list = []
841 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
842 scm.status(self._options, [], file_list)
843 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000844 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000845 # There are modified files in this entry. Keep warning until
846 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000847 print(('\nWARNING: \'%s\' is no longer part of this client. '
848 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000849 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000850 else:
851 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000852 print('\n________ deleting \'%s\' in \'%s\'' % (
853 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000854 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000856 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000857 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000858
859 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000860 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000861 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000862 # Load all the settings.
maruel@chromium.org049bced2010-08-12 13:37:20 +0000863 work_queue = ExecutionQueue(None)
864 for s in self.dependencies:
865 work_queue.enqueue(s)
866 work_queue.flush(self._options, {}, None, [], work_queue)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000868 def GetURLAndRev(dep):
869 """Returns the revision-qualified SCM url for a Dependency."""
870 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000871 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000872 if isinstance(dep.parsed_url, self.FileImpl):
873 original_url = dep.parsed_url.file_location
874 else:
875 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000876 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000877 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000878 if not os.path.isdir(scm.checkout_path):
879 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000880 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000881
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000882 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 new_gclient = ''
884 # First level at .gclient
885 for d in self.dependencies:
886 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000887 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000888 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000889 for d in dep.dependencies:
890 entries[d.name] = GetURLAndRev(d)
891 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000892 GrabDeps(d)
893 custom_deps = []
894 for k in sorted(entries.keys()):
895 if entries[k]:
896 # Quotes aren't escaped...
897 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
898 else:
899 custom_deps.append(' \"%s\": None,\n' % k)
900 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
901 'solution_name': d.name,
902 'solution_url': d.url,
903 'safesync_url' : d.safesync_url or '',
904 'solution_deps': ''.join(custom_deps),
905 }
906 # Print the snapshot configuration file
907 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000908 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000909 entries = {}
910 for d in self.tree(False):
911 if self._options.actual:
912 entries[d.name] = GetURLAndRev(d)
913 else:
914 entries[d.name] = d.parsed_url
915 keys = sorted(entries.keys())
916 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000917 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000918 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000919
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000920 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000921 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000922 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000923
maruel@chromium.org75a59272010-06-11 22:34:03 +0000924 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000925 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000926 return self._root_dir
927
maruel@chromium.org271375b2010-06-23 19:17:38 +0000928 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000929 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000930 return self._enforced_os
931
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000932 def recursion_limit(self):
933 """How recursive can each dependencies in DEPS file can load DEPS file."""
934 return self._recursion_limit
935
maruel@chromium.org0d812442010-08-10 12:41:08 +0000936 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000937 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000938 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000939
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000940
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000941#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942
943
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000944def CMDcleanup(parser, args):
945 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000946
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000947Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000948"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000949 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
950 help='override deps for the specified (comma-separated) '
951 'platform(s); \'all\' will process all deps_os '
952 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000953 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000954 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000956 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000957 if options.verbose:
958 # Print out the .gclient file. This is longer than if we just printed the
959 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000960 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000961 return client.RunOnDeps('cleanup', args)
962
963
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000964@attr('usage', '[command] [args ...]')
965def CMDrecurse(parser, args):
966 """Operates on all the entries.
967
968 Runs a shell command on all entries.
969 """
970 # Stop parsing at the first non-arg so that these go through to the command
971 parser.disable_interspersed_args()
972 parser.add_option('-s', '--scm', action='append', default=[],
973 help='choose scm types to operate upon')
974 options, args = parser.parse_args(args)
975 root, entries = gclient_utils.GetGClientRootAndEntries()
976 scm_set = set()
977 for scm in options.scm:
978 scm_set.update(scm.split(','))
979
980 # Pass in the SCM type as an env variable
981 env = os.environ.copy()
982
983 for path, url in entries.iteritems():
984 scm = gclient_scm.GetScmName(url)
985 if scm_set and scm not in scm_set:
986 continue
987 dir = os.path.normpath(os.path.join(root, path))
988 env['GCLIENT_SCM'] = scm
989 env['GCLIENT_URL'] = url
990 subprocess.Popen(args, cwd=dir, env=env).communicate()
991
992
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000993@attr('usage', '[url] [safesync url]')
994def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000995 """Create a .gclient file in the current directory.
996
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000997This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000998top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000999modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001000provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001001URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001002"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001003 parser.add_option('--spec',
1004 help='create a gclient file containing the provided '
1005 'string. Due to Cygwin/Python brokenness, it '
1006 'probably can\'t contain any newlines.')
1007 parser.add_option('--name',
1008 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001009 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001010 if ((options.spec and args) or len(args) > 2 or
1011 (not options.spec and not args)):
1012 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1013
maruel@chromium.org0329e672009-05-13 18:41:04 +00001014 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001015 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001016 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001017 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001018 if options.spec:
1019 client.SetConfig(options.spec)
1020 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001021 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001022 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001023 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001024 else:
1025 # specify an alternate relpath for the given URL.
1026 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001027 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028 if len(args) > 1:
1029 safesync_url = args[1]
1030 client.SetDefaultConfig(name, base_url, safesync_url)
1031 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001032 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033
1034
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001035def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001036 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001037 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1038 help='override deps for the specified (comma-separated) '
1039 'platform(s); \'all\' will process all deps_os '
1040 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001041 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001042 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001043 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001044 client = GClient.LoadCurrentConfig(options)
1045
1046 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001047 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001048
1049 if options.verbose:
1050 # Print out the .gclient file. This is longer than if we just printed the
1051 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001052 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001053 return client.RunOnDeps('export', args)
1054
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001056@attr('epilog', """Example:
1057 gclient pack > patch.txt
1058 generate simple patch for configured client and dependences
1059""")
1060def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001061 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001062
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001063Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001064dependencies, and performs minimal postprocessing of the output. The
1065resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001066checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001067"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001068 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1069 help='override deps for the specified (comma-separated) '
1070 'platform(s); \'all\' will process all deps_os '
1071 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001073 client = GClient.LoadCurrentConfig(options)
1074 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001075 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001076 if options.verbose:
1077 # Print out the .gclient file. This is longer than if we just printed the
1078 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001079 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001080 return client.RunOnDeps('pack', args)
1081
1082
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001083def CMDstatus(parser, args):
1084 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001085 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1086 help='override deps for the specified (comma-separated) '
1087 'platform(s); \'all\' will process all deps_os '
1088 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001090 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001092 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001093 if options.verbose:
1094 # Print out the .gclient file. This is longer than if we just printed the
1095 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001096 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 return client.RunOnDeps('status', args)
1098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001101 gclient sync
1102 update files from SCM according to current configuration,
1103 *for modules which have changed since last update or sync*
1104 gclient sync --force
1105 update files from SCM according to current configuration, for
1106 all modules (useful for recovering files deleted from local copy)
1107 gclient sync --revision src@31000
1108 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109""")
1110def CMDsync(parser, args):
1111 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001112 parser.add_option('-f', '--force', action='store_true',
1113 help='force update even for unchanged modules')
1114 parser.add_option('-n', '--nohooks', action='store_true',
1115 help='don\'t run hooks after the update is complete')
1116 parser.add_option('-r', '--revision', action='append',
1117 dest='revisions', metavar='REV', default=[],
1118 help='Enforces revision/hash for the solutions with the '
1119 'format src@rev. The src@ part is optional and can be '
1120 'skipped. -r can be used multiple times when .gclient '
1121 'has multiple solutions configured and will work even '
1122 'if the src@ part is skipped.')
1123 parser.add_option('-H', '--head', action='store_true',
1124 help='skips any safesync_urls specified in '
1125 'configured solutions and sync to head instead')
1126 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1127 help='delete any unexpected unversioned trees '
1128 'that are in the checkout')
1129 parser.add_option('-R', '--reset', action='store_true',
1130 help='resets any local changes before updating (git only)')
1131 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1132 help='override deps for the specified (comma-separated) '
1133 'platform(s); \'all\' will process all deps_os '
1134 'references')
1135 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1136 help='Skip svn up whenever possible by requesting '
1137 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001138 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001139 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140
1141 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001142 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001143
maruel@chromium.org307d1792010-05-31 20:03:13 +00001144 if options.revisions and options.head:
1145 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001146 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001147
1148 if options.verbose:
1149 # Print out the .gclient file. This is longer than if we just printed the
1150 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001151 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 return client.RunOnDeps('update', args)
1153
1154
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001155def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001156 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001157 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159def CMDdiff(parser, args):
1160 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001161 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1162 help='override deps for the specified (comma-separated) '
1163 'platform(s); \'all\' will process all deps_os '
1164 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001166 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001167 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001168 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001169 if options.verbose:
1170 # Print out the .gclient file. This is longer than if we just printed the
1171 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001172 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001173 return client.RunOnDeps('diff', args)
1174
1175
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176def CMDrevert(parser, args):
1177 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001178 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1179 help='override deps for the specified (comma-separated) '
1180 'platform(s); \'all\' will process all deps_os '
1181 'references')
1182 parser.add_option('-n', '--nohooks', action='store_true',
1183 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001184 (options, args) = parser.parse_args(args)
1185 # --force is implied.
1186 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001187 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001189 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001190 return client.RunOnDeps('revert', args)
1191
1192
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193def CMDrunhooks(parser, args):
1194 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001195 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1196 help='override deps for the specified (comma-separated) '
1197 'platform(s); \'all\' will process all deps_os '
1198 'references')
1199 parser.add_option('-f', '--force', action='store_true', default=True,
1200 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001201 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001202 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001204 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001205 if options.verbose:
1206 # Print out the .gclient file. This is longer than if we just printed the
1207 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001208 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001209 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001210 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001211 return client.RunOnDeps('runhooks', args)
1212
1213
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001214def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001215 """Output revision info mapping for the client and its dependencies.
1216
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001217 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001218 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001219 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1220 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001221 commit can change.
1222 """
1223 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1224 help='override deps for the specified (comma-separated) '
1225 'platform(s); \'all\' will process all deps_os '
1226 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001227 parser.add_option('-a', '--actual', action='store_true',
1228 help='gets the actual checked out revisions instead of the '
1229 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001230 parser.add_option('-s', '--snapshot', action='store_true',
1231 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001232 'version of all repositories to reproduce the tree, '
1233 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001234 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001235 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001236 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001237 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001238 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001239 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001240
1241
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242def Command(name):
1243 return getattr(sys.modules[__name__], 'CMD' + name, None)
1244
1245
1246def CMDhelp(parser, args):
1247 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001248 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001249 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001250 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001251 parser.print_help()
1252 return 0
1253
1254
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001255def GenUsage(parser, command):
1256 """Modify an OptParse object with the function's documentation."""
1257 obj = Command(command)
1258 if command == 'help':
1259 command = '<command>'
1260 # OptParser.description prefer nicely non-formatted strings.
1261 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1262 usage = getattr(obj, 'usage', '')
1263 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1264 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001265
1266
1267def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001268 """Doesn't parse the arguments here, just find the right subcommand to
1269 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001270 try:
1271 # Do it late so all commands are listed.
1272 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1273 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1274 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1275 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001276 parser.add_option('-v', '--verbose', action='count', default=0,
1277 help='Produces additional output for diagnostics. Can be '
1278 'used up to three times for more logging info.')
1279 parser.add_option('--gclientfile', dest='config_filename',
1280 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1281 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001282 # Integrate standard options processing.
1283 old_parser = parser.parse_args
1284 def Parse(args):
1285 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001286 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001287 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001288 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001289 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001290 level = logging.DEBUG
1291 logging.basicConfig(level=level,
1292 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1293 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001294
1295 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001296 if not hasattr(options, 'revisions'):
1297 # GClient.RunOnDeps expects it even if not applicable.
1298 options.revisions = []
1299 if not hasattr(options, 'head'):
1300 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001301 if not hasattr(options, 'nohooks'):
1302 options.nohooks = True
1303 if not hasattr(options, 'deps_os'):
1304 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001305 if not hasattr(options, 'manually_grab_svn_rev'):
1306 options.manually_grab_svn_rev = None
1307 if not hasattr(options, 'force'):
1308 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001309 return (options, args)
1310 parser.parse_args = Parse
1311 # We don't want wordwrapping in epilog (usually examples)
1312 parser.format_epilog = lambda _: parser.epilog or ''
1313 if argv:
1314 command = Command(argv[0])
1315 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001316 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001317 GenUsage(parser, argv[0])
1318 return command(parser, argv[1:])
1319 # Not a known command. Default to help.
1320 GenUsage(parser, 'help')
1321 return CMDhelp(parser, argv)
1322 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001323 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001324 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001325
1326
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001327if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001328 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329
1330# vim: ts=2:sw=2:tw=80:et: