diff options
-rw-r--r-- | man/emerge.1 | 4 | ||||
-rw-r--r-- | pym/_emerge/depgraph.py | 271 | ||||
-rw-r--r-- | pym/_emerge/help.py | 7 | ||||
-rw-r--r-- | pym/_emerge/main.py | 10 |
4 files changed, 216 insertions, 76 deletions
diff --git a/man/emerge.1 b/man/emerge.1 index 539b2e39f..0f2acc1d1 100644 --- a/man/emerge.1 +++ b/man/emerge.1 @@ -301,6 +301,10 @@ the specified configuration file(s). Currently, this only works for unstable KEYWORDS masks, LICENSE masks, and package.use settings. .TP +.BR "\-\-autounmask\-write [ y | n ]" +If \-\-autounmask is enabled, changes are written +to config files, respecting \fBCONFIG_PROTECT\fR. +.TP .BR \-\-backtrack=COUNT Specifies an integer number of times to backtrack if dependency calculation fails due to a conflict or an diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py index 16cb7fcbb..8558436bb 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -3,6 +3,7 @@ from __future__ import print_function +import codecs import difflib import gc import logging @@ -14,12 +15,12 @@ from itertools import chain import portage from portage import os, OrderedDict -from portage import _unicode_decode -from portage.const import PORTAGE_PACKAGE_ATOM +from portage import _unicode_decode, _unicode_encode, _encodings +from portage.const import PORTAGE_PACKAGE_ATOM, USER_CONFIG_PATH from portage.dbapi import dbapi from portage.dep import Atom, extract_affecting_use, check_required_use, human_readable_required_use, _repo_separator from portage.eapi import eapi_has_strong_blocks, eapi_has_required_use -from portage.exception import InvalidAtom, InvalidDependString +from portage.exception import InvalidAtom, InvalidDependString, PortageException from portage.output import colorize, create_color_func, \ darkgreen, green bad = create_color_func("BAD") @@ -27,8 +28,9 @@ from portage.package.ebuild.getmaskingstatus import \ _getmaskingstatus, _MaskReason from portage._sets import SETPREFIX from portage._sets.base import InternalPackageSet +from portage.util import ConfigProtect, shlex_split, new_protect_filename from portage.util import cmp_sort_key, writemsg, writemsg_stdout -from portage.util import writemsg_level +from portage.util import writemsg_level, write_atomic from portage.util.digraph import digraph from portage.versions import catpkgsplit @@ -5493,55 +5495,14 @@ class depgraph(object): return display(self, mylist, favorites, verbosity) - def display_problems(self): + def _display_autounmask(self): """ - Display problems with the dependency graph such as slot collisions. - This is called internally by display() to show the problems _after_ - the merge list where it is most likely to be seen, but if display() - is not going to be called then this method should be called explicitly - to ensure that the user is notified of problems with the graph. - - All output goes to stderr, except for unsatisfied dependencies which - go to stdout for parsing by programs such as autounmask. + Display --autounmask message and optionally write them to config files + (using CONFIG_PROTECT). The message includes the comments and the changes. """ - # Note that show_masked_packages() sends it's output to - # stdout, and some programs such as autounmask parse the - # output in cases when emerge bails out. However, when - # show_masked_packages() is called for installed packages - # here, the message is a warning that is more appropriate - # to send to stderr, so temporarily redirect stdout to - # stderr. TODO: Fix output code so there's a cleaner way - # to redirect everything to stderr. - sys.stdout.flush() - sys.stderr.flush() - stdout = sys.stdout - try: - sys.stdout = sys.stderr - self._display_problems() - finally: - sys.stdout = stdout - sys.stdout.flush() - sys.stderr.flush() - - # This goes to stdout for parsing by programs like autounmask. - for pargs, kwargs in self._dynamic_config._unsatisfied_deps_for_display: - self._show_unsatisfied_dep(*pargs, **kwargs) - - def _display_problems(self): - if self._dynamic_config._circular_deps_for_display is not None: - self._show_circular_deps( - self._dynamic_config._circular_deps_for_display) - - # The user is only notified of a slot conflict if - # there are no unresolvable blocker conflicts. - if self._dynamic_config._unsatisfied_blockers_for_display is not None: - self._show_unsatisfied_blockers( - self._dynamic_config._unsatisfied_blockers_for_display) - elif self._dynamic_config._slot_collision_info: - self._show_slot_collision_notice() - else: - self._show_missed_update() + autounmask_write = self._frozen_config.myopts.get("--autounmask-write", "n") == True + pretend = "--pretend" in self._frozen_config.myopts def check_if_latest(pkg): is_latest = True @@ -5569,11 +5530,16 @@ class depgraph(object): return is_latest, is_latest_in_slot + #Set of roots we have autounmask changes for. + roots = set() - unstable_keyword_msg = [] + unstable_keyword_msg = {} for pkg in self._dynamic_config._needed_unstable_keywords: self._show_merge_list() if pkg in self._dynamic_config.digraph: + root = pkg.root + roots.add(root) + unstable_keyword_msg.setdefault(root, []) is_latest, is_latest_in_slot = check_if_latest(pkg) pkgsettings = self._frozen_config.pkgsettings[pkg.root] mreasons = _get_masking_status(pkg, pkgsettings, pkg.root_config, @@ -5583,18 +5549,21 @@ class depgraph(object): reason.unmask_hint.key == 'unstable keyword': keyword = reason.unmask_hint.value - unstable_keyword_msg.append(self._get_dep_chain_as_comment(pkg)) + unstable_keyword_msg[root].append(self._get_dep_chain_as_comment(pkg)) if is_latest: - unstable_keyword_msg.append(">=%s %s\n" % (pkg.cpv, keyword)) + unstable_keyword_msg[root].append(">=%s %s\n" % (pkg.cpv, keyword)) elif is_latest_in_slot: - unstable_keyword_msg.append(">=%s:%s %s\n" % (pkg.cpv, pkg.metadata["SLOT"], keyword)) + unstable_keyword_msg[root].append(">=%s:%s %s\n" % (pkg.cpv, pkg.metadata["SLOT"], keyword)) else: - unstable_keyword_msg.append("=%s %s\n" % (pkg.cpv, keyword)) + unstable_keyword_msg[root].append("=%s %s\n" % (pkg.cpv, keyword)) - use_changes_msg = [] + use_changes_msg = {} for pkg, needed_use_config_change in self._dynamic_config._needed_use_config_changes.items(): self._show_merge_list() if pkg in self._dynamic_config.digraph: + root = pkg.root + roots.add(root) + use_changes_msg.setdefault(root, []) is_latest, is_latest_in_slot = check_if_latest(pkg) changes = needed_use_config_change[1] adjustments = [] @@ -5603,42 +5572,192 @@ class depgraph(object): adjustments.append(flag) else: adjustments.append("-" + flag) - use_changes_msg.append(self._get_dep_chain_as_comment(pkg, unsatisfied_dependency=True)) + use_changes_msg[root].append(self._get_dep_chain_as_comment(pkg, unsatisfied_dependency=True)) if is_latest: - use_changes_msg.append(">=%s %s\n" % (pkg.cpv, " ".join(adjustments))) + use_changes_msg[root].append(">=%s %s\n" % (pkg.cpv, " ".join(adjustments))) elif is_latest_in_slot: - use_changes_msg.append(">=%s:%s %s\n" % (pkg.cpv, pkg.metadata["SLOT"], " ".join(adjustments))) + use_changes_msg[root].append(">=%s:%s %s\n" % (pkg.cpv, pkg.metadata["SLOT"], " ".join(adjustments))) else: - use_changes_msg.append("=%s %s\n" % (pkg.cpv, " ".join(adjustments))) + use_changes_msg[root].append("=%s %s\n" % (pkg.cpv, " ".join(adjustments))) - license_msg = [] + license_msg = {} for pkg, missing_licenses in self._dynamic_config._needed_license_changes.items(): self._show_merge_list() if pkg in self._dynamic_config.digraph: + root = pkg.root + roots.add(root) + license_msg.setdefault(root, []) is_latest, is_latest_in_slot = check_if_latest(pkg) - license_msg.append(self._get_dep_chain_as_comment(pkg)) + license_msg[root].append(self._get_dep_chain_as_comment(pkg)) if is_latest: - license_msg.append(">=%s %s\n" % (pkg.cpv, " ".join(sorted(missing_licenses)))) + license_msg[root].append(">=%s %s\n" % (pkg.cpv, " ".join(sorted(missing_licenses)))) elif is_latest_in_slot: - license_msg.append(">=%s:%s %s\n" % (pkg.cpv, pkg.metadata["SLOT"], " ".join(sorted(missing_licenses)))) + license_msg[root].append(">=%s:%s %s\n" % (pkg.cpv, pkg.metadata["SLOT"], " ".join(sorted(missing_licenses)))) else: - license_msg.append("=%s %s\n" % (pkg.cpv, " ".join(sorted(missing_licenses)))) + license_msg[root].append("=%s %s\n" % (pkg.cpv, " ".join(sorted(missing_licenses)))) - if unstable_keyword_msg: - writemsg_stdout("\nThe following " + colorize("BAD", "keyword changes") + \ - " are necessary to proceed:\n", noiselevel=-1) - writemsg_stdout("".join(unstable_keyword_msg), noiselevel=-1) + def find_config_file(abs_user_config, file_name): + """ + Searches /etc/portage for an appropiate file to append changes to. + If the file_name is a file it is returned, if it is a directoy, the + last file in it is returned. - if use_changes_msg: - writemsg_stdout("\nThe following " + colorize("BAD", "USE changes") + \ - " are necessary to proceed:\n", noiselevel=-1) - writemsg_stdout("".join(use_changes_msg), noiselevel=-1) + file_name - String containg a file name like "package.use" + return value - String. Absolte path of file to write to. None if + no suitable file exists. + """ + file_path = os.path.join(abs_user_config, file_name) + if os.path.exists(file_path): + if os.path.isfile(file_path): + return file_path + elif os.path.isdir(file_path): + try: + files = sorted(f for f in os.listdir(file_path) \ + if os.path.isfile(os.path.join(file_path, f))) + if len(files) != 0: + return os.path.join(file_path, files[-1]) + except OSError: + pass + + + write_to_file = autounmask_write and not pretend + #Make sure we have a file to write to before doing any write. + file_to_write_to = {} + problems = [] + if write_to_file: + for root in roots: + abs_user_config = os.path.join(root, USER_CONFIG_PATH) + + if root in unstable_keyword_msg: + file_to_write_to[(abs_user_config, "package.keywords")] = \ + find_config_file(abs_user_config, "package.keywords") - if license_msg: - writemsg_stdout("\nThe following " + colorize("BAD", "license changes") + \ + if root in use_changes_msg: + file_to_write_to[(abs_user_config, "package.use")] = \ + find_config_file(abs_user_config, "package.use") + + if root in license_msg: + file_to_write_to[(abs_user_config, "package.license")] = \ + find_config_file(abs_user_config, "package.license") + + for (abs_user_config, f), path in file_to_write_to.items(): + if path is None: + problems.append("!!! No file to write for '%s'\n" % os.path.join(abs_user_config, f)) + + write_to_file = not problems + + + protect_obj = {} + if write_to_file: + for root in roots: + settings = self._frozen_config.pkgsettings[root] + protect_obj[root] = ConfigProtect(root, \ + shlex_split(settings.get("CONFIG_PROTECT", "")), + shlex_split(settings.get("CONFIG_PROTECT_MASK", ""))) + + def write_changes(root, change_type, changes, file_to_write_to): + writemsg_stdout("\nThe following " + colorize("BAD", "%s changes" % change_type) + \ " are necessary to proceed:\n", noiselevel=-1) - writemsg_stdout("".join(license_msg), noiselevel=-1) + writemsg_stdout("".join(changes), noiselevel=-1) + if write_to_file: + try: + file_contents = codecs.open( + _unicode_encode(file_to_write_to, + encoding=_encodings['fs'], errors='strict'), + mode='r', encoding=_encodings['content'], + errors='replace').readlines() + except IOError as e: + problems.append("!!! Failed to read '%s': %s\n" % (file_to_write_to, e)) + else: + file_contents.extend(changes) + if protect_obj[root].isprotected(file_to_write_to): + file_to_write_to = new_protect_filename(file_to_write_to) + try: + write_atomic(file_to_write_to, "".join(file_contents)) + except PortageException: + problems.append("!!! Failed to write '%s'\n" % file_to_write_to) + + for root in roots: + abs_user_config = os.path.join(root, USER_CONFIG_PATH) + if len(roots) > 1: + writemsg_stdout("\nFor %s:\n" % abs_user_config, noiselevel=-1) + + if root in unstable_keyword_msg: + write_changes(root, "keyword", unstable_keyword_msg[root], + file_to_write_to.get((abs_user_config, "package.keywords"))) + + if root in use_changes_msg: + write_changes(root, "USE", use_changes_msg[root], + file_to_write_to.get((abs_user_config, "package.use"))) + + if root in license_msg: + write_changes(root, "license", license_msg[root], + file_to_write_to.get((abs_user_config, "package.license"))) + + if problems: + writemsg_stdout("\nThe following problems occured while writing autounmask changes:\n", \ + noiselevel=-1) + writemsg_stdout("".join(problems), noiselevel=-1) + elif write_to_file and roots: + writemsg_stdout("\nAutounmask changes successfully written. Remeber to run etc-update.\n", \ + noiselevel=-1) + elif not pretend and not autounmask_write and roots: + writemsg_stdout("\nUse --autounmask-write to write changes to config files (honoring CONFIG_PROTECT).\n", \ + noiselevel=-1) + + + def display_problems(self): + """ + Display problems with the dependency graph such as slot collisions. + This is called internally by display() to show the problems _after_ + the merge list where it is most likely to be seen, but if display() + is not going to be called then this method should be called explicitly + to ensure that the user is notified of problems with the graph. + + All output goes to stderr, except for unsatisfied dependencies which + go to stdout for parsing by programs such as autounmask. + """ + + # Note that show_masked_packages() sends it's output to + # stdout, and some programs such as autounmask parse the + # output in cases when emerge bails out. However, when + # show_masked_packages() is called for installed packages + # here, the message is a warning that is more appropriate + # to send to stderr, so temporarily redirect stdout to + # stderr. TODO: Fix output code so there's a cleaner way + # to redirect everything to stderr. + sys.stdout.flush() + sys.stderr.flush() + stdout = sys.stdout + try: + sys.stdout = sys.stderr + self._display_problems() + finally: + sys.stdout = stdout + sys.stdout.flush() + sys.stderr.flush() + + # This goes to stdout for parsing by programs like autounmask. + for pargs, kwargs in self._dynamic_config._unsatisfied_deps_for_display: + self._show_unsatisfied_dep(*pargs, **kwargs) + + def _display_problems(self): + if self._dynamic_config._circular_deps_for_display is not None: + self._show_circular_deps( + self._dynamic_config._circular_deps_for_display) + + # The user is only notified of a slot conflict if + # there are no unresolvable blocker conflicts. + if self._dynamic_config._unsatisfied_blockers_for_display is not None: + self._show_unsatisfied_blockers( + self._dynamic_config._unsatisfied_blockers_for_display) + elif self._dynamic_config._slot_collision_info: + self._show_slot_collision_notice() + else: + self._show_missed_update() + + self._display_autounmask() # TODO: Add generic support for "set problem" handlers so that # the below warnings aren't special cases for world only. diff --git a/pym/_emerge/help.py b/pym/_emerge/help.py index 46b29eca7..ddaa62602 100644 --- a/pym/_emerge/help.py +++ b/pym/_emerge/help.py @@ -320,6 +320,13 @@ def help(myopts, havecolor=1): for line in wrap(desc, desc_width): print(desc_indent + line) print() + print(" " + green("--autounmask-write") + " [ %s | %s ]" % \ + (turquoise("y"), turquoise("n"))) + desc = "If --autounmask is enabled, changes are written " + \ + "to config files, respecting CONFIG_PROTECT." + for line in wrap(desc, desc_width): + print(desc_indent + line) + print() print(" " + green("--backtrack") + " " + turquoise("COUNT")) desc = "Specifies an integer number of times to backtrack if " + \ "dependency calculation fails due to a conflict or an " + \ diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py index ee0fc4ed0..7921d7d1c 100644 --- a/pym/_emerge/main.py +++ b/pym/_emerge/main.py @@ -427,6 +427,7 @@ def insert_optional_args(args): default_arg_opts = { '--ask' : y_or_n, '--autounmask' : y_or_n, + '--autounmask-write' : y_or_n, '--buildpkg' : y_or_n, '--complete-graph' : y_or_n, '--deep' : valid_integers, @@ -598,6 +599,12 @@ def parse_opts(tmpcmdline, silent=False): "choices" : true_y_or_n }, + "--autounmask-write": { + "help" : "write changes made by --autounmask to disk", + "type" : "choice", + "choices" : true_y_or_n + }, + "--accept-properties": { "help":"temporarily override ACCEPT_PROPERTIES", "action":"store" @@ -916,6 +923,9 @@ def parse_opts(tmpcmdline, silent=False): if myoptions.autounmask in true_y: myoptions.autounmask = True + if myoptions.autounmask_write in true_y: + myoptions.autounmask_write = True + if myoptions.buildpkg in true_y: myoptions.buildpkg = True else: |