1 # Copyright 2010 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
4 from itertools import chain, permutations
9 from portage.dbapi.vartree import vartree
10 from portage.dbapi.porttree import portagetree
11 from portage.dbapi.bintree import binarytree
12 from portage.dep import Atom
13 from portage.package.ebuild.config import config
14 from portage.sets import SetConfig
15 from portage.versions import catsplit
17 from _emerge.Blocker import Blocker
18 from _emerge.create_depgraph_params import create_depgraph_params
19 from _emerge.depgraph import backtrack_depgraph
20 from _emerge.RootConfig import RootConfig
21 from _emerge.main import setconfig_fallback
23 class ResolverPlayground(object):
25 This class help to create the necessary files on disk and
26 the needed settings instances, etc. for the resolver to do
30 def __init__(self, ebuilds={}, installed={}, profile={}, world=[], debug=False):
32 ebuilds: cpv -> metadata mapping simulating avaiable ebuilds.
33 installed: cpv -> metadata mapping simulating installed packages.
34 If a metadata key is missing, it gets a default value.
35 profile: settings defined by the profile.
38 self.root = tempfile.mkdtemp() + os.path.sep
39 self.portdir = os.path.join(self.root, "usr/portage")
40 self.vdbdir = os.path.join(self.root, "var/db/pkg")
41 os.makedirs(self.portdir)
42 os.makedirs(self.vdbdir)
44 self._create_ebuilds(ebuilds)
45 self._create_installed(installed)
46 self._create_profile(ebuilds, installed, profile)
47 self._create_world(world)
49 self.settings, self.trees = self._load_config()
51 self._create_ebuild_manifests(ebuilds)
53 def _create_ebuilds(self, ebuilds):
56 ebuild_dir = os.path.join(self.portdir, a.cp)
57 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
59 os.makedirs(ebuild_dir)
63 metadata = ebuilds[cpv]
64 eapi = metadata.get("EAPI", 0)
65 slot = metadata.get("SLOT", 0)
66 keywords = metadata.get("KEYWORDS", "x86")
67 iuse = metadata.get("IUSE", "")
68 depend = metadata.get("DEPEND", "")
69 rdepend = metadata.get("RDEPEND", None)
70 pdepend = metadata.get("PDEPEND", None)
71 required_use = metadata.get("REQUIRED_USE", None)
73 f = open(ebuild_path, "w")
74 f.write('EAPI="' + str(eapi) + '"\n')
75 f.write('SLOT="' + str(slot) + '"\n')
76 f.write('KEYWORDS="' + str(keywords) + '"\n')
77 f.write('IUSE="' + str(iuse) + '"\n')
78 f.write('DEPEND="' + str(depend) + '"\n')
79 if rdepend is not None:
80 f.write('RDEPEND="' + str(rdepend) + '"\n')
81 if pdepend is not None:
82 f.write('PDEPEND="' + str(pdepend) + '"\n')
83 if required_use is not None:
84 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
87 def _create_ebuild_manifests(self, ebuilds):
90 ebuild_dir = os.path.join(self.portdir, a.cp)
91 ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
93 portage.util.noiselimit = -1
94 tmpsettings = config(clone=self.settings)
95 portdb = self.trees[self.root]["porttree"].dbapi
96 portage.doebuild(ebuild_path, "digest", self.root, tmpsettings,
97 tree="porttree", mydbapi=portdb)
98 portage.util.noiselimit = 0
100 def _create_installed(self, installed):
101 for cpv in installed:
103 vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
105 os.makedirs(vdb_pkg_dir)
109 metadata = installed[cpv]
110 eapi = metadata.get("EAPI", 0)
111 slot = metadata.get("SLOT", 0)
112 keywords = metadata.get("KEYWORDS", "~x86")
113 iuse = metadata.get("IUSE", "")
114 use = metadata.get("USE", "")
115 depend = metadata.get("DEPEND", "")
116 rdepend = metadata.get("RDEPEND", None)
117 pdepend = metadata.get("PDEPEND", None)
118 required_use = metadata.get("REQUIRED_USE", None)
120 def write_key(key, value):
121 f = open(os.path.join(vdb_pkg_dir, key), "w")
122 f.write(str(value) + "\n")
125 write_key("EAPI", eapi)
126 write_key("SLOT", slot)
127 write_key("KEYWORDS", keywords)
128 write_key("IUSE", iuse)
129 write_key("USE", use)
130 write_key("DEPEND", depend)
131 if rdepend is not None:
132 write_key("RDEPEND", rdepend)
133 if pdepend is not None:
134 write_key("PDEPEND", pdepend)
135 if required_use is not None:
136 write_key("REQUIRED_USE", required_use)
138 def _create_profile(self, ebuilds, installed, profile):
139 #Create $PORTDIR/profiles/categories
141 for cpv in chain(ebuilds.keys(), installed.keys()):
142 categories.add(catsplit(cpv)[0])
144 profile_dir = os.path.join(self.portdir, "profiles")
146 os.makedirs(profile_dir)
150 categories_file = os.path.join(profile_dir, "categories")
152 f = open(categories_file, "w")
153 for cat in categories:
157 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
158 os.makedirs(os.path.join(self.portdir, "eclass"))
160 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
161 os.makedirs(sub_profile_dir)
163 eapi_file = os.path.join(sub_profile_dir, "eapi")
164 f = open(eapi_file, "w")
168 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
169 f = open(make_defaults_file, "w")
170 f.write("ARCH=\"x86\"\n")
171 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
174 use_force_file = os.path.join(sub_profile_dir, "use.force")
175 f = open(use_force_file, "w")
180 #This is meant to allow the consumer to set up his own profile,
181 #with package.mask and what not.
182 raise NotImplentedError()
184 #Create profile symlink
185 os.makedirs(os.path.join(self.root, "etc"))
186 os.symlink(sub_profile_dir, os.path.join(self.root, "etc", "make.profile"))
188 def _create_world(self, world):
189 #Create /var/lib/portage/world
190 var_lib_portage = os.path.join(self.root, "var", "lib", "portage")
191 os.makedirs(var_lib_portage)
193 world_file = os.path.join(var_lib_portage, "world")
195 f = open(world_file, "w")
197 f.write("%s\n" % atom)
200 def _load_config(self):
201 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
202 # need to be inherited by ebuild subprocesses.
204 "ACCEPT_KEYWORDS": "x86",
205 "PORTDIR": self.portdir,
207 'PORTAGE_TMPDIR' : os.path.join(self.root, 'var/tmp'),
208 'PORTAGE_USERNAME' : os.environ["PORTAGE_USERNAME"],
209 'PORTAGE_GRPNAME' : os.environ["PORTAGE_GRPNAME"],
212 settings = config(config_root=self.root, target_root=self.root, env=env)
217 "virtuals": settings.getvirtuals(),
218 "vartree": vartree(self.root, categories=settings.categories, settings=settings),
219 "porttree": portagetree(self.root, settings=settings),
220 "bintree": binarytree(self.root, os.path.join(self.root, "usr/portage/packages"), settings=settings)
224 for root, root_trees in trees.items():
225 settings = root_trees["vartree"].settings
226 settings._init_dirs()
227 setconfig = SetConfig([], settings, root_trees)
228 root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
229 setconfig_fallback(root_trees["root_config"])
231 return settings, trees
233 def run(self, atoms, options={}, action=None):
234 options = options.copy()
235 options["--pretend"] = True
236 options["--quiet"] = True
237 options["--root"] = self.root
238 options["--config-root"] = self.root
239 options["--root-deps"] = "rdeps"
241 options["--debug"] = True
242 # Add a fake _test_ option that can be used for
243 # conditional test code.
244 options["_test_"] = True
247 portage.util.noiselimit = -2
248 params = create_depgraph_params(options, action)
249 success, depgraph, favorites = backtrack_depgraph(
250 self.settings, self.trees, options, params, action, atoms, None)
251 depgraph.display_problems()
252 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
253 portage.util.noiselimit = 0
257 def run_TestCase(self, test_case):
258 if not isinstance(test_case, ResolverPlaygroundTestCase):
259 raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
260 for atoms in test_case.requests:
261 result = self.run(atoms, test_case.options, test_case.action)
262 if not test_case.compare_with_result(result):
267 print("\nROOT=%s" % self.root)
269 shutil.rmtree(self.root)
271 class ResolverPlaygroundTestCase(object):
273 def __init__(self, request, **kwargs):
278 "unstable_keywords": None,
279 "slot_collision_solutions": None,
282 self.all_permutations = kwargs.pop("all_permutations", False)
283 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
285 if self.all_permutations:
286 self.requests = list(permutations(request))
288 self.requests = [request]
290 self.options = kwargs.pop("options", {})
291 self.action = kwargs.pop("action", None)
292 self.test_success = True
295 for key, value in kwargs.items():
296 if not key in self.checks:
297 raise KeyError("Not an avaiable check: '%s'" % key)
298 self.checks[key] = value
300 def compare_with_result(self, result):
302 for key, value in self.checks.items():
303 got = getattr(result, key)
305 if key == "mergelist" and self.ignore_mergelist_order and got is not None :
307 expected = set(expected)
308 elif key == "unstable_keywords" and expected is not None:
309 expected = set(expected)
312 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
313 key + ", expected: " + str(expected) + ", got: " + str(got))
315 self.test_success = False
316 self.fail_msg = "\n".join(fail_msgs)
320 class ResolverPlaygroundResult(object):
321 def __init__(self, atoms, success, mydepgraph, favorites):
323 self.success = success
324 self.depgraph = mydepgraph
325 self.favorites = favorites
326 self.mergelist = None
327 self.use_changes = None
328 self.unstable_keywords = None
329 self.slot_collision_solutions = None
331 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
333 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
334 if isinstance(x, Blocker):
335 self.mergelist.append(x.atom)
337 self.mergelist.append(x.cpv)
339 if self.depgraph._dynamic_config._needed_use_config_changes:
340 self.use_changes = {}
341 for pkg, needed_use_config_changes in \
342 self.depgraph._dynamic_config._needed_use_config_changes.items():
343 new_use, changes = needed_use_config_changes
344 self.use_changes[pkg.cpv] = changes
346 if self.depgraph._dynamic_config._needed_unstable_keywords:
347 self.unstable_keywords = set()
348 for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
349 self.unstable_keywords.add(pkg.cpv)
351 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
352 self.slot_collision_solutions = []
353 handler = self.depgraph._dynamic_config._slot_conflict_handler
355 for solution in handler.solutions:
359 for flag, state in solution[pkg].items():
360 if state == "enabled":
363 changes[flag] = False
365 self.slot_collision_solutions.append(s)