1 # Copyright 2010-2014 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
4 from itertools import permutations
10 from portage import shutil
11 from portage.const import (GLOBAL_CONFIG_PATH, PORTAGE_BASE_PATH,
13 from portage.dep import Atom, _repo_separator
14 from portage.package.ebuild.config import config
15 from portage.package.ebuild.digestgen import digestgen
16 from portage._sets import load_default_config
17 from portage._sets.base import InternalPackageSet
18 from portage.util import ensure_dirs, normalize_path
19 from portage.versions import catsplit
22 from _emerge.actions import calc_depclean
23 from _emerge.Blocker import Blocker
24 from _emerge.create_depgraph_params import create_depgraph_params
25 from _emerge.depgraph import backtrack_depgraph
26 from _emerge.RootConfig import RootConfig
28 if sys.hexversion >= 0x3000000:
31 class ResolverPlayground(object):
33 This class helps to create the necessary files on disk and
34 the needed settings instances, etc. for the resolver to do
38 config_files = frozenset(("eapi", "layout.conf", "make.conf", "package.accept_keywords",
39 "package.keywords", "package.license", "package.mask", "package.properties",
40 "package.unmask", "package.use", "package.use.aliases", "package.use.stable.mask",
41 "unpack_dependencies", "use.aliases", "use.force", "use.mask", "layout.conf"))
43 metadata_xml_template = """<?xml version="1.0" encoding="UTF-8"?>
44 <!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
48 <email>maintainer-needed@gentoo.org</email>
49 <description>Description of the maintainership</description>
51 <longdescription>Long description of the package</longdescription>
58 def __init__(self, ebuilds={}, binpkgs={}, installed={}, profile={}, repo_configs={}, \
59 user_config={}, sets={}, world=[], world_sets=[], distfiles={},
60 targetroot=False, 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.
68 self.eprefix = normalize_path(tempfile.mkdtemp())
69 portage.const.EPREFIX = self.eprefix.rstrip(os.sep)
71 self.eroot = self.eprefix + os.sep
73 self.target_root = os.path.join(self.eroot, 'target_root')
75 self.target_root = os.sep
76 self.distdir = os.path.join(self.eroot, "var", "portage", "distfiles")
77 self.pkgdir = os.path.join(self.eprefix, "pkgdir")
78 self.vdbdir = os.path.join(self.eroot, "var/db/pkg")
79 os.makedirs(self.vdbdir)
82 portage.util.noiselimit = -2
84 self._repositories = {}
85 #Make sure the main repo is always created
86 self._get_repo_dir("test_repo")
88 self._create_distfiles(distfiles)
89 self._create_ebuilds(ebuilds)
90 self._create_binpkgs(binpkgs)
91 self._create_installed(installed)
92 self._create_profile(ebuilds, installed, profile, repo_configs, user_config, sets)
93 self._create_world(world, world_sets)
95 self.settings, self.trees = self._load_config()
97 self._create_ebuild_manifests(ebuilds)
99 portage.util.noiselimit = 0
101 def _get_repo_dir(self, repo):
103 Create the repo directory if needed.
105 if repo not in self._repositories:
106 if repo == "test_repo":
107 self._repositories["DEFAULT"] = {"main-repo": repo}
109 repo_path = os.path.join(self.eroot, "var", "repositories", repo)
110 self._repositories[repo] = {"location": repo_path}
111 profile_path = os.path.join(repo_path, "profiles")
114 os.makedirs(profile_path)
118 repo_name_file = os.path.join(profile_path, "repo_name")
119 with open(repo_name_file, "w") as f:
120 f.write("%s\n" % repo)
122 return self._repositories[repo]["location"]
124 def _create_distfiles(self, distfiles):
125 os.makedirs(self.distdir)
126 for k, v in distfiles.items():
127 with open(os.path.join(self.distdir, k), 'wb') as f:
130 def _create_ebuilds(self, ebuilds):
132 a = Atom("=" + cpv, allow_repo=True)
137 metadata = ebuilds[cpv].copy()
138 copyright_header = metadata.pop("COPYRIGHT_HEADER", None)
139 eapi = metadata.pop("EAPI", "0")
140 misc_content = metadata.pop("MISC_CONTENT", None)
141 metadata.setdefault("DEPEND", "")
142 metadata.setdefault("SLOT", "0")
143 metadata.setdefault("KEYWORDS", "x86")
144 metadata.setdefault("IUSE", "")
146 unknown_keys = set(metadata).difference(
147 portage.dbapi.dbapi._known_keys)
149 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" %
150 (cpv, sorted(unknown_keys)))
152 repo_dir = self._get_repo_dir(repo)
153 ebuild_dir = os.path.join(repo_dir, a.cp)
154 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
156 os.makedirs(ebuild_dir)
160 with open(ebuild_path, "w") as f:
161 if copyright_header is not None:
162 f.write(copyright_header)
163 f.write('EAPI="%s"\n' % eapi)
164 for k, v in metadata.items():
165 f.write('%s="%s"\n' % (k, v))
166 if misc_content is not None:
167 f.write(misc_content)
169 def _create_ebuild_manifests(self, ebuilds):
170 tmpsettings = config(clone=self.settings)
171 tmpsettings['PORTAGE_QUIET'] = '1'
173 a = Atom("=" + cpv, allow_repo=True)
178 repo_dir = self._get_repo_dir(repo)
179 ebuild_dir = os.path.join(repo_dir, a.cp)
180 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
182 portdb = self.trees[self.eroot]["porttree"].dbapi
183 tmpsettings['O'] = ebuild_dir
184 if not digestgen(mysettings=tmpsettings, myportdb=portdb):
185 raise AssertionError('digest creation failed for %s' % ebuild_path)
187 def _create_binpkgs(self, binpkgs):
188 for cpv, metadata in binpkgs.items():
189 a = Atom("=" + cpv, allow_repo=True)
194 cat, pf = catsplit(a.cpv)
195 metadata = metadata.copy()
196 metadata.setdefault("SLOT", "0")
197 metadata.setdefault("KEYWORDS", "x86")
198 metadata.setdefault("BUILD_TIME", "0")
199 metadata["repository"] = repo
200 metadata["CATEGORY"] = cat
203 repo_dir = self.pkgdir
204 category_dir = os.path.join(repo_dir, cat)
205 binpkg_path = os.path.join(category_dir, pf + ".tbz2")
206 ensure_dirs(category_dir)
207 t = portage.xpak.tbz2(binpkg_path)
208 t.recompose_mem(portage.xpak.xpak_mem(metadata))
210 def _create_installed(self, installed):
211 for cpv in installed:
212 a = Atom("=" + cpv, allow_repo=True)
217 vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
219 os.makedirs(vdb_pkg_dir)
223 metadata = installed[cpv].copy()
224 metadata.setdefault("SLOT", "0")
225 metadata.setdefault("BUILD_TIME", "0")
226 metadata.setdefault("COUNTER", "0")
227 metadata.setdefault("KEYWORDS", "~x86")
229 unknown_keys = set(metadata).difference(
230 portage.dbapi.dbapi._known_keys)
231 unknown_keys.discard("BUILD_TIME")
232 unknown_keys.discard("COUNTER")
233 unknown_keys.discard("repository")
234 unknown_keys.discard("USE")
236 raise ValueError("metadata of installed '%s' contains unknown keys: %s" %
237 (cpv, sorted(unknown_keys)))
239 metadata["repository"] = repo
240 for k, v in metadata.items():
241 with open(os.path.join(vdb_pkg_dir, k), "w") as f:
244 def _create_profile(self, ebuilds, installed, profile, repo_configs, user_config, sets):
246 user_config_dir = os.path.join(self.eroot, USER_CONFIG_PATH)
249 os.makedirs(user_config_dir)
253 for repo in self._repositories:
254 if repo == "DEFAULT":
257 repo_dir = self._get_repo_dir(repo)
258 profile_dir = os.path.join(repo_dir, "profiles")
259 metadata_dir = os.path.join(repo_dir, "metadata")
260 os.makedirs(metadata_dir)
262 #Create $REPO/profiles/categories
265 ebuilds_repo = Atom("="+cpv, allow_repo=True).repo
266 if ebuilds_repo is None:
267 ebuilds_repo = "test_repo"
268 if ebuilds_repo == repo:
269 categories.add(catsplit(cpv)[0])
271 categories_file = os.path.join(profile_dir, "categories")
272 with open(categories_file, "w") as f:
273 for cat in categories:
276 #Create $REPO/profiles/license_groups
277 license_file = os.path.join(profile_dir, "license_groups")
278 with open(license_file, "w") as f:
279 f.write("EULA TEST\n")
281 repo_config = repo_configs.get(repo)
283 for config_file, lines in repo_config.items():
284 if config_file not in self.config_files and not any(fnmatch.fnmatch(config_file, os.path.join(x, "*")) for x in self.config_files):
285 raise ValueError("Unknown config file: '%s'" % config_file)
287 if config_file in ("layout.conf",):
288 file_name = os.path.join(repo_dir, "metadata", config_file)
290 file_name = os.path.join(profile_dir, config_file)
291 if "/" in config_file and not os.path.isdir(os.path.dirname(file_name)):
292 os.makedirs(os.path.dirname(file_name))
293 with open(file_name, "w") as f:
295 f.write("%s\n" % line)
296 # Temporarily write empty value of masters until it becomes default.
297 # TODO: Delete all references to "# use implicit masters" when empty value becomes default.
298 if config_file == "layout.conf" and not any(line.startswith(("masters =", "# use implicit masters")) for line in lines):
299 f.write("masters =\n")
301 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
302 os.makedirs(os.path.join(repo_dir, "eclass"))
304 # Temporarily write empty value of masters until it becomes default.
305 if not repo_config or "layout.conf" not in repo_config:
306 layout_conf_path = os.path.join(repo_dir, "metadata", "layout.conf")
307 with open(layout_conf_path, "w") as f:
308 f.write("masters =\n")
310 if repo == "test_repo":
311 #Create a minimal profile in /usr/portage
312 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
313 os.makedirs(sub_profile_dir)
315 if not (profile and "eapi" in profile):
316 eapi_file = os.path.join(sub_profile_dir, "eapi")
317 with open(eapi_file, "w") as f:
320 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
321 with open(make_defaults_file, "w") as f:
322 f.write("ARCH=\"x86\"\n")
323 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
325 use_force_file = os.path.join(sub_profile_dir, "use.force")
326 with open(use_force_file, "w") as f:
329 parent_file = os.path.join(sub_profile_dir, "parent")
330 with open(parent_file, "w") as f:
334 for config_file, lines in profile.items():
335 if config_file not in self.config_files:
336 raise ValueError("Unknown config file: '%s'" % config_file)
338 file_name = os.path.join(sub_profile_dir, config_file)
339 with open(file_name, "w") as f:
341 f.write("%s\n" % line)
343 #Create profile symlink
344 os.symlink(sub_profile_dir, os.path.join(user_config_dir, "make.profile"))
346 #Create minimal herds.xml
347 herds_xml = """<?xml version="1.0" encoding="UTF-8"?>
348 <!DOCTYPE herds SYSTEM "http://www.gentoo.org/dtd/herds.dtd">
349 <?xml-stylesheet href="/xsl/herds.xsl" type="text/xsl" ?>
350 <?xml-stylesheet href="/xsl/guide.xsl" type="text/xsl" ?>
353 <name>base-system</name>
354 <email>base-system@gentoo.org</email>
355 <description>Core system utilities and libraries.</description>
357 <email>base-system@gentoo.orgg</email>
358 <name>Base System</name>
359 <role>Base System Maintainer</role>
364 with open(os.path.join(metadata_dir, "metadata.xml"), 'w') as f:
368 "ACCEPT_KEYWORDS": "x86",
370 "DISTDIR" : self.distdir,
371 "EMERGE_WARNING_DELAY": "0",
372 "PKGDIR": self.pkgdir,
373 "PORTAGE_INST_GID": str(portage.data.portage_gid),
374 "PORTAGE_INST_UID": str(portage.data.portage_uid),
375 "PORTAGE_TMPDIR": os.path.join(self.eroot, 'var/tmp'),
378 if os.environ.get("NOCOLOR"):
379 make_conf["NOCOLOR"] = os.environ["NOCOLOR"]
381 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
382 # need to be inherited by ebuild subprocesses.
383 if 'PORTAGE_USERNAME' in os.environ:
384 make_conf['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
385 if 'PORTAGE_GRPNAME' in os.environ:
386 make_conf['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
389 for k_v in make_conf.items():
390 make_conf_lines.append('%s="%s"' % k_v)
392 if "make.conf" in user_config:
393 make_conf_lines.extend(user_config["make.conf"])
395 if not portage.process.sandbox_capable or \
396 os.environ.get("SANDBOX_ON") == "1":
397 # avoid problems from nested sandbox instances
398 make_conf_lines.append('FEATURES="${FEATURES} -sandbox -usersandbox"')
400 configs = user_config.copy()
401 configs["make.conf"] = make_conf_lines
403 for config_file, lines in configs.items():
404 if config_file not in self.config_files:
405 raise ValueError("Unknown config file: '%s'" % config_file)
407 file_name = os.path.join(user_config_dir, config_file)
408 with open(file_name, "w") as f:
410 f.write("%s\n" % line)
412 #Create /usr/share/portage/config/make.globals
413 make_globals_path = os.path.join(self.eroot,
414 GLOBAL_CONFIG_PATH.lstrip(os.sep), "make.globals")
415 ensure_dirs(os.path.dirname(make_globals_path))
416 os.symlink(os.path.join(PORTAGE_BASE_PATH, "cnf", "make.globals"),
419 #Create /usr/share/portage/config/sets/portage.conf
420 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
423 os.makedirs(default_sets_conf_dir)
427 provided_sets_portage_conf = \
428 os.path.join(PORTAGE_BASE_PATH, "cnf/sets/portage.conf")
429 os.symlink(provided_sets_portage_conf, os.path.join(default_sets_conf_dir, "portage.conf"))
431 set_config_dir = os.path.join(user_config_dir, "sets")
434 os.makedirs(set_config_dir)
438 for sets_file, lines in sets.items():
439 file_name = os.path.join(set_config_dir, sets_file)
440 with open(file_name, "w") as f:
442 f.write("%s\n" % line)
444 def _create_world(self, world, world_sets):
445 #Create /var/lib/portage/world
446 var_lib_portage = os.path.join(self.eroot, "var", "lib", "portage")
447 os.makedirs(var_lib_portage)
449 world_file = os.path.join(var_lib_portage, "world")
450 world_set_file = os.path.join(var_lib_portage, "world_sets")
452 with open(world_file, "w") as f:
454 f.write("%s\n" % atom)
456 with open(world_set_file, "w") as f:
457 for atom in world_sets:
458 f.write("%s\n" % atom)
460 def _load_config(self):
462 create_trees_kwargs = {}
463 if self.target_root != os.sep:
464 create_trees_kwargs["target_root"] = self.target_root
467 "PORTAGE_REPOSITORIES": "\n".join("[%s]\n%s" % (repo_name, "\n".join("%s = %s" % (k, v) for k, v in repo_config.items())) for repo_name, repo_config in self._repositories.items())
470 trees = portage.create_trees(env=env, eprefix=self.eprefix,
471 **create_trees_kwargs)
473 for root, root_trees in trees.items():
474 settings = root_trees["vartree"].settings
475 settings._init_dirs()
476 setconfig = load_default_config(settings, root_trees)
477 root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
479 return trees[trees._target_eroot]["vartree"].settings, trees
481 def run(self, atoms, options={}, action=None):
482 options = options.copy()
483 options["--pretend"] = True
485 options["--debug"] = True
488 if options.get("--depclean"):
490 elif options.get("--prune"):
493 if "--usepkgonly" in options:
494 options["--usepkg"] = True
496 global_noiselimit = portage.util.noiselimit
497 global_emergelog_disable = _emerge.emergelog._disable
501 portage.util.noiselimit = -2
502 _emerge.emergelog._disable = True
504 if action in ("depclean", "prune"):
505 rval, cleanlist, ordered, req_pkg_count = \
506 calc_depclean(self.settings, self.trees, None,
507 options, action, InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
508 result = ResolverPlaygroundDepcleanResult(
509 atoms, rval, cleanlist, ordered, req_pkg_count)
511 params = create_depgraph_params(options, action)
512 success, depgraph, favorites = backtrack_depgraph(
513 self.settings, self.trees, options, params, action, atoms, None)
514 depgraph._show_merge_list()
515 depgraph.display_problems()
516 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
518 portage.util.noiselimit = global_noiselimit
519 _emerge.emergelog._disable = global_emergelog_disable
523 def run_TestCase(self, test_case):
524 if not isinstance(test_case, ResolverPlaygroundTestCase):
525 raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
526 for atoms in test_case.requests:
527 result = self.run(atoms, test_case.options, test_case.action)
528 if not test_case.compare_with_result(result):
532 for eroot in self.trees:
533 portdb = self.trees[eroot]["porttree"].dbapi
534 portdb.close_caches()
536 print("\nEROOT=%s" % self.eroot)
538 shutil.rmtree(self.eroot)
540 class ResolverPlaygroundTestCase(object):
542 def __init__(self, request, **kwargs):
543 self.all_permutations = kwargs.pop("all_permutations", False)
544 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
545 self.ambiguous_merge_order = kwargs.pop("ambiguous_merge_order", False)
546 self.check_repo_names = kwargs.pop("check_repo_names", False)
547 self.merge_order_assertions = kwargs.pop("merge_order_assertions", False)
549 if self.all_permutations:
550 self.requests = list(permutations(request))
552 self.requests = [request]
554 self.options = kwargs.pop("options", {})
555 self.action = kwargs.pop("action", None)
556 self.test_success = True
558 self._checks = kwargs.copy()
560 def compare_with_result(self, result):
561 checks = dict.fromkeys(result.checks)
562 for key, value in self._checks.items():
563 if not key in checks:
564 raise KeyError("Not an available check: '%s'" % key)
568 for key, value in checks.items():
569 got = getattr(result, key)
572 if key in result.optional_checks and expected is None:
575 if key == "mergelist":
576 if not self.check_repo_names:
577 #Strip repo names if we don't check them
584 new_got.append(cpv.split(_repo_separator)[0])
589 if isinstance(obj, basestring):
591 new_expected.append(obj)
594 obj.split(_repo_separator)[0])
596 new_expected.append(set())
599 cpv = cpv.split(_repo_separator)[0]
600 new_expected[-1].add(cpv)
601 expected = new_expected
602 if self.ignore_mergelist_order and got is not None:
604 expected = set(expected)
606 if self.ambiguous_merge_order and got:
607 expected_stack = list(reversed(expected))
608 got_stack = list(reversed(got))
611 while got_stack and expected_stack:
612 got_token = got_stack.pop()
613 expected_obj = expected_stack.pop()
614 if isinstance(expected_obj, basestring):
615 new_expected.append(expected_obj)
616 if got_token == expected_obj:
618 # result doesn't match, so stop early
621 expected_obj = set(expected_obj)
623 expected_obj.remove(got_token)
625 # result doesn't match, so stop early
628 new_expected.append(got_token)
629 while got_stack and expected_obj:
630 got_token = got_stack.pop()
632 expected_obj.remove(got_token)
636 new_expected.append(got_token)
638 # result doesn't match, so stop early
641 # result does not match, so stop early
643 new_expected.append(tuple(expected_obj))
646 # result does not match, add leftovers to new_expected
648 expected_stack.reverse()
649 new_expected.extend(expected_stack)
650 expected = new_expected
652 if match and self.merge_order_assertions:
653 for node1, node2 in self.merge_order_assertions:
654 if not (got.index(node1) < got.index(node2)):
655 fail_msgs.append("atoms: (" + \
656 ", ".join(result.atoms) + "), key: " + \
657 ("merge_order_assertions, expected: %s" % \
658 str((node1, node2))) + \
659 ", got: " + str(got))
661 elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None:
662 expected = set(expected)
665 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
666 key + ", expected: " + str(expected) + ", got: " + str(got))
668 self.test_success = False
669 self.fail_msg = "\n".join(fail_msgs)
673 class ResolverPlaygroundResult(object):
676 "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions",
677 "circular_dependency_solutions", "needed_p_mask_changes",
682 def __init__(self, atoms, success, mydepgraph, favorites):
684 self.success = success
685 self.depgraph = mydepgraph
686 self.favorites = favorites
687 self.mergelist = None
688 self.use_changes = None
689 self.license_changes = None
690 self.unstable_keywords = None
691 self.needed_p_mask_changes = None
692 self.slot_collision_solutions = None
693 self.circular_dependency_solutions = None
695 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
697 host_root = self.depgraph._frozen_config._running_root.root
698 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
699 if isinstance(x, Blocker):
700 self.mergelist.append(x.atom)
703 if x.repo != "test_repo":
704 repo_str = _repo_separator + x.repo
705 mergelist_str = x.cpv + repo_str
707 if x.operation == "merge":
711 mergelist_str = "[%s]%s" % (desc, mergelist_str)
712 if x.root != host_root:
713 mergelist_str += "{targetroot}"
714 self.mergelist.append(mergelist_str)
716 if self.depgraph._dynamic_config._needed_use_config_changes:
717 self.use_changes = {}
718 for pkg, needed_use_config_changes in \
719 self.depgraph._dynamic_config._needed_use_config_changes.items():
720 new_use, changes = needed_use_config_changes
721 self.use_changes[pkg.cpv] = changes
723 if self.depgraph._dynamic_config._needed_unstable_keywords:
724 self.unstable_keywords = set()
725 for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
726 self.unstable_keywords.add(pkg.cpv)
728 if self.depgraph._dynamic_config._needed_p_mask_changes:
729 self.needed_p_mask_changes = set()
730 for pkg in self.depgraph._dynamic_config._needed_p_mask_changes:
731 self.needed_p_mask_changes.add(pkg.cpv)
733 if self.depgraph._dynamic_config._needed_license_changes:
734 self.license_changes = {}
735 for pkg, missing_licenses in self.depgraph._dynamic_config._needed_license_changes.items():
736 self.license_changes[pkg.cpv] = missing_licenses
738 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
739 self.slot_collision_solutions = []
740 handler = self.depgraph._dynamic_config._slot_conflict_handler
742 for change in handler.changes:
745 new_change[pkg.cpv] = change[pkg]
746 self.slot_collision_solutions.append(new_change)
748 if self.depgraph._dynamic_config._circular_dependency_handler is not None:
749 handler = self.depgraph._dynamic_config._circular_dependency_handler
750 sol = handler.solutions
751 self.circular_dependency_solutions = dict(zip([x.cpv for x in sol.keys()], sol.values()))
753 class ResolverPlaygroundDepcleanResult(object):
756 "success", "cleanlist", "ordered", "req_pkg_count",
759 "ordered", "req_pkg_count",
762 def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
764 self.success = rval == 0
765 self.cleanlist = cleanlist
766 self.ordered = ordered
767 self.req_pkg_count = req_pkg_count