From: Fabian Groffen Date: Sun, 19 Apr 2009 07:15:02 +0000 (-0000) Subject: Merged from trunk -r13355:13366 X-Git-Url: http://git.tremily.us/gitweb.cgi?a=commitdiff_plain;h=b3f5ae5d2fc722af8079a24881fee465851a5aa6;p=portage.git Merged from trunk -r13355:13366 | 13356 | Add support to emerge --metadata for transfering cache from | | zmedico | overlays that have a metadata/cache directory. | | 13357 | Use a new ProfileDesc class to handle the data for each | | zmedico | profile listed in profiles.desc. | | 13358 | Support use.desc from overlays. | | zmedico | | | 13359 | Support profiles.desc from overlays. | | zmedico | | | 13360 | Handle IOError from codecs.open(). | | zmedico | | | 13361 | If a repo doesn't have metadata/layout.conf then use either | | zmedico | missing eclass directory or missing profiles/profiles.desc | | | to trigger assignment of a default master. | | 13362 | Fix default master code so that it doesn't trigger when | | zmedico | PORTDIR is empty. | | 13363 | Add a new --deselect action which removes atoms from the | | zmedico | world file. This action is implied by uninstall actions, | | | including --depclean, --prune and --unmerge. Use | | | --deselect=n in order to prevent uninstall actions from | | | removing atoms from the world file. This solves bug #259994 | | | and bug #265206. | | 13364 | Fix bugs in action_deselect() and add --pretend support. | | zmedico | | | 13365 | Add --ask support to action_deselect(). | | zmedico | | | 13366 | In action_deselect(), use the installed packages to expand | | zmedico | atoms into slot atoms. | svn path=/main/branches/prefix/; revision=13369 --- diff --git a/bin/repoman b/bin/repoman index 5fb6182fc..3b084d895 100755 --- a/bin/repoman +++ b/bin/repoman @@ -540,32 +540,105 @@ repodir = os.path.realpath(repodir) def caterror(mycat): err(mycat+" is not an official category. Skipping QA checks in this directory.\nPlease ensure that you add "+catdir+" to "+repodir+"/profiles/categories\nif it is a new category.") -# setup a uselist from portage -uselist=[] -try: - uselist=portage.grabfile(portdir+"/profiles/use.desc") - for l in range(0,len(uselist)): - uselist[l]=uselist[l].split()[0] - for var in repoman_settings["USE_EXPAND"].split(): - vardescs = portage.grabfile(portdir+"/profiles/desc/"+var.lower()+".desc") - for l in range(0, len(vardescs)): - uselist.append(var.lower() + "_" + vardescs[l].split()[0]) -except (IOError, OSError, ParseError), e: - logging.exception("Couldn't read USE flags from use.desc") - sys.exit(1) +class ProfileDesc(object): + __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',) + def __init__(self, arch, status, sub_path, tree_path): + self.arch = arch + self.status = status + self.sub_path = normalize_path(sub_path.lstrip(os.sep)) + self.tree_path = tree_path + self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path) + +profile_list = [] +valid_profile_types = frozenset(['dev', 'exp', 'stable']) + +# get lists of valid keywords, licenses, and use +kwlist = set() +liclist = set() +uselist = set() + +for path in portdb.porttrees: + try: + liclist.update(os.listdir(os.path.join(path, "licenses"))) + except OSError: + pass + kwlist.update(portage.grabfile(os.path.join(path, + "profiles", "arch.list"))) + + use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc')) + for x in use_desc: + x = x.split() + if x: + uselist.add(x[0]) + + expand_desc_dir = os.path.join(path, 'profiles', 'desc') + try: + expand_list = os.listdir(expand_desc_dir) + except OSError: + pass + else: + for fn in expand_list: + if not fn[-5:] == '.desc': + continue + use_prefix = fn[:-5].lower() + '_' + for x in portage.grabfile(os.path.join(expand_desc_dir, fn)): + x = x.split() + if x: + uselist.add(use_prefix + x[0]) -# get lists of valid keywords and licenses -kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list"))) -liclist = set(portage.listdir(os.path.join(portdir, "licenses"))) + desc_path = os.path.join(path, 'profiles', 'profiles.desc') + try: + desc_file = codecs.open(desc_path, mode='r', errors='replace') + except EnvironmentError: + pass + else: + for i, x in enumerate(desc_file): + if x[0] == "#": + continue + arch = x.split() + if len(arch) == 0: + continue + if len(arch) != 3: + err("wrong format: \"" + bad(x.strip()) + "\" in " + \ + desc_path + " line %d" % (i+1, )) + elif arch[0] not in kwlist: + err("invalid arch: \"" + bad(arch[0]) + "\" in " + \ + desc_path + " line %d" % (i+1, )) + elif arch[2] not in valid_profile_types: + err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \ + desc_path + " line %d" % (i+1, )) + profile_desc = ProfileDesc(arch[0], arch[2], arch[1], portdir) + if not os.path.isdir(profile_desc.abs_path): + logging.error( + "Invalid %s profile (%s) for arch %s in %s line %d", + arch[2], arch[1], arch[0], desc_path, i+1) + continue + profile_list.append(profile_desc) + desc_file.close() + +repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist)) +repoman_settings.backup_changes('PORTAGE_ARCHLIST') + +# Ensure that profile sub_path attributes are unique. Process in reverse order +# so that profiles with duplicate sub_path from overlays will override +# profiles with the same sub_path from parent repos. +profiles = {} +profile_list.reverse() +profile_sub_paths = set() +for prof in profile_list: + if prof.sub_path in profile_sub_paths: + continue + profile_sub_paths.add(prof.sub_path) + profiles.setdefault(prof.arch, []).append(prof) -if portdir_overlay != portdir: - for porttree in portdb.porttrees[1:]: - try: - liclist.update(os.listdir(os.path.join(porttree, "licenses"))) - except OSError: - pass - kwlist.update(portage.grabfile(os.path.join(porttree, - "profiles", "arch.list"))) +for x in repoman_settings.archlist(): + if x[0] == "~": + continue + if x not in profiles: + print red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.") + print red("You need to either \"cvs update\" your profiles dir or follow this") + print red("up with the "+x+" team.") + print if not liclist: logging.fatal("Couldn't find licenses?") @@ -575,6 +648,10 @@ if not kwlist: logging.fatal("Couldn't read KEYWORDS from arch.list") sys.exit(1) +if not uselist: + logging.fatal("Couldn't find use.desc?") + sys.exit(1) + scanlist=[] if repolevel==2: #we are inside a category directory @@ -609,47 +686,6 @@ scanlist.sort() logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist)) -profiles={} -valid_profile_types = frozenset(["dev", "exp", "stable"]) -descfile=portdir+"/profiles/profiles.desc" -if os.path.exists(descfile): - for i, x in enumerate(open(descfile)): - if x[0]=="#": - continue - arch=x.split() - if len(arch) == 0: - continue - if len(arch)!=3: - err("wrong format: \"" + bad(x.strip()) + "\" in " + \ - descfile + " line %d" % (i+1, )) - elif arch[0] not in kwlist: - err("invalid arch: \"" + bad(arch[0]) + "\" in " + \ - descfile + " line %d" % (i+1, )) - elif arch[2] not in valid_profile_types: - err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \ - descfile + " line %d" % (i+1, )) - if not os.path.isdir(portdir+"/profiles/"+arch[1]): - print "Invalid "+arch[2]+" profile ("+arch[1]+") for arch "+arch[0] - continue - if arch[0] in profiles: - profiles[arch[0]]+= [[arch[1], arch[2]]] - else: - profiles[arch[0]] = [[arch[1], arch[2]]] - - for x in repoman_settings.archlist(): - if x[0] == "~": - continue - if x not in profiles: - print red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.") - print red("You need to either \"cvs update\" your profiles dir or follow this") - print red("up with the "+x+" team.") - print -else: - print red("profiles.desc does not exist: "+descfile) - print red("You need to do \"cvs update\" in profiles dir.") - print - sys.exit(1) - def dev_keywords(profiles): """ Create a set of KEYWORDS values that exist in 'dev' @@ -659,11 +695,11 @@ def dev_keywords(profiles): """ type_arch_map = {} for arch, arch_profiles in profiles.iteritems(): - for profile_path, profile_type in arch_profiles: - arch_set = type_arch_map.get(profile_type) + for prof in arch_profiles: + arch_set = type_arch_map.get(prof.status) if arch_set is None: arch_set = set() - type_arch_map[profile_type] = arch_set + type_arch_map[prof.status] = arch_set arch_set.add(arch) dev_keywords = type_arch_map.get('dev', set()) @@ -1524,23 +1560,20 @@ for x in scanlist: for prof in profiles[arch]: - if prof[1] not in ("stable", "dev") or \ - prof[1] == "dev" and not options.include_dev: + if prof.status not in ("stable", "dev") or \ + prof.status == "dev" and not options.include_dev: continue - profdir = portdir+"/profiles/"+prof[0] - - if prof[0] in arch_caches: - dep_settings = arch_caches[prof[0]] - else: + dep_settings = arch_caches.get(prof.sub_path) + if dep_settings is None: dep_settings = portage.config( - config_profile_path=profdir, + config_profile_path=prof.abs_path, config_incrementals=portage.const.INCREMENTALS, local_config=False, env=env) if options.without_mask: dep_settings.pmaskdict.clear() - arch_caches[prof[0]] = dep_settings + arch_caches[prof.sub_path] = dep_settings while True: try: # Protect ACCEPT_KEYWORDS from config.regenerate() @@ -1549,7 +1582,7 @@ for x in scanlist: except ValueError: break - xmatch_cache_key = (prof[0], tuple(groups)) + xmatch_cache_key = (prof.sub_path, tuple(groups)) xcache = arch_xmatch_caches.get(xmatch_cache_key) if xcache is None: portdb.melt() @@ -1571,7 +1604,8 @@ for x in scanlist: prov_cp = portage.dep_getkey(myprovide) if prov_cp not in dep_settings.getvirtuals(): stats["virtual.unavailable"]+=1 - fails["virtual.unavailable"].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+prov_cp) + fails["virtual.unavailable"].append("%s: %s(%s) %s" % \ + (relative_path, keyword, prof.sub_path, prov_cp)) if not baddepsyntax: ismasked = os.path.join(catdir, y) not in \ @@ -1593,7 +1627,7 @@ for x in scanlist: have_dev_keywords = \ bool(dev_keywords.intersection(keywords)) - if prof[1] == "dev": + if prof.status == "dev": suffix=suffix+"indev" for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]: @@ -1602,33 +1636,28 @@ for x in scanlist: myvalue = myaux[mytype] if not myvalue: continue - try: - mydep = portage.dep_check(myvalue, portdb, - dep_settings, use="all", mode=matchmode, - trees=trees) - except KeyError, e: - stats[mykey]=stats[mykey]+1 - fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0])) - continue - - if mydep[0]==1: - if mydep[1]!=[]: + + success, atoms = portage.dep_check(myvalue, portdb, + dep_settings, use="all", mode=matchmode, + trees=trees) + + if success: + if atoms: #we have some unsolvable deps #remove ! deps, which always show up as unsatisfiable - d=0 - while d= self.min_latency: + self._last_update = cur_time + self.display() + + def display(self): + raise NotImplementedError(self) + class Scheduler(PollScheduler): _opts_ignore_blockers = \ @@ -11871,6 +11890,8 @@ def unmerge(root_config, myopts, unmerge_action, clean_world=1, clean_delay=1, ordered=0, raise_on_error=0, scheduler=None, writemsg_level=portage.util.writemsg_level): + if clean_world: + clean_world = myopts.get('--deselect') != 'n' quiet = "--quiet" in myopts settings = root_config.settings sets = root_config.sets @@ -12780,7 +12801,8 @@ def show_mask_docs(): def action_sync(settings, trees, mtimedb, myopts, myaction): xterm_titles = "notitles" not in settings.features emergelog(xterm_titles, " === sync") - myportdir = settings.get("PORTDIR", None) + portdb = trees[settings["ROOT"]]["porttree"].dbapi + myportdir = portdb.porttree_root out = portage.output.EOutput() if not myportdir: sys.stderr.write("!!! PORTDIR is undefined. Is /etc/make.globals missing?\n") @@ -13285,8 +13307,12 @@ def action_sync(settings, trees, mtimedb, myopts, myaction): root_config = trees[settings["ROOT"]]["root_config"] portdb = trees[settings["ROOT"]]["porttree"].dbapi - if os.path.exists(myportdir+"/metadata/cache") and updatecache_flg: - action_metadata(settings, portdb, myopts) + if updatecache_flg and \ + os.path.exists(os.path.join(myportdir, 'metadata', 'cache')): + + # Only update cache for myportdir since that's + # the only one that's been synced here. + action_metadata(settings, portdb, myopts, porttrees=[myportdir]) if portage._global_updates(trees, mtimedb["updates"]): mtimedb.commit() @@ -13454,8 +13480,10 @@ def git_sync_timestamps(settings, portdir): return os.EX_OK -def action_metadata(settings, portdb, myopts): - portage.writemsg_stdout("\n>>> Updating Portage cache: ") +def action_metadata(settings, portdb, myopts, porttrees=None): + if porttrees is None: + porttrees = portdb.porttrees + portage.writemsg_stdout("\n>>> Updating Portage cache\n") old_umask = os.umask(0002) cachedir = os.path.normpath(settings.depcachedir) if cachedir in ["/", "/bin", "/dev", "/etc", "/home", @@ -13467,60 +13495,213 @@ def action_metadata(settings, portdb, myopts): "!!! This is ALMOST CERTAINLY NOT what you want: '%s'" % cachedir sys.exit(73) if not os.path.exists(cachedir): - os.mkdir(cachedir) - - ec = portage.eclass_cache.cache(portdb.porttree_root) - myportdir = os.path.realpath(settings["PORTDIR"]) - cm = settings.load_best_module("portdbapi.metadbmodule")( - myportdir, "metadata/cache", portage.auxdbkeys[:], readonly=True) - - from portage.cache import util - - class percentage_noise_maker(util.quiet_mirroring): - def __init__(self, dbapi): - self.dbapi = dbapi - self.cp_all = dbapi.cp_all() - l = len(self.cp_all) - self.call_update_min = 100000000 - self.min_cp_all = l/100.0 - self.count = 1 - self.pstr = '' - - def __iter__(self): - for x in self.cp_all: - self.count += 1 - if self.count > self.min_cp_all: - self.call_update_min = 0 - self.count = 0 - for y in self.dbapi.cp_list(x): - yield y - self.call_update_mine = 0 - - def update(self, *arg): + os.makedirs(cachedir) + + auxdbkeys = [x for x in portage.auxdbkeys if not x.startswith("UNUSED_0")] + auxdbkeys = tuple(auxdbkeys) + + class TreeData(object): + __slots__ = ('dest_db', 'eclass_db', 'path', 'src_db', 'valid_nodes') + def __init__(self, dest_db, eclass_db, path, src_db): + self.dest_db = dest_db + self.eclass_db = eclass_db + self.path = path + self.src_db = src_db + self.valid_nodes = set() + + porttrees_data = [] + for path in porttrees: + src_db = portdb._pregen_auxdb.get(path) + if src_db is None and \ + os.path.isdir(os.path.join(path, 'metadata', 'cache')): + src_db = portdb.metadbmodule( + path, 'metadata/cache', auxdbkeys, readonly=True) try: - self.pstr = int(self.pstr) + 1 - except ValueError: - self.pstr = 1 - sys.stdout.write("%s%i%%" % \ - ("\b" * (len(str(self.pstr))+1), self.pstr)) - sys.stdout.flush() - self.call_update_min = 10000000 + src_db.ec = portdb._repo_info[path].eclass_db + except AttributeError: + pass - def finish(self, *arg): - sys.stdout.write("\b\b\b\b100%\n") - sys.stdout.flush() + if src_db is not None: + porttrees_data.append(TreeData(portdb.auxdb[path], + portdb._repo_info[path].eclass_db, path, src_db)) + + porttrees = [tree_data.path for tree_data in porttrees_data] + + isatty = sys.stdout.isatty() + quiet = not isatty or '--quiet' in myopts + onProgress = None + if not quiet: + progressBar = portage.output.TermProgressBar() + progressHandler = ProgressHandler() + onProgress = progressHandler.onProgress + def display(): + progressBar.set(progressHandler.curval, progressHandler.maxval) + progressHandler.display = display + def sigwinch_handler(signum, frame): + lines, progressBar.term_columns = \ + portage.output.get_term_size() + signal.signal(signal.SIGWINCH, sigwinch_handler) + + # Temporarily override portdb.porttrees so portdb.cp_all() + # will only return the relevant subset. + portdb_porttrees = portdb.porttrees + portdb.porttrees = porttrees + try: + cp_all = portdb.cp_all() + finally: + portdb.porttrees = portdb_porttrees + + curval = 0 + maxval = len(cp_all) + if onProgress is not None: + onProgress(maxval, curval) + + from portage.cache.util import quiet_mirroring + from portage import eapi_is_supported, \ + _validate_cache_for_unsupported_eapis + + # TODO: Display error messages, but do not interfere with the progress bar. + # Here's how: + # 1) erase the progress bar + # 2) show the error message + # 3) redraw the progress bar on a new line + noise = quiet_mirroring() + + for cp in cp_all: + for tree_data in porttrees_data: + for cpv in portdb.cp_list(cp, mytree=tree_data.path): + tree_data.valid_nodes.add(cpv) + try: + src = tree_data.src_db[cpv] + except KeyError, e: + noise.missing_entry(cpv) + del e + continue + except CacheError, ce: + noise.exception(cpv, ce) + del ce + continue - if "--quiet" in myopts: - def quicky_cpv_generator(cp_all_list): - for x in cp_all_list: - for y in portdb.cp_list(x): - yield y - source = quicky_cpv_generator(portdb.cp_all()) - noise_maker = portage.cache.util.quiet_mirroring() - else: - noise_maker = source = percentage_noise_maker(portdb) - portage.cache.util.mirror_cache(source, cm, portdb.auxdb[myportdir], - eclass_cache=ec, verbose_instance=noise_maker) + eapi = src.get('EAPI') + if not eapi: + eapi = '0' + eapi = eapi.lstrip('-') + eapi_supported = eapi_is_supported(eapi) + if not eapi_supported: + if not _validate_cache_for_unsupported_eapis: + noise.misc(cpv, "unable to validate " + \ + "cache for EAPI='%s'" % eapi) + continue + + dest = None + try: + dest = tree_data.dest_db[cpv] + except (KeyError, CacheError): + pass + + for d in (src, dest): + if d is not None and d.get('EAPI') in ('', '0'): + del d['EAPI'] + + if dest is not None: + if not (dest['_mtime_'] == src['_mtime_'] and \ + tree_data.eclass_db.is_eclass_data_valid( + dest['_eclasses_']) and \ + set(dest['_eclasses_']) == set(src['_eclasses_'])): + dest = None + else: + # We don't want to skip the write unless we're really + # sure that the existing cache is identical, so don't + # trust _mtime_ and _eclasses_ alone. + for k in set(chain(src, dest)).difference( + ('_mtime_', '_eclasses_')): + if dest.get(k, '') != src.get(k, ''): + dest = None + break + + if dest is not None: + # The existing data is valid and identical, + # so there's no need to overwrite it. + continue + + try: + inherited = src.get('INHERITED', '') + eclasses = src.get('_eclasses_') + except CacheError, ce: + noise.exception(cpv, ce) + del ce + continue + + if eclasses is not None: + if not tree_data.eclass_db.is_eclass_data_valid( + src['_eclasses_']): + noise.eclass_stale(cpv) + continue + inherited = eclasses + else: + inherited = inherited.split() + + if inherited: + if tree_data.src_db.complete_eclass_entries and \ + eclasses is None: + noise.corruption(cpv, "missing _eclasses_ field") + continue + + # Even if _eclasses_ already exists, replace it with data from + # eclass_cache, in order to insert local eclass paths. + try: + eclasses = tree_data.eclass_db.get_eclass_data(inherited) + except KeyError: + # INHERITED contains a non-existent eclass. + noise.eclass_stale(cpv) + continue + + if eclasses is None: + noise.eclass_stale(cpv) + continue + src['_eclasses_'] = eclasses + + if not eapi_supported: + src = { + 'EAPI' : '-' + eapi, + '_mtime_' : src['_mtime_'], + '_eclasses_' : src['_eclasses_'], + } + + try: + tree_data.dest_db[cpv] = src + except CacheError, ce: + noise.exception(cpv, ce) + del ce + + curval += 1 + if onProgress is not None: + onProgress(maxval, curval) + + if onProgress is not None: + onProgress(maxval, curval) + + for tree_data in porttrees_data: + try: + dead_nodes = set(tree_data.dest_db.iterkeys()) + except CacheError, e: + writemsg_level("Error listing cache entries for " + \ + "'%s': %s, continuing...\n" % (tree_data.path, e), + level=logging.ERROR, noiselevel=-1) + del e + else: + dead_nodes.difference_update(tree_data.valid_nodes) + for cpv in dead_nodes: + try: + tree_data.dest_db[cpv] + except (KeyError, CacheError): + pass + + if not quiet: + # make sure the final progress is displayed + progressHandler.display() + print + signal.signal(signal.SIGWINCH, signal.SIG_DFL) sys.stdout.flush() os.umask(old_umask) @@ -13955,12 +14136,71 @@ def action_uninstall(settings, trees, ldpath_mtimes, unmerge(trees[settings["ROOT"]]['root_config'], opts, action, valid_atoms, ldpath_mtimes, ordered=ordered) rval = os.EX_OK + elif action == 'deselect': + rval = action_deselect(settings, trees, opts, valid_atoms) else: rval = action_depclean(settings, trees, ldpath_mtimes, opts, action, valid_atoms, spinner) return rval +def action_deselect(settings, trees, opts, atoms): + root_config = trees[settings['ROOT']]['root_config'] + world_set = root_config.sets['world'] + if not hasattr(world_set, 'update'): + writemsg_level("World set does not appear to be mutable.\n", + level=logging.ERROR, noiselevel=-1) + return 1 + + vardb = root_config.trees['vartree'].dbapi + expanded_atoms = set(atoms) + from portage.dep import Atom + for atom in atoms: + for cpv in vardb.match(atom): + slot, = vardb.aux_get(cpv, ['SLOT']) + if not slot: + slot = '0' + expanded_atoms.add(Atom('%s:%s' % (portage.cpv_getkey(cpv), slot))) + + pretend = '--pretend' in opts + locked = False + if not pretend and hasattr(world_set, 'lock'): + world_set.lock() + locked = True + try: + discard_atoms = set() + world_set.load() + for atom in world_set: + if not isinstance(atom, Atom): + # nested set + continue + for arg_atom in expanded_atoms: + if arg_atom.intersects(atom) and \ + not (arg_atom.slot and not atom.slot): + discard_atoms.add(atom) + break + if discard_atoms: + for atom in sorted(discard_atoms): + print ">>> Removing %s from \"world\" favorites file..." % \ + colorize("INFORM", str(atom)) + + if '--ask' in opts: + prompt = "Would you like to remove these " + \ + "packages from your world favorites?" + if userquery(prompt) == 'No': + return os.EX_OK + + remaining = set(world_set) + remaining.difference_update(discard_atoms) + if not pretend: + world_set.replace(remaining) + else: + print ">>> No matching atoms found in \"world\" favorites file..." + finally: + if locked: + world_set.unlock() + return os.EX_OK + def action_depclean(settings, trees, ldpath_mtimes, myopts, action, myfiles, spinner): # Kill packages that aren't explicitly merged or are required as a @@ -13999,6 +14239,7 @@ def action_depclean(settings, trees, ldpath_mtimes, root_config = trees[myroot]["root_config"] getSetAtoms = root_config.setconfig.getSetAtoms vardb = trees[myroot]["vartree"].dbapi + deselect = myopts.get('--deselect') != 'n' required_set_names = ("system", "world") required_sets = {} @@ -14056,11 +14297,13 @@ def action_depclean(settings, trees, ldpath_mtimes, if action == "depclean": if args_set: + + if deselect: + world_temp_set.clear() + # Pull in everything that's installed but not matched # by an argument atom since we don't want to clean any # package if something depends on it. - - world_temp_set.clear() for pkg in vardb: spinner.update() @@ -14077,9 +14320,11 @@ def action_depclean(settings, trees, ldpath_mtimes, elif action == "prune": + if deselect: + world_temp_set.clear() + # Pull in everything that's installed since we don't # to prune a package if something depends on it. - world_temp_set.clear() world_temp_set.update(vardb.cp_all()) if not args_set: @@ -15081,16 +15326,19 @@ def insert_optional_args(args): new_args = [] jobs_opts = ("-j", "--jobs") - root_deps_opt = '--root-deps' - root_deps_choices = ('True', 'rdeps') + default_arg_opts = { + '--deselect' : ('n',), + '--root-deps' : ('rdeps',), + } arg_stack = args[:] arg_stack.reverse() while arg_stack: arg = arg_stack.pop() - if arg == root_deps_opt: + default_arg_choices = default_arg_opts.get(arg) + if default_arg_choices is not None: new_args.append(arg) - if arg_stack and arg_stack[-1] in root_deps_choices: + if arg_stack and arg_stack[-1] in default_arg_choices: new_args.append(arg_stack.pop()) else: # insert default argument @@ -15158,6 +15406,12 @@ def parse_opts(tmpcmdline, silent=False): "choices":("y", "n") }, + "--deselect": { + "help" : "remove atoms from the world file", + "type" : "choice", + "choices" : ("True", "n") + }, + "--jobs": { "help" : "Specifies the number of packages to build " + \ @@ -15223,6 +15477,9 @@ def parse_opts(tmpcmdline, silent=False): myoptions, myargs = parser.parse_args(args=tmpcmdline) + if myoptions.deselect == "True": + myoptions.deselect = True + if myoptions.root_deps == "True": myoptions.root_deps = True @@ -15280,6 +15537,9 @@ def parse_opts(tmpcmdline, silent=False): sys.exit(1) myaction = action_opt + if myaction is None and myoptions.deselect is True: + myaction = 'deselect' + myfiles += myargs return myaction, myopts, myfiles @@ -15948,7 +16208,7 @@ def emerge_main(): if portage.secpass < 2: # We've already allowed "--version" and "--help" above. if "--pretend" not in myopts and myaction not in ("search","info"): - need_superuser = not \ + need_superuser = myaction in ('deselect',) or not \ (fetchonly or \ (buildpkgonly and secpass >= 1) or \ myaction in ("metadata", "regen") or \ @@ -16046,11 +16306,11 @@ def emerge_main(): action_search(trees[settings["ROOT"]]["root_config"], myopts, myfiles, spinner) - elif myaction in ('clean', 'depclean', 'prune', 'unmerge'): + elif myaction in ('clean', 'depclean', 'deselect', 'prune', 'unmerge'): validate_ebuild_environment(trees) rval = action_uninstall(settings, trees, mtimedb["ldpath"], myopts, myaction, myfiles, spinner) - if not (buildpkgonly or fetchonly or pretend): + if not (myaction == 'deselect' or buildpkgonly or fetchonly or pretend): post_emerge(root_config, myopts, mtimedb, rval) return rval diff --git a/pym/_emerge/help.py b/pym/_emerge/help.py index fd57cc3be..4afece3a3 100644 --- a/pym/_emerge/help.py +++ b/pym/_emerge/help.py @@ -103,6 +103,18 @@ def help(myaction,myopts,havecolor=1): "matched packages that have no reverse dependencies. Use " + \ "--depclean together with --verbose to show reverse dependencies." + for line in wrap(paragraph, desc_width): + print desc_indent + line + print + print " " + green("--deselect") + "[=%s]" % turquoise("n") + + paragraph = \ + "Remove atoms from the world file. This action is implied " + \ + "by uninstall actions, including --depclean, " + \ + "--prune and --unmerge. Use --deselect=n " + \ + "in order to prevent uninstall actions from removing " + \ + "atoms from the world file." + for line in wrap(paragraph, desc_width): print desc_indent + line print diff --git a/pym/portage/dbapi/porttree.py b/pym/portage/dbapi/porttree.py index c07cf4ff2..318d00c11 100644 --- a/pym/portage/dbapi/porttree.py +++ b/pym/portage/dbapi/porttree.py @@ -253,11 +253,13 @@ class portdbapi(dbapi): else: porttrees.append(master_path) - if not porttrees: - # Make PORTDIR the default master, but only if this - # repo doesn't provide profiles.desc itself. + if not porttrees and path != porttree_root: + # Make PORTDIR the default master, but only if our + # heuristics suggest that it's necessary. profiles_desc = os.path.join(path, 'profiles', 'profiles.desc') - if not os.path.exists(profiles_desc): + eclass_dir = os.path.join(path, 'eclass') + if not os.path.isfile(profiles_desc) or \ + not os.path.isdir(eclass_dir): porttrees.append(porttree_root) porttrees.append(path) diff --git a/pym/portage/dep.py b/pym/portage/dep.py index 866258715..80ebdadbc 100644 --- a/pym/portage/dep.py +++ b/pym/portage/dep.py @@ -558,6 +558,36 @@ class Atom(object): raise AttributeError("Atom instances are immutable", self.__class__, name, value) + def intersects(self, other): + """ + Atoms with different operator or cpv attributes cause this method to + return False. TODO: Detect intersection when operators are present. + @param other: The package atom to match + @type other: Atom + @rtype: Boolean + @return: True if this atom and the other atom intersect, + False otherwise. + """ + if not isinstance(other, Atom): + raise TypeError("expected %s, got %s" % \ + (Atom, type(other))) + + if self == other: + return True + + if self.cp != other.cp or \ + self.use != other.use or \ + self.operator != other.operator or \ + self.cpv != other.cpv: + return False + + if self.slot is None or \ + other.slot is None or \ + self.slot == other.slot: + return True + + return False + # Implement some common str methods. def __eq__(self, other):