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

Side by Side Diff: recipe_engine/package.py

Issue 1570333002: 'recipes.py isolate' command to build an isolate of the current package's toolchain (Closed) Base URL: git@github.com:luci/recipes-py.git@master
Patch Set: Created 4 years, 11 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 | recipes.extra » ('j') | recipes.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
OLDNEW
« no previous file with comments | « no previous file | recipes.extra » ('j') | recipes.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698