test_merge_order: test blocker resolution
[portage.git] / pym / portage / tests / resolver / ResolverPlayground.py
1 # Copyright 2010-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from itertools import permutations
5 import shutil
6 import sys
7 import tempfile
8 import portage
9 from portage import os
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
20
21 import _emerge
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
27
28 if sys.hexversion >= 0x3000000:
29         basestring = str
30
31 class ResolverPlayground(object):
32         """
33         This class helps to create the necessary files on disk and
34         the needed settings instances, etc. for the resolver to do
35         its work.
36         """
37
38         config_files = frozenset(("package.use", "package.mask", "package.keywords", \
39                 "package.unmask", "package.properties", "package.license", "use.mask", "use.force"))
40
41         def __init__(self, ebuilds={}, installed={}, profile={}, repo_configs={}, \
42                 user_config={}, sets={}, world=[], debug=False):
43                 """
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.
48                 """
49                 self.debug = debug
50                 self.root = "/"
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)
57
58                 if not debug:
59                         portage.util.noiselimit = -2
60
61                 self.repo_dirs = {}
62                 #Make sure the main repo is always created
63                 self._get_repo_dir("test_repo")
64
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)
69
70                 self.settings, self.trees = self._load_config()
71
72                 self._create_ebuild_manifests(ebuilds)
73                 
74                 portage.util.noiselimit = 0
75
76         def _get_repo_dir(self, repo):
77                 """
78                 Create the repo directory if needed.
79                 """
80                 if repo not in self.repo_dirs:
81                         if repo == "test_repo":
82                                 repo_path = self.portdir
83                         else:
84                                 repo_path = os.path.join(self.eroot, "usr", "local", repo)
85
86                         self.repo_dirs[repo] = repo_path
87                         profile_path = os.path.join(repo_path, "profiles")
88
89                         try:
90                                 os.makedirs(profile_path)
91                         except os.error:
92                                 pass
93
94                         repo_name_file = os.path.join(profile_path, "repo_name")
95                         f = open(repo_name_file, "w")
96                         f.write("%s\n" % repo)
97                         f.close()
98
99                 return self.repo_dirs[repo]
100
101         def _create_ebuilds(self, ebuilds):
102                 for cpv in ebuilds:
103                         a = Atom("=" + cpv, allow_repo=True)
104                         repo = a.repo
105                         if repo is None:
106                                 repo = "test_repo"
107
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)
119
120                         if metadata:
121                                 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
122
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")
126                         try:
127                                 os.makedirs(ebuild_dir)
128                         except os.error:
129                                 pass
130
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')
145                         f.close()
146
147         def _create_ebuild_manifests(self, ebuilds):
148                 tmpsettings = config(clone=self.settings)
149                 tmpsettings['PORTAGE_QUIET'] = '1'
150                 for cpv in ebuilds:
151                         a = Atom("=" + cpv, allow_repo=True)
152                         repo = a.repo
153                         if repo is None:
154                                 repo = "test_repo"
155
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")
159
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)
164
165         def _create_installed(self, installed):
166                 for cpv in installed:
167                         a = Atom("=" + cpv, allow_repo=True)
168                         repo = a.repo
169                         if repo is None:
170                                 repo = "test_repo"
171
172                         vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
173                         try:
174                                 os.makedirs(vdb_pkg_dir)
175                         except os.error:
176                                 pass
177
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)
190
191                         if metadata:
192                                 raise ValueError("metadata of installed '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
193
194                         def write_key(key, value):
195                                 f = open(os.path.join(vdb_pkg_dir, key), "w")
196                                 f.write(str(value) + "\n")
197                                 f.close()
198                         
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)
216
217         def _create_profile(self, ebuilds, installed, profile, repo_configs, user_config, sets):
218
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")
222
223                         #Create $REPO/profiles/categories
224                         categories = set()
225                         for cpv in ebuilds:
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])
231
232                         categories_file = os.path.join(profile_dir, "categories")
233                         f = open(categories_file, "w")
234                         for cat in categories:
235                                 f.write(cat + "\n")
236                         f.close()
237                         
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")
242                         f.close()
243
244                         repo_config = repo_configs.get(repo) 
245                         if repo_config:
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)
249                 
250                                         file_name = os.path.join(profile_dir, config_file)
251                                         f = open(file_name, "w")
252                                         for line in lines:
253                                                 f.write("%s\n" % line)
254                                         f.close()
255
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"))
258
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)
263
264                                 eapi_file = os.path.join(sub_profile_dir, "eapi")
265                                 f = open(eapi_file, "w")
266                                 f.write("0\n")
267                                 f.close()
268
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")
273                                 f.close()
274
275                                 use_force_file = os.path.join(sub_profile_dir, "use.force")
276                                 f = open(use_force_file, "w")
277                                 f.write("x86\n")
278                                 f.close()
279
280                                 if profile:
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)
284
285                                                 file_name = os.path.join(sub_profile_dir, config_file)
286                                                 f = open(file_name, "w")
287                                                 for line in lines:
288                                                         f.write("%s\n" % line)
289                                                 f.close()
290
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"))
294
295                 user_config_dir = os.path.join(self.eroot, "etc", "portage")
296
297                 try:
298                         os.makedirs(user_config_dir)
299                 except os.error:
300                         pass
301
302                 repos_conf_file = os.path.join(user_config_dir, "repos.conf")           
303                 f = open(repos_conf_file, "w")
304                 priority = 0
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)
310                         else:
311                                 f.write("PRIORITY=%s\n" % priority)
312                                 priority += 1
313                 f.close()
314
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)
318
319                         file_name = os.path.join(user_config_dir, config_file)
320                         f = open(file_name, "w")
321                         for line in lines:
322                                 f.write("%s\n" % line)
323                         f.close()
324
325                 #Create /usr/share/portage/config/sets/portage.conf
326                 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
327                 
328                 try:
329                         os.makedirs(default_sets_conf_dir)
330                 except os.error:
331                         pass
332
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"))
336
337                 set_config_dir = os.path.join(user_config_dir, "sets")
338
339                 try:
340                         os.makedirs(set_config_dir)
341                 except os.error:
342                         pass
343
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")
347                         for line in lines:
348                                 f.write("%s\n" % line)
349                         f.close()
350
351                 user_config_dir = os.path.join(self.eroot, "etc", "portage")
352
353                 try:
354                         os.makedirs(user_config_dir)
355                 except os.error:
356                         pass
357
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)
361
362                         file_name = os.path.join(user_config_dir, config_file)
363                         f = open(file_name, "w")
364                         for line in lines:
365                                 f.write("%s\n" % line)
366                         f.close()
367
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)
372
373                 world_file = os.path.join(var_lib_portage, "world")
374
375                 f = open(world_file, "w")
376                 for atom in world:
377                         f.write("%s\n" % atom)
378                 f.close()
379
380         def _load_config(self):
381                 portdir_overlay = []
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)
386
387                 env = {
388                         "ACCEPT_KEYWORDS": "x86",
389                         "PORTDIR": self.portdir,
390                         "PORTDIR_OVERLAY": " ".join(portdir_overlay),
391                         'PORTAGE_TMPDIR'       : os.path.join(self.eroot, 'var/tmp'),
392                 }
393
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']
400
401                 settings = config(_eprefix=self.eprefix, env=env)
402                 settings.lock()
403
404                 trees = {
405                         self.root: {
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"),
410                                                 settings=settings)
411                                 }
412                         }
413
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)
419                 
420                 return settings, trees
421
422         def run(self, atoms, options={}, action=None):
423                 options = options.copy()
424                 options["--pretend"] = True
425                 if self.debug:
426                         options["--debug"] = True
427
428                 global_noiselimit = portage.util.noiselimit
429                 global_emergelog_disable = _emerge.emergelog._disable
430                 try:
431
432                         if not self.debug:
433                                 portage.util.noiselimit = -2
434                         _emerge.emergelog._disable = True
435
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)
442                         else:
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)
449                 finally:
450                         portage.util.noiselimit = global_noiselimit
451                         _emerge.emergelog._disable = global_emergelog_disable
452
453                 return result
454
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):
461                                 return
462
463         def cleanup(self):
464                 portdb = self.trees[self.root]["porttree"].dbapi
465                 portdb.close_caches()
466                 portage.dbapi.porttree.portdbapi.portdbapi_instances.remove(portdb)
467                 if self.debug:
468                         print("\nEROOT=%s" % self.eroot)
469                 else:
470                         shutil.rmtree(self.eroot)
471
472 class ResolverPlaygroundTestCase(object):
473
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)
479
480                 if self.all_permutations:
481                         self.requests = list(permutations(request))
482                 else:
483                         self.requests = [request]
484
485                 self.options = kwargs.pop("options", {})
486                 self.action = kwargs.pop("action", None)
487                 self.test_success = True
488                 self.fail_msg = None
489                 self._checks = kwargs.copy()
490
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)
496                         checks[key] = value
497
498                 fail_msgs = []
499                 for key, value in checks.items():
500                         got = getattr(result, key)
501                         expected = value
502
503                         if key in result.optional_checks and expected is None:
504                                 continue
505
506                         if key == "mergelist":
507                                 if not self.check_repo_names:
508                                         #Strip repo names if we don't check them
509                                         if got:
510                                                 new_got = []
511                                                 for cpv in got:
512                                                         if cpv[:1] == "!":
513                                                                 new_got.append(cpv)
514                                                                 continue
515                                                         a = Atom("="+cpv, allow_repo=True)
516                                                         new_got.append(a.cpv)
517                                                 got = new_got
518                                         if expected:
519                                                 new_expected = []
520                                                 for obj in expected:
521                                                         if isinstance(obj, basestring):
522                                                                 if obj[:1] == "!":
523                                                                         new_expected.append(obj)
524                                                                         continue
525                                                                 a = Atom("="+obj, allow_repo=True)
526                                                                 new_expected.append(a.cpv)
527                                                                 continue
528                                                         new_expected.append(set())
529                                                         for cpv in obj:
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:
534                                         got = set(got)
535                                         expected = set(expected)
536
537                                 if self.ambigous_merge_order and got:
538                                         expected_stack = list(reversed(expected))
539                                         got_stack = list(reversed(got))
540                                         new_expected = []
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:
547                                                                 continue
548                                                         # result doesn't match, so stop early
549                                                         break
550                                                 expected_obj = set(expected_obj)
551                                                 try:
552                                                         expected_obj.remove(got_token)
553                                                 except KeyError:
554                                                         # result doesn't match, so stop early
555                                                         break
556                                                 new_expected.append(got_token)
557                                                 match = True
558                                                 while got_stack and expected_obj:
559                                                         got_token = got_stack.pop()
560                                                         try:
561                                                                 expected_obj.remove(got_token)
562                                                         except KeyError:
563                                                                 match = False
564                                                                 break
565                                                         new_expected.append(got_token)
566                                                 if not match:
567                                                         # result doesn't match, so stop early
568                                                         break
569                                         expected = new_expected
570
571                         elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None:
572                                 expected = set(expected)
573
574                         if got != expected:
575                                 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
576                                         key + ", expected: " + str(expected) + ", got: " + str(got))
577                 if fail_msgs:
578                         self.test_success = False
579                         self.fail_msg = "\n".join(fail_msgs)
580                         return False
581                 return True
582
583 class ResolverPlaygroundResult(object):
584
585         checks = (
586                 "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions",
587                 "circular_dependency_solutions", "needed_p_mask_changes",
588                 )
589         optional_checks = (
590                 )
591
592         def __init__(self, atoms, success, mydepgraph, favorites):
593                 self.atoms = atoms
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
604
605                 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
606                         self.mergelist = []
607                         for x in self.depgraph._dynamic_config._serialized_tasks_cache:
608                                 if isinstance(x, Blocker):
609                                         self.mergelist.append(x.atom)
610                                 else:
611                                         repo_str = ""
612                                         if x.metadata["repository"] != "test_repo":
613                                                 repo_str = _repo_separator + x.metadata["repository"]
614                                         self.mergelist.append(x.cpv + repo_str)
615
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
622
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)
627
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)
632
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
637
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
641
642                         for change in handler.changes:
643                                 new_change = {}
644                                 for pkg in change:
645                                         new_change[pkg.cpv] = change[pkg]
646                                 self.slot_collision_solutions.append(new_change)
647
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()) )
652
653 class ResolverPlaygroundDepcleanResult(object):
654
655         checks = (
656                 "success", "cleanlist", "ordered", "req_pkg_count",
657                 )
658         optional_checks = (
659                 "ordered", "req_pkg_count",
660                 )
661
662         def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
663                 self.atoms = atoms
664                 self.success = rval == 0
665                 self.cleanlist = cleanlist
666                 self.ordered = ordered
667                 self.req_pkg_count = req_pkg_count