Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import ast | 5 import ast |
| 6 import collections | 6 import collections |
| 7 import contextlib | 7 import contextlib |
| 8 import copy | 8 import copy |
| 9 import functools | 9 import functools |
| 10 import itertools | 10 import itertools |
| 11 import logging | 11 import logging |
| 12 import os | 12 import os |
| 13 import re | |
| 13 import subprocess | 14 import subprocess |
| 14 import sys | 15 import sys |
| 15 import tempfile | 16 import tempfile |
| 16 | 17 |
| 17 from .third_party.google.protobuf import text_format | 18 from .third_party.google.protobuf import text_format |
| 18 from . import package_pb2 | 19 from . import package_pb2 |
| 19 | 20 |
| 20 | 21 |
| 21 class UncleanFilesystemError(Exception): | 22 class UncleanFilesystemError(Exception): |
| 22 pass | 23 pass |
| (...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 348 return False | 349 return False |
| 349 return self._proto_file == other._proto_file | 350 return self._proto_file == other._proto_file |
| 350 | 351 |
| 351 | 352 |
| 352 class Package(object): | 353 class Package(object): |
| 353 """Package represents a loaded package, and contains path and dependency | 354 """Package represents a loaded package, and contains path and dependency |
| 354 information. | 355 information. |
| 355 | 356 |
| 356 This is accessed by loader.py through RecipeDeps.get_package. | 357 This is accessed by loader.py through RecipeDeps.get_package. |
| 357 """ | 358 """ |
| 358 def __init__(self, name, repo_spec, deps, recipes_dir): | 359 def __init__(self, name, repo_spec, deps, repo_root, relative_recipes_dir): |
| 359 self.name = name | 360 self.name = name |
| 360 self.repo_spec = repo_spec | 361 self.repo_spec = repo_spec |
| 361 self.deps = deps | 362 self.deps = deps |
| 362 self.recipes_dir = recipes_dir | 363 self.relative_recipes_dir = relative_recipes_dir |
| 364 self._repo_root = repo_root | |
| 365 | |
| 366 @property | |
| 367 def recipes_dir(self): | |
| 368 """Returns the root of the recipes area (the part containing recipes/ and | |
| 369 recipe_modules/ | |
| 370 """ | |
| 371 return os.path.join(self._repo_root, self.relative_recipes_dir) | |
| 363 | 372 |
| 364 @property | 373 @property |
| 365 def recipe_dirs(self): | 374 def recipe_dirs(self): |
| 375 """Returns a collection of directories containing recipes.""" | |
|
M-A Ruel
2016/01/09 00:51:59
Returns the list of
luqui
2016/01/15 23:11:27
Done.
| |
| 366 return [os.path.join(self.recipes_dir, 'recipes')] | 376 return [os.path.join(self.recipes_dir, 'recipes')] |
| 367 | 377 |
| 368 @property | 378 @property |
| 369 def module_dirs(self): | 379 def module_dirs(self): |
| 380 """Returns a collection of directories containing recipe modules.""" | |
|
M-A Ruel
2016/01/09 00:51:59
same
luqui
2016/01/15 23:11:27
Done.
| |
| 370 return [os.path.join(self.recipes_dir, 'recipe_modules')] | 381 return [os.path.join(self.recipes_dir, 'recipe_modules')] |
| 371 | 382 |
| 372 def find_dep(self, dep_name): | 383 def find_dep(self, dep_name): |
| 373 assert dep_name in self.deps, ( | 384 assert dep_name in self.deps, ( |
| 374 '%s does not exist or is not declared as a dependency of %s' % ( | 385 '%s does not exist or is not declared as a dependency of %s' % ( |
| 375 dep_name, self.name)) | 386 dep_name, self.name)) |
| 376 return self.deps[dep_name] | 387 return self.deps[dep_name] |
| 377 | 388 |
| 378 def module_path(self, module_name): | 389 def module_path(self, module_name): |
| 379 return os.path.join(self.recipes_dir, 'recipe_modules', module_name) | 390 return os.path.join(self.recipes_dir, 'recipe_modules', module_name) |
| 380 | 391 |
| 381 def loop_over_recipe_modules(): | 392 def loop_over_recipe_modules(): |
| 382 for path in self.module_dirs: | 393 for path in self.module_dirs: |
| 383 if os.path.isdir(path): | 394 if os.path.isdir(path): |
| 384 for item in os.listdir(path): | 395 for item in os.listdir(path): |
| 385 subpath = os.path.join(path, item) | 396 subpath = os.path.join(path, item) |
| 386 if _is_recipe_module_dir(subpath): | 397 if _is_recipe_module_dir(subpath): |
| 387 yield subpath | 398 yield subpath |
| 388 | 399 |
| 400 def all_files(self, context, seen=None): | |
| 401 """Returns a generator listing all file paths this package needs to run.""" | |
|
M-A Ruel
2016/01/09 00:51:59
Yields all file ...
luqui
2016/01/15 23:11:27
Done.
| |
| 402 if seen is None: | |
| 403 seen = set() | |
| 404 if self in seen: | |
| 405 return | |
| 406 seen.add(self) | |
| 407 | |
| 408 EXCLUDE = re.compile('\.pyc$') # pycs are not portable | |
| 409 | |
| 410 dirs = list(self.recipe_dirs) + list(self.module_dirs) | |
|
M-A Ruel
2016/01/09 00:51:59
The the two properties return a list, I don't thin
luqui
2016/01/15 23:11:27
Good obs. One upon a time they were generators.
| |
| 411 recipes_extra = os.path.join(self.recipes_dir, 'recipes.extra') | |
| 412 if os.path.exists(recipes_extra): | |
| 413 yield recipes_extra | |
| 414 with open(recipes_extra, 'r') as fh: | |
| 415 for fname in itertools.imap(self._normalize_extra_line, fh.readlines()): | |
| 416 f = os.path.join(self.recipes_dir, fname) | |
| 417 if os.path.isdir(f): | |
| 418 dirs.append(f) | |
| 419 else: | |
| 420 yield f | |
| 421 | |
| 422 yield self.repo_spec.proto_file(context).path | |
| 423 yield os.path.join(self.recipes_dir, 'recipes.py') | |
| 424 for basedir in dirs: | |
| 425 for root, dirs, files in os.walk(basedir): | |
| 426 for fname in files: | |
| 427 if not EXCLUDE.search(fname): | |
|
M-A Ruel
2016/01/09 00:51:59
a regexp is overkill here when this would be fine:
luqui
2016/01/15 23:11:27
I want the idea of EXCLUDE to be more visible than
| |
| 428 yield os.path.join(root, fname) | |
| 429 | |
| 430 for dep in self.deps.values(): | |
|
M-A Ruel
2016/01/09 00:51:59
itervalues
luqui
2016/01/15 23:11:27
Done.
| |
| 431 for f in dep.all_files(context, seen): | |
| 432 yield f | |
| 433 | |
| 434 def _normalize_extra_line(self, line): | |
|
M-A Ruel
2016/01/09 00:51:59
This doesn't need to be a method. IMHO, it'd be mo
luqui
2016/01/15 23:11:27
Ah yeah, and get rid of the imap. Agreed, Done.
| |
| 435 return re.sub(r'#.*', '', line).replace('/', os.sep).strip() | |
| 436 | |
| 389 | 437 |
| 390 class PackageSpec(object): | 438 class PackageSpec(object): |
| 391 API_VERSION = 1 | 439 API_VERSION = 1 |
| 392 | 440 |
| 393 def __init__(self, project_id, recipes_path, deps): | 441 def __init__(self, project_id, recipes_path, deps): |
| 394 self._project_id = project_id | 442 self._project_id = project_id |
| 395 self._recipes_path = recipes_path | 443 self._recipes_path = recipes_path |
| 396 self._deps = deps | 444 self._deps = deps |
| 397 | 445 |
| 398 @classmethod | 446 @classmethod |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 546 return not self.__eq__(other) | 594 return not self.__eq__(other) |
| 547 | 595 |
| 548 | 596 |
| 549 class PackageDeps(object): | 597 class PackageDeps(object): |
| 550 """An object containing all the transitive dependencies of the root package. | 598 """An object containing all the transitive dependencies of the root package. |
| 551 """ | 599 """ |
| 552 def __init__(self, context, overrides=None): | 600 def __init__(self, context, overrides=None): |
| 553 self._context = context | 601 self._context = context |
| 554 self._packages = {} | 602 self._packages = {} |
| 555 self._overrides = overrides or {} | 603 self._overrides = overrides or {} |
| 604 self._root_package = None | |
| 556 | 605 |
| 557 @classmethod | 606 @classmethod |
| 558 def create(cls, repo_root, proto_file, allow_fetch=False, overrides=None): | 607 def create(cls, repo_root, proto_file, allow_fetch=False, overrides=None): |
| 559 """Creates a PackageDeps object. | 608 """Creates a PackageDeps object. |
| 560 | 609 |
| 561 Arguments: | 610 Arguments: |
| 562 repo_root: the root of the repository containing this package. | 611 repo_root: the root of the repository containing this package. |
| 563 proto_file: a ProtoFile object corresponding to the repos recipes.cfg | 612 proto_file: a ProtoFile object corresponding to the repos recipes.cfg |
| 564 allow_fetch: whether to fetch dependencies rather than just checking for | 613 allow_fetch: whether to fetch dependencies rather than just checking for |
| 565 them. | 614 them. |
| 566 overrides: if not None, a dictionary of project overrides. Dictionary keys | 615 overrides: if not None, a dictionary of project overrides. Dictionary keys |
| 567 are the `project_id` field to override, and dictionary values | 616 are the `project_id` field to override, and dictionary values |
| 568 are the override path. | 617 are the override path. |
| 569 """ | 618 """ |
| 570 context = PackageContext.from_proto_file(repo_root, proto_file) | 619 context = PackageContext.from_proto_file(repo_root, proto_file) |
| 571 if overrides: | 620 if overrides: |
| 572 overrides = {project_id: PathRepoSpec(path) | 621 overrides = {project_id: PathRepoSpec(path) |
| 573 for project_id, path in overrides.iteritems()} | 622 for project_id, path in overrides.iteritems()} |
| 574 package_deps = cls(context, overrides=overrides) | 623 package_deps = cls(context, overrides=overrides) |
| 575 | 624 |
| 576 package_deps._create_package(RootRepoSpec(proto_file), allow_fetch) | 625 package_deps._root_package = package_deps._create_package( |
| 626 RootRepoSpec(proto_file), allow_fetch) | |
| 577 return package_deps | 627 return package_deps |
| 578 | 628 |
| 579 def _create_package(self, repo_spec, allow_fetch): | 629 def _create_package(self, repo_spec, allow_fetch): |
| 580 if allow_fetch: | 630 if allow_fetch: |
| 581 repo_spec.checkout(self._context) | 631 repo_spec.checkout(self._context) |
| 582 else: | 632 else: |
| 583 try: | 633 try: |
| 584 repo_spec.check_checkout(self._context) | 634 repo_spec.check_checkout(self._context) |
| 585 except UncleanFilesystemError as e: | 635 except UncleanFilesystemError as e: |
| 586 logging.warn( | 636 logging.warn( |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 603 'Package specs do not match: %s vs %s' % | 653 'Package specs do not match: %s vs %s' % |
| 604 (repo_spec, self._packages[project_id].repo_spec)) | 654 (repo_spec, self._packages[project_id].repo_spec)) |
| 605 self._packages[project_id] = None | 655 self._packages[project_id] = None |
| 606 | 656 |
| 607 deps = {} | 657 deps = {} |
| 608 for dep, dep_repo in sorted(package_spec.deps.items()): | 658 for dep, dep_repo in sorted(package_spec.deps.items()): |
| 609 deps[dep] = self._create_package(dep_repo, allow_fetch) | 659 deps[dep] = self._create_package(dep_repo, allow_fetch) |
| 610 | 660 |
| 611 package = Package( | 661 package = Package( |
| 612 project_id, repo_spec, deps, | 662 project_id, repo_spec, deps, |
| 613 os.path.join(repo_spec.repo_root(self._context), | 663 repo_root=repo_spec.repo_root(self._context), |
| 614 package_spec.recipes_path)) | 664 relative_recipes_dir=package_spec.recipes_path) |
| 615 | 665 |
| 616 self._packages[project_id] = package | 666 self._packages[project_id] = package |
| 617 return package | 667 return package |
| 618 | 668 |
| 619 # TODO(luqui): Remove this, so all accesses to packages are done | 669 # TODO(luqui): Remove this, so all accesses to packages are done |
| 620 # via other packages with properly scoped deps. | 670 # via other packages with properly scoped deps. |
| 621 def get_package(self, package_id): | 671 def get_package(self, package_id): |
| 622 return self._packages[package_id] | 672 return self._packages[package_id] |
| 623 | 673 |
| 624 @property | 674 @property |
| 675 def root_package(self): | |
| 676 """Returns the Package at the front of the dependency graph.""" | |
| 677 return self._root_package | |
| 678 | |
| 679 @property | |
| 625 def packages(self): | 680 def packages(self): |
| 626 for p in self._packages.values(): | 681 for p in self._packages.values(): |
| 627 yield p | 682 yield p |
| 628 | 683 |
| 629 @property | 684 @property |
| 630 def engine_recipes_py(self): | 685 def engine_recipes_py(self): |
| 631 return os.path.join(self._context.repo_root, 'recipes.py') | 686 return os.path.join(self._context.repo_root, 'recipes.py') |
| 632 | 687 |
| 633 | 688 |
| 634 def _run_cmd(cmd, cwd=None): | 689 def _run_cmd(cmd, cwd=None): |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 705 >>> d = { 'x': 1, 'y': 2 } | 760 >>> d = { 'x': 1, 'y': 2 } |
| 706 >>> sorted(_updated(d, { 'y': 3, 'z': 4 }).items()) | 761 >>> sorted(_updated(d, { 'y': 3, 'z': 4 }).items()) |
| 707 [('x', 1), ('y', 3), ('z', 4)] | 762 [('x', 1), ('y', 3), ('z', 4)] |
| 708 >>> sorted(d.items()) | 763 >>> sorted(d.items()) |
| 709 [('x', 1), ('y', 2)] | 764 [('x', 1), ('y', 2)] |
| 710 """ | 765 """ |
| 711 | 766 |
| 712 d = copy.copy(d) | 767 d = copy.copy(d) |
| 713 d.update(updates) | 768 d.update(updates) |
| 714 return d | 769 return d |
| OLD | NEW |