53317449949eee81c79d513667f5dba4cabb4fac
[portage.git] / pym / _emerge / unmerge.py
1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from __future__ import print_function
5
6 import logging
7 import sys
8 import textwrap
9 import portage
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
16
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
22
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):
27
28         if clean_world:
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"]
35         candidate_catpkgs=[]
36         global_unmerge=0
37         xterm_titles = "notitles" not in settings.features
38         out = portage.output.EOutput()
39         pkg_cache = {}
40         db_keys = list(vartree.dbapi._aux_cache_keys)
41
42         def _pkg(cpv):
43                 pkg = pkg_cache.get(cpv)
44                 if pkg is None:
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")
49                         pkg_cache[cpv] = pkg
50                 return pkg
51
52         vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
53         try:
54                 # At least the parent needs to exist for the lock file.
55                 portage.util.ensure_dirs(vdb_path)
56         except portage.exception.PortageException:
57                 pass
58         vdb_lock = None
59         try:
60                 if os.access(vdb_path, os.W_OK):
61                         vartree.dbapi.lock()
62                         vdb_lock = True
63
64                 realsyslist = []
65                 for x in sets["system"].getAtoms():
66                         for atom in expand_new_virt(vartree.dbapi, x):
67                                 if not atom.blocker:
68                                         realsyslist.append(atom)
69
70                 syslist = []
71                 for x in realsyslist:
72                         mycp = x.cp
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():
80                                 providers = []
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)
86                         else:
87                                 syslist.append(mycp)
88                 syslist = frozenset(syslist)
89         
90                 mysettings = portage.config(clone=settings)
91         
92                 if not unmerge_files:
93                         if unmerge_action == "unmerge":
94                                 print()
95                                 print(bold("emerge unmerge") + " can only be used with specific package names")
96                                 print()
97                                 return 0
98                         else:
99                                 global_unmerge = 1
100         
101                 localtree = vartree
102                 # process all arguments and add all
103                 # valid db entries to candidate_catpkgs
104                 if global_unmerge:
105                         if not unmerge_files:
106                                 candidate_catpkgs.extend(vartree.dbapi.cp_all())
107                 else:
108                         #we've got command-line arguments
109                         if not unmerge_files:
110                                 print("\nNo packages to unmerge have been provided.\n")
111                                 return 0
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")
121                                         continue
122                                 else:
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")
127                                                 return 0
128         
129                                         absx   = os.path.abspath(x)
130                                         sp_absx = absx.split("/")
131                                         if sp_absx[-1][-7:] == ".ebuild":
132                                                 del sp_absx[-1]
133                                                 absx = "/".join(sp_absx)
134         
135                                         sp_absx_len = len(sp_absx)
136         
137                                         vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
138                                         vdb_len  = len(vdb_path)
139         
140                                         sp_vdb     = vdb_path.split("/")
141                                         sp_vdb_len = len(sp_vdb)
142         
143                                         if not os.path.exists(absx+"/CONTENTS"):
144                                                 print("!!! Not a valid db dir: "+str(absx))
145                                                 return 0
146         
147                                         if sp_absx_len <= sp_vdb_len:
148                                                 # The Path is shorter... so it can't be inside the vdb.
149                                                 print(sp_absx)
150                                                 print(absx)
151                                                 print("\n!!!",x,"cannot be inside "+ \
152                                                         vdb_path+"; aborting.\n")
153                                                 return 0
154         
155                                         for idx in range(0,sp_vdb_len):
156                                                 if idx >= sp_absx_len or sp_vdb[idx] != sp_absx[idx]:
157                                                         print(sp_absx)
158                                                         print(absx)
159                                                         print("\n!!!", x, "is not inside "+\
160                                                                 vdb_path+"; aborting.\n")
161                                                         return 0
162         
163                                         print("="+"/".join(sp_absx[sp_vdb_len:]))
164                                         candidate_catpkgs.append(
165                                                 "="+"/".join(sp_absx[sp_vdb_len:]))
166         
167                 newline=""
168                 if (not "--quiet" in myopts):
169                         newline="\n"
170                 if settings["ROOT"] != "/":
171                         writemsg_level(darkgreen(newline+ \
172                                 ">>> Using system located in ROOT tree %s\n" % \
173                                 settings["ROOT"]))
174
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"))
179
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
183                 # multiple atoms.
184                 pkgmap = []
185                 all_selected = set()
186                 for x in candidate_catpkgs:
187                         # cycle through all our candidate deps and determine
188                         # what will and will not get unmerged
189                         try:
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")
196                                 for i in errpkgs[0]:
197                                         print("    " + green(i))
198                                 print()
199                                 sys.exit(1)
200         
201                         if not mymatch and x[0] not in "<>=~":
202                                 mymatch = localtree.dep_match(x)
203                         if not mymatch:
204                                 portage.writemsg("\n--- Couldn't find '%s' to %s.\n" % \
205                                         (x.replace("null/", ""), unmerge_action), noiselevel=-1)
206                                 continue
207
208                         pkgmap.append(
209                                 {"protected": set(), "selected": set(), "omitted": set()})
210                         mykey = len(pkgmap) - 1
211                         if unmerge_action=="unmerge":
212                                         for y in mymatch:
213                                                 if y not in all_selected:
214                                                         pkgmap[mykey]["selected"].add(y)
215                                                         all_selected.add(y)
216                         elif unmerge_action == "prune":
217                                 if len(mymatch) == 1:
218                                         continue
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.
232                                                                 continue
233                                                 best_version = mypkg
234                                                 best_slot = myslot
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"])
240                         else:
241                                 # unmerge_action == "clean"
242                                 slotmap={}
243                                 for mypkg in mymatch:
244                                         if unmerge_action == "clean":
245                                                 myslot = localtree.getslot(mypkg)
246                                         else:
247                                                 # since we're pruning, we don't care about slots
248                                                 # and put all the pkgs in together
249                                                 myslot = 0
250                                         if myslot not in slotmap:
251                                                 slotmap[myslot] = {}
252                                         slotmap[myslot][localtree.dbapi.cpv_counter(mypkg)] = mypkg
253
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:
258                                                 slotmap[myslot] = {}
259                                         slotmap[myslot][vartree.dbapi.cpv_counter(mypkg)] = mypkg
260
261                                 for myslot in slotmap:
262                                         counterkeys = list(slotmap[myslot])
263                                         if not counterkeys:
264                                                 continue
265                                         counterkeys.sort()
266                                         pkgmap[mykey]["protected"].add(
267                                                 slotmap[myslot][counterkeys[-1]])
268                                         del counterkeys[-1]
269
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])
276
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")
288                         return 0
289         
290                 if not numselected:
291                         portage.writemsg_stdout(
292                                 "\n>>> No packages selected for removal by " + \
293                                 unmerge_action + "\n")
294                         return 0
295         finally:
296                 if vdb_lock:
297                         vartree.dbapi.flush_cache()
298                         vartree.dbapi.unlock()
299
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"]
303         stop = False
304         pos = 0
305         while not stop:
306                 stop = True
307                 pos = len(installed_sets)
308                 for s in installed_sets[pos - 1:]:
309                         if s not in sets:
310                                 continue
311                         candidates = [x[len(SETPREFIX):] for x in sets[s].getNonAtoms() if x.startswith(SETPREFIX)]
312                         if candidates:
313                                 stop = False
314                                 installed_sets += candidates
315         installed_sets = [x for x in installed_sets if x not in root_config.setconfig.active]
316         del stop, pos
317
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.
321         unknown_sets = set()
322         for cp in range(len(pkgmap)):
323                 for cpv in pkgmap[cp]["selected"].copy():
324                         try:
325                                 pkg = _pkg(cpv)
326                         except KeyError:
327                                 # It could have been uninstalled
328                                 # by a concurrent process.
329                                 continue
330
331                         if unmerge_action != "clean" and root_config.root == "/":
332                                 skip_pkg = False
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,)
336                                         skip_pkg = True
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,)
340                                         skip_pkg = True
341                                 if skip_pkg:
342                                         for line in textwrap.wrap(msg, 75):
343                                                 out.eerror(line)
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)
348                                         continue
349
350                         parents = []
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
354                                 # that set later on.
355                                 if s in root_config.setconfig.active or s == "selected":
356                                         continue
357
358                                 if s not in sets:
359                                         if s in unknown_sets:
360                                                 continue
361                                         unknown_sets.add(s)
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))
365                                         continue
366
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):
371
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
377                                                 higher_slot = None
378                                                 for inst_cpv in inst_matches:
379                                                         try:
380                                                                 inst_pkg = _pkg(inst_cpv)
381                                                         except KeyError:
382                                                                 # It could have been uninstalled
383                                                                 # by a concurrent process.
384                                                                 continue
385
386                                                         if inst_pkg.cp != atom.cp:
387                                                                 continue
388                                                         if pkg >= inst_pkg:
389                                                                 # This is descending order, and we're not
390                                                                 # interested in any versions <= pkg given.
391                                                                 break
392                                                         if pkg.slot_atom != inst_pkg.slot_atom:
393                                                                 higher_slot = inst_pkg
394                                                                 break
395                                                 if higher_slot is None:
396                                                         parents.append(s)
397                                                         break
398                         if parents:
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))
402
403         del installed_sets
404
405         numselected = len(all_selected)
406         if not numselected:
407                 writemsg_level(
408                         "\n>>> No packages selected for removal by " + \
409                         unmerge_action + "\n")
410                 return 0
411
412         # Unmerge order only matters in some cases
413         if not ordered:
414                 unordered = {}
415                 for d in pkgmap:
416                         selected = d["selected"]
417                         if not selected:
418                                 continue
419                         cp = portage.cpv_getkey(next(iter(selected)))
420                         cp_dict = unordered.get(cp)
421                         if cp_dict is None:
422                                 cp_dict = {}
423                                 unordered[cp] = cp_dict
424                                 for k in d:
425                                         cp_dict[k] = set()
426                         for k, v in d.items():
427                                 cp_dict[k].update(v)
428                 pkgmap = [unordered[cp] for cp in sorted(unordered)]
429
430         for x in range(len(pkgmap)):
431                 selected = pkgmap[x]["selected"]
432                 if not selected:
433                         continue
434                 for mytype, mylist in pkgmap[x].items():
435                         if mytype == "selected":
436                                 continue
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
447                         continue
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"))
458                 if not quiet:
459                         writemsg_level("\n %s\n" % (bold(cp),), noiselevel=-1)
460                 else:
461                         writemsg_level(bold(cp) + ": ", noiselevel=-1)
462                 for mytype in ["selected","protected","omitted"]:
463                         if not quiet:
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:
469                                         if rev == "r0":
470                                                 myversion = ver
471                                         else:
472                                                 myversion = ver + "-" + rev
473                                         if mytype == "selected":
474                                                 writemsg_level(
475                                                         colorize("UNMERGE_WARN", myversion + " "),
476                                                         noiselevel=-1)
477                                         else:
478                                                 writemsg_level(
479                                                         colorize("GOOD", myversion + " "), noiselevel=-1)
480                         else:
481                                 writemsg_level("none ", noiselevel=-1)
482                         if not quiet:
483                                 writemsg_level("\n", noiselevel=-1)
484                 if quiet:
485                         writemsg_level("\n", noiselevel=-1)
486
487         writemsg_level("\nAll selected packages: %s\n" % " ".join(all_selected), noiselevel=-1)
488
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")
494
495         if "--pretend" in myopts:
496                 #we're done... return
497                 return 0
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
503                         print()
504                         print("Quitting.")
505                         print()
506                         return 0
507         #the real unmerging begins, after a short delay....
508         if clean_delay and not autoclean:
509                 countdown(int(settings["CLEAN_DELAY"]), ">>> Unmerging")
510
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("/")
516                         #unmerge...
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,
520                                 scheduler=scheduler)
521
522                         if retval != os.EX_OK:
523                                 emergelog(xterm_titles, " !!! unmerge FAILURE: "+y)
524                                 if raise_on_error:
525                                         raise UninstallFailure(retval)
526                                 sys.exit(retval)
527                         else:
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)
536
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()
544
545         return 1
546