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 PORTAGE_BASE_PATH
11 from portage.dbapi.vartree import vartree
12 from portage.dbapi.porttree import portagetree
13 from portage.dbapi.bintree import binarytree
14 from portage.dep import Atom, _repo_separator
15 from portage.package.ebuild.config import config
16 from portage.package.ebuild.digestgen import digestgen
17 from portage._sets import load_default_config
18 from portage._sets.base import InternalPackageSet
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(("package.use", "package.mask", "package.keywords", \
39 "package.unmask", "package.properties", "package.license", "use.mask", "use.force"))
41 def __init__(self, ebuilds={}, installed={}, profile={}, repo_configs={}, \
42 user_config={}, sets={}, world=[], debug=False):
44 ebuilds: cpv -> metadata mapping simulating available ebuilds.
45 installed: cpv -> metadata mapping simulating installed packages.
46 If a metadata key is missing, it gets a default value.
47 profile: settings defined by the profile.
51 self.eprefix = tempfile.mkdtemp()
52 self.eroot = self.root + self.eprefix.lstrip(os.sep) + os.sep
53 self.portdir = os.path.join(self.eroot, "usr/portage")
54 self.vdbdir = os.path.join(self.eroot, "var/db/pkg")
55 os.makedirs(self.portdir)
56 os.makedirs(self.vdbdir)
59 portage.util.noiselimit = -2
62 #Make sure the main repo is always created
63 self._get_repo_dir("test_repo")
65 self._create_ebuilds(ebuilds)
66 self._create_installed(installed)
67 self._create_profile(ebuilds, installed, profile, repo_configs, user_config, sets)
68 self._create_world(world)
70 self.settings, self.trees = self._load_config()
72 self._create_ebuild_manifests(ebuilds)
74 portage.util.noiselimit = 0
76 def _get_repo_dir(self, repo):
78 Create the repo directory if needed.
80 if repo not in self.repo_dirs:
81 if repo == "test_repo":
82 repo_path = self.portdir
84 repo_path = os.path.join(self.eroot, "usr", "local", repo)
86 self.repo_dirs[repo] = repo_path
87 profile_path = os.path.join(repo_path, "profiles")
90 os.makedirs(profile_path)
94 repo_name_file = os.path.join(profile_path, "repo_name")
95 f = open(repo_name_file, "w")
96 f.write("%s\n" % repo)
99 return self.repo_dirs[repo]
101 def _create_ebuilds(self, ebuilds):
103 a = Atom("=" + cpv, allow_repo=True)
108 metadata = ebuilds[cpv].copy()
109 eapi = metadata.pop("EAPI", 0)
110 lic = metadata.pop("LICENSE", "")
111 properties = metadata.pop("PROPERTIES", "")
112 slot = metadata.pop("SLOT", 0)
113 keywords = metadata.pop("KEYWORDS", "x86")
114 iuse = metadata.pop("IUSE", "")
115 depend = metadata.pop("DEPEND", "")
116 rdepend = metadata.pop("RDEPEND", None)
117 pdepend = metadata.pop("PDEPEND", None)
118 required_use = metadata.pop("REQUIRED_USE", None)
121 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
123 repo_dir = self._get_repo_dir(repo)
124 ebuild_dir = os.path.join(repo_dir, a.cp)
125 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
127 os.makedirs(ebuild_dir)
131 f = open(ebuild_path, "w")
132 f.write('EAPI="' + str(eapi) + '"\n')
133 f.write('LICENSE="' + str(lic) + '"\n')
134 f.write('PROPERTIES="' + str(properties) + '"\n')
135 f.write('SLOT="' + str(slot) + '"\n')
136 f.write('KEYWORDS="' + str(keywords) + '"\n')
137 f.write('IUSE="' + str(iuse) + '"\n')
138 f.write('DEPEND="' + str(depend) + '"\n')
139 if rdepend is not None:
140 f.write('RDEPEND="' + str(rdepend) + '"\n')
141 if pdepend is not None:
142 f.write('PDEPEND="' + str(pdepend) + '"\n')
143 if required_use is not None:
144 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
147 def _create_ebuild_manifests(self, ebuilds):
148 tmpsettings = config(clone=self.settings)
149 tmpsettings['PORTAGE_QUIET'] = '1'
151 a = Atom("=" + cpv, allow_repo=True)
156 repo_dir = self._get_repo_dir(repo)
157 ebuild_dir = os.path.join(repo_dir, a.cp)
158 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
160 portdb = self.trees[self.root]["porttree"].dbapi
161 tmpsettings['O'] = ebuild_dir
162 if not digestgen(mysettings=tmpsettings, myportdb=portdb):
163 raise AssertionError('digest creation failed for %s' % ebuild_path)
165 def _create_installed(self, installed):
166 for cpv in installed:
167 a = Atom("=" + cpv, allow_repo=True)
172 vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
174 os.makedirs(vdb_pkg_dir)
178 metadata = installed[cpv].copy()
179 eapi = metadata.pop("EAPI", 0)
180 lic = metadata.pop("LICENSE", "")
181 properties = metadata.pop("PROPERTIES", "")
182 slot = metadata.pop("SLOT", 0)
183 keywords = metadata.pop("KEYWORDS", "~x86")
184 iuse = metadata.pop("IUSE", "")
185 use = metadata.pop("USE", "")
186 depend = metadata.pop("DEPEND", "")
187 rdepend = metadata.pop("RDEPEND", None)
188 pdepend = metadata.pop("PDEPEND", None)
189 required_use = metadata.pop("REQUIRED_USE", None)
192 raise ValueError("metadata of installed '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
194 def write_key(key, value):
195 f = open(os.path.join(vdb_pkg_dir, key), "w")
196 f.write(str(value) + "\n")
199 write_key("EAPI", eapi)
200 write_key("LICENSE", lic)
201 write_key("PROPERTIES", properties)
202 write_key("SLOT", slot)
203 write_key("LICENSE", lic)
204 write_key("PROPERTIES", properties)
205 write_key("repository", repo)
206 write_key("KEYWORDS", keywords)
207 write_key("IUSE", iuse)
208 write_key("USE", use)
209 write_key("DEPEND", depend)
210 if rdepend is not None:
211 write_key("RDEPEND", rdepend)
212 if pdepend is not None:
213 write_key("PDEPEND", pdepend)
214 if required_use is not None:
215 write_key("REQUIRED_USE", required_use)
217 def _create_profile(self, ebuilds, installed, profile, repo_configs, user_config, sets):
219 for repo in self.repo_dirs:
220 repo_dir = self._get_repo_dir(repo)
221 profile_dir = os.path.join(self._get_repo_dir(repo), "profiles")
223 #Create $REPO/profiles/categories
226 ebuilds_repo = Atom("="+cpv, allow_repo=True).repo
227 if ebuilds_repo is None:
228 ebuilds_repo = "test_repo"
229 if ebuilds_repo == repo:
230 categories.add(catsplit(cpv)[0])
232 categories_file = os.path.join(profile_dir, "categories")
233 f = open(categories_file, "w")
234 for cat in categories:
238 #Create $REPO/profiles/license_groups
239 license_file = os.path.join(profile_dir, "license_groups")
240 f = open(license_file, "w")
241 f.write("EULA TEST\n")
244 repo_config = repo_configs.get(repo)
246 for config_file, lines in repo_config.items():
247 if config_file not in self.config_files:
248 raise ValueError("Unknown config file: '%s'" % config_file)
250 file_name = os.path.join(profile_dir, config_file)
251 f = open(file_name, "w")
253 f.write("%s\n" % line)
256 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
257 os.makedirs(os.path.join(repo_dir, "eclass"))
259 if repo == "test_repo":
260 #Create a minimal profile in /usr/portage
261 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
262 os.makedirs(sub_profile_dir)
264 eapi_file = os.path.join(sub_profile_dir, "eapi")
265 f = open(eapi_file, "w")
269 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
270 f = open(make_defaults_file, "w")
271 f.write("ARCH=\"x86\"\n")
272 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
275 use_force_file = os.path.join(sub_profile_dir, "use.force")
276 f = open(use_force_file, "w")
281 for config_file, lines in profile.items():
282 if config_file not in self.config_files:
283 raise ValueError("Unknown config file: '%s'" % config_file)
285 file_name = os.path.join(sub_profile_dir, config_file)
286 f = open(file_name, "w")
288 f.write("%s\n" % line)
291 #Create profile symlink
292 os.makedirs(os.path.join(self.eroot, "etc"))
293 os.symlink(sub_profile_dir, os.path.join(self.eroot, "etc", "make.profile"))
295 user_config_dir = os.path.join(self.eroot, "etc", "portage")
298 os.makedirs(user_config_dir)
302 repos_conf_file = os.path.join(user_config_dir, "repos.conf")
303 f = open(repos_conf_file, "w")
305 for repo in sorted(self.repo_dirs.keys()):
306 f.write("[%s]\n" % repo)
307 f.write("LOCATION=%s\n" % self.repo_dirs[repo])
308 if repo == "test_repo":
309 f.write("PRIORITY=%s\n" % -1000)
311 f.write("PRIORITY=%s\n" % priority)
315 for config_file, lines in user_config.items():
316 if config_file not in self.config_files:
317 raise ValueError("Unknown config file: '%s'" % config_file)
319 file_name = os.path.join(user_config_dir, config_file)
320 f = open(file_name, "w")
322 f.write("%s\n" % line)
325 #Create /usr/share/portage/config/sets/portage.conf
326 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
329 os.makedirs(default_sets_conf_dir)
333 provided_sets_portage_conf = \
334 os.path.join(PORTAGE_BASE_PATH, "cnf/sets/portage.conf")
335 os.symlink(provided_sets_portage_conf, os.path.join(default_sets_conf_dir, "portage.conf"))
337 set_config_dir = os.path.join(user_config_dir, "sets")
340 os.makedirs(set_config_dir)
344 for sets_file, lines in sets.items():
345 file_name = os.path.join(set_config_dir, sets_file)
346 f = open(file_name, "w")
348 f.write("%s\n" % line)
351 user_config_dir = os.path.join(self.eroot, "etc", "portage")
354 os.makedirs(user_config_dir)
358 for config_file, lines in user_config.items():
359 if config_file not in self.config_files:
360 raise ValueError("Unknown config file: '%s'" % config_file)
362 file_name = os.path.join(user_config_dir, config_file)
363 f = open(file_name, "w")
365 f.write("%s\n" % line)
368 def _create_world(self, world):
369 #Create /var/lib/portage/world
370 var_lib_portage = os.path.join(self.eroot, "var", "lib", "portage")
371 os.makedirs(var_lib_portage)
373 world_file = os.path.join(var_lib_portage, "world")
375 f = open(world_file, "w")
377 f.write("%s\n" % atom)
380 def _load_config(self):
382 for repo_name in sorted(self.repo_dirs):
383 path = self.repo_dirs[repo_name]
384 if path != self.portdir:
385 portdir_overlay.append(path)
388 "ACCEPT_KEYWORDS": "x86",
389 "PORTDIR": self.portdir,
390 "PORTDIR_OVERLAY": " ".join(portdir_overlay),
391 'PORTAGE_TMPDIR' : os.path.join(self.eroot, 'var/tmp'),
394 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
395 # need to be inherited by ebuild subprocesses.
396 if 'PORTAGE_USERNAME' in os.environ:
397 env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
398 if 'PORTAGE_GRPNAME' in os.environ:
399 env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
401 settings = config(_eprefix=self.eprefix, env=env)
406 "vartree": vartree(settings=settings),
407 "porttree": portagetree(self.root, settings=settings),
408 "bintree": binarytree(self.root,
409 os.path.join(self.eroot, "usr/portage/packages"),
414 for root, root_trees in trees.items():
415 settings = root_trees["vartree"].settings
416 settings._init_dirs()
417 setconfig = load_default_config(settings, root_trees)
418 root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
420 return settings, trees
422 def run(self, atoms, options={}, action=None):
423 options = options.copy()
424 options["--pretend"] = True
426 options["--debug"] = True
428 global_noiselimit = portage.util.noiselimit
429 global_emergelog_disable = _emerge.emergelog._disable
433 portage.util.noiselimit = -2
434 _emerge.emergelog._disable = True
436 if options.get("--depclean"):
437 rval, cleanlist, ordered, req_pkg_count = \
438 calc_depclean(self.settings, self.trees, None,
439 options, "depclean", InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
440 result = ResolverPlaygroundDepcleanResult( \
441 atoms, rval, cleanlist, ordered, req_pkg_count)
443 params = create_depgraph_params(options, action)
444 success, depgraph, favorites = backtrack_depgraph(
445 self.settings, self.trees, options, params, action, atoms, None)
446 depgraph._show_merge_list()
447 depgraph.display_problems()
448 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
450 portage.util.noiselimit = global_noiselimit
451 _emerge.emergelog._disable = global_emergelog_disable
455 def run_TestCase(self, test_case):
456 if not isinstance(test_case, ResolverPlaygroundTestCase):
457 raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
458 for atoms in test_case.requests:
459 result = self.run(atoms, test_case.options, test_case.action)
460 if not test_case.compare_with_result(result):
464 portdb = self.trees[self.root]["porttree"].dbapi
465 portdb.close_caches()
466 portage.dbapi.porttree.portdbapi.portdbapi_instances.remove(portdb)
468 print("\nEROOT=%s" % self.eroot)
470 shutil.rmtree(self.eroot)
472 class ResolverPlaygroundTestCase(object):
474 def __init__(self, request, **kwargs):
475 self.all_permutations = kwargs.pop("all_permutations", False)
476 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
477 self.ambigous_merge_order = kwargs.pop("ambigous_merge_order", False)
478 self.check_repo_names = kwargs.pop("check_repo_names", False)
480 if self.all_permutations:
481 self.requests = list(permutations(request))
483 self.requests = [request]
485 self.options = kwargs.pop("options", {})
486 self.action = kwargs.pop("action", None)
487 self.test_success = True
489 self._checks = kwargs.copy()
491 def compare_with_result(self, result):
492 checks = dict.fromkeys(result.checks)
493 for key, value in self._checks.items():
494 if not key in checks:
495 raise KeyError("Not an available check: '%s'" % key)
499 for key, value in checks.items():
500 got = getattr(result, key)
503 if key in result.optional_checks and expected is None:
506 if key == "mergelist":
507 if not self.check_repo_names:
508 #Strip repo names if we don't check them
515 a = Atom("="+cpv, allow_repo=True)
516 new_got.append(a.cpv)
521 if isinstance(obj, basestring):
523 new_expected.append(obj)
525 a = Atom("="+obj, allow_repo=True)
526 new_expected.append(a.cpv)
528 new_expected.append(set())
530 a = Atom("="+cpv, allow_repo=True)
531 new_expected[-1].add(a.cpv)
532 expected = new_expected
533 if self.ignore_mergelist_order and got is not None:
535 expected = set(expected)
537 if self.ambigous_merge_order and got:
538 expected_stack = list(reversed(expected))
539 got_stack = list(reversed(got))
541 while got_stack and expected_stack:
542 got_token = got_stack.pop()
543 expected_obj = expected_stack.pop()
544 if isinstance(expected_obj, basestring):
545 new_expected.append(expected_obj)
546 if got_token == expected_obj:
548 # result doesn't match, so stop early
550 expected_obj = set(expected_obj)
552 expected_obj.remove(got_token)
554 # result doesn't match, so stop early
556 new_expected.append(got_token)
558 while got_stack and expected_obj:
559 got_token = got_stack.pop()
561 expected_obj.remove(got_token)
565 new_expected.append(got_token)
567 # result doesn't match, so stop early
569 expected = new_expected
571 elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None:
572 expected = set(expected)
575 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
576 key + ", expected: " + str(expected) + ", got: " + str(got))
578 self.test_success = False
579 self.fail_msg = "\n".join(fail_msgs)
583 class ResolverPlaygroundResult(object):
586 "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions",
587 "circular_dependency_solutions", "needed_p_mask_changes",
592 def __init__(self, atoms, success, mydepgraph, favorites):
594 self.success = success
595 self.depgraph = mydepgraph
596 self.favorites = favorites
597 self.mergelist = None
598 self.use_changes = None
599 self.license_changes = None
600 self.unstable_keywords = None
601 self.needed_p_mask_changes = None
602 self.slot_collision_solutions = None
603 self.circular_dependency_solutions = None
605 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
607 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
608 if isinstance(x, Blocker):
609 self.mergelist.append(x.atom)
612 if x.metadata["repository"] != "test_repo":
613 repo_str = _repo_separator + x.metadata["repository"]
614 self.mergelist.append(x.cpv + repo_str)
616 if self.depgraph._dynamic_config._needed_use_config_changes:
617 self.use_changes = {}
618 for pkg, needed_use_config_changes in \
619 self.depgraph._dynamic_config._needed_use_config_changes.items():
620 new_use, changes = needed_use_config_changes
621 self.use_changes[pkg.cpv] = changes
623 if self.depgraph._dynamic_config._needed_unstable_keywords:
624 self.unstable_keywords = set()
625 for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
626 self.unstable_keywords.add(pkg.cpv)
628 if self.depgraph._dynamic_config._needed_p_mask_changes:
629 self.needed_p_mask_changes = set()
630 for pkg in self.depgraph._dynamic_config._needed_p_mask_changes:
631 self.needed_p_mask_changes.add(pkg.cpv)
633 if self.depgraph._dynamic_config._needed_license_changes:
634 self.license_changes = {}
635 for pkg, missing_licenses in self.depgraph._dynamic_config._needed_license_changes.items():
636 self.license_changes[pkg.cpv] = missing_licenses
638 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
639 self.slot_collision_solutions = []
640 handler = self.depgraph._dynamic_config._slot_conflict_handler
642 for change in handler.changes:
645 new_change[pkg.cpv] = change[pkg]
646 self.slot_collision_solutions.append(new_change)
648 if self.depgraph._dynamic_config._circular_dependency_handler is not None:
649 handler = self.depgraph._dynamic_config._circular_dependency_handler
650 sol = handler.solutions
651 self.circular_dependency_solutions = dict( zip([x.cpv for x in sol.keys()], sol.values()) )
653 class ResolverPlaygroundDepcleanResult(object):
656 "success", "cleanlist", "ordered", "req_pkg_count",
659 "ordered", "req_pkg_count",
662 def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
664 self.success = rval == 0
665 self.cleanlist = cleanlist
666 self.ordered = ordered
667 self.req_pkg_count = req_pkg_count