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):
202 "ACCEPT_KEYWORDS": "x86",
203 "PORTDIR": self.portdir,
205 'PORTAGE_TMPDIR' : os.path.join(self.root, 'var/tmp'),
208 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
209 # need to be inherited by ebuild subprocesses.
210 if 'PORTAGE_USERNAME' in os.environ:
211 env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
212 if 'PORTAGE_GRPNAME' in os.environ:
213 env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
215 settings = config(config_root=self.root, target_root=self.root, env=env)
220 "virtuals": settings.getvirtuals(),
221 "vartree": vartree(self.root, categories=settings.categories, settings=settings),
222 "porttree": portagetree(self.root, settings=settings),
223 "bintree": binarytree(self.root, os.path.join(self.root, "usr/portage/packages"), settings=settings)
227 for root, root_trees in trees.items():
228 settings = root_trees["vartree"].settings
229 settings._init_dirs()
230 setconfig = SetConfig([], settings, root_trees)
231 root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
232 setconfig_fallback(root_trees["root_config"])
234 return settings, trees
236 def run(self, atoms, options={}, action=None):
237 options = options.copy()
238 options["--pretend"] = True
239 options["--quiet"] = True
240 options["--root"] = self.root
241 options["--config-root"] = self.root
242 options["--root-deps"] = "rdeps"
244 options["--debug"] = True
245 # Add a fake _test_ option that can be used for
246 # conditional test code.
247 options["_test_"] = True
250 portage.util.noiselimit = -2
251 params = create_depgraph_params(options, action)
252 success, depgraph, favorites = backtrack_depgraph(
253 self.settings, self.trees, options, params, action, atoms, None)
254 depgraph.display_problems()
255 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
256 portage.util.noiselimit = 0
260 def run_TestCase(self, test_case):
261 if not isinstance(test_case, ResolverPlaygroundTestCase):
262 raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
263 for atoms in test_case.requests:
264 result = self.run(atoms, test_case.options, test_case.action)
265 if not test_case.compare_with_result(result):
270 print("\nROOT=%s" % self.root)
272 shutil.rmtree(self.root)
274 class ResolverPlaygroundTestCase(object):
276 def __init__(self, request, **kwargs):
281 "unstable_keywords": None,
282 "slot_collision_solutions": None,
285 self.all_permutations = kwargs.pop("all_permutations", False)
286 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
288 if self.all_permutations:
289 self.requests = list(permutations(request))
291 self.requests = [request]
293 self.options = kwargs.pop("options", {})
294 self.action = kwargs.pop("action", None)
295 self.test_success = True
298 for key, value in kwargs.items():
299 if not key in self.checks:
300 raise KeyError("Not an avaiable check: '%s'" % key)
301 self.checks[key] = value
303 def compare_with_result(self, result):
305 for key, value in self.checks.items():
306 got = getattr(result, key)
308 if key == "mergelist" and self.ignore_mergelist_order and got is not None :
310 expected = set(expected)
311 elif key == "unstable_keywords" and expected is not None:
312 expected = set(expected)
315 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
316 key + ", expected: " + str(expected) + ", got: " + str(got))
318 self.test_success = False
319 self.fail_msg = "\n".join(fail_msgs)
323 class ResolverPlaygroundResult(object):
324 def __init__(self, atoms, success, mydepgraph, favorites):
326 self.success = success
327 self.depgraph = mydepgraph
328 self.favorites = favorites
329 self.mergelist = None
330 self.use_changes = None
331 self.unstable_keywords = None
332 self.slot_collision_solutions = None
334 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
336 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
337 if isinstance(x, Blocker):
338 self.mergelist.append(x.atom)
340 self.mergelist.append(x.cpv)
342 if self.depgraph._dynamic_config._needed_use_config_changes:
343 self.use_changes = {}
344 for pkg, needed_use_config_changes in \
345 self.depgraph._dynamic_config._needed_use_config_changes.items():
346 new_use, changes = needed_use_config_changes
347 self.use_changes[pkg.cpv] = changes
349 if self.depgraph._dynamic_config._needed_unstable_keywords:
350 self.unstable_keywords = set()
351 for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
352 self.unstable_keywords.add(pkg.cpv)
354 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
355 self.slot_collision_solutions = []
356 handler = self.depgraph._dynamic_config._slot_conflict_handler
358 for solution in handler.solutions:
362 for flag, state in solution[pkg].items():
363 if state == "enabled":
366 changes[flag] = False
368 self.slot_collision_solutions.append(s)