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

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: moar review comments 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 the list of directories containing recipes."""
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 the list of directories containing recipe modules."""
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 """Yields all file paths this package needs to run."""
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 = self.recipe_dirs + self.module_dirs
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 line in fh.readlines():
416 fname = re.sub(r'#.*', '', line).replace('/', os.sep).strip()
M-A Ruel 2016/01/16 23:30:48 You should use r'^#.*' otherwise no file name can
417 f = os.path.join(self.recipes_dir, fname)
418 if os.path.isdir(f):
419 dirs.append(f)
420 else:
421 yield f
422
423 yield self.repo_spec.proto_file(context).path
424 yield os.path.join(self.recipes_dir, 'recipes.py')
425 for basedir in dirs:
426 for root, dirs, files in os.walk(basedir):
427 for fname in files:
428 if not EXCLUDE.search(fname):
429 yield os.path.join(root, fname)
430
431 for dep in self.deps.itervalues():
432 for f in dep.all_files(context, seen):
433 yield f
434
389 435
390 class PackageSpec(object): 436 class PackageSpec(object):
391 API_VERSION = 1 437 API_VERSION = 1
392 438
393 def __init__(self, project_id, recipes_path, deps): 439 def __init__(self, project_id, recipes_path, deps):
394 self._project_id = project_id 440 self._project_id = project_id
395 self._recipes_path = recipes_path 441 self._recipes_path = recipes_path
396 self._deps = deps 442 self._deps = deps
397 443
398 @classmethod 444 @classmethod
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
546 return not self.__eq__(other) 592 return not self.__eq__(other)
547 593
548 594
549 class PackageDeps(object): 595 class PackageDeps(object):
550 """An object containing all the transitive dependencies of the root package. 596 """An object containing all the transitive dependencies of the root package.
551 """ 597 """
552 def __init__(self, context, overrides=None): 598 def __init__(self, context, overrides=None):
553 self._context = context 599 self._context = context
554 self._packages = {} 600 self._packages = {}
555 self._overrides = overrides or {} 601 self._overrides = overrides or {}
602 self._root_package = None
556 603
557 @classmethod 604 @classmethod
558 def create(cls, repo_root, proto_file, allow_fetch=False, overrides=None): 605 def create(cls, repo_root, proto_file, allow_fetch=False, overrides=None):
559 """Creates a PackageDeps object. 606 """Creates a PackageDeps object.
560 607
561 Arguments: 608 Arguments:
562 repo_root: the root of the repository containing this package. 609 repo_root: the root of the repository containing this package.
563 proto_file: a ProtoFile object corresponding to the repos recipes.cfg 610 proto_file: a ProtoFile object corresponding to the repos recipes.cfg
564 allow_fetch: whether to fetch dependencies rather than just checking for 611 allow_fetch: whether to fetch dependencies rather than just checking for
565 them. 612 them.
566 overrides: if not None, a dictionary of project overrides. Dictionary keys 613 overrides: if not None, a dictionary of project overrides. Dictionary keys
567 are the `project_id` field to override, and dictionary values 614 are the `project_id` field to override, and dictionary values
568 are the override path. 615 are the override path.
569 """ 616 """
570 context = PackageContext.from_proto_file(repo_root, proto_file) 617 context = PackageContext.from_proto_file(repo_root, proto_file)
571 if overrides: 618 if overrides:
572 overrides = {project_id: PathRepoSpec(path) 619 overrides = {project_id: PathRepoSpec(path)
573 for project_id, path in overrides.iteritems()} 620 for project_id, path in overrides.iteritems()}
574 package_deps = cls(context, overrides=overrides) 621 package_deps = cls(context, overrides=overrides)
575 622
576 package_deps._create_package(RootRepoSpec(proto_file), allow_fetch) 623 package_deps._root_package = package_deps._create_package(
624 RootRepoSpec(proto_file), allow_fetch)
577 return package_deps 625 return package_deps
578 626
579 def _create_package(self, repo_spec, allow_fetch): 627 def _create_package(self, repo_spec, allow_fetch):
580 if allow_fetch: 628 if allow_fetch:
581 repo_spec.checkout(self._context) 629 repo_spec.checkout(self._context)
582 else: 630 else:
583 try: 631 try:
584 repo_spec.check_checkout(self._context) 632 repo_spec.check_checkout(self._context)
585 except UncleanFilesystemError as e: 633 except UncleanFilesystemError as e:
586 logging.warn( 634 logging.warn(
(...skipping 16 matching lines...) Expand all
603 'Package specs do not match: %s vs %s' % 651 'Package specs do not match: %s vs %s' %
604 (repo_spec, self._packages[project_id].repo_spec)) 652 (repo_spec, self._packages[project_id].repo_spec))
605 self._packages[project_id] = None 653 self._packages[project_id] = None
606 654
607 deps = {} 655 deps = {}
608 for dep, dep_repo in sorted(package_spec.deps.items()): 656 for dep, dep_repo in sorted(package_spec.deps.items()):
609 deps[dep] = self._create_package(dep_repo, allow_fetch) 657 deps[dep] = self._create_package(dep_repo, allow_fetch)
610 658
611 package = Package( 659 package = Package(
612 project_id, repo_spec, deps, 660 project_id, repo_spec, deps,
613 os.path.join(repo_spec.repo_root(self._context), 661 repo_root=repo_spec.repo_root(self._context),
614 package_spec.recipes_path)) 662 relative_recipes_dir=package_spec.recipes_path)
615 663
616 self._packages[project_id] = package 664 self._packages[project_id] = package
617 return package 665 return package
618 666
619 # TODO(luqui): Remove this, so all accesses to packages are done 667 # TODO(luqui): Remove this, so all accesses to packages are done
620 # via other packages with properly scoped deps. 668 # via other packages with properly scoped deps.
621 def get_package(self, package_id): 669 def get_package(self, package_id):
622 return self._packages[package_id] 670 return self._packages[package_id]
623 671
624 @property 672 @property
673 def root_package(self):
674 """Returns the Package at the front of the dependency graph."""
675 return self._root_package
676
677 @property
625 def packages(self): 678 def packages(self):
626 for p in self._packages.values(): 679 for p in self._packages.values():
627 yield p 680 yield p
628 681
629 @property 682 @property
630 def engine_recipes_py(self): 683 def engine_recipes_py(self):
631 return os.path.join(self._context.repo_root, 'recipes.py') 684 return os.path.join(self._context.repo_root, 'recipes.py')
632 685
633 686
634 def _run_cmd(cmd, cwd=None): 687 def _run_cmd(cmd, cwd=None):
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
705 >>> d = { 'x': 1, 'y': 2 } 758 >>> d = { 'x': 1, 'y': 2 }
706 >>> sorted(_updated(d, { 'y': 3, 'z': 4 }).items()) 759 >>> sorted(_updated(d, { 'y': 3, 'z': 4 }).items())
707 [('x', 1), ('y', 3), ('z', 4)] 760 [('x', 1), ('y', 3), ('z', 4)]
708 >>> sorted(d.items()) 761 >>> sorted(d.items())
709 [('x', 1), ('y', 2)] 762 [('x', 1), ('y', 2)]
710 """ 763 """
711 764
712 d = copy.copy(d) 765 d = copy.copy(d)
713 d.update(updates) 766 d.update(updates)
714 return d 767 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