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
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.util import ensure_dirs
20 from portage.versions import catsplit
23 from _emerge.actions import calc_depclean
24 from _emerge.Blocker import Blocker
25 from _emerge.create_depgraph_params import create_depgraph_params
26 from _emerge.depgraph import backtrack_depgraph
27 from _emerge.RootConfig import RootConfig
29 if sys.hexversion >= 0x3000000:
32 class ResolverPlayground(object):
34 This class helps to create the necessary files on disk and
35 the needed settings instances, etc. for the resolver to do
39 config_files = frozenset(("package.use", "package.mask", "package.keywords", \
40 "package.unmask", "package.properties", "package.license", "use.mask", "use.force"))
42 metadata_xml_template = """<?xml version="1.0" encoding="UTF-8"?>
43 <!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
47 <email>maintainer-needed@gentoo.org</email>
48 <description>Description of the maintainership</description>
50 <longdescription>Long description of the package</longdescription>
57 def __init__(self, ebuilds={}, installed={}, profile={}, repo_configs={}, \
58 user_config={}, sets={}, world=[], debug=False):
60 ebuilds: cpv -> metadata mapping simulating available ebuilds.
61 installed: cpv -> metadata mapping simulating installed packages.
62 If a metadata key is missing, it gets a default value.
63 profile: settings defined by the profile.
67 self.eprefix = tempfile.mkdtemp()
68 self.eroot = self.root + self.eprefix.lstrip(os.sep) + os.sep
69 self.portdir = os.path.join(self.eroot, "usr/portage")
70 self.vdbdir = os.path.join(self.eroot, "var/db/pkg")
71 os.makedirs(self.portdir)
72 os.makedirs(self.vdbdir)
75 portage.util.noiselimit = -2
78 #Make sure the main repo is always created
79 self._get_repo_dir("test_repo")
81 self._create_ebuilds(ebuilds)
82 self._create_installed(installed)
83 self._create_profile(ebuilds, installed, profile, repo_configs, user_config, sets)
84 self._create_world(world)
86 self.settings, self.trees = self._load_config()
88 self._create_ebuild_manifests(ebuilds)
90 portage.util.noiselimit = 0
92 def _get_repo_dir(self, repo):
94 Create the repo directory if needed.
96 if repo not in self.repo_dirs:
97 if repo == "test_repo":
98 repo_path = self.portdir
100 repo_path = os.path.join(self.eroot, "usr", "local", repo)
102 self.repo_dirs[repo] = repo_path
103 profile_path = os.path.join(repo_path, "profiles")
106 os.makedirs(profile_path)
110 repo_name_file = os.path.join(profile_path, "repo_name")
111 f = open(repo_name_file, "w")
112 f.write("%s\n" % repo)
115 return self.repo_dirs[repo]
117 def _create_ebuilds(self, ebuilds):
119 a = Atom("=" + cpv, allow_repo=True)
124 metadata = ebuilds[cpv].copy()
125 copyright_header = metadata.pop("COPYRIGHT_HEADER", None)
126 desc = metadata.pop("DESCRIPTION", None)
127 eapi = metadata.pop("EAPI", 0)
128 lic = metadata.pop("LICENSE", "")
129 properties = metadata.pop("PROPERTIES", "")
130 slot = metadata.pop("SLOT", 0)
131 keywords = metadata.pop("KEYWORDS", "x86")
132 homepage = metadata.pop("HOMEPAGE", None)
133 iuse = metadata.pop("IUSE", "")
134 depend = metadata.pop("DEPEND", "")
135 rdepend = metadata.pop("RDEPEND", None)
136 pdepend = metadata.pop("PDEPEND", None)
137 required_use = metadata.pop("REQUIRED_USE", None)
138 misc_content = metadata.pop("MISC_CONTENT", None)
141 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
143 repo_dir = self._get_repo_dir(repo)
144 ebuild_dir = os.path.join(repo_dir, a.cp)
145 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
147 os.makedirs(ebuild_dir)
151 f = open(ebuild_path, "w")
152 if copyright_header is not None:
153 f.write(copyright_header)
154 f.write('EAPI="' + str(eapi) + '"\n')
156 f.write('DESCRIPTION="%s"\n' % desc)
157 if homepage is not None:
158 f.write('HOMEPAGE="%s"\n' % homepage)
159 f.write('LICENSE="' + str(lic) + '"\n')
160 f.write('PROPERTIES="' + str(properties) + '"\n')
161 f.write('SLOT="' + str(slot) + '"\n')
162 f.write('KEYWORDS="' + str(keywords) + '"\n')
163 f.write('IUSE="' + str(iuse) + '"\n')
164 f.write('DEPEND="' + str(depend) + '"\n')
165 if rdepend is not None:
166 f.write('RDEPEND="' + str(rdepend) + '"\n')
167 if pdepend is not None:
168 f.write('PDEPEND="' + str(pdepend) + '"\n')
169 if required_use is not None:
170 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
171 if misc_content is not None:
172 f.write(misc_content)
175 def _create_ebuild_manifests(self, ebuilds):
176 tmpsettings = config(clone=self.settings)
177 tmpsettings['PORTAGE_QUIET'] = '1'
179 a = Atom("=" + cpv, allow_repo=True)
184 repo_dir = self._get_repo_dir(repo)
185 ebuild_dir = os.path.join(repo_dir, a.cp)
186 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
188 portdb = self.trees[self.root]["porttree"].dbapi
189 tmpsettings['O'] = ebuild_dir
190 if not digestgen(mysettings=tmpsettings, myportdb=portdb):
191 raise AssertionError('digest creation failed for %s' % ebuild_path)
193 def _create_installed(self, installed):
194 for cpv in installed:
195 a = Atom("=" + cpv, allow_repo=True)
200 vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
202 os.makedirs(vdb_pkg_dir)
206 metadata = installed[cpv].copy()
207 eapi = metadata.pop("EAPI", 0)
208 lic = metadata.pop("LICENSE", "")
209 properties = metadata.pop("PROPERTIES", "")
210 slot = metadata.pop("SLOT", 0)
211 keywords = metadata.pop("KEYWORDS", "~x86")
212 iuse = metadata.pop("IUSE", "")
213 use = metadata.pop("USE", "")
214 depend = metadata.pop("DEPEND", "")
215 rdepend = metadata.pop("RDEPEND", None)
216 pdepend = metadata.pop("PDEPEND", None)
217 required_use = metadata.pop("REQUIRED_USE", None)
220 raise ValueError("metadata of installed '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
222 def write_key(key, value):
223 f = open(os.path.join(vdb_pkg_dir, key), "w")
224 f.write(str(value) + "\n")
227 write_key("EAPI", eapi)
228 write_key("COUNTER", "0")
229 write_key("LICENSE", lic)
230 write_key("PROPERTIES", properties)
231 write_key("SLOT", slot)
232 write_key("LICENSE", lic)
233 write_key("PROPERTIES", properties)
234 write_key("repository", repo)
235 write_key("KEYWORDS", keywords)
236 write_key("IUSE", iuse)
237 write_key("USE", use)
238 write_key("DEPEND", depend)
239 if rdepend is not None:
240 write_key("RDEPEND", rdepend)
241 if pdepend is not None:
242 write_key("PDEPEND", pdepend)
243 if required_use is not None:
244 write_key("REQUIRED_USE", required_use)
246 def _create_profile(self, ebuilds, installed, profile, repo_configs, user_config, sets):
248 for repo in self.repo_dirs:
249 repo_dir = self._get_repo_dir(repo)
250 profile_dir = os.path.join(self._get_repo_dir(repo), "profiles")
252 #Create $REPO/profiles/categories
255 ebuilds_repo = Atom("="+cpv, allow_repo=True).repo
256 if ebuilds_repo is None:
257 ebuilds_repo = "test_repo"
258 if ebuilds_repo == repo:
259 categories.add(catsplit(cpv)[0])
261 categories_file = os.path.join(profile_dir, "categories")
262 f = open(categories_file, "w")
263 for cat in categories:
267 #Create $REPO/profiles/license_groups
268 license_file = os.path.join(profile_dir, "license_groups")
269 f = open(license_file, "w")
270 f.write("EULA TEST\n")
273 repo_config = repo_configs.get(repo)
275 for config_file, lines in repo_config.items():
276 if config_file not in self.config_files:
277 raise ValueError("Unknown config file: '%s'" % config_file)
279 file_name = os.path.join(profile_dir, config_file)
280 f = open(file_name, "w")
282 f.write("%s\n" % line)
285 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
286 os.makedirs(os.path.join(repo_dir, "eclass"))
288 if repo == "test_repo":
289 #Create a minimal profile in /usr/portage
290 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
291 os.makedirs(sub_profile_dir)
293 eapi_file = os.path.join(sub_profile_dir, "eapi")
294 f = open(eapi_file, "w")
298 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
299 f = open(make_defaults_file, "w")
300 f.write("ARCH=\"x86\"\n")
301 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
304 use_force_file = os.path.join(sub_profile_dir, "use.force")
305 f = open(use_force_file, "w")
309 parent_file = os.path.join(sub_profile_dir, "parent")
310 f = open(parent_file, "w")
315 for config_file, lines in profile.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(sub_profile_dir, config_file)
320 f = open(file_name, "w")
322 f.write("%s\n" % line)
325 #Create profile symlink
326 os.makedirs(os.path.join(self.eroot, "etc"))
327 os.symlink(sub_profile_dir, os.path.join(self.eroot, "etc", "make.profile"))
329 #Create minimal herds.xml
330 metadata_dir = os.path.join(repo_dir, "metadata")
331 os.makedirs(metadata_dir)
332 herds_xml = """<?xml version="1.0" encoding="UTF-8"?>
333 <!DOCTYPE herds SYSTEM "http://www.gentoo.org/dtd/herds.dtd">
334 <?xml-stylesheet href="/xsl/herds.xsl" type="text/xsl" ?>
335 <?xml-stylesheet href="/xsl/guide.xsl" type="text/xsl" ?>
338 <name>base-system</name>
339 <email>base-system@gentoo.org</email>
340 <description>Core system utilities and libraries.</description>
342 <email>base-system@gentoo.orgg</email>
343 <name>Base System</name>
344 <role>Base System Maintainer</role>
349 with open(os.path.join(metadata_dir, "metadata.xml"), 'w') as f:
352 user_config_dir = os.path.join(self.eroot, "etc", "portage")
355 os.makedirs(user_config_dir)
359 repos_conf_file = os.path.join(user_config_dir, "repos.conf")
360 f = open(repos_conf_file, "w")
362 for repo in sorted(self.repo_dirs.keys()):
363 f.write("[%s]\n" % repo)
364 f.write("LOCATION=%s\n" % self.repo_dirs[repo])
365 if repo == "test_repo":
366 f.write("PRIORITY=%s\n" % -1000)
368 f.write("PRIORITY=%s\n" % priority)
372 for config_file, lines in user_config.items():
373 if config_file not in self.config_files:
374 raise ValueError("Unknown config file: '%s'" % config_file)
376 file_name = os.path.join(user_config_dir, config_file)
377 f = open(file_name, "w")
379 f.write("%s\n" % line)
382 #Create /usr/share/portage/config/make.globals
383 make_globals_path = os.path.join(self.eroot,
384 GLOBAL_CONFIG_PATH.lstrip(os.sep), "make.globals")
385 ensure_dirs(os.path.dirname(make_globals_path))
386 os.symlink(os.path.join(PORTAGE_BASE_PATH, "cnf", "make.globals"),
389 #Create /usr/share/portage/config/sets/portage.conf
390 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
393 os.makedirs(default_sets_conf_dir)
397 provided_sets_portage_conf = \
398 os.path.join(PORTAGE_BASE_PATH, "cnf/sets/portage.conf")
399 os.symlink(provided_sets_portage_conf, os.path.join(default_sets_conf_dir, "portage.conf"))
401 set_config_dir = os.path.join(user_config_dir, "sets")
404 os.makedirs(set_config_dir)
408 for sets_file, lines in sets.items():
409 file_name = os.path.join(set_config_dir, sets_file)
410 f = open(file_name, "w")
412 f.write("%s\n" % line)
415 user_config_dir = os.path.join(self.eroot, "etc", "portage")
418 os.makedirs(user_config_dir)
422 for config_file, lines in user_config.items():
423 if config_file not in self.config_files:
424 raise ValueError("Unknown config file: '%s'" % config_file)
426 file_name = os.path.join(user_config_dir, config_file)
427 f = open(file_name, "w")
429 f.write("%s\n" % line)
432 def _create_world(self, world):
433 #Create /var/lib/portage/world
434 var_lib_portage = os.path.join(self.eroot, "var", "lib", "portage")
435 os.makedirs(var_lib_portage)
437 world_file = os.path.join(var_lib_portage, "world")
439 f = open(world_file, "w")
441 f.write("%s\n" % atom)
444 def _load_config(self):
446 for repo_name in sorted(self.repo_dirs):
447 path = self.repo_dirs[repo_name]
448 if path != self.portdir:
449 portdir_overlay.append(path)
452 "ACCEPT_KEYWORDS": "x86",
453 "PORTDIR": self.portdir,
454 "PORTDIR_OVERLAY": " ".join(portdir_overlay),
455 'PORTAGE_TMPDIR' : os.path.join(self.eroot, 'var/tmp'),
458 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
459 # need to be inherited by ebuild subprocesses.
460 if 'PORTAGE_USERNAME' in os.environ:
461 env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
462 if 'PORTAGE_GRPNAME' in os.environ:
463 env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
465 settings = config(_eprefix=self.eprefix, env=env)
470 "vartree": vartree(settings=settings),
471 "porttree": portagetree(self.root, settings=settings),
472 "bintree": binarytree(self.root,
473 os.path.join(self.eroot, "usr/portage/packages"),
478 for root, root_trees in trees.items():
479 settings = root_trees["vartree"].settings
480 settings._init_dirs()
481 setconfig = load_default_config(settings, root_trees)
482 root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
484 return settings, trees
486 def run(self, atoms, options={}, action=None):
487 options = options.copy()
488 options["--pretend"] = True
490 options["--debug"] = True
492 global_noiselimit = portage.util.noiselimit
493 global_emergelog_disable = _emerge.emergelog._disable
497 portage.util.noiselimit = -2
498 _emerge.emergelog._disable = True
500 if options.get("--depclean"):
501 rval, cleanlist, ordered, req_pkg_count = \
502 calc_depclean(self.settings, self.trees, None,
503 options, "depclean", InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
504 result = ResolverPlaygroundDepcleanResult( \
505 atoms, rval, cleanlist, ordered, req_pkg_count)
507 params = create_depgraph_params(options, action)
508 success, depgraph, favorites = backtrack_depgraph(
509 self.settings, self.trees, options, params, action, atoms, None)
510 depgraph._show_merge_list()
511 depgraph.display_problems()
512 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
514 portage.util.noiselimit = global_noiselimit
515 _emerge.emergelog._disable = global_emergelog_disable
519 def run_TestCase(self, test_case):
520 if not isinstance(test_case, ResolverPlaygroundTestCase):
521 raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
522 for atoms in test_case.requests:
523 result = self.run(atoms, test_case.options, test_case.action)
524 if not test_case.compare_with_result(result):
528 portdb = self.trees[self.root]["porttree"].dbapi
529 portdb.close_caches()
530 portage.dbapi.porttree.portdbapi.portdbapi_instances.remove(portdb)
532 print("\nEROOT=%s" % self.eroot)
534 shutil.rmtree(self.eroot)
536 class ResolverPlaygroundTestCase(object):
538 def __init__(self, request, **kwargs):
539 self.all_permutations = kwargs.pop("all_permutations", False)
540 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
541 self.ambiguous_merge_order = kwargs.pop("ambiguous_merge_order", False)
542 self.check_repo_names = kwargs.pop("check_repo_names", False)
543 self.merge_order_assertions = kwargs.pop("merge_order_assertions", False)
545 if self.all_permutations:
546 self.requests = list(permutations(request))
548 self.requests = [request]
550 self.options = kwargs.pop("options", {})
551 self.action = kwargs.pop("action", None)
552 self.test_success = True
554 self._checks = kwargs.copy()
556 def compare_with_result(self, result):
557 checks = dict.fromkeys(result.checks)
558 for key, value in self._checks.items():
559 if not key in checks:
560 raise KeyError("Not an available check: '%s'" % key)
564 for key, value in checks.items():
565 got = getattr(result, key)
568 if key in result.optional_checks and expected is None:
571 if key == "mergelist":
572 if not self.check_repo_names:
573 #Strip repo names if we don't check them
580 a = Atom("="+cpv, allow_repo=True)
581 new_got.append(a.cpv)
586 if isinstance(obj, basestring):
588 new_expected.append(obj)
590 a = Atom("="+obj, allow_repo=True)
591 new_expected.append(a.cpv)
593 new_expected.append(set())
596 cpv = Atom("="+cpv, allow_repo=True).cpv
597 new_expected[-1].add(cpv)
598 expected = new_expected
599 if self.ignore_mergelist_order and got is not None:
601 expected = set(expected)
603 if self.ambiguous_merge_order and got:
604 expected_stack = list(reversed(expected))
605 got_stack = list(reversed(got))
608 while got_stack and expected_stack:
609 got_token = got_stack.pop()
610 expected_obj = expected_stack.pop()
611 if isinstance(expected_obj, basestring):
612 new_expected.append(expected_obj)
613 if got_token == expected_obj:
615 # result doesn't match, so stop early
618 expected_obj = set(expected_obj)
620 expected_obj.remove(got_token)
622 # result doesn't match, so stop early
625 new_expected.append(got_token)
626 while got_stack and expected_obj:
627 got_token = got_stack.pop()
629 expected_obj.remove(got_token)
633 new_expected.append(got_token)
635 # result doesn't match, so stop early
638 # result does not match, so stop early
640 new_expected.append(tuple(expected_obj))
643 # result does not match, add leftovers to new_expected
645 expected_stack.reverse()
646 new_expected.extend(expected_stack)
647 expected = new_expected
649 if match and self.merge_order_assertions:
650 for node1, node2 in self.merge_order_assertions:
651 if not (got.index(node1) < got.index(node2)):
652 fail_msgs.append("atoms: (" + \
653 ", ".join(result.atoms) + "), key: " + \
654 ("merge_order_assertions, expected: %s" % \
655 str((node1, node2))) + \
656 ", got: " + str(got))
658 elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None:
659 expected = set(expected)
662 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
663 key + ", expected: " + str(expected) + ", got: " + str(got))
665 self.test_success = False
666 self.fail_msg = "\n".join(fail_msgs)
670 class ResolverPlaygroundResult(object):
673 "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions",
674 "circular_dependency_solutions", "needed_p_mask_changes",
679 def __init__(self, atoms, success, mydepgraph, favorites):
681 self.success = success
682 self.depgraph = mydepgraph
683 self.favorites = favorites
684 self.mergelist = None
685 self.use_changes = None
686 self.license_changes = None
687 self.unstable_keywords = None
688 self.needed_p_mask_changes = None
689 self.slot_collision_solutions = None
690 self.circular_dependency_solutions = None
692 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
694 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
695 if isinstance(x, Blocker):
696 self.mergelist.append(x.atom)
699 if x.metadata["repository"] != "test_repo":
700 repo_str = _repo_separator + x.metadata["repository"]
701 self.mergelist.append(x.cpv + repo_str)
703 if self.depgraph._dynamic_config._needed_use_config_changes:
704 self.use_changes = {}
705 for pkg, needed_use_config_changes in \
706 self.depgraph._dynamic_config._needed_use_config_changes.items():
707 new_use, changes = needed_use_config_changes
708 self.use_changes[pkg.cpv] = changes
710 if self.depgraph._dynamic_config._needed_unstable_keywords:
711 self.unstable_keywords = set()
712 for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
713 self.unstable_keywords.add(pkg.cpv)
715 if self.depgraph._dynamic_config._needed_p_mask_changes:
716 self.needed_p_mask_changes = set()
717 for pkg in self.depgraph._dynamic_config._needed_p_mask_changes:
718 self.needed_p_mask_changes.add(pkg.cpv)
720 if self.depgraph._dynamic_config._needed_license_changes:
721 self.license_changes = {}
722 for pkg, missing_licenses in self.depgraph._dynamic_config._needed_license_changes.items():
723 self.license_changes[pkg.cpv] = missing_licenses
725 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
726 self.slot_collision_solutions = []
727 handler = self.depgraph._dynamic_config._slot_conflict_handler
729 for change in handler.changes:
732 new_change[pkg.cpv] = change[pkg]
733 self.slot_collision_solutions.append(new_change)
735 if self.depgraph._dynamic_config._circular_dependency_handler is not None:
736 handler = self.depgraph._dynamic_config._circular_dependency_handler
737 sol = handler.solutions
738 self.circular_dependency_solutions = dict( zip([x.cpv for x in sol.keys()], sol.values()) )
740 class ResolverPlaygroundDepcleanResult(object):
743 "success", "cleanlist", "ordered", "req_pkg_count",
746 "ordered", "req_pkg_count",
749 def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
751 self.success = rval == 0
752 self.cleanlist = cleanlist
753 self.ordered = ordered
754 self.req_pkg_count = req_pkg_count