def post_emerge(settings, mtimedb, retval):
+ """
+ Misc. things to run at the end of a merge session.
+
+ Update Info Files
+ Update Config Files
+ Update News Items
+ Commit mtimeDB
+ Exit Emerge
+
+ @param settings: Configuration settings (typically portage.settings)
+ @type settings: portage.config()
+ @param mtimedb: The mtimeDB to store data needed across merge invocations
+ @type mtimedb: MtimeDB class instance
+ @param retval: Emerge's return value
+ @type retval: Int
+ @rype: None
+ @returns:
+ 1. Calls sys.exit(retval)
+ """
target_root = settings["ROOT"]
info_mtimes = mtimedb["info"]
config_protect = settings.get("CONFIG_PROTECT","").split()
normalize_path(target_root + EPREFIX),
infodirs, info_mtimes, retval)
chk_updated_cfg_files(normalize_path(target_root + EPREFIX), config_protect)
+
+ NEWS_PATH = os.path.join( "metadata", "news" )
+ UNREAD_PATH = os.path.join( target_root, portage.PRIVATE_PATH, 'news')
+ repo_id = 'gentoo'
+ unreadItems = checkUpdatedNewsItems(target_root, NEWS_PATH, UNREAD_PATH, repo_id)
+ if unreadItems:
+ print colorize("WARN", " * IMPORTANT:"),
+ print "%s news items need reading." % unreadItems
+ print colorize("WARN", " *"),
+ print "Type something to read the news"
mtimedb.commit()
sys.exit(retval)
print " "+yellow("*")+" Type "+green("emerge --help config")+" to learn how to update config files."
print
+def checkUpdatedNewsItems( root, NEWS_PATH, UNREAD_PATH, repo_id ):
+ """
+ Examines news items in repodir + '/' + NEWS_PATH and attempts to find unread items
+ Returns the number of unread (yet relevent) items.
+
+ @param root:
+ @type root:
+ @param NEWS_PATH:
+ @type NEWS_PATH:
+ @param UNREAD_PATH:
+ @type UNREAD_PATH:
+ @param repo_id:
+ @type repo_id:
+ @rtype: Integer
+ @returns:
+ 1. The number of unread but relevant news items.
+
+ """
+ from portage_news import NewsManager
+ manager = NewsManager( root, NEWS_PATH, UNREAD_PATH )
+ return manager.getUnreadItems( repo_id, update=True )
+
def is_valid_package_atom(x):
try:
testkey = portage.dep_getkey(x)
MOVE_BINARY, PRELINK_BINARY, WORLD_FILE, MAKE_CONF_FILE, MAKE_DEFAULTS_FILE, \
DEPRECATED_PROFILE_FILE, USER_VIRTUALS_FILE, EBUILD_SH_ENV_FILE, \
INVALID_ENV_FILE, CUSTOM_MIRRORS_FILE, CONFIG_MEMORY_FILE,\
- INCREMENTALS, EAPI, MISC_SH_BINARY, EPREFIX
+ INCREMENTALS, EAPI, MISC_SH_BINARY, REPO_NAME_LOC, REPO_NAME_FILE, \
+ EPREFIX
from portage_data import ostype, lchown, userland, secpass, uid, wheelgid, \
portage_uid, portage_gid, userpriv_groups
return rValue
class portagetree:
- def __init__(self, root="/", virtual=None, clone=None, settings=None):
+ def __init__(self, root="/", virtual=None, clone=None, settings=None, portroot=None):
+ """
+ Constructor for a PortageTree
+
+ Note: Portroot was added for GLEP 42 functionality and defaults to the $PORTDIR
+ env variable.
+
+ @param root: ${ROOT}, defaults to '/', see make.conf(5)
+ @type root: String/Path
+ @param virtual: UNUSED
+ @type virtual: No Idea
+ @param clone: Set this if you want a copy of Clone
+ @type clone: Existing portagetree Instance
+ @param settings: Portage Configuration object (portage.settings)
+ @type settings: Instance of portage.config
+ """
if clone:
- self.root=clone.root
- self.portroot=clone.portroot
- self.pkglines=clone.pkglines
+ self.root = clone.root
+ self.portroot = clone.portroot
+ self.pkglines = clone.pkglines
else:
- self.root=root
+ self.root = root
if settings is None:
settings = globals()["settings"]
self.settings = settings
- self.portroot=settings["PORTDIR"]
- self.virtual=virtual
+ self.portroot = settings["PORTDIR"]
+ self.virtual = virtual
self.dbapi = portdbapi(
settings["PORTDIR"], mysettings=settings)
self.porttrees = [self.porttree_root] + \
[os.path.realpath(t) for t in self.mysettings["PORTDIR_OVERLAY"].split()]
+ self.treemap = {}
+ for path in self.porttrees:
+ try:
+ repo_name = open( os.path.join( path , REPO_NAME_LOC ) ,'r').readline().rstrip()
+ self.treemap[repo_name] = path
+ except (OSError,IOError):
+ pass
+
self.auxdbmodule = self.mysettings.load_best_module("portdbapi.auxdbmodule")
self.auxdb = {}
self._init_cache_dirs()
def findname(self,mycpv):
return self.findname2(mycpv)[0]
+ def getRepositoryPath( self, repository_id ):
+ """
+ This function is required for GLEP 42 compliance; given a valid repository ID
+ it must return a path to the repository
+ TreeMap = { id:path }
+ """
+ if repository_id in self.treemap:
+ return self.treemap[repository_id]
+ return None
+
+ def getRepositories( self ):
+ """
+ This function is required for GLEP 42 compliance; it will return a list of
+ repository ID's
+ TreeMap = { id:path }
+ """
+ return [k for k in self.treemap.keys() if k]
+
def findname2(self, mycpv, mytree=None):
"""
Returns the location of the CPV, and what overlay it was in.
--- /dev/null
+# portage: news management code
+# Copyright 2006 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+from portage_const import PRIVATE_PATH, INCREMENTALS, PROFILE_PATH
+from portage import config, vartree, vardbapi, portdbapi
+from portage_util import ensure_dirs
+from portage_data import portage_gid
+from portage_locks import lockfile, unlockfile
+
+import os, re
+
+class NewsManager(object):
+ """
+ This object manages GLEP 42 style news items. It will cache news items
+ that have previously shown up and notify users when there are relevant news
+ items that apply to their packages that the user has not previously read.
+
+ Creating a news manager requires:
+ root - typically ${ROOT} see man make.conf and man emerge for details
+ NEWS_PATH - path to news items; usually $REPODIR/metadata/news
+ UNREAD_PATH - path to the news.repoid.unread file; this helps us track news items
+
+ """
+
+ TIMESTAMP_FILE = "news-timestamp"
+
+ def __init__( self, root, NEWS_PATH, UNREAD_PATH, LANGUAGE_ID='en' ):
+ self.NEWS_PATH = NEWS_PATH
+ self.UNREAD_PATH = UNREAD_PATH
+ self.TIMESTAMP_PATH = os.path.join( root, PRIVATE_PATH, NewsManager.TIMESTAMP_FILE )
+ self.target_root = root
+ self.LANGUAGE_ID = LANGUAGE_ID
+ self.config = config( config_root = os.environ.get("PORTAGE_CONFIGROOT", "/"),
+ target_root = root, config_incrementals = INCREMENTALS)
+ self.vdb = vardbapi( settings = self.config, root = root,
+ vartree = vartree( root = root, settings = self.config ) )
+ self.portdb = portdbapi( porttree_root = self.config["PORTDIR"], mysettings = self.config )
+
+ # Ensure that the unread path exists and is writable.
+ dirmode = 02070
+ modemask = 02
+ ensure_dirs(self.UNREAD_PATH, mode=dirmode, mask=modemask, gid=portage_gid)
+
+ def updateItems( self, repoid ):
+ """
+ Figure out which news items from NEWS_PATH are both unread and relevant to
+ the user (according to the GLEP 42 standards of relevancy). Then add these
+ items into the news.repoid.unread file.
+ """
+
+ repos = self.portdb.getRepositories()
+ if repoid not in repos:
+ raise ValueError("Invalid repoID: %s" % repoid)
+
+ if os.path.exists(self.TIMESTAMP_PATH):
+ timestamp = os.stat(self.TIMESTAMP_PATH).st_mtime
+ else:
+ timestamp = 0
+
+ path = os.path.join( self.portdb.getRepositoryPath( repoid ), self.NEWS_PATH )
+ news = os.listdir( path )
+ updates = []
+ for item in news:
+ try:
+ file = os.path.join( path, item, item + "." + self.LANGUAGE_ID + ".txt")
+ tmp = NewsItem( file , timestamp )
+ except TypeError:
+ continue
+
+ if tmp.isRelevant( profile=os.readlink(PROFILE_PATH), config=config, vardb=self.vdb):
+ updates.append( tmp )
+ del path
+
+ path = os.path.join( self.UNREAD_PATH, "news-" + repoid + ".unread" )
+ lockfile( path )
+ unread_file = open( path, "a" )
+ for item in updates:
+ unread_file.write( item.path + "\n" )
+
+ unread_file.close()
+ unlockfile(path)
+
+ # Touch the timestamp file
+ f = open(self.TIMESTAMP_PATH, "w")
+ f.close()
+
+ def getUnreadItems( self, repoid, update=False ):
+ """
+ Determine if there are unread relevant items in news.repoid.unread.
+ If there are unread items return their number.
+ If update is specified, updateNewsItems( repoid ) will be called to
+ check for new items.
+ """
+
+ if update:
+ self.updateItems( repoid )
+
+ unreadfile = os.path.join( self.UNREAD_PATH, "news-"+ repoid +".unread" )
+ lockfile(unreadfile)
+ if os.path.exists( unreadfile ):
+ unread = open( unreadfile ).readlines()
+ if len(unread):
+ return len(unread)
+ unlockfile(unread)
+
+_installedRE = re.compile("Display-If-Installed:(.*)\n")
+_profileRE = re.compile("Display-If-Profile:(.*)\n")
+_keywordRE = re.compile("Display-If-Keyword:(.*)\n")
+
+class NewsItem(object):
+ """
+ This class encapsulates a GLEP 42 style news item.
+ It's purpose is to wrap parsing of these news items such that portage can determine
+ whether a particular item is 'relevant' or not. This requires parsing the item
+ and determining 'relevancy restrictions'; these include "Display if Installed" or
+ "display if arch: x86" and so forth.
+
+ Creation of a news item involves passing in the path to the particular news item.
+
+ """
+
+ def __init__( self, path, cache_mtime = 0 ):
+ """
+ For a given news item we only want if it path is a file and it's
+ mtime is newer than the cache'd timestamp.
+ """
+ if not os.path.isfile( path ):
+ raise TypeError
+ if not os.stat( path ).st_mtime > cache_mtime:
+ raise TypeError
+ self.path = path
+ self._parsed = False
+
+ def isRelevant( self, vardb, config, profile ):
+ """
+ This function takes a dict of keyword arguments; one should pass in any
+ objects need to do to lookups (like what keywords we are on, what profile,
+ and a vardb so we can look at installed packages).
+ Each restriction will pluck out the items that are required for it to match
+ or raise a ValueError exception if the required object is not present.
+ """
+
+ if not len(self.restrictions):
+ return True # no restrictions to match means everyone should see it
+
+ kwargs = { 'vardb' : vardb,
+ 'config' : config,
+ 'profile' : profile }
+
+ for restriction in self.restrictions:
+ if restriction.checkRestriction( **kwargs ):
+ return True
+
+ return False # No restrictions were met; thus we aren't relevant :(
+
+ def parse( self ):
+ lines = open(self.path).readlines()
+ self.restrictions = []
+ for line in lines:
+ #Optimization to ignore regex matchines on lines that
+ #will never match
+ if not line.startswith("D"):
+ continue
+ match = _installedRE.match( line )
+ if match:
+ self.restrictions.append(
+ DisplayInstalledRestriction( match.groups()[0].strip().rstrip() ) )
+ continue
+ match = _profileRE.match( line )
+ if match:
+ self.restrictions.append(
+ DisplayProfileRestriction( match.groups()[0].strip().rstrip() ) )
+ continue
+ match = _keywordRE.match( line )
+ if match:
+ self.restrictions.append(
+ DisplayKeywordRestriction( match.groups()[0].strip().rstrip() ) )
+ continue
+ self._parsed = True
+
+ def __getattr__( self, attr ):
+ if not self._parsed:
+ self.parse()
+ return self.__dict__[attr]
+
+class DisplayRestriction(object):
+ """
+ A base restriction object representing a restriction of display.
+ news items may have 'relevancy restrictions' preventing them from
+ being important. In this case we need a manner of figuring out if
+ a particular item is relevant or not. If any of it's restrictions
+ are met, then it is displayed
+ """
+
+ def checkRestriction( self, **kwargs ):
+ raise NotImplementedError("Derived class should over-ride this method")
+
+class DisplayProfileRestriction(DisplayRestriction):
+ """
+ A profile restriction where a particular item shall only be displayed
+ if the user is running a specific profile.
+ """
+
+ def __init__( self, profile ):
+ self.profile = profile
+
+ def checkRestriction( self, **kwargs ):
+ if self.profile == kwargs['profile']:
+ return True
+ return False
+
+class DisplayKeywordRestriction(DisplayRestriction):
+ """
+ A keyword restriction where a particular item shall only be displayed
+ if the user is running a specific keyword.
+ """
+
+ def __init__( self, keyword ):
+ self.keyword = keyword
+
+ def checkRestriction( self, **kwargs ):
+ if kwargs['config']["ARCH"] == self.keyword:
+ return True
+ return False
+
+class DisplayInstalledRestriction(DisplayRestriction):
+ """
+ An Installation restriction where a particular item shall only be displayed
+ if the user has that item installed.
+ """
+
+ def __init__( self, cpv ):
+ self.cpv = cpv
+
+ def checkRestriction( self, **kwargs ):
+ vdb = kwargs['vardb']
+ if vdb.match( self.cpv ):
+ return True
+ return False