1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
4 from __future__ import print_function
10 from portage import os
11 from portage.dbapi._expand_new_virt import expand_new_virt
12 from portage.output import bold, colorize, darkgreen, green
13 from portage._sets import SETPREFIX
14 from portage._sets.base import EditablePackageSet
15 from portage.util import cmp_sort_key
17 from _emerge.emergelog import emergelog
18 from _emerge.Package import Package
19 from _emerge.UninstallFailure import UninstallFailure
20 from _emerge.userquery import userquery
21 from _emerge.countdown import countdown
23 def unmerge(root_config, myopts, unmerge_action,
24 unmerge_files, ldpath_mtimes, autoclean=0,
25 clean_world=1, clean_delay=1, ordered=0, raise_on_error=0,
26 scheduler=None, writemsg_level=portage.util.writemsg_level):
29 clean_world = myopts.get('--deselect') != 'n'
30 quiet = "--quiet" in myopts
31 enter_invalid = '--ask-enter-invalid' in myopts
32 settings = root_config.settings
33 sets = root_config.sets
34 vartree = root_config.trees["vartree"]
37 xterm_titles = "notitles" not in settings.features
38 out = portage.output.EOutput()
40 db_keys = list(vartree.dbapi._aux_cache_keys)
43 pkg = pkg_cache.get(cpv)
45 pkg = Package(built=True, cpv=cpv, installed=True,
46 metadata=zip(db_keys, vartree.dbapi.aux_get(cpv, db_keys)),
47 operation="uninstall", root_config=root_config,
48 type_name="installed")
52 vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
54 # At least the parent needs to exist for the lock file.
55 portage.util.ensure_dirs(vdb_path)
56 except portage.exception.PortageException:
60 if os.access(vdb_path, os.W_OK):
65 for x in sets["system"].getAtoms():
66 for atom in expand_new_virt(vartree.dbapi, x):
68 realsyslist.append(atom)
73 # Since Gentoo stopped using old-style virtuals in
74 # 2011, typically it's possible to avoid getvirtuals()
75 # calls entirely. It will not be triggered here by
76 # new-style virtuals since those are expanded to
77 # non-virtual atoms above by expand_new_virt().
78 if mycp.startswith("virtual/") and \
79 mycp in settings.getvirtuals():
81 for provider in settings.getvirtuals()[mycp]:
82 if vartree.dbapi.match(provider):
83 providers.append(provider)
84 if len(providers) == 1:
85 syslist.extend(providers)
88 syslist = frozenset(syslist)
90 mysettings = portage.config(clone=settings)
93 if unmerge_action == "unmerge":
95 print(bold("emerge unmerge") + " can only be used with specific package names")
102 # process all arguments and add all
103 # valid db entries to candidate_catpkgs
105 if not unmerge_files:
106 candidate_catpkgs.extend(vartree.dbapi.cp_all())
108 #we've got command-line arguments
109 if not unmerge_files:
110 print("\nNo packages to unmerge have been provided.\n")
112 for x in unmerge_files:
113 arg_parts = x.split('/')
114 if x[0] not in [".","/"] and \
115 arg_parts[-1][-7:] != ".ebuild":
116 #possible cat/pkg or dep; treat as such
117 candidate_catpkgs.append(x)
118 elif unmerge_action in ["prune","clean"]:
119 print("\n!!! Prune and clean do not accept individual" + \
120 " ebuilds as arguments;\n skipping.\n")
123 # it appears that the user is specifying an installed
124 # ebuild and we're in "unmerge" mode, so it's ok.
125 if not os.path.exists(x):
126 print("\n!!! The path '"+x+"' doesn't exist.\n")
129 absx = os.path.abspath(x)
130 sp_absx = absx.split("/")
131 if sp_absx[-1][-7:] == ".ebuild":
133 absx = "/".join(sp_absx)
135 sp_absx_len = len(sp_absx)
137 vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
138 vdb_len = len(vdb_path)
140 sp_vdb = vdb_path.split("/")
141 sp_vdb_len = len(sp_vdb)
143 if not os.path.exists(absx+"/CONTENTS"):
144 print("!!! Not a valid db dir: "+str(absx))
147 if sp_absx_len <= sp_vdb_len:
148 # The Path is shorter... so it can't be inside the vdb.
151 print("\n!!!",x,"cannot be inside "+ \
152 vdb_path+"; aborting.\n")
155 for idx in range(0,sp_vdb_len):
156 if idx >= sp_absx_len or sp_vdb[idx] != sp_absx[idx]:
159 print("\n!!!", x, "is not inside "+\
160 vdb_path+"; aborting.\n")
163 print("="+"/".join(sp_absx[sp_vdb_len:]))
164 candidate_catpkgs.append(
165 "="+"/".join(sp_absx[sp_vdb_len:]))
168 if (not "--quiet" in myopts):
170 if settings["ROOT"] != "/":
171 writemsg_level(darkgreen(newline+ \
172 ">>> Using system located in ROOT tree %s\n" % \
175 if (("--pretend" in myopts) or ("--ask" in myopts)) and \
176 not ("--quiet" in myopts):
177 writemsg_level(darkgreen(newline+\
178 ">>> These are the packages that would be unmerged:\n"))
180 # Preservation of order is required for --depclean and --prune so
181 # that dependencies are respected. Use all_selected to eliminate
182 # duplicate packages since the same package may be selected by
186 for x in candidate_catpkgs:
187 # cycle through all our candidate deps and determine
188 # what will and will not get unmerged
190 mymatch = vartree.dbapi.match(x)
191 except portage.exception.AmbiguousPackageName as errpkgs:
192 print("\n\n!!! The short ebuild name \"" + \
193 x + "\" is ambiguous. Please specify")
194 print("!!! one of the following fully-qualified " + \
195 "ebuild names instead:\n")
197 print(" " + green(i))
201 if not mymatch and x[0] not in "<>=~":
202 mymatch = localtree.dep_match(x)
204 portage.writemsg("\n--- Couldn't find '%s' to %s.\n" % \
205 (x.replace("null/", ""), unmerge_action), noiselevel=-1)
209 {"protected": set(), "selected": set(), "omitted": set()})
210 mykey = len(pkgmap) - 1
211 if unmerge_action=="unmerge":
213 if y not in all_selected:
214 pkgmap[mykey]["selected"].add(y)
216 elif unmerge_action == "prune":
217 if len(mymatch) == 1:
219 best_version = mymatch[0]
220 best_slot = vartree.getslot(best_version)
221 best_counter = vartree.dbapi.cpv_counter(best_version)
222 for mypkg in mymatch[1:]:
223 myslot = vartree.getslot(mypkg)
224 mycounter = vartree.dbapi.cpv_counter(mypkg)
225 if (myslot == best_slot and mycounter > best_counter) or \
226 mypkg == portage.best([mypkg, best_version]):
227 if myslot == best_slot:
228 if mycounter < best_counter:
229 # On slot collision, keep the one with the
230 # highest counter since it is the most
231 # recently installed.
235 best_counter = mycounter
236 pkgmap[mykey]["protected"].add(best_version)
237 pkgmap[mykey]["selected"].update(mypkg for mypkg in mymatch \
238 if mypkg != best_version and mypkg not in all_selected)
239 all_selected.update(pkgmap[mykey]["selected"])
241 # unmerge_action == "clean"
243 for mypkg in mymatch:
244 if unmerge_action == "clean":
245 myslot = localtree.getslot(mypkg)
247 # since we're pruning, we don't care about slots
248 # and put all the pkgs in together
250 if myslot not in slotmap:
252 slotmap[myslot][localtree.dbapi.cpv_counter(mypkg)] = mypkg
254 for mypkg in vartree.dbapi.cp_list(
255 portage.cpv_getkey(mymatch[0])):
256 myslot = vartree.getslot(mypkg)
257 if myslot not in slotmap:
259 slotmap[myslot][vartree.dbapi.cpv_counter(mypkg)] = mypkg
261 for myslot in slotmap:
262 counterkeys = list(slotmap[myslot])
266 pkgmap[mykey]["protected"].add(
267 slotmap[myslot][counterkeys[-1]])
270 for counter in counterkeys[:]:
271 mypkg = slotmap[myslot][counter]
272 if mypkg not in mymatch:
273 counterkeys.remove(counter)
274 pkgmap[mykey]["protected"].add(
275 slotmap[myslot][counter])
277 #be pretty and get them in order of merge:
278 for ckey in counterkeys:
279 mypkg = slotmap[myslot][ckey]
280 if mypkg not in all_selected:
281 pkgmap[mykey]["selected"].add(mypkg)
282 all_selected.add(mypkg)
283 # ok, now the last-merged package
284 # is protected, and the rest are selected
285 numselected = len(all_selected)
286 if global_unmerge and not numselected:
287 portage.writemsg_stdout("\n>>> No outdated packages were found on your system.\n")
291 portage.writemsg_stdout(
292 "\n>>> No packages selected for removal by " + \
293 unmerge_action + "\n")
297 vartree.dbapi.flush_cache()
298 vartree.dbapi.unlock()
300 # generate a list of package sets that are directly or indirectly listed in "selected",
301 # as there is no persistent list of "installed" sets
302 installed_sets = ["selected"]
307 pos = len(installed_sets)
308 for s in installed_sets[pos - 1:]:
311 candidates = [x[len(SETPREFIX):] for x in sets[s].getNonAtoms() if x.startswith(SETPREFIX)]
314 installed_sets += candidates
315 installed_sets = [x for x in installed_sets if x not in root_config.setconfig.active]
318 # we don't want to unmerge packages that are still listed in user-editable package sets
319 # listed in "world" as they would be remerged on the next update of "world" or the
320 # relevant package sets.
322 for cp in range(len(pkgmap)):
323 for cpv in pkgmap[cp]["selected"].copy():
327 # It could have been uninstalled
328 # by a concurrent process.
331 if unmerge_action != "clean" and root_config.root == "/":
333 if portage.match_from_list(portage.const.PORTAGE_PACKAGE_ATOM, [pkg]):
334 msg = ("Not unmerging package %s since there is no valid reason "
335 "for Portage to unmerge itself.") % (pkg.cpv,)
337 elif vartree.dbapi._dblink(cpv).isowner(portage._python_interpreter):
338 msg = ("Not unmerging package %s since there is no valid reason "
339 "for Portage to unmerge currently used Python interpreter.") % (pkg.cpv,)
342 for line in textwrap.wrap(msg, 75):
344 # adjust pkgmap so the display output is correct
345 pkgmap[cp]["selected"].remove(cpv)
346 all_selected.remove(cpv)
347 pkgmap[cp]["protected"].add(cpv)
351 for s in installed_sets:
352 # skip sets that the user requested to unmerge, and skip world
353 # user-selected set, since the package will be removed from
355 if s in root_config.setconfig.active or s == "selected":
359 if s in unknown_sets:
362 out = portage.output.EOutput()
363 out.eerror(("Unknown set '@%s' in %s%s") % \
364 (s, root_config.settings['EROOT'], portage.const.WORLD_SETS_FILE))
367 # only check instances of EditablePackageSet as other classes are generally used for
368 # special purposes and can be ignored here (and are usually generated dynamically, so the
369 # user can't do much about them anyway)
370 if isinstance(sets[s], EditablePackageSet):
372 # This is derived from a snippet of code in the
373 # depgraph._iter_atoms_for_pkg() method.
374 for atom in sets[s].iterAtomsForPackage(pkg):
375 inst_matches = vartree.dbapi.match(atom)
376 inst_matches.reverse() # descending order
378 for inst_cpv in inst_matches:
380 inst_pkg = _pkg(inst_cpv)
382 # It could have been uninstalled
383 # by a concurrent process.
386 if inst_pkg.cp != atom.cp:
389 # This is descending order, and we're not
390 # interested in any versions <= pkg given.
392 if pkg.slot_atom != inst_pkg.slot_atom:
393 higher_slot = inst_pkg
395 if higher_slot is None:
399 print(colorize("WARN", "Package %s is going to be unmerged," % cpv))
400 print(colorize("WARN", "but still listed in the following package sets:"))
401 print(" %s\n" % ", ".join(parents))
405 numselected = len(all_selected)
408 "\n>>> No packages selected for removal by " + \
409 unmerge_action + "\n")
412 # Unmerge order only matters in some cases
416 selected = d["selected"]
419 cp = portage.cpv_getkey(next(iter(selected)))
420 cp_dict = unordered.get(cp)
423 unordered[cp] = cp_dict
426 for k, v in d.items():
428 pkgmap = [unordered[cp] for cp in sorted(unordered)]
430 for x in range(len(pkgmap)):
431 selected = pkgmap[x]["selected"]
434 for mytype, mylist in pkgmap[x].items():
435 if mytype == "selected":
437 mylist.difference_update(all_selected)
438 cp = portage.cpv_getkey(next(iter(selected)))
439 for y in localtree.dep_match(cp):
440 if y not in pkgmap[x]["omitted"] and \
441 y not in pkgmap[x]["selected"] and \
442 y not in pkgmap[x]["protected"] and \
443 y not in all_selected:
444 pkgmap[x]["omitted"].add(y)
445 if global_unmerge and not pkgmap[x]["selected"]:
446 #avoid cluttering the preview printout with stuff that isn't getting unmerged
448 if not (pkgmap[x]["protected"] or pkgmap[x]["omitted"]) and cp in syslist:
449 writemsg_level(colorize("BAD","\n\n!!! " + \
450 "'%s' is part of your system profile.\n" % cp),
451 level=logging.WARNING, noiselevel=-1)
452 writemsg_level(colorize("WARN","!!! Unmerging it may " + \
453 "be damaging to your system.\n\n"),
454 level=logging.WARNING, noiselevel=-1)
455 if clean_delay and "--pretend" not in myopts and "--ask" not in myopts:
456 countdown(int(settings["EMERGE_WARNING_DELAY"]),
457 colorize("UNMERGE_WARN", "Press Ctrl-C to Stop"))
459 writemsg_level("\n %s\n" % (bold(cp),), noiselevel=-1)
461 writemsg_level(bold(cp) + ": ", noiselevel=-1)
462 for mytype in ["selected","protected","omitted"]:
464 writemsg_level((mytype + ": ").rjust(14), noiselevel=-1)
465 if pkgmap[x][mytype]:
466 sorted_pkgs = [portage.catpkgsplit(mypkg)[1:] for mypkg in pkgmap[x][mytype]]
467 sorted_pkgs.sort(key=cmp_sort_key(portage.pkgcmp))
468 for pn, ver, rev in sorted_pkgs:
472 myversion = ver + "-" + rev
473 if mytype == "selected":
475 colorize("UNMERGE_WARN", myversion + " "),
479 colorize("GOOD", myversion + " "), noiselevel=-1)
481 writemsg_level("none ", noiselevel=-1)
483 writemsg_level("\n", noiselevel=-1)
485 writemsg_level("\n", noiselevel=-1)
487 writemsg_level("\nAll selected packages: %s\n" % " ".join(all_selected), noiselevel=-1)
489 writemsg_level("\n>>> " + colorize("UNMERGE_WARN", "'Selected'") + \
490 " packages are slated for removal.\n")
491 writemsg_level(">>> " + colorize("GOOD", "'Protected'") + \
492 " and " + colorize("GOOD", "'omitted'") + \
493 " packages will not be removed.\n\n")
495 if "--pretend" in myopts:
496 #we're done... return
498 if "--ask" in myopts:
499 if userquery("Would you like to unmerge these packages?",
500 enter_invalid) == "No":
501 # enter pretend mode for correct formatting of results
502 myopts["--pretend"] = True
507 #the real unmerging begins, after a short delay....
508 if clean_delay and not autoclean:
509 countdown(int(settings["CLEAN_DELAY"]), ">>> Unmerging")
511 for x in range(len(pkgmap)):
512 for y in pkgmap[x]["selected"]:
513 writemsg_level(">>> Unmerging "+y+"...\n", noiselevel=-1)
514 emergelog(xterm_titles, "=== Unmerging... ("+y+")")
515 mysplit = y.split("/")
517 retval = portage.unmerge(mysplit[0], mysplit[1], settings["ROOT"],
518 mysettings, unmerge_action not in ["clean","prune"],
519 vartree=vartree, ldpath_mtimes=ldpath_mtimes,
522 if retval != os.EX_OK:
523 emergelog(xterm_titles, " !!! unmerge FAILURE: "+y)
525 raise UninstallFailure(retval)
528 if clean_world and hasattr(sets["selected"], "cleanPackage")\
529 and hasattr(sets["selected"], "lock"):
530 sets["selected"].lock()
531 if hasattr(sets["selected"], "load"):
532 sets["selected"].load()
533 sets["selected"].cleanPackage(vartree.dbapi, y)
534 sets["selected"].unlock()
535 emergelog(xterm_titles, " >>> unmerge success: "+y)
537 if clean_world and hasattr(sets["selected"], "remove")\
538 and hasattr(sets["selected"], "lock"):
539 sets["selected"].lock()
540 # load is called inside remove()
541 for s in root_config.setconfig.active:
542 sets["selected"].remove(SETPREFIX + s)
543 sets["selected"].unlock()