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

Side by Side Diff: gclient.py

Issue 2886017: Revert both r51761 and r51760 as it broke the webkit slaves. (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 an SVN repo.""" 118 from an 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 options.revision = self.parsed_url.GetRevision()
308 scm = gclient_scm.CreateSCM(self.parsed_url.GetPath(), self.root_dir(),
309 self.name)
310 scm.RunCommand("updatesingle", options,
311 args + [self.parsed_url.GetFilename()], self.file_list)
312 else:
313 options.revision = revision_overrides.get(self.name)
314 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
315 scm.RunCommand(command, options, args, self.file_list)
316 self.file_list = [os.path.join(self.name, f.strip())
317 for f in self.file_list]
318 options.revision = None
319 if pm:
320 # The + 1 comes from the fact that .gclient is considered a step in
321 # itself, .i.e. this code is called one time for the .gclient. This is not
322 # conceptually correct but it simplifies code.
323 pm._total = len(self.tree(False)) + 1
324 pm.update()
325 if self.recursion_limit():
326 # Then we can parse the DEPS file.
327 self.ParseDepsFile(True)
328 if pm:
329 pm._total = len(self.tree(False)) + 1
330 pm.update(0)
331 # Parse the dependencies of this dependency.
332 for s in self.dependencies:
333 # TODO(maruel): All these can run concurrently! No need for threads,
334 # just buffer stdout&stderr on pipes and flush as they complete.
335 # Watch out for stdin.
336 s.RunCommandRecursively(options, revision_overrides, command, args, pm)
337 255
338 def RunHooksRecursively(self, options): 256 Returns:
339 """Evaluates all hooks, running actions as needed. RunCommandRecursively() 257 A dict mapping module names (as relative paths) to URLs corresponding
340 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
341 # 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
342 # changed. 335 # changed. If the user is using git, then we don't know what files have
343 if self.deps_hooks: 336 # changed so we always run all hooks.
344 # 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:
345 # what files have changed so we always run all hooks. It'd be nice to fix 338 for hook_dict in hooks:
346 # that. 339 self._RunHookAction(hook_dict, [])
347 if (options.force or 340 return
348 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
349 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
350 for hook_dict in self.deps_hooks:
351 self._RunHookAction(hook_dict, [])
352 else:
353 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
354 # Convert all absolute paths to relative.
355 for i in range(len(self.file_list)):
356 # It depends on the command being executed (like runhooks vs sync).
357 if not os.path.isabs(self.file_list[i]):
358 continue
359 341
360 prefix = os.path.commonprefix([self.root_dir().lower(), 342 # Run hooks on the basis of whether the files from the gclient operation
361 self.file_list[i].lower()]) 343 # match each hook's pattern.
362 self.file_list[i] = self.file_list[i][len(prefix):] 344 for hook_dict in hooks:
363 345 pattern = re.compile(hook_dict['pattern'])
364 # Strip any leading path separators. 346 matching_file_list = [f for f in file_list if pattern.search(f)]
365 while (self.file_list[i].startswith('\\') or 347 if matching_file_list:
366 self.file_list[i].startswith('/')): 348 self._RunHookAction(hook_dict, matching_file_list)
367 self.file_list[i] = self.file_list[i][1:]
368
369 # Run hooks on the basis of whether the files from the gclient operation
370 # match each hook's pattern.
371 for hook_dict in self.deps_hooks:
372 pattern = re.compile(hook_dict['pattern'])
373 matching_file_list = [f for f in self.file_list if pattern.search(f)]
374 if matching_file_list:
375 self._RunHookAction(hook_dict, matching_file_list)
376 if self.recursion_limit():
377 for s in self.dependencies:
378 s.RunHooksRecursively(options)
379 349
380 def _RunHookAction(self, hook_dict, matching_file_list): 350 def _RunHookAction(self, hook_dict, matching_file_list):
381 """Runs the action from a single hook.""" 351 """Runs the action from a single hook."""
382 logging.info(hook_dict) 352 logging.info(hook_dict)
383 logging.info(matching_file_list) 353 logging.info(matching_file_list)
384 command = hook_dict['action'][:] 354 command = hook_dict['action'][:]
385 if command[0] == 'python': 355 if command[0] == 'python':
386 # 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
387 # 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
388 # interpreter. 358 # interpreter.
(...skipping 23 matching lines...) Expand all
412 def get_custom_deps(self, name, url): 382 def get_custom_deps(self, name, url):
413 """Returns a custom deps if applicable.""" 383 """Returns a custom deps if applicable."""
414 if self.parent: 384 if self.parent:
415 url = self.parent.get_custom_deps(name, url) 385 url = self.parent.get_custom_deps(name, url)
416 # None is a valid return value to disable a dependency. 386 # None is a valid return value to disable a dependency.
417 return self.custom_deps.get(name, url) 387 return self.custom_deps.get(name, url)
418 388
419 def __str__(self): 389 def __str__(self):
420 out = [] 390 out = []
421 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars', 391 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
422 'deps_hooks', 'file_list'): 392 'deps_hooks'):
423 # 'deps_file' 393 # 'deps_file'
424 if self.__dict__[i]: 394 if self.__dict__[i]:
425 out.append('%s: %s' % (i, self.__dict__[i])) 395 out.append('%s: %s' % (i, self.__dict__[i]))
426 396
427 for d in self.dependencies: 397 for d in self.dependencies:
428 out.extend([' ' + x for x in str(d).splitlines()]) 398 out.extend([' ' + x for x in str(d).splitlines()])
429 out.append('') 399 out.append('')
430 return '\n'.join(out) 400 return '\n'.join(out)
431 401
432 def __repr__(self): 402 def __repr__(self):
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
528 os.path.join(path, options.config_filename))) 498 os.path.join(path, options.config_filename)))
529 return client 499 return client
530 500
531 def SetDefaultConfig(self, solution_name, solution_url, safesync_url): 501 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
532 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { 502 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
533 'solution_name': solution_name, 503 'solution_name': solution_name,
534 'solution_url': solution_url, 504 'solution_url': solution_url,
535 'safesync_url' : safesync_url, 505 'safesync_url' : safesync_url,
536 }) 506 })
537 507
538 def _SaveEntries(self): 508 def _SaveEntries(self, entries):
539 """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.
540 510
541 The .gclient_entries file lives in the same directory as .gclient. 511 The .gclient_entries file lives in the same directory as .gclient.
542 """ 512 """
543 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It 513 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
544 # makes testing a bit too fun. 514 # makes testing a bit too fun.
545 result = 'entries = {\n' 515 result = pprint.pformat(entries, 2)
546 for entry in self.tree(False): 516 if result.startswith('{\''):
547 result += ' %s: %s,\n' % (pprint.pformat(entry.name), 517 result = '{ \'' + result[2:]
548 pprint.pformat(entry.parsed_url)) 518 text = 'entries = \\\n' + result + '\n'
549 result += '}\n'
550 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)
551 logging.info(result) 520 gclient_utils.FileWrite(file_path, text)
552 gclient_utils.FileWrite(file_path, result)
553 521
554 def _ReadEntries(self): 522 def _ReadEntries(self):
555 """Read the .gclient_entries file for the given client. 523 """Read the .gclient_entries file for the given client.
556 524
557 Returns: 525 Returns:
558 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
559 entries file hasn't been created yet. 527 entries file hasn't been created yet.
560 """ 528 """
561 scope = {} 529 scope = {}
562 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
603 def RunOnDeps(self, command, args): 571 def RunOnDeps(self, command, args):
604 """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.
605 573
606 Args: 574 Args:
607 command: The command to use (e.g., 'status' or 'diff') 575 command: The command to use (e.g., 'status' or 'diff')
608 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.
609 """ 577 """
610 if not self.dependencies: 578 if not self.dependencies:
611 raise gclient_utils.Error('No solution specified') 579 raise gclient_utils.Error('No solution specified')
612 revision_overrides = self._EnforceRevisions() 580 revision_overrides = self._EnforceRevisions()
613 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.
614 if command == 'update' and not self._options.verbose: 610 if command == 'update' and not self._options.verbose:
615 pm = Progress('Syncing projects', len(self.tree(False)) + 1) 611 pm = Progress('Syncing projects', len(deps_to_process))
616 self.RunCommandRecursively(self._options, revision_overrides, 612 for d in deps_to_process:
617 command, args, pm) 613 if command == 'update' and not self._options.verbose:
618 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 file_dep = deps[d]
625 self._options.revision = file_dep.GetRevision()
626 if run_scm:
627 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
628 scm.RunCommand("updatesingle", self._options,
629 args + [file_dep.GetFilename()], file_list)
630
631 if command == 'update' and not self._options.verbose:
619 pm.end() 632 pm.end()
620 633
621 # Once all the dependencies have been processed, it's now safe to run the 634 # Second pass for inherited deps (via the From keyword)
622 # hooks. 635 for d in deps_to_process:
623 if not self._options.nohooks: 636 if isinstance(deps[d], self.FromImpl):
624 self.RunHooksRecursively(self._options) 637 # Getting the URL from the sub_deps file can involve having to resolve
638 # a File() or having to resolve a relative URL. To resolve relative
639 # URLs, we need to pass in the orignal sub deps URL.
640 sub_deps_base_url = deps[deps[d].module_name]
641 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
642 ).ParseDepsFile(False)
643 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
644 entries[d] = url
645 if run_scm:
646 self._options.revision = revision_overrides.get(d)
647 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
648 scm.RunCommand(command, self._options, args, file_list)
649 self._options.revision = None
650
651 # Convert all absolute paths to relative.
652 for i in range(len(file_list)):
653 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
654 # It depends on the command being executed (like runhooks vs sync).
655 if not os.path.isabs(file_list[i]):
656 continue
657
658 prefix = os.path.commonprefix([self.root_dir().lower(),
659 file_list[i].lower()])
660 file_list[i] = file_list[i][len(prefix):]
661
662 # Strip any leading path separators.
663 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
664 file_list[i] = file_list[i][1:]
665
666 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
667 self._RunHooks(command, file_list, is_using_git)
625 668
626 if command == 'update': 669 if command == 'update':
627 # Notify the user if there is an orphaned entry in their working copy. 670 # Notify the user if there is an orphaned entry in their working copy.
628 # Only delete the directory if there are no changes in it, and 671 # Only delete the directory if there are no changes in it, and
629 # delete_unversioned_trees is set to true. 672 # delete_unversioned_trees is set to true.
630 entries = [i.name for i in self.tree(False)] 673 prev_entries = self._ReadEntries()
631 for entry, prev_url in self._ReadEntries().iteritems(): 674 for entry in prev_entries:
632 # Fix path separator on Windows. 675 # Fix path separator on Windows.
633 entry_fixed = entry.replace('/', os.path.sep) 676 entry_fixed = entry.replace('/', os.path.sep)
634 e_dir = os.path.join(self.root_dir(), entry_fixed) 677 e_dir = os.path.join(self.root_dir(), entry_fixed)
635 # Use entry and not entry_fixed there. 678 # Use entry and not entry_fixed there.
636 if entry not in entries and os.path.exists(e_dir): 679 if entry not in entries and os.path.exists(e_dir):
637 file_list = [] 680 modified_files = False
638 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed) 681 if isinstance(prev_entries, list):
639 scm.status(self._options, [], file_list) 682 # old .gclient_entries format was list, now dict
640 modified_files = file_list != [] 683 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
684 else:
685 file_list = []
686 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
687 entry_fixed)
688 scm.status(self._options, [], file_list)
689 modified_files = file_list != []
641 if not self._options.delete_unversioned_trees or modified_files: 690 if not self._options.delete_unversioned_trees or modified_files:
642 # There are modified files in this entry. Keep warning until 691 # There are modified files in this entry. Keep warning until
643 # removed. 692 # removed.
644 print(('\nWARNING: \'%s\' is no longer part of this client. ' 693 print(('\nWARNING: \'%s\' is no longer part of this client. '
645 'It is recommended that you manually remove it.\n') % 694 'It is recommended that you manually remove it.\n') %
646 entry_fixed) 695 entry_fixed)
647 else: 696 else:
648 # Delete the entry 697 # Delete the entry
649 print('\n________ deleting \'%s\' in \'%s\'' % ( 698 print('\n________ deleting \'%s\' in \'%s\'' % (
650 entry_fixed, self.root_dir())) 699 entry_fixed, self.root_dir()))
651 gclient_utils.RemoveDirectory(e_dir) 700 gclient_utils.RemoveDirectory(e_dir)
652 # record the current list of entries for next time 701 # record the current list of entries for next time
653 self._SaveEntries() 702 self._SaveEntries(entries)
654 return 0 703 return 0
655 704
656 def PrintRevInfo(self): 705 def PrintRevInfo(self):
657 """Output revision info mapping for the client and its dependencies. 706 """Output revision info mapping for the client and its dependencies.
658 707
659 This allows the capture of an overall "revision" for the source tree that 708 This allows the capture of an overall "revision" for the source tree that
660 can be used to reproduce the same tree in the future. It is only useful for 709 can be used to reproduce the same tree in the future. It is only useful for
661 "unpinned dependencies", i.e. DEPS/deps references without a svn revision 710 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
662 number or a git hash. A git branch name isn't "pinned" since the actual 711 number or a git hash. A git branch name isn't "pinned" since the actual
663 commit can change. 712 commit can change.
664 713
665 The --snapshot option allows creating a .gclient file to reproduce the tree. 714 The --snapshot option allows creating a .gclient file to reproduce the tree.
666 """ 715 """
667 if not self.dependencies: 716 if not self.dependencies:
668 raise gclient_utils.Error('No solution specified') 717 raise gclient_utils.Error('No solution specified')
669 # Load all the settings.
670 self.RunCommandRecursively(self._options, {}, None, [], None)
671 718
719 # Inner helper to generate base url and rev tuple
672 def GetURLAndRev(name, original_url): 720 def GetURLAndRev(name, original_url):
673 """Returns the revision-qualified SCM url."""
674 url, _ = gclient_utils.SplitUrlRevision(original_url) 721 url, _ = gclient_utils.SplitUrlRevision(original_url)
675 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name) 722 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
676 if not os.path.isdir(scm.checkout_path): 723 return (url, scm.revinfo(self._options, [], None))
677 return None
678 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
679 724
725 # text of the snapshot gclient file
726 new_gclient = ""
727 # Dictionary of { path : SCM url } to ensure no duplicate solutions
728 solution_names = {}
729 entries = {}
730 # Run on the base solutions first.
731 for solution in self.dependencies:
732 # Dictionary of { path : SCM url } to describe the gclient checkout
733 name = solution.name
734 if name in solution_names:
735 raise gclient_utils.Error("solution %s specified more than once" % name)
736 (url, rev) = GetURLAndRev(name, solution.url)
737 entries[name] = "%s@%s" % (url, rev)
738 solution_names[name] = "%s@%s" % (url, rev)
739
740 # Process the dependencies next (sort alphanumerically to ensure that
741 # containing directories get populated first and for readability)
742 deps = self._ParseAllDeps(entries)
743 deps_to_process = deps.keys()
744 deps_to_process.sort()
745
746 # First pass for direct dependencies.
747 for d in deps_to_process:
748 if type(deps[d]) == str:
749 (url, rev) = GetURLAndRev(d, deps[d])
750 entries[d] = "%s@%s" % (url, rev)
751
752 # Second pass for inherited deps (via the From keyword)
753 for d in deps_to_process:
754 if isinstance(deps[d], self.FromImpl):
755 deps_parent_url = entries[deps[d].module_name]
756 if deps_parent_url.find("@") < 0:
757 raise gclient_utils.Error("From %s missing revisioned url" %
758 deps[d].module_name)
759 sub_deps_base_url = deps[deps[d].module_name]
760 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
761 ).ParseDepsFile(False)
762 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
763 (url, rev) = GetURLAndRev(d, url)
764 entries[d] = "%s@%s" % (url, rev)
765
766 # Build the snapshot configuration string
680 if self._options.snapshot: 767 if self._options.snapshot:
681 new_gclient = '' 768 url = entries.pop(name)
682 # First level at .gclient 769 custom_deps = ''.join([' \"%s\": \"%s\",\n' % (x, entries[x])
683 for d in self.dependencies: 770 for x in sorted(entries.keys())])
684 entries = {} 771
685 def GrabDeps(sol): 772 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
686 """Recursively grab dependencies.""" 773 'solution_name': name,
687 for i in sol.dependencies: 774 'solution_url': url,
688 entries[i.name] = GetURLAndRev(i.name, i.parsed_url) 775 'safesync_url' : '',
689 GrabDeps(i) 776 'solution_deps': custom_deps,
690 GrabDeps(d) 777 }
691 custom_deps = []
692 for k in sorted(entries.keys()):
693 if entries[k]:
694 # Quotes aren't escaped...
695 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
696 else:
697 custom_deps.append(' \"%s\": None,\n' % k)
698 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
699 'solution_name': d.name,
700 'solution_url': d.url,
701 'safesync_url' : d.safesync_url or '',
702 'solution_deps': ''.join(custom_deps),
703 }
704 # Print the snapshot configuration file
705 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
706 else: 778 else:
707 entries = sorted(self.tree(False), key=lambda i: i.name) 779 print(';\n'.join(['%s: %s' % (x, entries[x])
708 for entry in entries: 780 for x in sorted(entries.keys())]))
709 revision = GetURLAndRev(entry.name, entry.parsed_url) 781
710 line = '%s: %s' % (entry.name, revision) 782 # Print the snapshot configuration file
711 if not entry is entries[-1]: 783 if self._options.snapshot:
712 line += ';' 784 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
713 print line 785 snapclient = GClient(self.root_dir(), self._options)
786 snapclient.SetConfig(config)
787 print(snapclient.config_content)
714 788
715 def ParseDepsFile(self, direct_reference): 789 def ParseDepsFile(self, direct_reference):
716 """No DEPS to parse for a .gclient file.""" 790 """No DEPS to parse for a .gclient file."""
717 self.direct_reference = direct_reference 791 self.direct_reference = direct_reference
718 self.deps_parsed = True 792 self.deps_parsed = True
719 793
720 def root_dir(self): 794 def root_dir(self):
721 """Root directory of gclient checkout.""" 795 """Root directory of gclient checkout."""
722 return self._root_dir 796 return self._root_dir
723 797
(...skipping 300 matching lines...) Expand 10 before | Expand all | Expand 10 after
1024 'unpinned dependencies', i.e. DEPS/deps references without a svn revision 1098 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1025 number or a git hash. A git branch name isn't 'pinned' since the actual 1099 number or a git hash. A git branch name isn't 'pinned' since the actual
1026 commit can change. 1100 commit can change.
1027 """ 1101 """
1028 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST', 1102 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1029 help='override deps for the specified (comma-separated) ' 1103 help='override deps for the specified (comma-separated) '
1030 'platform(s); \'all\' will process all deps_os ' 1104 'platform(s); \'all\' will process all deps_os '
1031 'references') 1105 'references')
1032 parser.add_option('-s', '--snapshot', action='store_true', 1106 parser.add_option('-s', '--snapshot', action='store_true',
1033 help='creates a snapshot .gclient file of the current ' 1107 help='creates a snapshot .gclient file of the current '
1034 'version of all repositories to reproduce the tree') 1108 'version of all repositories to reproduce the tree, '
1109 'implies -a')
1035 (options, args) = parser.parse_args(args) 1110 (options, args) = parser.parse_args(args)
1036 client = GClient.LoadCurrentConfig(options) 1111 client = GClient.LoadCurrentConfig(options)
1037 if not client: 1112 if not client:
1038 raise gclient_utils.Error('client not configured; see \'gclient config\'') 1113 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1039 client.PrintRevInfo() 1114 client.PrintRevInfo()
1040 return 0 1115 return 0
1041 1116
1042 1117
1043 def Command(name): 1118 def Command(name):
1044 return getattr(sys.modules[__name__], 'CMD' + name, None) 1119 return getattr(sys.modules[__name__], 'CMD' + name, None)
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1116 return CMDhelp(parser, argv) 1191 return CMDhelp(parser, argv)
1117 except gclient_utils.Error, e: 1192 except gclient_utils.Error, e:
1118 print >> sys.stderr, 'Error: %s' % str(e) 1193 print >> sys.stderr, 'Error: %s' % str(e)
1119 return 1 1194 return 1
1120 1195
1121 1196
1122 if '__main__' == __name__: 1197 if '__main__' == __name__:
1123 sys.exit(Main(sys.argv[1:])) 1198 sys.exit(Main(sys.argv[1:]))
1124 1199
1125 # vim: ts=2:sw=2:tw=80:et: 1200 # 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