Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: gclient.py

Issue 2973001: Revert the refactor (r52005) again, it broke webkit checkout. (Closed)
Patch Set: Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | gclient_utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 # 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Meta checkout manager supporting both Subversion and GIT. 6 """Meta checkout manager supporting both Subversion and GIT.
7 7
8 Files 8 Files
9 .gclient : Current client configuration, written by 'config' command. 9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose 10 Format is a Python script defining 'solutions', a list whose
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
42 it will be removed from the list and the list will be extended 42 it will be removed from the list and the list will be extended
43 by the list of matching files. 43 by the list of matching files.
44 44
45 Example: 45 Example:
46 hooks = [ 46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", 47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]}, 48 "action": ["python", "image_indexer.py", "--all"]},
49 ] 49 ]
50 """ 50 """
51 51
52 __version__ = "0.5" 52 __version__ = "0.4.1"
53 53
54 import errno
54 import logging 55 import logging
55 import optparse 56 import optparse
56 import os 57 import os
57 import pprint 58 import pprint
58 import re 59 import re
59 import subprocess 60 import subprocess
60 import sys 61 import sys
61 import urlparse 62 import urlparse
62 import urllib 63 import urllib
63 64
(...skipping 25 matching lines...) Expand all
89 90
90 sub_target_name is an optional parameter if the module name in the other 91 sub_target_name is an optional parameter if the module name in the other
91 DEPS file is different. E.g., you might want to map src/net to net.""" 92 DEPS file is different. E.g., you might want to map src/net to net."""
92 self.module_name = module_name 93 self.module_name = module_name
93 self.sub_target_name = sub_target_name 94 self.sub_target_name = sub_target_name
94 95
95 def __str__(self): 96 def __str__(self):
96 return 'From(%s, %s)' % (repr(self.module_name), 97 return 'From(%s, %s)' % (repr(self.module_name),
97 repr(self.sub_target_name)) 98 repr(self.sub_target_name))
98 99
100 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
101 """Resolve the URL for this From entry."""
102 sub_deps_target_name = target_name
103 if self.sub_target_name:
104 sub_deps_target_name = self.sub_target_name
105 url = sub_deps[sub_deps_target_name]
106 if url.startswith('/'):
107 # If it's a relative URL, we need to resolve the URL relative to the
108 # sub deps base URL.
109 if not isinstance(sub_deps_base_url, basestring):
110 sub_deps_base_url = sub_deps_base_url.GetPath()
111 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
112 None)
113 url = scm.FullUrlForRelativeUrl(url)
114 return url
115
99 class FileImpl(object): 116 class FileImpl(object):
100 """Used to implement the File('') syntax which lets you sync a single file 117 """Used to implement the File('') syntax which lets you sync a single file
101 from a SVN repo.""" 118 from a SVN repo."""
102 119
103 def __init__(self, file_location): 120 def __init__(self, file_location):
104 self.file_location = file_location 121 self.file_location = file_location
105 122
106 def __str__(self): 123 def __str__(self):
107 return 'File("%s")' % self.file_location 124 return 'File("%s")' % self.file_location
108 125
(...skipping 27 matching lines...) Expand all
136 class Dependency(GClientKeywords): 153 class Dependency(GClientKeywords):
137 """Object that represents a dependency checkout.""" 154 """Object that represents a dependency checkout."""
138 DEPS_FILE = 'DEPS' 155 DEPS_FILE = 'DEPS'
139 156
140 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None, 157 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
141 custom_vars=None, deps_file=None): 158 custom_vars=None, deps_file=None):
142 GClientKeywords.__init__(self) 159 GClientKeywords.__init__(self)
143 self.parent = parent 160 self.parent = parent
144 self.name = name 161 self.name = name
145 self.url = url 162 self.url = url
146 self.parsed_url = None
147 # These 2 are only set in .gclient and not in DEPS files. 163 # These 2 are only set in .gclient and not in DEPS files.
148 self.safesync_url = safesync_url 164 self.safesync_url = safesync_url
149 self.custom_vars = custom_vars or {} 165 self.custom_vars = custom_vars or {}
150 self.custom_deps = custom_deps or {} 166 self.custom_deps = custom_deps or {}
151 self.deps_hooks = [] 167 self.deps_hooks = []
152 self.dependencies = [] 168 self.dependencies = []
153 self.deps_file = deps_file or self.DEPS_FILE 169 self.deps_file = deps_file or self.DEPS_FILE
154 # A cache of the files affected by the current operation, necessary for
155 # hooks.
156 self.file_list = []
157 self.deps_parsed = False 170 self.deps_parsed = False
158 self.direct_reference = False 171 self.direct_reference = False
159 172
160 # Sanity checks 173 # Sanity checks
161 if not self.name and self.parent: 174 if not self.name and self.parent:
162 raise gclient_utils.Error('Dependency without name') 175 raise gclient_utils.Error('Dependency without name')
163 if self.name in [d.name for d in self.tree(False)]:
164 raise gclient_utils.Error('Dependency %s specified more than once' %
165 self.name)
166 if not isinstance(self.url, 176 if not isinstance(self.url,
167 (basestring, self.FromImpl, self.FileImpl, None.__class__)): 177 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
168 raise gclient_utils.Error('dependency url must be either a string, None, ' 178 raise gclient_utils.Error('dependency url must be either a string, None, '
169 'File() or From() instead of %s' % 179 'File() or From() instead of %s' %
170 self.url.__class__.__name__) 180 self.url.__class__.__name__)
171 if '/' in self.deps_file or '\\' in self.deps_file: 181 if '/' in self.deps_file or '\\' in self.deps_file:
172 raise gclient_utils.Error('deps_file name must not be a path, just a ' 182 raise gclient_utils.Error('deps_file name must not be a path, just a '
173 'filename. %s' % self.deps_file) 183 'filename. %s' % self.deps_file)
174 184
175 def LateOverride(self, url):
176 overriden_url = self.get_custom_deps(self.name, url)
177 if overriden_url != url:
178 self.parsed_url = overriden_url
179 logging.debug('%s, %s was overriden to %s' % (self.name, url,
180 self.parsed_url))
181 elif isinstance(url, self.FromImpl):
182 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
183 if not len(ref) == 1:
184 raise Exception('Failed to find one reference to %s. %s' % (
185 url.module_name, ref))
186 ref = ref[0]
187 sub_target = url.sub_target_name or url
188 # Make sure the referenced dependency DEPS file is loaded and file the
189 # inner referenced dependency.
190 ref.ParseDepsFile(False)
191 found_dep = None
192 for d in ref.dependencies:
193 if d.name == sub_target:
194 found_dep = d
195 break
196 if not found_dep:
197 raise Exception('Couldn\'t find %s in %s, referenced by %s' % (
198 sub_target, ref.name, self.name))
199 # Call LateOverride() again.
200 self.parsed_url = found_dep.LateOverride(found_dep.url)
201 logging.debug('%s, %s to %s' % (self.name, url, self.parsed_url))
202 elif isinstance(url, basestring):
203 parsed_url = urlparse.urlparse(url)
204 if not parsed_url[0]:
205 # A relative url. Fetch the real base.
206 path = parsed_url[2]
207 if not path.startswith('/'):
208 raise gclient_utils.Error(
209 'relative DEPS entry \'%s\' must begin with a slash' % url)
210 # Create a scm just to query the full url.
211 scm = gclient_scm.CreateSCM(self.parent.parsed_url, self.root_dir(),
212 None)
213 self.parsed_url = scm.FullUrlForRelativeUrl(url)
214 else:
215 self.parsed_url = url
216 logging.debug('%s, %s -> %s' % (self.name, url, self.parsed_url))
217 elif isinstance(url, self.FileImpl):
218 self.parsed_url = url
219 logging.debug('%s, %s -> %s (File)' % (self.name, url, self.parsed_url))
220 return self.parsed_url
221
222 def ParseDepsFile(self, direct_reference): 185 def ParseDepsFile(self, direct_reference):
223 """Parses the DEPS file for this dependency.""" 186 """Parses the DEPS file for this dependency."""
224 if direct_reference: 187 if direct_reference:
225 # Maybe it was referenced earlier by a From() keyword but it's now 188 # Maybe it was referenced earlier by a From() keyword but it's now
226 # directly referenced. 189 # directly referenced.
227 self.direct_reference = direct_reference 190 self.direct_reference = direct_reference
228 if self.deps_parsed:
229 return
230 self.deps_parsed = True 191 self.deps_parsed = True
231 filepath = os.path.join(self.root_dir(), self.name, self.deps_file) 192 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
232 if not os.path.isfile(filepath): 193 if not os.path.isfile(filepath):
233 return 194 return {}
234 deps_content = gclient_utils.FileRead(filepath) 195 deps_content = gclient_utils.FileRead(filepath)
235 196
236 # Eval the content. 197 # Eval the content.
237 # One thing is unintuitive, vars= {} must happen before Var() use. 198 # One thing is unintuitive, vars= {} must happen before Var() use.
238 local_scope = {} 199 local_scope = {}
239 var = self.VarImpl(self.custom_vars, local_scope) 200 var = self.VarImpl(self.custom_vars, local_scope)
240 global_scope = { 201 global_scope = {
241 'File': self.FileImpl, 202 'File': self.FileImpl,
242 'From': self.FromImpl, 203 'From': self.FromImpl,
243 'Var': var.Lookup, 204 'Var': var.Lookup,
(...skipping 30 matching lines...) Expand all
274 # the dictionary using paths relative to the directory containing 235 # the dictionary using paths relative to the directory containing
275 # the DEPS file. 236 # the DEPS file.
276 use_relative_paths = local_scope.get('use_relative_paths', False) 237 use_relative_paths = local_scope.get('use_relative_paths', False)
277 if use_relative_paths: 238 if use_relative_paths:
278 rel_deps = {} 239 rel_deps = {}
279 for d, url in deps.items(): 240 for d, url in deps.items():
280 # normpath is required to allow DEPS to use .. in their 241 # normpath is required to allow DEPS to use .. in their
281 # dependency local path. 242 # dependency local path.
282 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url 243 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
283 deps = rel_deps 244 deps = rel_deps
245 # TODO(maruel): Add these dependencies into self.dependencies.
246 return deps
284 247
285 # Convert the deps into real Dependency. 248 def _ParseAllDeps(self, solution_urls):
286 for name, url in deps.iteritems(): 249 """Parse the complete list of dependencies for the client.
287 if name in [s.name for s in self.dependencies]:
288 raise
289 self.dependencies.append(Dependency(self, name, url))
290 # Sort by name.
291 self.dependencies.sort(key=lambda x: x.name)
292 logging.info('Loaded: %s' % str(self))
293 250
294 def RunCommandRecursively(self, options, revision_overrides, 251 Args:
295 command, args, pm): 252 solution_urls: A dict mapping module names (as relative paths) to URLs
296 """Runs 'command' before parsing the DEPS in case it's a initial checkout 253 corresponding to the solutions specified by the client. This parameter
297 or a revert.""" 254 is passed as an optimization.
298 assert self.file_list == []
299 # When running runhooks, there's no need to consult the SCM.
300 # All known hooks are expected to run unconditionally regardless of working
301 # copy state, so skip the SCM status check.
302 run_scm = command not in ('runhooks', None)
303 self.LateOverride(self.url)
304 if run_scm and self.parsed_url:
305 if isinstance(self.parsed_url, self.FileImpl):
306 # Special support for single-file checkout.
307 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
308 options.revision = self.parsed_url.GetRevision()
309 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
310 self.root_dir(),
311 self.name)
312 scm.RunCommand('updatesingle', options,
313 args + [self.parsed_url.GetFilename()],
314 self.file_list)
315 else:
316 options.revision = revision_overrides.get(self.name)
317 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
318 scm.RunCommand(command, options, args, self.file_list)
319 self.file_list = [os.path.join(self.name, f.strip())
320 for f in self.file_list]
321 options.revision = None
322 if pm:
323 # The + 1 comes from the fact that .gclient is considered a step in
324 # itself, .i.e. this code is called one time for the .gclient. This is not
325 # conceptually correct but it simplifies code.
326 pm._total = len(self.tree(False)) + 1
327 pm.update()
328 if self.recursion_limit():
329 # Then we can parse the DEPS file.
330 self.ParseDepsFile(True)
331 if pm:
332 pm._total = len(self.tree(False)) + 1
333 pm.update(0)
334 # Parse the dependencies of this dependency.
335 for s in self.dependencies:
336 # TODO(maruel): All these can run concurrently! No need for threads,
337 # just buffer stdout&stderr on pipes and flush as they complete.
338 # Watch out for stdin.
339 s.RunCommandRecursively(options, revision_overrides, command, args, pm)
340 255
341 def RunHooksRecursively(self, options): 256 Returns:
342 """Evaluates all hooks, running actions as needed. RunCommandRecursively() 257 A dict mapping module names (as relative paths) to URLs corresponding
343 must have been called before to load the DEPS.""" 258 to the entire set of dependencies to checkout for the given client.
259
260 Raises:
261 Error: If a dependency conflicts with another dependency or of a solution.
262 """
263 deps = {}
264 for solution in self.dependencies:
265 solution_deps = solution.ParseDepsFile(True)
266
267 # If a line is in custom_deps, but not in the solution, we want to append
268 # this line to the solution.
269 for d in solution.custom_deps:
270 if d not in solution_deps:
271 solution_deps[d] = solution.custom_deps[d]
272
273 for d in solution_deps:
274 if d in solution.custom_deps:
275 # Dependency is overriden.
276 url = solution.custom_deps[d]
277 if url is None:
278 continue
279 else:
280 url = solution_deps[d]
281 # if we have a From reference dependent on another solution, then
282 # just skip the From reference. When we pull deps for the solution,
283 # we will take care of this dependency.
284 #
285 # If multiple solutions all have the same From reference, then we
286 # should only add one to our list of dependencies.
287 if isinstance(url, self.FromImpl):
288 if url.module_name in solution_urls:
289 # Already parsed.
290 continue
291 if d in deps and type(deps[d]) != str:
292 if url.module_name == deps[d].module_name:
293 continue
294 elif isinstance(url, str):
295 parsed_url = urlparse.urlparse(url)
296 scheme = parsed_url[0]
297 if not scheme:
298 # A relative url. Fetch the real base.
299 path = parsed_url[2]
300 if path[0] != "/":
301 raise gclient_utils.Error(
302 "relative DEPS entry \"%s\" must begin with a slash" % d)
303 # Create a scm just to query the full url.
304 scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
305 None)
306 url = scm.FullUrlForRelativeUrl(url)
307 if d in deps and deps[d] != url:
308 raise gclient_utils.Error(
309 "Solutions have conflicting versions of dependency \"%s\"" % d)
310 if d in solution_urls and solution_urls[d] != url:
311 raise gclient_utils.Error(
312 "Dependency \"%s\" conflicts with specified solution" % d)
313 # Grab the dependency.
314 deps[d] = url
315 return deps
316
317 def _RunHooks(self, command, file_list, is_using_git):
318 """Evaluates all hooks, running actions as needed.
319 """
320 # Hooks only run for these command types.
321 if not command in ('update', 'revert', 'runhooks'):
322 return
323
324 # Hooks only run when --nohooks is not specified
325 if self._options.nohooks:
326 return
327
328 # Get any hooks from the .gclient file.
329 hooks = self.deps_hooks[:]
330 # Add any hooks found in DEPS files.
331 for d in self.dependencies:
332 hooks.extend(d.deps_hooks)
333
344 # If "--force" was specified, run all hooks regardless of what files have 334 # If "--force" was specified, run all hooks regardless of what files have
345 # changed. 335 # changed. If the user is using git, then we don't know what files have
346 if self.deps_hooks: 336 # changed so we always run all hooks.
347 # TODO(maruel): If the user is using git or git-svn, then we don't know 337 if self._options.force or is_using_git:
348 # what files have changed so we always run all hooks. It'd be nice to fix 338 for hook_dict in hooks:
349 # that. 339 self._RunHookAction(hook_dict, [])
350 if (options.force or 340 return
351 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
352 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
353 for hook_dict in self.deps_hooks:
354 self._RunHookAction(hook_dict, [])
355 else:
356 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
357 # Convert all absolute paths to relative.
358 for i in range(len(self.file_list)):
359 # It depends on the command being executed (like runhooks vs sync).
360 if not os.path.isabs(self.file_list[i]):
361 continue
362 341
363 prefix = os.path.commonprefix([self.root_dir().lower(), 342 # Run hooks on the basis of whether the files from the gclient operation
364 self.file_list[i].lower()]) 343 # match each hook's pattern.
365 self.file_list[i] = self.file_list[i][len(prefix):] 344 for hook_dict in hooks:
366 345 pattern = re.compile(hook_dict['pattern'])
367 # Strip any leading path separators. 346 matching_file_list = [f for f in file_list if pattern.search(f)]
368 while (self.file_list[i].startswith('\\') or 347 if matching_file_list:
369 self.file_list[i].startswith('/')): 348 self._RunHookAction(hook_dict, matching_file_list)
370 self.file_list[i] = self.file_list[i][1:]
371
372 # Run hooks on the basis of whether the files from the gclient operation
373 # match each hook's pattern.
374 for hook_dict in self.deps_hooks:
375 pattern = re.compile(hook_dict['pattern'])
376 matching_file_list = [f for f in self.file_list if pattern.search(f)]
377 if matching_file_list:
378 self._RunHookAction(hook_dict, matching_file_list)
379 if self.recursion_limit():
380 for s in self.dependencies:
381 s.RunHooksRecursively(options)
382 349
383 def _RunHookAction(self, hook_dict, matching_file_list): 350 def _RunHookAction(self, hook_dict, matching_file_list):
384 """Runs the action from a single hook.""" 351 """Runs the action from a single hook."""
385 logging.info(hook_dict) 352 logging.info(hook_dict)
386 logging.info(matching_file_list) 353 logging.info(matching_file_list)
387 command = hook_dict['action'][:] 354 command = hook_dict['action'][:]
388 if command[0] == 'python': 355 if command[0] == 'python':
389 # If the hook specified "python" as the first item, the action is a 356 # If the hook specified "python" as the first item, the action is a
390 # Python script. Run it by starting a new copy of the same 357 # Python script. Run it by starting a new copy of the same
391 # interpreter. 358 # interpreter.
(...skipping 23 matching lines...) Expand all
415 def get_custom_deps(self, name, url): 382 def get_custom_deps(self, name, url):
416 """Returns a custom deps if applicable.""" 383 """Returns a custom deps if applicable."""
417 if self.parent: 384 if self.parent:
418 url = self.parent.get_custom_deps(name, url) 385 url = self.parent.get_custom_deps(name, url)
419 # None is a valid return value to disable a dependency. 386 # None is a valid return value to disable a dependency.
420 return self.custom_deps.get(name, url) 387 return self.custom_deps.get(name, url)
421 388
422 def __str__(self): 389 def __str__(self):
423 out = [] 390 out = []
424 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars', 391 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
425 'deps_hooks', 'file_list'): 392 'deps_hooks'):
426 # 'deps_file' 393 # 'deps_file'
427 if self.__dict__[i]: 394 if self.__dict__[i]:
428 out.append('%s: %s' % (i, self.__dict__[i])) 395 out.append('%s: %s' % (i, self.__dict__[i]))
429 396
430 for d in self.dependencies: 397 for d in self.dependencies:
431 out.extend([' ' + x for x in str(d).splitlines()]) 398 out.extend([' ' + x for x in str(d).splitlines()])
432 out.append('') 399 out.append('')
433 return '\n'.join(out) 400 return '\n'.join(out)
434 401
435 def __repr__(self): 402 def __repr__(self):
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
531 os.path.join(path, options.config_filename))) 498 os.path.join(path, options.config_filename)))
532 return client 499 return client
533 500
534 def SetDefaultConfig(self, solution_name, solution_url, safesync_url): 501 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
535 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { 502 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
536 'solution_name': solution_name, 503 'solution_name': solution_name,
537 'solution_url': solution_url, 504 'solution_url': solution_url,
538 'safesync_url' : safesync_url, 505 'safesync_url' : safesync_url,
539 }) 506 })
540 507
541 def _SaveEntries(self): 508 def _SaveEntries(self, entries):
542 """Creates a .gclient_entries file to record the list of unique checkouts. 509 """Creates a .gclient_entries file to record the list of unique checkouts.
543 510
544 The .gclient_entries file lives in the same directory as .gclient. 511 The .gclient_entries file lives in the same directory as .gclient.
545 """ 512 """
546 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It 513 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
547 # makes testing a bit too fun. 514 # makes testing a bit too fun.
548 result = 'entries = {\n' 515 result = pprint.pformat(entries, 2)
549 for entry in self.tree(False): 516 if result.startswith('{\''):
550 # Skip over File() dependencies as we can't version them. 517 result = '{ \'' + result[2:]
551 if not isinstance(entry.parsed_url, self.FileImpl): 518 text = 'entries = \\\n' + result + '\n'
552 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
553 pprint.pformat(entry.parsed_url))
554 result += '}\n'
555 file_path = os.path.join(self.root_dir(), self._options.entries_filename) 519 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
556 logging.info(result) 520 gclient_utils.FileWrite(file_path, text)
557 gclient_utils.FileWrite(file_path, result)
558 521
559 def _ReadEntries(self): 522 def _ReadEntries(self):
560 """Read the .gclient_entries file for the given client. 523 """Read the .gclient_entries file for the given client.
561 524
562 Returns: 525 Returns:
563 A sequence of solution names, which will be empty if there is the 526 A sequence of solution names, which will be empty if there is the
564 entries file hasn't been created yet. 527 entries file hasn't been created yet.
565 """ 528 """
566 scope = {} 529 scope = {}
567 filename = os.path.join(self.root_dir(), self._options.entries_filename) 530 filename = os.path.join(self.root_dir(), self._options.entries_filename)
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
608 def RunOnDeps(self, command, args): 571 def RunOnDeps(self, command, args):
609 """Runs a command on each dependency in a client and its dependencies. 572 """Runs a command on each dependency in a client and its dependencies.
610 573
611 Args: 574 Args:
612 command: The command to use (e.g., 'status' or 'diff') 575 command: The command to use (e.g., 'status' or 'diff')
613 args: list of str - extra arguments to add to the command line. 576 args: list of str - extra arguments to add to the command line.
614 """ 577 """
615 if not self.dependencies: 578 if not self.dependencies:
616 raise gclient_utils.Error('No solution specified') 579 raise gclient_utils.Error('No solution specified')
617 revision_overrides = self._EnforceRevisions() 580 revision_overrides = self._EnforceRevisions()
618 pm = None 581
582 # When running runhooks --force, there's no need to consult the SCM.
583 # All known hooks are expected to run unconditionally regardless of working
584 # copy state, so skip the SCM status check.
585 run_scm = not (command == 'runhooks' and self._options.force)
586
587 entries = {}
588 file_list = []
589 # Run on the base solutions first.
590 for solution in self.dependencies:
591 name = solution.name
592 if name in entries:
593 raise gclient_utils.Error("solution %s specified more than once" % name)
594 url = solution.url
595 entries[name] = url
596 if run_scm and url:
597 self._options.revision = revision_overrides.get(name)
598 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
599 scm.RunCommand(command, self._options, args, file_list)
600 file_list = [os.path.join(name, f.strip()) for f in file_list]
601 self._options.revision = None
602
603 # Process the dependencies next (sort alphanumerically to ensure that
604 # containing directories get populated first and for readability)
605 deps = self._ParseAllDeps(entries)
606 deps_to_process = deps.keys()
607 deps_to_process.sort()
608
609 # First pass for direct dependencies.
619 if command == 'update' and not self._options.verbose: 610 if command == 'update' and not self._options.verbose:
620 pm = Progress('Syncing projects', len(self.tree(False)) + 1) 611 pm = Progress('Syncing projects', len(deps_to_process))
621 self.RunCommandRecursively(self._options, revision_overrides, 612 for d in deps_to_process:
622 command, args, pm) 613 if command == 'update' and not self._options.verbose:
623 if pm: 614 pm.update()
615 if type(deps[d]) == str:
616 url = deps[d]
617 entries[d] = url
618 if run_scm:
619 self._options.revision = revision_overrides.get(d)
620 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
621 scm.RunCommand(command, self._options, args, file_list)
622 self._options.revision = None
623 elif isinstance(deps[d], self.FileImpl):
624 if command in (None, 'cleanup', 'diff', 'pack', 'status'):
625 continue
626 file_dep = deps[d]
627 self._options.revision = file_dep.GetRevision()
628 if run_scm:
629 scm = gclient_scm.SVNWrapper(file_dep.GetPath(), self.root_dir(), d)
630 scm.RunCommand('updatesingle', self._options,
631 args + [file_dep.GetFilename()], file_list)
632
633 if command == 'update' and not self._options.verbose:
624 pm.end() 634 pm.end()
625 635
626 # Once all the dependencies have been processed, it's now safe to run the 636 # Second pass for inherited deps (via the From keyword)
627 # hooks. 637 for d in deps_to_process:
628 if not self._options.nohooks: 638 if isinstance(deps[d], self.FromImpl):
629 self.RunHooksRecursively(self._options) 639 # Getting the URL from the sub_deps file can involve having to resolve
640 # a File() or having to resolve a relative URL. To resolve relative
641 # URLs, we need to pass in the orignal sub deps URL.
642 sub_deps_base_url = deps[deps[d].module_name]
643 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
644 ).ParseDepsFile(False)
645 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
646 entries[d] = url
647 if run_scm:
648 self._options.revision = revision_overrides.get(d)
649 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
650 scm.RunCommand(command, self._options, args, file_list)
651 self._options.revision = None
652
653 # Convert all absolute paths to relative.
654 for i in range(len(file_list)):
655 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
656 # It depends on the command being executed (like runhooks vs sync).
657 if not os.path.isabs(file_list[i]):
658 continue
659
660 prefix = os.path.commonprefix([self.root_dir().lower(),
661 file_list[i].lower()])
662 file_list[i] = file_list[i][len(prefix):]
663
664 # Strip any leading path separators.
665 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
666 file_list[i] = file_list[i][1:]
667
668 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
669 self._RunHooks(command, file_list, is_using_git)
630 670
631 if command == 'update': 671 if command == 'update':
632 # Notify the user if there is an orphaned entry in their working copy. 672 # Notify the user if there is an orphaned entry in their working copy.
633 # Only delete the directory if there are no changes in it, and 673 # Only delete the directory if there are no changes in it, and
634 # delete_unversioned_trees is set to true. 674 # delete_unversioned_trees is set to true.
635 entries = [i.name for i in self.tree(False)] 675 prev_entries = self._ReadEntries()
636 for entry, prev_url in self._ReadEntries().iteritems(): 676 for entry in prev_entries:
637 # Fix path separator on Windows. 677 # Fix path separator on Windows.
638 entry_fixed = entry.replace('/', os.path.sep) 678 entry_fixed = entry.replace('/', os.path.sep)
639 e_dir = os.path.join(self.root_dir(), entry_fixed) 679 e_dir = os.path.join(self.root_dir(), entry_fixed)
640 # Use entry and not entry_fixed there. 680 # Use entry and not entry_fixed there.
641 if entry not in entries and os.path.exists(e_dir): 681 if entry not in entries and os.path.exists(e_dir):
642 file_list = [] 682 modified_files = False
643 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed) 683 if isinstance(prev_entries, list):
644 scm.status(self._options, [], file_list) 684 # old .gclient_entries format was list, now dict
645 modified_files = file_list != [] 685 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
686 else:
687 file_list = []
688 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
689 entry_fixed)
690 scm.status(self._options, [], file_list)
691 modified_files = file_list != []
646 if not self._options.delete_unversioned_trees or modified_files: 692 if not self._options.delete_unversioned_trees or modified_files:
647 # There are modified files in this entry. Keep warning until 693 # There are modified files in this entry. Keep warning until
648 # removed. 694 # removed.
649 print(('\nWARNING: \'%s\' is no longer part of this client. ' 695 print(('\nWARNING: \'%s\' is no longer part of this client. '
650 'It is recommended that you manually remove it.\n') % 696 'It is recommended that you manually remove it.\n') %
651 entry_fixed) 697 entry_fixed)
652 else: 698 else:
653 # Delete the entry 699 # Delete the entry
654 print('\n________ deleting \'%s\' in \'%s\'' % ( 700 print('\n________ deleting \'%s\' in \'%s\'' % (
655 entry_fixed, self.root_dir())) 701 entry_fixed, self.root_dir()))
656 gclient_utils.RemoveDirectory(e_dir) 702 gclient_utils.RemoveDirectory(e_dir)
657 # record the current list of entries for next time 703 # record the current list of entries for next time
658 self._SaveEntries() 704 self._SaveEntries(entries)
659 return 0 705 return 0
660 706
661 def PrintRevInfo(self): 707 def PrintRevInfo(self):
662 """Output revision info mapping for the client and its dependencies. 708 """Output revision info mapping for the client and its dependencies.
663 709
664 This allows the capture of an overall "revision" for the source tree that 710 This allows the capture of an overall "revision" for the source tree that
665 can be used to reproduce the same tree in the future. It is only useful for 711 can be used to reproduce the same tree in the future. It is only useful for
666 "unpinned dependencies", i.e. DEPS/deps references without a svn revision 712 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
667 number or a git hash. A git branch name isn't "pinned" since the actual 713 number or a git hash. A git branch name isn't "pinned" since the actual
668 commit can change. 714 commit can change.
669 715
670 The --snapshot option allows creating a .gclient file to reproduce the tree. 716 The --snapshot option allows creating a .gclient file to reproduce the tree.
671 """ 717 """
672 if not self.dependencies: 718 if not self.dependencies:
673 raise gclient_utils.Error('No solution specified') 719 raise gclient_utils.Error('No solution specified')
674 # Load all the settings.
675 self.RunCommandRecursively(self._options, {}, None, [], None)
676 720
721 # Inner helper to generate base url and rev tuple
677 def GetURLAndRev(name, original_url): 722 def GetURLAndRev(name, original_url):
678 """Returns the revision-qualified SCM url."""
679 if isinstance(original_url, self.FileImpl):
680 return original_url.file_location
681 url, _ = gclient_utils.SplitUrlRevision(original_url) 723 url, _ = gclient_utils.SplitUrlRevision(original_url)
682 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name) 724 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
683 if not os.path.isdir(scm.checkout_path): 725 return (url, scm.revinfo(self._options, [], None))
684 return None
685 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
686 726
727 # text of the snapshot gclient file
728 new_gclient = ""
729 # Dictionary of { path : SCM url } to ensure no duplicate solutions
730 solution_names = {}
731 entries = {}
732 # Run on the base solutions first.
733 for solution in self.dependencies:
734 # Dictionary of { path : SCM url } to describe the gclient checkout
735 name = solution.name
736 if name in solution_names:
737 raise gclient_utils.Error("solution %s specified more than once" % name)
738 (url, rev) = GetURLAndRev(name, solution.url)
739 entries[name] = "%s@%s" % (url, rev)
740 solution_names[name] = "%s@%s" % (url, rev)
741
742 # Process the dependencies next (sort alphanumerically to ensure that
743 # containing directories get populated first and for readability)
744 deps = self._ParseAllDeps(entries)
745 deps_to_process = deps.keys()
746 deps_to_process.sort()
747
748 # First pass for direct dependencies.
749 for d in deps_to_process:
750 if type(deps[d]) == str:
751 (url, rev) = GetURLAndRev(d, deps[d])
752 entries[d] = "%s@%s" % (url, rev)
753
754 # Second pass for inherited deps (via the From keyword)
755 for d in deps_to_process:
756 if isinstance(deps[d], self.FromImpl):
757 deps_parent_url = entries[deps[d].module_name]
758 if deps_parent_url.find("@") < 0:
759 raise gclient_utils.Error("From %s missing revisioned url" %
760 deps[d].module_name)
761 sub_deps_base_url = deps[deps[d].module_name]
762 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
763 ).ParseDepsFile(False)
764 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
765 (url, rev) = GetURLAndRev(d, url)
766 entries[d] = "%s@%s" % (url, rev)
767
768 # Build the snapshot configuration string
687 if self._options.snapshot: 769 if self._options.snapshot:
688 new_gclient = '' 770 url = entries.pop(name)
689 # First level at .gclient 771 custom_deps = ''.join([' \"%s\": \"%s\",\n' % (x, entries[x])
690 for d in self.dependencies: 772 for x in sorted(entries.keys())])
691 entries = {} 773
692 def GrabDeps(sol): 774 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
693 """Recursively grab dependencies.""" 775 'solution_name': name,
694 for i in sol.dependencies: 776 'solution_url': url,
695 entries[i.name] = GetURLAndRev(i.name, i.parsed_url) 777 'safesync_url' : '',
696 GrabDeps(i) 778 'solution_deps': custom_deps,
697 GrabDeps(d) 779 }
698 custom_deps = []
699 for k in sorted(entries.keys()):
700 if entries[k]:
701 # Quotes aren't escaped...
702 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
703 else:
704 custom_deps.append(' \"%s\": None,\n' % k)
705 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
706 'solution_name': d.name,
707 'solution_url': d.url,
708 'safesync_url' : d.safesync_url or '',
709 'solution_deps': ''.join(custom_deps),
710 }
711 # Print the snapshot configuration file
712 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
713 else: 780 else:
714 entries = sorted(self.tree(False), key=lambda i: i.name) 781 print(';\n'.join(['%s: %s' % (x, entries[x])
715 for entry in entries: 782 for x in sorted(entries.keys())]))
716 revision = GetURLAndRev(entry.name, entry.parsed_url) 783
717 line = '%s: %s' % (entry.name, revision) 784 # Print the snapshot configuration file
718 if not entry is entries[-1]: 785 if self._options.snapshot:
719 line += ';' 786 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
720 print line 787 snapclient = GClient(self.root_dir(), self._options)
788 snapclient.SetConfig(config)
789 print(snapclient.config_content)
721 790
722 def ParseDepsFile(self, direct_reference): 791 def ParseDepsFile(self, direct_reference):
723 """No DEPS to parse for a .gclient file.""" 792 """No DEPS to parse for a .gclient file."""
724 self.direct_reference = direct_reference 793 self.direct_reference = direct_reference
725 self.deps_parsed = True 794 self.deps_parsed = True
726 795
727 def root_dir(self): 796 def root_dir(self):
728 """Root directory of gclient checkout.""" 797 """Root directory of gclient checkout."""
729 return self._root_dir 798 return self._root_dir
730 799
(...skipping 300 matching lines...) Expand 10 before | Expand all | Expand 10 after
1031 'unpinned dependencies', i.e. DEPS/deps references without a svn revision 1100 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1032 number or a git hash. A git branch name isn't 'pinned' since the actual 1101 number or a git hash. A git branch name isn't 'pinned' since the actual
1033 commit can change. 1102 commit can change.
1034 """ 1103 """
1035 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST', 1104 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1036 help='override deps for the specified (comma-separated) ' 1105 help='override deps for the specified (comma-separated) '
1037 'platform(s); \'all\' will process all deps_os ' 1106 'platform(s); \'all\' will process all deps_os '
1038 'references') 1107 'references')
1039 parser.add_option('-s', '--snapshot', action='store_true', 1108 parser.add_option('-s', '--snapshot', action='store_true',
1040 help='creates a snapshot .gclient file of the current ' 1109 help='creates a snapshot .gclient file of the current '
1041 'version of all repositories to reproduce the tree') 1110 'version of all repositories to reproduce the tree, '
1111 'implies -a')
1042 (options, args) = parser.parse_args(args) 1112 (options, args) = parser.parse_args(args)
1043 client = GClient.LoadCurrentConfig(options) 1113 client = GClient.LoadCurrentConfig(options)
1044 if not client: 1114 if not client:
1045 raise gclient_utils.Error('client not configured; see \'gclient config\'') 1115 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1046 client.PrintRevInfo() 1116 client.PrintRevInfo()
1047 return 0 1117 return 0
1048 1118
1049 1119
1050 def Command(name): 1120 def Command(name):
1051 return getattr(sys.modules[__name__], 'CMD' + name, None) 1121 return getattr(sys.modules[__name__], 'CMD' + name, None)
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
1129 return CMDhelp(parser, argv) 1199 return CMDhelp(parser, argv)
1130 except gclient_utils.Error, e: 1200 except gclient_utils.Error, e:
1131 print >> sys.stderr, 'Error: %s' % str(e) 1201 print >> sys.stderr, 'Error: %s' % str(e)
1132 return 1 1202 return 1
1133 1203
1134 1204
1135 if '__main__' == __name__: 1205 if '__main__' == __name__:
1136 sys.exit(Main(sys.argv[1:])) 1206 sys.exit(Main(sys.argv[1:]))
1137 1207
1138 # vim: ts=2:sw=2:tw=80:et: 1208 # vim: ts=2:sw=2:tw=80:et:
OLDNEW
« no previous file with comments | « no previous file | gclient_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698