blob: 4170f5fc62e716bfa8a024f5abfb227acf8a615c [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.org049bced2010-08-12 13:37:20 +000052__version__ = "0.5.1"
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,
247 custom_vars, deps_file):
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.org85c2a192010-07-22 21:14:43 +0000266 # A direct reference is dependency that is referenced by a deps, deps_os or
267 # solution. A indirect one is one that was loaded with From() or that
268 # exceeded recursion limit.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000269 self.direct_reference = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000270 # This dependency has been processed, i.e. checked out
271 self.processed = False
272 # This dependency had its hook run
273 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000274 # Required dependencies to run before running this one:
275 self.requirements = []
276 if self.parent and self.parent.name:
277 self.requirements.append(self.parent.name)
278 if isinstance(self.url, self.FromImpl):
279 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000280
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000281 # Sanity checks
282 if not self.name and self.parent:
283 raise gclient_utils.Error('Dependency without name')
284 if not isinstance(self.url,
285 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
286 raise gclient_utils.Error('dependency url must be either a string, None, '
287 'File() or From() instead of %s' %
288 self.url.__class__.__name__)
289 if '/' in self.deps_file or '\\' in self.deps_file:
290 raise gclient_utils.Error('deps_file name must not be a path, just a '
291 'filename. %s' % self.deps_file)
292
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000293 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000294 """Resolves the parsed url from url.
295
296 Manages From() keyword accordingly. Do not touch self.parsed_url nor
297 self.url because it may called with other urls due to From()."""
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000298 overriden_url = self.get_custom_deps(self.name, url)
299 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000300 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000301 overriden_url))
302 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000303 elif isinstance(url, self.FromImpl):
304 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000305 if not ref:
306 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
307 url.module_name, ref))
308 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000309 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000310 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000311 # Make sure the referenced dependency DEPS file is loaded and file the
312 # inner referenced dependency.
313 ref.ParseDepsFile(False)
314 found_dep = None
315 for d in ref.dependencies:
316 if d.name == sub_target:
317 found_dep = d
318 break
319 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000320 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000321 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
322 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000323 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000324 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000325 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000326 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000327 elif isinstance(url, basestring):
328 parsed_url = urlparse.urlparse(url)
329 if not parsed_url[0]:
330 # A relative url. Fetch the real base.
331 path = parsed_url[2]
332 if not path.startswith('/'):
333 raise gclient_utils.Error(
334 'relative DEPS entry \'%s\' must begin with a slash' % url)
335 # Create a scm just to query the full url.
336 parent_url = self.parent.parsed_url
337 if isinstance(parent_url, self.FileImpl):
338 parent_url = parent_url.file_location
339 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000340 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000341 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000342 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000343 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000344 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000346 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000347 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000348 return parsed_url
349 elif url is None:
350 return None
351 else:
352 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000353
maruel@chromium.org271375b2010-06-23 19:17:38 +0000354 def ParseDepsFile(self, direct_reference):
355 """Parses the DEPS file for this dependency."""
356 if direct_reference:
357 # Maybe it was referenced earlier by a From() keyword but it's now
358 # directly referenced.
359 self.direct_reference = direct_reference
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000361 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000363 self.deps_parsed = True
364 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
365 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000366 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000367 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000368 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000369 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000370
maruel@chromium.org271375b2010-06-23 19:17:38 +0000371 # Eval the content.
372 # One thing is unintuitive, vars= {} must happen before Var() use.
373 local_scope = {}
374 var = self.VarImpl(self.custom_vars, local_scope)
375 global_scope = {
376 'File': self.FileImpl,
377 'From': self.FromImpl,
378 'Var': var.Lookup,
379 'deps_os': {},
380 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000381 try:
382 exec(deps_content, global_scope, local_scope)
383 except SyntaxError, e:
384 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000385 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000386 # load os specific dependencies if defined. these dependencies may
387 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000388 if 'deps_os' in local_scope:
389 for deps_os_key in self.enforced_os():
390 os_deps = local_scope['deps_os'].get(deps_os_key, {})
391 if len(self.enforced_os()) > 1:
392 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000393 # platform, so we collect the broadest set of dependencies available.
394 # We may end up with the wrong revision of something for our
395 # platform, but this is the best we can do.
396 deps.update([x for x in os_deps.items() if not x[0] in deps])
397 else:
398 deps.update(os_deps)
399
maruel@chromium.org271375b2010-06-23 19:17:38 +0000400 self.deps_hooks.extend(local_scope.get('hooks', []))
401
402 # If a line is in custom_deps, but not in the solution, we want to append
403 # this line to the solution.
404 for d in self.custom_deps:
405 if d not in deps:
406 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000407
408 # If use_relative_paths is set in the DEPS file, regenerate
409 # the dictionary using paths relative to the directory containing
410 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000411 use_relative_paths = local_scope.get('use_relative_paths', False)
412 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000413 rel_deps = {}
414 for d, url in deps.items():
415 # normpath is required to allow DEPS to use .. in their
416 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000417 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
418 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000419
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420 # Convert the deps into real Dependency.
421 for name, url in deps.iteritems():
422 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000423 raise gclient_utils.Error(
424 'The same name "%s" appears multiple times in the deps section' %
425 name)
maruel@chromium.org0d812442010-08-10 12:41:08 +0000426 self.dependencies.append(Dependency(self, name, url, None, None, None,
427 None))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000428 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000429
maruel@chromium.org049bced2010-08-12 13:37:20 +0000430 def run(self, options, revision_overrides, command, args, work_queue):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000431 """Runs 'command' before parsing the DEPS in case it's a initial checkout
432 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000433 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000434 # When running runhooks, there's no need to consult the SCM.
435 # All known hooks are expected to run unconditionally regardless of working
436 # copy state, so skip the SCM status check.
437 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000438 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000439 if run_scm and self.parsed_url:
440 if isinstance(self.parsed_url, self.FileImpl):
441 # Special support for single-file checkout.
442 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
443 options.revision = self.parsed_url.GetRevision()
444 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
445 self.root_dir(),
446 self.name)
447 scm.RunCommand('updatesingle', options,
448 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000449 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 else:
451 options.revision = revision_overrides.get(self.name)
452 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000453 scm.RunCommand(command, options, args, self._file_list)
454 self._file_list = [os.path.join(self.name, f.strip())
455 for f in self._file_list]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 options.revision = None
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000457 self.processed = True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458 if self.recursion_limit():
459 # Then we can parse the DEPS file.
460 self.ParseDepsFile(True)
maruel@chromium.org621939b2010-08-10 20:12:00 +0000461 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
462 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000463 # src/foo. Yes, it's O(n^2)... It's important to do that before
464 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000465 for s in self.dependencies:
466 for s2 in self.dependencies:
467 if s is s2:
468 continue
469 if s.name.startswith(posixpath.join(s2.name, '')):
470 s.requirements.append(s2.name)
471
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000472 # Parse the dependencies of this dependency.
473 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000474 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000475
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000477 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000478 must have been called before to load the DEPS."""
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000479 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000480 # changed.
481 if self.deps_hooks and self.direct_reference:
482 # TODO(maruel): If the user is using git or git-svn, then we don't know
483 # what files have changed so we always run all hooks. It'd be nice to fix
484 # that.
485 if (options.force or
486 isinstance(self.parsed_url, self.FileImpl) or
487 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
488 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
489 for hook_dict in self.deps_hooks:
490 self._RunHookAction(hook_dict, [])
491 else:
492 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
493 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000494 file_list = self.file_list()
495 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000496 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000497 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000499
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000500 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000501 file_list[i].lower()])
502 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503
504 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000505 while (file_list[i].startswith('\\') or
506 file_list[i].startswith('/')):
507 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508
509 # Run hooks on the basis of whether the files from the gclient operation
510 # match each hook's pattern.
511 for hook_dict in self.deps_hooks:
512 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000513 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 if matching_file_list:
515 self._RunHookAction(hook_dict, matching_file_list)
516 if self.recursion_limit():
517 for s in self.dependencies:
518 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000520 def _RunHookAction(self, hook_dict, matching_file_list):
521 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000522 # A single DEPS file can specify multiple hooks so this function can be
523 # called multiple times on a single Dependency.
524 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000525 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000526 logging.debug(hook_dict)
527 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000528 command = hook_dict['action'][:]
529 if command[0] == 'python':
530 # If the hook specified "python" as the first item, the action is a
531 # Python script. Run it by starting a new copy of the same
532 # interpreter.
533 command[0] = sys.executable
534
535 if '$matching_files' in command:
536 splice_index = command.index('$matching_files')
537 command[splice_index:splice_index + 1] = matching_file_list
538
539 # Use a discrete exit status code of 2 to indicate that a hook action
540 # failed. Users of this script may wish to treat hook action failures
541 # differently from VC failures.
542 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
543
maruel@chromium.org271375b2010-06-23 19:17:38 +0000544 def root_dir(self):
545 return self.parent.root_dir()
546
547 def enforced_os(self):
548 return self.parent.enforced_os()
549
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000550 def recursion_limit(self):
551 return self.parent.recursion_limit() - 1
552
maruel@chromium.org0d812442010-08-10 12:41:08 +0000553 def tree(self, include_all):
554 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000555
maruel@chromium.org0d812442010-08-10 12:41:08 +0000556 def subtree(self, include_all):
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000557 result = []
558 # Add breadth-first.
maruel@chromium.org0d812442010-08-10 12:41:08 +0000559 if self.direct_reference or include_all:
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000560 for d in self.dependencies:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000561 result.append(d)
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000562 for d in self.dependencies:
maruel@chromium.org0d812442010-08-10 12:41:08 +0000563 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000564 return result
565
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000566 def get_custom_deps(self, name, url):
567 """Returns a custom deps if applicable."""
568 if self.parent:
569 url = self.parent.get_custom_deps(name, url)
570 # None is a valid return value to disable a dependency.
571 return self.custom_deps.get(name, url)
572
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000573 def file_list(self):
574 result = self._file_list[:]
575 for d in self.dependencies:
576 result.extend(d.file_list())
577 return result
578
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000579 def __str__(self):
580 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000581 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
582 'custom_vars', 'deps_hooks', '_file_list', 'processed',
maruel@chromium.org049bced2010-08-12 13:37:20 +0000583 'hooks_ran', 'deps_parsed', 'requirements', 'direct_reference'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000584 # 'deps_file'
585 if self.__dict__[i]:
586 out.append('%s: %s' % (i, self.__dict__[i]))
587
588 for d in self.dependencies:
589 out.extend([' ' + x for x in str(d).splitlines()])
590 out.append('')
591 return '\n'.join(out)
592
593 def __repr__(self):
594 return '%s: %s' % (self.name, self.url)
595
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000596 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000597 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000598 out = '%s(%s)' % (self.name, self.url)
599 i = self.parent
600 while i and i.name:
601 out = '%s(%s) -> %s' % (i.name, i.url, out)
602 i = i.parent
603 return out
604
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000605 def root_parent(self):
606 """Returns the root object, normally a GClient object."""
607 d = self
608 while d.parent:
609 d = d.parent
610 return d
611
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000612
613class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000614 """Object that represent a gclient checkout. A tree of Dependency(), one per
615 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000616
617 DEPS_OS_CHOICES = {
618 "win32": "win",
619 "win": "win",
620 "cygwin": "win",
621 "darwin": "mac",
622 "mac": "mac",
623 "unix": "unix",
624 "linux": "unix",
625 "linux2": "unix",
626 }
627
628 DEFAULT_CLIENT_FILE_TEXT = ("""\
629solutions = [
630 { "name" : "%(solution_name)s",
631 "url" : "%(solution_url)s",
632 "custom_deps" : {
633 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000634 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000635 },
636]
637""")
638
639 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
640 { "name" : "%(solution_name)s",
641 "url" : "%(solution_url)s",
642 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000643%(solution_deps)s },
644 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000645 },
646""")
647
648 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
649# Snapshot generated with gclient revinfo --snapshot
650solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000651%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000652""")
653
654 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000655 # Do not change previous behavior. Only solution level and immediate DEPS
656 # are processed.
657 self._recursion_limit = 2
658 Dependency.__init__(self, None, None, None, None, None, None, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000659 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000660 if options.deps_os:
661 enforced_os = options.deps_os.split(',')
662 else:
663 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
664 if 'all' in enforced_os:
665 enforced_os = self.DEPS_OS_CHOICES.itervalues()
666 self._enforced_os = list(set(enforced_os))
667 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000668 self.config_content = None
669
670 def SetConfig(self, content):
671 assert self.dependencies == []
672 config_dict = {}
673 self.config_content = content
674 try:
675 exec(content, config_dict)
676 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000677 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000678 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000679 try:
680 self.dependencies.append(Dependency(
681 self, s['name'], s['url'],
682 s.get('safesync_url', None),
683 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000684 s.get('custom_vars', {}),
685 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000686 except KeyError:
687 raise gclient_utils.Error('Invalid .gclient file. Solution is '
688 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000689 # .gclient can have hooks.
690 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000691 self.direct_reference = True
692 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000693
694 def SaveConfig(self):
695 gclient_utils.FileWrite(os.path.join(self.root_dir(),
696 self._options.config_filename),
697 self.config_content)
698
699 @staticmethod
700 def LoadCurrentConfig(options):
701 """Searches for and loads a .gclient file relative to the current working
702 dir. Returns a GClient object."""
703 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
704 if not path:
705 return None
706 client = GClient(path, options)
707 client.SetConfig(gclient_utils.FileRead(
708 os.path.join(path, options.config_filename)))
709 return client
710
711 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
712 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
713 'solution_name': solution_name,
714 'solution_url': solution_url,
715 'safesync_url' : safesync_url,
716 })
717
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000718 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000719 """Creates a .gclient_entries file to record the list of unique checkouts.
720
721 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000722 """
723 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
724 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000725 result = 'entries = {\n'
726 for entry in self.tree(False):
727 # Skip over File() dependencies as we can't version them.
728 if not isinstance(entry.parsed_url, self.FileImpl):
729 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
730 pprint.pformat(entry.parsed_url))
731 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000732 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000733 logging.info(result)
734 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000735
736 def _ReadEntries(self):
737 """Read the .gclient_entries file for the given client.
738
739 Returns:
740 A sequence of solution names, which will be empty if there is the
741 entries file hasn't been created yet.
742 """
743 scope = {}
744 filename = os.path.join(self.root_dir(), self._options.entries_filename)
745 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000746 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000747 try:
748 exec(gclient_utils.FileRead(filename), scope)
749 except SyntaxError, e:
750 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000751 return scope['entries']
752
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000753 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000754 """Checks for revision overrides."""
755 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000756 if self._options.head:
757 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000758 for s in self.dependencies:
759 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000760 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000761 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000762 rev = handle.read().strip()
763 handle.close()
764 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000765 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000766 if not self._options.revisions:
767 return revision_overrides
768 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000769 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000770 index = 0
771 for revision in self._options.revisions:
772 if not '@' in revision:
773 # Support for --revision 123
774 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000775 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000776 if not sol in solutions_names:
777 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
778 print >> sys.stderr, ('Please fix your script, having invalid '
779 '--revision flags will soon considered an error.')
780 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000781 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000782 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000783 return revision_overrides
784
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000785 def RunOnDeps(self, command, args):
786 """Runs a command on each dependency in a client and its dependencies.
787
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000788 Args:
789 command: The command to use (e.g., 'status' or 'diff')
790 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000791 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000792 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000793 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000794 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000795 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000796 if command == 'update' and not self._options.verbose:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000797 pm = Progress('Syncing projects', 1)
798 work_queue = ExecutionQueue(pm)
799 for s in self.dependencies:
800 work_queue.enqueue(s)
801 work_queue.flush(self._options, revision_overrides, command, args,
802 work_queue)
piman@chromium.org6f363722010-04-27 00:41:09 +0000803
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000804 # Once all the dependencies have been processed, it's now safe to run the
805 # hooks.
806 if not self._options.nohooks:
807 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808
809 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000810 # Notify the user if there is an orphaned entry in their working copy.
811 # Only delete the directory if there are no changes in it, and
812 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000813 entries = [i.name for i in self.tree(False)]
814 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000815 # Fix path separator on Windows.
816 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000817 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000818 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000819 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000820 file_list = []
821 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
822 scm.status(self._options, [], file_list)
823 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000824 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000825 # There are modified files in this entry. Keep warning until
826 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000827 print(('\nWARNING: \'%s\' is no longer part of this client. '
828 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000829 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830 else:
831 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000832 print('\n________ deleting \'%s\' in \'%s\'' % (
833 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000834 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000836 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000837 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838
839 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000840 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000841 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000842 # Load all the settings.
maruel@chromium.org049bced2010-08-12 13:37:20 +0000843 work_queue = ExecutionQueue(None)
844 for s in self.dependencies:
845 work_queue.enqueue(s)
846 work_queue.flush(self._options, {}, None, [], work_queue)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000847
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000848 def GetURLAndRev(dep):
849 """Returns the revision-qualified SCM url for a Dependency."""
850 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000851 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000852 if isinstance(dep.parsed_url, self.FileImpl):
853 original_url = dep.parsed_url.file_location
854 else:
855 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000856 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000857 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000858 if not os.path.isdir(scm.checkout_path):
859 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000860 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000862 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000863 new_gclient = ''
864 # First level at .gclient
865 for d in self.dependencies:
866 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000867 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000869 for d in dep.dependencies:
870 entries[d.name] = GetURLAndRev(d)
871 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000872 GrabDeps(d)
873 custom_deps = []
874 for k in sorted(entries.keys()):
875 if entries[k]:
876 # Quotes aren't escaped...
877 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
878 else:
879 custom_deps.append(' \"%s\": None,\n' % k)
880 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
881 'solution_name': d.name,
882 'solution_url': d.url,
883 'safesync_url' : d.safesync_url or '',
884 'solution_deps': ''.join(custom_deps),
885 }
886 # Print the snapshot configuration file
887 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000888 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000889 entries = {}
890 for d in self.tree(False):
891 if self._options.actual:
892 entries[d.name] = GetURLAndRev(d)
893 else:
894 entries[d.name] = d.parsed_url
895 keys = sorted(entries.keys())
896 for x in keys:
897 line = '%s: %s' % (x, entries[x])
898 if x is not keys[-1]:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000899 line += ';'
900 print line
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000901 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000902
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000903 def ParseDepsFile(self, direct_reference):
904 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000905 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000906
maruel@chromium.org75a59272010-06-11 22:34:03 +0000907 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000908 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000909 return self._root_dir
910
maruel@chromium.org271375b2010-06-23 19:17:38 +0000911 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000912 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000913 return self._enforced_os
914
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000915 def recursion_limit(self):
916 """How recursive can each dependencies in DEPS file can load DEPS file."""
917 return self._recursion_limit
918
maruel@chromium.org0d812442010-08-10 12:41:08 +0000919 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000920 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000921 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000922
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000923
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000924#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925
926
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000927def CMDcleanup(parser, args):
928 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000929
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000930Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000931"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000932 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
933 help='override deps for the specified (comma-separated) '
934 'platform(s); \'all\' will process all deps_os '
935 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000936 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000937 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000938 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000939 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000940 if options.verbose:
941 # Print out the .gclient file. This is longer than if we just printed the
942 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000943 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944 return client.RunOnDeps('cleanup', args)
945
946
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000947@attr('usage', '[command] [args ...]')
948def CMDrecurse(parser, args):
949 """Operates on all the entries.
950
951 Runs a shell command on all entries.
952 """
953 # Stop parsing at the first non-arg so that these go through to the command
954 parser.disable_interspersed_args()
955 parser.add_option('-s', '--scm', action='append', default=[],
956 help='choose scm types to operate upon')
957 options, args = parser.parse_args(args)
958 root, entries = gclient_utils.GetGClientRootAndEntries()
959 scm_set = set()
960 for scm in options.scm:
961 scm_set.update(scm.split(','))
962
963 # Pass in the SCM type as an env variable
964 env = os.environ.copy()
965
966 for path, url in entries.iteritems():
967 scm = gclient_scm.GetScmName(url)
968 if scm_set and scm not in scm_set:
969 continue
970 dir = os.path.normpath(os.path.join(root, path))
971 env['GCLIENT_SCM'] = scm
972 env['GCLIENT_URL'] = url
973 subprocess.Popen(args, cwd=dir, env=env).communicate()
974
975
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000976@attr('usage', '[url] [safesync url]')
977def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000978 """Create a .gclient file in the current directory.
979
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000980This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000981top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000982modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000983provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000984URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000985"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000986 parser.add_option('--spec',
987 help='create a gclient file containing the provided '
988 'string. Due to Cygwin/Python brokenness, it '
989 'probably can\'t contain any newlines.')
990 parser.add_option('--name',
991 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000992 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000993 if ((options.spec and args) or len(args) > 2 or
994 (not options.spec and not args)):
995 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
996
maruel@chromium.org0329e672009-05-13 18:41:04 +0000997 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000998 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000999 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001000 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001001 if options.spec:
1002 client.SetConfig(options.spec)
1003 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001004 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001005 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001006 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001007 else:
1008 # specify an alternate relpath for the given URL.
1009 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001010 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001011 if len(args) > 1:
1012 safesync_url = args[1]
1013 client.SetDefaultConfig(name, base_url, safesync_url)
1014 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001015 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001016
1017
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001018def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001019 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001020 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1021 help='override deps for the specified (comma-separated) '
1022 'platform(s); \'all\' will process all deps_os '
1023 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001024 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001025 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001026 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001027 client = GClient.LoadCurrentConfig(options)
1028
1029 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001030 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001031
1032 if options.verbose:
1033 # Print out the .gclient file. This is longer than if we just printed the
1034 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001035 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001036 return client.RunOnDeps('export', args)
1037
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001039@attr('epilog', """Example:
1040 gclient pack > patch.txt
1041 generate simple patch for configured client and dependences
1042""")
1043def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001044 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001045
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001046Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001047dependencies, and performs minimal postprocessing of the output. The
1048resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001049checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001050"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001051 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1052 help='override deps for the specified (comma-separated) '
1053 'platform(s); \'all\' will process all deps_os '
1054 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001055 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001056 client = GClient.LoadCurrentConfig(options)
1057 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001058 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001059 if options.verbose:
1060 # Print out the .gclient file. This is longer than if we just printed the
1061 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001062 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001063 return client.RunOnDeps('pack', args)
1064
1065
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001066def CMDstatus(parser, args):
1067 """Show modification status for every dependencies."""
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)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001073 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001074 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001075 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +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)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001080 return client.RunOnDeps('status', args)
1081
1082
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001083@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001084 gclient sync
1085 update files from SCM according to current configuration,
1086 *for modules which have changed since last update or sync*
1087 gclient sync --force
1088 update files from SCM according to current configuration, for
1089 all modules (useful for recovering files deleted from local copy)
1090 gclient sync --revision src@31000
1091 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001092""")
1093def CMDsync(parser, args):
1094 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001095 parser.add_option('-f', '--force', action='store_true',
1096 help='force update even for unchanged modules')
1097 parser.add_option('-n', '--nohooks', action='store_true',
1098 help='don\'t run hooks after the update is complete')
1099 parser.add_option('-r', '--revision', action='append',
1100 dest='revisions', metavar='REV', default=[],
1101 help='Enforces revision/hash for the solutions with the '
1102 'format src@rev. The src@ part is optional and can be '
1103 'skipped. -r can be used multiple times when .gclient '
1104 'has multiple solutions configured and will work even '
1105 'if the src@ part is skipped.')
1106 parser.add_option('-H', '--head', action='store_true',
1107 help='skips any safesync_urls specified in '
1108 'configured solutions and sync to head instead')
1109 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1110 help='delete any unexpected unversioned trees '
1111 'that are in the checkout')
1112 parser.add_option('-R', '--reset', action='store_true',
1113 help='resets any local changes before updating (git only)')
1114 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1115 help='override deps for the specified (comma-separated) '
1116 'platform(s); \'all\' will process all deps_os '
1117 'references')
1118 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1119 help='Skip svn up whenever possible by requesting '
1120 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001122 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123
1124 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001125 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001126
maruel@chromium.org307d1792010-05-31 20:03:13 +00001127 if options.revisions and options.head:
1128 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001129 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130
1131 if options.verbose:
1132 # Print out the .gclient file. This is longer than if we just printed the
1133 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001134 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135 return client.RunOnDeps('update', args)
1136
1137
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001138def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001139 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001140 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001141
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001142def CMDdiff(parser, args):
1143 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001144 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1145 help='override deps for the specified (comma-separated) '
1146 'platform(s); \'all\' will process all deps_os '
1147 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001148 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001149 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001150 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001151 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 if options.verbose:
1153 # Print out the .gclient file. This is longer than if we just printed the
1154 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001155 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156 return client.RunOnDeps('diff', args)
1157
1158
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159def CMDrevert(parser, args):
1160 """Revert all modifications in 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')
1165 parser.add_option('-n', '--nohooks', action='store_true',
1166 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001167 (options, args) = parser.parse_args(args)
1168 # --force is implied.
1169 options.force = True
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 return client.RunOnDeps('revert', args)
1174
1175
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176def CMDrunhooks(parser, args):
1177 """Runs hooks for files that have been modified in the local working copy."""
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('-f', '--force', action='store_true', default=True,
1183 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001184 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001185 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001187 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 if options.verbose:
1189 # Print out the .gclient file. This is longer than if we just printed the
1190 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001191 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001192 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001194 return client.RunOnDeps('runhooks', args)
1195
1196
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001197def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001198 """Output revision info mapping for the client and its dependencies.
1199
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001200 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001201 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001202 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1203 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001204 commit can change.
1205 """
1206 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1207 help='override deps for the specified (comma-separated) '
1208 'platform(s); \'all\' will process all deps_os '
1209 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001210 parser.add_option('-a', '--actual', action='store_true',
1211 help='gets the actual checked out revisions instead of the '
1212 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001213 parser.add_option('-s', '--snapshot', action='store_true',
1214 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001215 'version of all repositories to reproduce the tree, '
1216 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001217 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001218 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001219 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001220 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001221 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001222 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001223
1224
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001225def Command(name):
1226 return getattr(sys.modules[__name__], 'CMD' + name, None)
1227
1228
1229def CMDhelp(parser, args):
1230 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001231 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001232 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001233 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001234 parser.print_help()
1235 return 0
1236
1237
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001238def GenUsage(parser, command):
1239 """Modify an OptParse object with the function's documentation."""
1240 obj = Command(command)
1241 if command == 'help':
1242 command = '<command>'
1243 # OptParser.description prefer nicely non-formatted strings.
1244 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1245 usage = getattr(obj, 'usage', '')
1246 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1247 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248
1249
1250def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001251 """Doesn't parse the arguments here, just find the right subcommand to
1252 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001253 try:
1254 # Do it late so all commands are listed.
1255 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1256 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1257 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1258 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001259 parser.add_option('-v', '--verbose', action='count', default=0,
1260 help='Produces additional output for diagnostics. Can be '
1261 'used up to three times for more logging info.')
1262 parser.add_option('--gclientfile', dest='config_filename',
1263 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1264 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001265 # Integrate standard options processing.
1266 old_parser = parser.parse_args
1267 def Parse(args):
1268 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001269 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001270 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001271 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001272 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001273 level = logging.DEBUG
1274 logging.basicConfig(level=level,
1275 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1276 options.entries_filename = options.config_filename + '_entries'
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)
1305 except gclient_utils.Error, 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.org6e29d572010-06-04 17:32:20 +00001311 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001312
1313# vim: ts=2:sw=2:tw=80:et: