summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/emerge.14
-rw-r--r--pym/_emerge/depgraph.py271
-rw-r--r--pym/_emerge/help.py7
-rw-r--r--pym/_emerge/main.py10
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: