OLD | NEW |
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 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 raise gclient_utils.Error('deps_file name must not be a path, just a ' | 178 raise gclient_utils.Error('deps_file name must not be a path, just a ' |
179 'filename. %s' % self.deps_file) | 179 'filename. %s' % self.deps_file) |
180 | 180 |
181 def LateOverride(self, url): | 181 def LateOverride(self, url): |
182 """Resolves the parsed url from url. | 182 """Resolves the parsed url from url. |
183 | 183 |
184 Manages From() keyword accordingly. Do not touch self.parsed_url nor | 184 Manages From() keyword accordingly. Do not touch self.parsed_url nor |
185 self.url because it may called with other urls due to From().""" | 185 self.url because it may called with other urls due to From().""" |
186 overriden_url = self.get_custom_deps(self.name, url) | 186 overriden_url = self.get_custom_deps(self.name, url) |
187 if overriden_url != url: | 187 if overriden_url != url: |
188 logging.debug('%s, %s was overriden to %s' % (self.name, url, | 188 logging.info('%s, %s was overriden to %s' % (self.name, url, |
189 overriden_url)) | 189 overriden_url)) |
190 return overriden_url | 190 return overriden_url |
191 elif isinstance(url, self.FromImpl): | 191 elif isinstance(url, self.FromImpl): |
192 ref = [dep for dep in self.tree(True) if url.module_name == dep.name] | 192 ref = [dep for dep in self.tree(True) if url.module_name == dep.name] |
193 if not ref: | 193 if not ref: |
194 raise gclient_utils.Error('Failed to find one reference to %s. %s' % ( | 194 raise gclient_utils.Error('Failed to find one reference to %s. %s' % ( |
195 url.module_name, ref)) | 195 url.module_name, ref)) |
196 # It may happen that len(ref) > 1 but it's no big deal. | 196 # It may happen that len(ref) > 1 but it's no big deal. |
197 ref = ref[0] | 197 ref = ref[0] |
198 sub_target = url.sub_target_name or self.name | 198 sub_target = url.sub_target_name or self.name |
199 # Make sure the referenced dependency DEPS file is loaded and file the | 199 # Make sure the referenced dependency DEPS file is loaded and file the |
200 # inner referenced dependency. | 200 # inner referenced dependency. |
201 ref.ParseDepsFile(False) | 201 ref.ParseDepsFile(False) |
202 found_dep = None | 202 found_dep = None |
203 for d in ref.dependencies: | 203 for d in ref.dependencies: |
204 if d.name == sub_target: | 204 if d.name == sub_target: |
205 found_dep = d | 205 found_dep = d |
206 break | 206 break |
207 if not found_dep: | 207 if not found_dep: |
208 raise gclient_utils.Error( | 208 raise gclient_utils.Error( |
209 'Couldn\'t find %s in %s, referenced by %s' % ( | 209 'Couldn\'t find %s in %s, referenced by %s\n%s' % ( |
210 sub_target, ref.name, self.name)) | 210 sub_target, ref.name, self.name, str(self.root_parent()))) |
211 # Call LateOverride() again. | 211 # Call LateOverride() again. |
212 parsed_url = found_dep.LateOverride(found_dep.url) | 212 parsed_url = found_dep.LateOverride(found_dep.url) |
213 logging.debug('%s, %s to %s' % (self.name, url, parsed_url)) | 213 logging.info('%s, %s to %s' % (self.name, url, parsed_url)) |
214 return parsed_url | 214 return parsed_url |
215 elif isinstance(url, basestring): | 215 elif isinstance(url, basestring): |
216 parsed_url = urlparse.urlparse(url) | 216 parsed_url = urlparse.urlparse(url) |
217 if not parsed_url[0]: | 217 if not parsed_url[0]: |
218 # A relative url. Fetch the real base. | 218 # A relative url. Fetch the real base. |
219 path = parsed_url[2] | 219 path = parsed_url[2] |
220 if not path.startswith('/'): | 220 if not path.startswith('/'): |
221 raise gclient_utils.Error( | 221 raise gclient_utils.Error( |
222 'relative DEPS entry \'%s\' must begin with a slash' % url) | 222 'relative DEPS entry \'%s\' must begin with a slash' % url) |
223 # Create a scm just to query the full url. | 223 # Create a scm just to query the full url. |
224 parent_url = self.parent.parsed_url | 224 parent_url = self.parent.parsed_url |
225 if isinstance(parent_url, self.FileImpl): | 225 if isinstance(parent_url, self.FileImpl): |
226 parent_url = parent_url.file_location | 226 parent_url = parent_url.file_location |
227 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None) | 227 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None) |
228 parsed_url = scm.FullUrlForRelativeUrl(url) | 228 parsed_url = scm.FullUrlForRelativeUrl(url) |
229 else: | 229 else: |
230 parsed_url = url | 230 parsed_url = url |
231 logging.debug('%s, %s -> %s' % (self.name, url, parsed_url)) | 231 logging.info('%s, %s -> %s' % (self.name, url, parsed_url)) |
232 return parsed_url | 232 return parsed_url |
233 elif isinstance(url, self.FileImpl): | 233 elif isinstance(url, self.FileImpl): |
234 parsed_url = url | 234 parsed_url = url |
235 logging.debug('%s, %s -> %s (File)' % (self.name, url, parsed_url)) | 235 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url)) |
236 return parsed_url | 236 return parsed_url |
237 elif url is None: | 237 elif url is None: |
238 return None | 238 return None |
239 else: | 239 else: |
240 raise gclient_utils.Error('Unkown url type') | 240 raise gclient_utils.Error('Unkown url type') |
241 | 241 |
242 def ParseDepsFile(self, direct_reference): | 242 def ParseDepsFile(self, direct_reference): |
243 """Parses the DEPS file for this dependency.""" | 243 """Parses the DEPS file for this dependency.""" |
244 if direct_reference: | 244 if direct_reference: |
245 # Maybe it was referenced earlier by a From() keyword but it's now | 245 # Maybe it was referenced earlier by a From() keyword but it's now |
246 # directly referenced. | 246 # directly referenced. |
247 self.direct_reference = direct_reference | 247 self.direct_reference = direct_reference |
248 if self.deps_parsed: | 248 if self.deps_parsed: |
| 249 logging.debug('%s was already parsed' % self.name) |
249 return | 250 return |
250 self.deps_parsed = True | 251 self.deps_parsed = True |
251 filepath = os.path.join(self.root_dir(), self.name, self.deps_file) | 252 filepath = os.path.join(self.root_dir(), self.name, self.deps_file) |
252 if not os.path.isfile(filepath): | 253 if not os.path.isfile(filepath): |
| 254 logging.info('%s: No DEPS file found at %s' % (self.name, filepath)) |
253 return | 255 return |
254 deps_content = gclient_utils.FileRead(filepath) | 256 deps_content = gclient_utils.FileRead(filepath) |
| 257 logging.debug(deps_content) |
255 | 258 |
256 # Eval the content. | 259 # Eval the content. |
257 # One thing is unintuitive, vars= {} must happen before Var() use. | 260 # One thing is unintuitive, vars= {} must happen before Var() use. |
258 local_scope = {} | 261 local_scope = {} |
259 var = self.VarImpl(self.custom_vars, local_scope) | 262 var = self.VarImpl(self.custom_vars, local_scope) |
260 global_scope = { | 263 global_scope = { |
261 'File': self.FileImpl, | 264 'File': self.FileImpl, |
262 'From': self.FromImpl, | 265 'From': self.FromImpl, |
263 'Var': var.Lookup, | 266 'Var': var.Lookup, |
264 'deps_os': {}, | 267 'deps_os': {}, |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
298 rel_deps = {} | 301 rel_deps = {} |
299 for d, url in deps.items(): | 302 for d, url in deps.items(): |
300 # normpath is required to allow DEPS to use .. in their | 303 # normpath is required to allow DEPS to use .. in their |
301 # dependency local path. | 304 # dependency local path. |
302 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url | 305 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url |
303 deps = rel_deps | 306 deps = rel_deps |
304 | 307 |
305 # Convert the deps into real Dependency. | 308 # Convert the deps into real Dependency. |
306 for name, url in deps.iteritems(): | 309 for name, url in deps.iteritems(): |
307 if name in [s.name for s in self.dependencies]: | 310 if name in [s.name for s in self.dependencies]: |
308 raise | 311 raise gclient_utils.Error( |
| 312 'The same name "%s" appears multiple times in the deps section' % |
| 313 name) |
309 self.dependencies.append(Dependency(self, name, url, None, None, None, | 314 self.dependencies.append(Dependency(self, name, url, None, None, None, |
310 None)) | 315 None)) |
311 # Sorting by name would in theory make the whole thing coherent, since | 316 # Sorting by name would in theory make the whole thing coherent, since |
312 # subdirectories will be sorted after the parent directory, but that doens't | 317 # subdirectories will be sorted after the parent directory, but that doens't |
313 # work with From() that fetch from a dependency with a name being sorted | 318 # work with From() that fetch from a dependency with a name being sorted |
314 # later. But if this would be removed right now, many projects wouldn't be | 319 # later. But if this would be removed right now, many projects wouldn't be |
315 # able to sync anymore. | 320 # able to sync anymore. |
316 self.dependencies.sort(key=lambda x: x.name) | 321 self.dependencies.sort(key=lambda x: x.name) |
317 logging.info('Loaded: %s' % str(self)) | 322 logging.debug('Loaded: %s' % str(self)) |
318 | 323 |
319 def RunCommandRecursively(self, options, revision_overrides, | 324 def RunCommandRecursively(self, options, revision_overrides, |
320 command, args, pm): | 325 command, args, pm): |
321 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 326 """Runs 'command' before parsing the DEPS in case it's a initial checkout |
322 or a revert.""" | 327 or a revert.""" |
323 assert self._file_list == [] | 328 assert self._file_list == [] |
324 # When running runhooks, there's no need to consult the SCM. | 329 # When running runhooks, there's no need to consult the SCM. |
325 # All known hooks are expected to run unconditionally regardless of working | 330 # All known hooks are expected to run unconditionally regardless of working |
326 # copy state, so skip the SCM status check. | 331 # copy state, so skip the SCM status check. |
327 run_scm = command not in ('runhooks', None) | 332 run_scm = command not in ('runhooks', None) |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
403 pattern = re.compile(hook_dict['pattern']) | 408 pattern = re.compile(hook_dict['pattern']) |
404 matching_file_list = [f for f in file_list if pattern.search(f)] | 409 matching_file_list = [f for f in file_list if pattern.search(f)] |
405 if matching_file_list: | 410 if matching_file_list: |
406 self._RunHookAction(hook_dict, matching_file_list) | 411 self._RunHookAction(hook_dict, matching_file_list) |
407 if self.recursion_limit(): | 412 if self.recursion_limit(): |
408 for s in self.dependencies: | 413 for s in self.dependencies: |
409 s.RunHooksRecursively(options) | 414 s.RunHooksRecursively(options) |
410 | 415 |
411 def _RunHookAction(self, hook_dict, matching_file_list): | 416 def _RunHookAction(self, hook_dict, matching_file_list): |
412 """Runs the action from a single hook.""" | 417 """Runs the action from a single hook.""" |
| 418 # A single DEPS file can specify multiple hooks so this function can be |
| 419 # called multiple times on a single Dependency. |
| 420 #assert self.hooks_ran == False |
413 self.hooks_ran = True | 421 self.hooks_ran = True |
414 logging.info(hook_dict) | 422 logging.debug(hook_dict) |
415 logging.info(matching_file_list) | 423 logging.debug(matching_file_list) |
416 command = hook_dict['action'][:] | 424 command = hook_dict['action'][:] |
417 if command[0] == 'python': | 425 if command[0] == 'python': |
418 # If the hook specified "python" as the first item, the action is a | 426 # If the hook specified "python" as the first item, the action is a |
419 # Python script. Run it by starting a new copy of the same | 427 # Python script. Run it by starting a new copy of the same |
420 # interpreter. | 428 # interpreter. |
421 command[0] = sys.executable | 429 command[0] = sys.executable |
422 | 430 |
423 if '$matching_files' in command: | 431 if '$matching_files' in command: |
424 splice_index = command.index('$matching_files') | 432 splice_index = command.index('$matching_files') |
425 command[splice_index:splice_index + 1] = matching_file_list | 433 command[splice_index:splice_index + 1] = matching_file_list |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
459 return self.custom_deps.get(name, url) | 467 return self.custom_deps.get(name, url) |
460 | 468 |
461 def file_list(self): | 469 def file_list(self): |
462 result = self._file_list[:] | 470 result = self._file_list[:] |
463 for d in self.dependencies: | 471 for d in self.dependencies: |
464 result.extend(d.file_list()) | 472 result.extend(d.file_list()) |
465 return result | 473 return result |
466 | 474 |
467 def __str__(self): | 475 def __str__(self): |
468 out = [] | 476 out = [] |
469 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars', | 477 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', |
470 'deps_hooks', '_file_list', 'processed', | 478 'custom_vars', 'deps_hooks', '_file_list', 'processed', |
471 'hooks_ran'): | 479 'hooks_ran', 'deps_parsed'): |
472 # 'deps_file' | 480 # 'deps_file' |
473 if self.__dict__[i]: | 481 if self.__dict__[i]: |
474 out.append('%s: %s' % (i, self.__dict__[i])) | 482 out.append('%s: %s' % (i, self.__dict__[i])) |
475 | 483 |
476 for d in self.dependencies: | 484 for d in self.dependencies: |
477 out.extend([' ' + x for x in str(d).splitlines()]) | 485 out.extend([' ' + x for x in str(d).splitlines()]) |
478 out.append('') | 486 out.append('') |
479 return '\n'.join(out) | 487 return '\n'.join(out) |
480 | 488 |
481 def __repr__(self): | 489 def __repr__(self): |
482 return '%s: %s' % (self.name, self.url) | 490 return '%s: %s' % (self.name, self.url) |
483 | 491 |
484 def hierarchy(self): | 492 def hierarchy(self): |
485 """Returns a human-readable hierarchical reference to a Dependency.""" | 493 """Returns a human-readable hierarchical reference to a Dependency.""" |
486 out = '%s(%s)' % (self.name, self.url) | 494 out = '%s(%s)' % (self.name, self.url) |
487 i = self.parent | 495 i = self.parent |
488 while i and i.name: | 496 while i and i.name: |
489 out = '%s(%s) -> %s' % (i.name, i.url, out) | 497 out = '%s(%s) -> %s' % (i.name, i.url, out) |
490 i = i.parent | 498 i = i.parent |
491 return out | 499 return out |
492 | 500 |
| 501 def root_parent(self): |
| 502 """Returns the root object, normally a GClient object.""" |
| 503 d = self |
| 504 while d.parent: |
| 505 d = d.parent |
| 506 return d |
| 507 |
493 | 508 |
494 class GClient(Dependency): | 509 class GClient(Dependency): |
495 """Object that represent a gclient checkout. A tree of Dependency(), one per | 510 """Object that represent a gclient checkout. A tree of Dependency(), one per |
496 solution or DEPS entry.""" | 511 solution or DEPS entry.""" |
497 | 512 |
498 DEPS_OS_CHOICES = { | 513 DEPS_OS_CHOICES = { |
499 "win32": "win", | 514 "win32": "win", |
500 "win": "win", | 515 "win": "win", |
501 "cygwin": "win", | 516 "cygwin": "win", |
502 "darwin": "mac", | 517 "darwin": "mac", |
(...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
769 # Print the snapshot configuration file | 784 # Print the snapshot configuration file |
770 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) | 785 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) |
771 else: | 786 else: |
772 entries = sorted(self.tree(False), key=lambda i: i.name) | 787 entries = sorted(self.tree(False), key=lambda i: i.name) |
773 for entry in entries: | 788 for entry in entries: |
774 entry_url = GetURLAndRev(entry.name, entry.parsed_url) | 789 entry_url = GetURLAndRev(entry.name, entry.parsed_url) |
775 line = '%s: %s' % (entry.name, entry_url) | 790 line = '%s: %s' % (entry.name, entry_url) |
776 if not entry is entries[-1]: | 791 if not entry is entries[-1]: |
777 line += ';' | 792 line += ';' |
778 print line | 793 print line |
779 logging.debug(str(self)) | 794 logging.info(str(self)) |
780 | 795 |
781 def ParseDepsFile(self, direct_reference): | 796 def ParseDepsFile(self, direct_reference): |
782 """No DEPS to parse for a .gclient file.""" | 797 """No DEPS to parse for a .gclient file.""" |
783 self.direct_reference = True | 798 self.direct_reference = True |
784 self.deps_parsed = True | 799 self.deps_parsed = True |
785 | 800 |
786 def root_dir(self): | 801 def root_dir(self): |
787 """Root directory of gclient checkout.""" | 802 """Root directory of gclient checkout.""" |
788 return self._root_dir | 803 return self._root_dir |
789 | 804 |
(...skipping 389 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1179 return CMDhelp(parser, argv) | 1194 return CMDhelp(parser, argv) |
1180 except gclient_utils.Error, e: | 1195 except gclient_utils.Error, e: |
1181 print >> sys.stderr, 'Error: %s' % str(e) | 1196 print >> sys.stderr, 'Error: %s' % str(e) |
1182 return 1 | 1197 return 1 |
1183 | 1198 |
1184 | 1199 |
1185 if '__main__' == __name__: | 1200 if '__main__' == __name__: |
1186 sys.exit(Main(sys.argv[1:])) | 1201 sys.exit(Main(sys.argv[1:])) |
1187 | 1202 |
1188 # vim: ts=2:sw=2:tw=80:et: | 1203 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |