1 # Copyright 2010-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
4 from itertools import permutations
10 from portage.const import (GLOBAL_CONFIG_PATH, PORTAGE_BASE_PATH,
12 from portage.dbapi.vartree import vartree
13 from portage.dbapi.porttree import portagetree
14 from portage.dbapi.bintree import binarytree
15 from portage.dep import Atom, _repo_separator
16 from portage.package.ebuild.config import config
17 from portage.package.ebuild.digestgen import digestgen
18 from portage._sets import load_default_config
19 from portage._sets.base import InternalPackageSet
20 from portage.util import ensure_dirs
21 from portage.versions import catsplit
24 from _emerge.actions import calc_depclean
25 from _emerge.Blocker import Blocker
26 from _emerge.create_depgraph_params import create_depgraph_params
27 from _emerge.depgraph import backtrack_depgraph
28 from _emerge.RootConfig import RootConfig
30 if sys.hexversion >= 0x3000000:
33 class ResolverPlayground(object):
35 This class helps to create the necessary files on disk and
36 the needed settings instances, etc. for the resolver to do
40 config_files = frozenset(("package.accept_keywords", "package.use", "package.mask", "package.keywords", \
41 "package.unmask", "package.properties", "package.license", "use.mask", "use.force",
44 metadata_xml_template = """<?xml version="1.0" encoding="UTF-8"?>
45 <!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
49 <email>maintainer-needed@gentoo.org</email>
50 <description>Description of the maintainership</description>
52 <longdescription>Long description of the package</longdescription>
59 def __init__(self, ebuilds={}, installed={}, profile={}, repo_configs={}, \
60 user_config={}, sets={}, world=[], world_sets=[], distfiles={}, debug=False):
62 ebuilds: cpv -> metadata mapping simulating available ebuilds.
63 installed: cpv -> metadata mapping simulating installed packages.
64 If a metadata key is missing, it gets a default value.
65 profile: settings defined by the profile.
69 self.eprefix = tempfile.mkdtemp()
70 self.eroot = self.root + self.eprefix.lstrip(os.sep) + os.sep
71 self.distdir = os.path.join(self.eroot, "var", "portage", "distfiles")
72 self.portdir = os.path.join(self.eroot, "usr/portage")
73 self.vdbdir = os.path.join(self.eroot, "var/db/pkg")
74 os.makedirs(self.portdir)
75 os.makedirs(self.vdbdir)
78 portage.util.noiselimit = -2
81 #Make sure the main repo is always created
82 self._get_repo_dir("test_repo")
84 self._create_distfiles(distfiles)
85 self._create_ebuilds(ebuilds)
86 self._create_installed(installed)
87 self._create_profile(ebuilds, installed, profile, repo_configs, user_config, sets)
88 self._create_world(world, world_sets)
90 self.settings, self.trees = self._load_config()
92 self._create_ebuild_manifests(ebuilds)
94 portage.util.noiselimit = 0
96 def _get_repo_dir(self, repo):
98 Create the repo directory if needed.
100 if repo not in self.repo_dirs:
101 if repo == "test_repo":
102 repo_path = self.portdir
104 repo_path = os.path.join(self.eroot, "usr", "local", repo)
106 self.repo_dirs[repo] = repo_path
107 profile_path = os.path.join(repo_path, "profiles")
110 os.makedirs(profile_path)
114 repo_name_file = os.path.join(profile_path, "repo_name")
115 f = open(repo_name_file, "w")
116 f.write("%s\n" % repo)
119 return self.repo_dirs[repo]
121 def _create_distfiles(self, distfiles):
122 os.makedirs(self.distdir)
123 for k, v in distfiles.items():
124 with open(os.path.join(self.distdir, k), 'wb') as f:
127 def _create_ebuilds(self, ebuilds):
129 a = Atom("=" + cpv, allow_repo=True)
134 metadata = ebuilds[cpv].copy()
135 copyright_header = metadata.pop("COPYRIGHT_HEADER", None)
136 desc = metadata.pop("DESCRIPTION", None)
137 eapi = metadata.pop("EAPI", 0)
138 lic = metadata.pop("LICENSE", "")
139 properties = metadata.pop("PROPERTIES", "")
140 slot = metadata.pop("SLOT", 0)
141 keywords = metadata.pop("KEYWORDS", "x86")
142 homepage = metadata.pop("HOMEPAGE", None)
143 src_uri = metadata.pop("SRC_URI", None)
144 iuse = metadata.pop("IUSE", "")
145 provide = metadata.pop("PROVIDE", None)
146 depend = metadata.pop("DEPEND", "")
147 rdepend = metadata.pop("RDEPEND", None)
148 pdepend = metadata.pop("PDEPEND", None)
149 required_use = metadata.pop("REQUIRED_USE", None)
150 misc_content = metadata.pop("MISC_CONTENT", None)
153 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
155 repo_dir = self._get_repo_dir(repo)
156 ebuild_dir = os.path.join(repo_dir, a.cp)
157 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
159 os.makedirs(ebuild_dir)
163 f = open(ebuild_path, "w")
164 if copyright_header is not None:
165 f.write(copyright_header)
166 f.write('EAPI="' + str(eapi) + '"\n')
168 f.write('DESCRIPTION="%s"\n' % desc)
169 if homepage is not None:
170 f.write('HOMEPAGE="%s"\n' % homepage)
171 if src_uri is not None:
172 f.write('SRC_URI="%s"\n' % src_uri)
173 f.write('LICENSE="' + str(lic) + '"\n')
174 f.write('PROPERTIES="' + str(properties) + '"\n')
175 f.write('SLOT="' + str(slot) + '"\n')
176 f.write('KEYWORDS="' + str(keywords) + '"\n')
177 f.write('IUSE="' + str(iuse) + '"\n')
178 if provide is not None:
179 f.write('PROVIDE="%s"\n' % provide)
180 f.write('DEPEND="' + str(depend) + '"\n')
181 if rdepend is not None:
182 f.write('RDEPEND="' + str(rdepend) + '"\n')
183 if pdepend is not None:
184 f.write('PDEPEND="' + str(pdepend) + '"\n')
185 if required_use is not None:
186 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
187 if misc_content is not None:
188 f.write(misc_content)
191 def _create_ebuild_manifests(self, ebuilds):
192 tmpsettings = config(clone=self.settings)
193 tmpsettings['PORTAGE_QUIET'] = '1'
195 a = Atom("=" + cpv, allow_repo=True)
200 repo_dir = self._get_repo_dir(repo)
201 ebuild_dir = os.path.join(repo_dir, a.cp)
202 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
204 portdb = self.trees[self.eroot]["porttree"].dbapi
205 tmpsettings['O'] = ebuild_dir
206 if not digestgen(mysettings=tmpsettings, myportdb=portdb):
207 raise AssertionError('digest creation failed for %s' % ebuild_path)
209 def _create_installed(self, installed):
210 for cpv in installed:
211 a = Atom("=" + cpv, allow_repo=True)
216 vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
218 os.makedirs(vdb_pkg_dir)
222 metadata = installed[cpv].copy()
223 eapi = metadata.pop("EAPI", 0)
224 lic = metadata.pop("LICENSE", "")
225 properties = metadata.pop("PROPERTIES", "")
226 slot = metadata.pop("SLOT", 0)
227 keywords = metadata.pop("KEYWORDS", "~x86")
228 iuse = metadata.pop("IUSE", "")
229 use = metadata.pop("USE", "")
230 provide = metadata.pop("PROVIDE", None)
231 depend = metadata.pop("DEPEND", "")
232 rdepend = metadata.pop("RDEPEND", None)
233 pdepend = metadata.pop("PDEPEND", None)
234 required_use = metadata.pop("REQUIRED_USE", None)
237 raise ValueError("metadata of installed '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
239 def write_key(key, value):
240 f = open(os.path.join(vdb_pkg_dir, key), "w")
241 f.write(str(value) + "\n")
244 write_key("EAPI", eapi)
245 write_key("COUNTER", "0")
246 write_key("LICENSE", lic)
247 write_key("PROPERTIES", properties)
248 write_key("SLOT", slot)
249 write_key("LICENSE", lic)
250 write_key("PROPERTIES", properties)
251 write_key("repository", repo)
252 write_key("KEYWORDS", keywords)
253 write_key("IUSE", iuse)
254 write_key("USE", use)
255 if provide is not None:
256 write_key("PROVIDE", provide)
257 write_key("DEPEND", depend)
258 if rdepend is not None:
259 write_key("RDEPEND", rdepend)
260 if pdepend is not None:
261 write_key("PDEPEND", pdepend)
262 if required_use is not None:
263 write_key("REQUIRED_USE", required_use)
265 def _create_profile(self, ebuilds, installed, profile, repo_configs, user_config, sets):
267 user_config_dir = os.path.join(self.eroot, USER_CONFIG_PATH)
270 os.makedirs(user_config_dir)
274 for repo in self.repo_dirs:
275 repo_dir = self._get_repo_dir(repo)
276 profile_dir = os.path.join(self._get_repo_dir(repo), "profiles")
277 metadata_dir = os.path.join(repo_dir, "metadata")
278 os.makedirs(metadata_dir)
280 #Create $REPO/profiles/categories
283 ebuilds_repo = Atom("="+cpv, allow_repo=True).repo
284 if ebuilds_repo is None:
285 ebuilds_repo = "test_repo"
286 if ebuilds_repo == repo:
287 categories.add(catsplit(cpv)[0])
289 categories_file = os.path.join(profile_dir, "categories")
290 f = open(categories_file, "w")
291 for cat in categories:
295 #Create $REPO/profiles/license_groups
296 license_file = os.path.join(profile_dir, "license_groups")
297 f = open(license_file, "w")
298 f.write("EULA TEST\n")
301 repo_config = repo_configs.get(repo)
303 for config_file, lines in repo_config.items():
304 if config_file not in self.config_files:
305 raise ValueError("Unknown config file: '%s'" % config_file)
307 if config_file in ("layout.conf",):
308 file_name = os.path.join(repo_dir, "metadata", config_file)
310 file_name = os.path.join(profile_dir, config_file)
311 f = open(file_name, "w")
313 f.write("%s\n" % line)
316 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
317 os.makedirs(os.path.join(repo_dir, "eclass"))
319 if repo == "test_repo":
320 #Create a minimal profile in /usr/portage
321 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
322 os.makedirs(sub_profile_dir)
324 eapi_file = os.path.join(sub_profile_dir, "eapi")
325 f = open(eapi_file, "w")
329 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
330 f = open(make_defaults_file, "w")
331 f.write("ARCH=\"x86\"\n")
332 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
335 use_force_file = os.path.join(sub_profile_dir, "use.force")
336 f = open(use_force_file, "w")
340 parent_file = os.path.join(sub_profile_dir, "parent")
341 f = open(parent_file, "w")
346 for config_file, lines in profile.items():
347 if config_file not in self.config_files:
348 raise ValueError("Unknown config file: '%s'" % config_file)
350 file_name = os.path.join(sub_profile_dir, config_file)
351 f = open(file_name, "w")
353 f.write("%s\n" % line)
356 #Create profile symlink
357 os.symlink(sub_profile_dir, os.path.join(user_config_dir, "make.profile"))
359 #Create minimal herds.xml
360 herds_xml = """<?xml version="1.0" encoding="UTF-8"?>
361 <!DOCTYPE herds SYSTEM "http://www.gentoo.org/dtd/herds.dtd">
362 <?xml-stylesheet href="/xsl/herds.xsl" type="text/xsl" ?>
363 <?xml-stylesheet href="/xsl/guide.xsl" type="text/xsl" ?>
366 <name>base-system</name>
367 <email>base-system@gentoo.org</email>
368 <description>Core system utilities and libraries.</description>
370 <email>base-system@gentoo.orgg</email>
371 <name>Base System</name>
372 <role>Base System Maintainer</role>
377 with open(os.path.join(metadata_dir, "metadata.xml"), 'w') as f:
380 repos_conf_file = os.path.join(user_config_dir, "repos.conf")
381 f = open(repos_conf_file, "w")
383 for repo in sorted(self.repo_dirs.keys()):
384 f.write("[%s]\n" % repo)
385 f.write("LOCATION=%s\n" % self.repo_dirs[repo])
386 if repo == "test_repo":
387 f.write("PRIORITY=%s\n" % -1000)
389 f.write("PRIORITY=%s\n" % priority)
393 for config_file, lines in user_config.items():
394 if config_file not in self.config_files:
395 raise ValueError("Unknown config file: '%s'" % config_file)
397 file_name = os.path.join(user_config_dir, config_file)
398 f = open(file_name, "w")
400 f.write("%s\n" % line)
403 #Create /usr/share/portage/config/make.globals
404 make_globals_path = os.path.join(self.eroot,
405 GLOBAL_CONFIG_PATH.lstrip(os.sep), "make.globals")
406 ensure_dirs(os.path.dirname(make_globals_path))
407 os.symlink(os.path.join(PORTAGE_BASE_PATH, "cnf", "make.globals"),
410 #Create /usr/share/portage/config/sets/portage.conf
411 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
414 os.makedirs(default_sets_conf_dir)
418 provided_sets_portage_conf = \
419 os.path.join(PORTAGE_BASE_PATH, "cnf/sets/portage.conf")
420 os.symlink(provided_sets_portage_conf, os.path.join(default_sets_conf_dir, "portage.conf"))
422 set_config_dir = os.path.join(user_config_dir, "sets")
425 os.makedirs(set_config_dir)
429 for sets_file, lines in sets.items():
430 file_name = os.path.join(set_config_dir, sets_file)
431 f = open(file_name, "w")
433 f.write("%s\n" % line)
436 user_config_dir = os.path.join(self.eroot, "etc", "portage")
439 os.makedirs(user_config_dir)
443 for config_file, lines in user_config.items():
444 if config_file not in self.config_files:
445 raise ValueError("Unknown config file: '%s'" % config_file)
447 file_name = os.path.join(user_config_dir, config_file)
448 f = open(file_name, "w")
450 f.write("%s\n" % line)
453 def _create_world(self, world, world_sets):
454 #Create /var/lib/portage/world
455 var_lib_portage = os.path.join(self.eroot, "var", "lib", "portage")
456 os.makedirs(var_lib_portage)
458 world_file = os.path.join(var_lib_portage, "world")
459 world_set_file = os.path.join(var_lib_portage, "world_sets")
461 f = open(world_file, "w")
463 f.write("%s\n" % atom)
466 f = open(world_set_file, "w")
467 for atom in world_sets:
468 f.write("%s\n" % atom)
471 def _load_config(self):
473 for repo_name in sorted(self.repo_dirs):
474 path = self.repo_dirs[repo_name]
475 if path != self.portdir:
476 portdir_overlay.append(path)
479 "__PORTAGE_TEST_EPREFIX": self.eprefix,
480 "ACCEPT_KEYWORDS": "x86",
481 "DISTDIR" : self.distdir,
482 "PKGDIR": os.path.join(self.eroot, "usr/portage/packages"),
483 "PORTDIR": self.portdir,
484 "PORTDIR_OVERLAY": " ".join(portdir_overlay),
485 'PORTAGE_TMPDIR' : os.path.join(self.eroot, 'var/tmp'),
488 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
489 # need to be inherited by ebuild subprocesses.
490 if 'PORTAGE_USERNAME' in os.environ:
491 env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
492 if 'PORTAGE_GRPNAME' in os.environ:
493 env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
495 trees = portage.create_trees(env=env)
496 for root, root_trees in trees.items():
497 settings = root_trees["vartree"].settings
498 settings._init_dirs()
499 setconfig = load_default_config(settings, root_trees)
500 root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
502 return settings, trees
504 def run(self, atoms, options={}, action=None):
505 options = options.copy()
506 options["--pretend"] = True
508 options["--debug"] = True
511 if options.get("--depclean"):
513 elif options.get("--prune"):
516 global_noiselimit = portage.util.noiselimit
517 global_emergelog_disable = _emerge.emergelog._disable
521 portage.util.noiselimit = -2
522 _emerge.emergelog._disable = True
524 if action in ("depclean", "prune"):
525 rval, cleanlist, ordered, req_pkg_count = \
526 calc_depclean(self.settings, self.trees, None,
527 options, action, InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
528 result = ResolverPlaygroundDepcleanResult( \
529 atoms, rval, cleanlist, ordered, req_pkg_count)
531 params = create_depgraph_params(options, action)
532 success, depgraph, favorites = backtrack_depgraph(
533 self.settings, self.trees, options, params, action, atoms, None)
534 depgraph._show_merge_list()
535 depgraph.display_problems()
536 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
538 portage.util.noiselimit = global_noiselimit
539 _emerge.emergelog._disable = global_emergelog_disable
543 def run_TestCase(self, test_case):
544 if not isinstance(test_case, ResolverPlaygroundTestCase):
545 raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
546 for atoms in test_case.requests:
547 result = self.run(atoms, test_case.options, test_case.action)
548 if not test_case.compare_with_result(result):
552 portdb = self.trees[self.eroot]["porttree"].dbapi
553 portdb.close_caches()
554 portage.dbapi.porttree.portdbapi.portdbapi_instances.remove(portdb)
556 print("\nEROOT=%s" % self.eroot)
558 shutil.rmtree(self.eroot)
560 class ResolverPlaygroundTestCase(object):
562 def __init__(self, request, **kwargs):
563 self.all_permutations = kwargs.pop("all_permutations", False)
564 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
565 self.ambiguous_merge_order = kwargs.pop("ambiguous_merge_order", False)
566 self.check_repo_names = kwargs.pop("check_repo_names", False)
567 self.merge_order_assertions = kwargs.pop("merge_order_assertions", False)
569 if self.all_permutations:
570 self.requests = list(permutations(request))
572 self.requests = [request]
574 self.options = kwargs.pop("options", {})
575 self.action = kwargs.pop("action", None)
576 self.test_success = True
578 self._checks = kwargs.copy()
580 def compare_with_result(self, result):
581 checks = dict.fromkeys(result.checks)
582 for key, value in self._checks.items():
583 if not key in checks:
584 raise KeyError("Not an available check: '%s'" % key)
588 for key, value in checks.items():
589 got = getattr(result, key)
592 if key in result.optional_checks and expected is None:
595 if key == "mergelist":
596 if not self.check_repo_names:
597 #Strip repo names if we don't check them
604 a = Atom("="+cpv, allow_repo=True)
605 new_got.append(a.cpv)
610 if isinstance(obj, basestring):
612 new_expected.append(obj)
614 a = Atom("="+obj, allow_repo=True)
615 new_expected.append(a.cpv)
617 new_expected.append(set())
620 cpv = Atom("="+cpv, allow_repo=True).cpv
621 new_expected[-1].add(cpv)
622 expected = new_expected
623 if self.ignore_mergelist_order and got is not None:
625 expected = set(expected)
627 if self.ambiguous_merge_order and got:
628 expected_stack = list(reversed(expected))
629 got_stack = list(reversed(got))
632 while got_stack and expected_stack:
633 got_token = got_stack.pop()
634 expected_obj = expected_stack.pop()
635 if isinstance(expected_obj, basestring):
636 new_expected.append(expected_obj)
637 if got_token == expected_obj:
639 # result doesn't match, so stop early
642 expected_obj = set(expected_obj)
644 expected_obj.remove(got_token)
646 # result doesn't match, so stop early
649 new_expected.append(got_token)
650 while got_stack and expected_obj:
651 got_token = got_stack.pop()
653 expected_obj.remove(got_token)
657 new_expected.append(got_token)
659 # result doesn't match, so stop early
662 # result does not match, so stop early
664 new_expected.append(tuple(expected_obj))
667 # result does not match, add leftovers to new_expected
669 expected_stack.reverse()
670 new_expected.extend(expected_stack)
671 expected = new_expected
673 if match and self.merge_order_assertions:
674 for node1, node2 in self.merge_order_assertions:
675 if not (got.index(node1) < got.index(node2)):
676 fail_msgs.append("atoms: (" + \
677 ", ".join(result.atoms) + "), key: " + \
678 ("merge_order_assertions, expected: %s" % \
679 str((node1, node2))) + \
680 ", got: " + str(got))
682 elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None:
683 expected = set(expected)
686 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
687 key + ", expected: " + str(expected) + ", got: " + str(got))
689 self.test_success = False
690 self.fail_msg = "\n".join(fail_msgs)
694 class ResolverPlaygroundResult(object):
697 "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions",
698 "circular_dependency_solutions", "needed_p_mask_changes",
703 def __init__(self, atoms, success, mydepgraph, favorites):
705 self.success = success
706 self.depgraph = mydepgraph
707 self.favorites = favorites
708 self.mergelist = None
709 self.use_changes = None
710 self.license_changes = None
711 self.unstable_keywords = None
712 self.needed_p_mask_changes = None
713 self.slot_collision_solutions = None
714 self.circular_dependency_solutions = None
716 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
718 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
719 if isinstance(x, Blocker):
720 self.mergelist.append(x.atom)
723 if x.metadata["repository"] != "test_repo":
724 repo_str = _repo_separator + x.metadata["repository"]
725 self.mergelist.append(x.cpv + repo_str)
727 if self.depgraph._dynamic_config._needed_use_config_changes:
728 self.use_changes = {}
729 for pkg, needed_use_config_changes in \
730 self.depgraph._dynamic_config._needed_use_config_changes.items():
731 new_use, changes = needed_use_config_changes
732 self.use_changes[pkg.cpv] = changes
734 if self.depgraph._dynamic_config._needed_unstable_keywords:
735 self.unstable_keywords = set()
736 for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
737 self.unstable_keywords.add(pkg.cpv)
739 if self.depgraph._dynamic_config._needed_p_mask_changes:
740 self.needed_p_mask_changes = set()
741 for pkg in self.depgraph._dynamic_config._needed_p_mask_changes:
742 self.needed_p_mask_changes.add(pkg.cpv)
744 if self.depgraph._dynamic_config._needed_license_changes:
745 self.license_changes = {}
746 for pkg, missing_licenses in self.depgraph._dynamic_config._needed_license_changes.items():
747 self.license_changes[pkg.cpv] = missing_licenses
749 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
750 self.slot_collision_solutions = []
751 handler = self.depgraph._dynamic_config._slot_conflict_handler
753 for change in handler.changes:
756 new_change[pkg.cpv] = change[pkg]
757 self.slot_collision_solutions.append(new_change)
759 if self.depgraph._dynamic_config._circular_dependency_handler is not None:
760 handler = self.depgraph._dynamic_config._circular_dependency_handler
761 sol = handler.solutions
762 self.circular_dependency_solutions = dict( zip([x.cpv for x in sol.keys()], sol.values()) )
764 class ResolverPlaygroundDepcleanResult(object):
767 "success", "cleanlist", "ordered", "req_pkg_count",
770 "ordered", "req_pkg_count",
773 def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
775 self.success = rval == 0
776 self.cleanlist = cleanlist
777 self.ordered = ordered
778 self.req_pkg_count = req_pkg_count