wrapped within any conditionals\fR of any kind. Specification of the eclasses
contains only their name and not the \fI.eclass\fR extension. Also note that
the inherit statement must come before other variable declarations.
-.SH "FUNCTIONS"
+.SH "PHASE FUNCTIONS"
.TP
.B pkg_nofetch
If you turn on \fIfetch\fR in \fBRESTRICT\fR, then this function will be
.br
Initial working directory of $WORKDIR.
.TP
+.B src_prepare
+All preparation of source code, such as application of patches, should be done
+here. This function is supported beginning with \fBEAPI 2\fR.
+.br
+Initial working directory of $S.
+.TP
+.B src_configure
+All necessary steps for configuration should be done here. This function is
+supported beginning with \fBEAPI 2\fR.
+.br
+Initial working directory of $S.
+.TP
.B src_compile
-All necessary steps for configuration and compilation should be done in here.
+With less than \fBEAPI 2\fR, all necessary steps for both configuration and
+compilation should be done here. Beginning with \fBEAPI 2\fR, only compilation
+steps should be done here.
.br
Initial working directory of $S.
.TP
This function should contain optional basic configuration steps.
.br
Initial working directory of $PWD.
+.SH "HELPER FUNCTIONS: PHASES"
+.TP
+.B default
+Calls the default phase function implementation for the currently executing
+phase. This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_pkg_nofetch
+Calls the default pkg_nofetch phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_unpack
+Calls the default src_unpack phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_prepare
+Calls the default src_prepare phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_configure
+Calls the default src_configure phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_compile
+Calls the default src_compile phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_test
+Calls the default src_test phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
.SH "HELPER FUNCTIONS: GENERAL"
.TP
\fBdie\fR \fI[reason]\fR
manual file ending. The files are compressed if they are not already. You
can specify locale\-specific manpages with the \fI\-i18n\fR option. Then the
man\-page will be installed into /usr/share/man/\fI<locale>\fR/man[0\-9n].
+Beginning with \fBEAPI 2\fR, a locale\-specific manpage which contains a locale
+in the file name will be installed in /usr/share/man/\fI<locale>\fR/man[0\-9n],
+with the locale portion of the file name removed, and the \fI\-i18n\fR option
+has no effect. For example, with \fBEAPI 2\fR, a manpage named
+foo.\fI<locale>\fR.1 will be installed as
+/usr/share/man/\fI<locale>\fR/man1/foo.1.
.PD 0
.TP
\fBdohard\fR \fI<filename> <linkname>\fR
"DisplayProfileRestriction", "DisplayKeywordRestriction",
"DisplayInstalledRestriction"]
-import errno
+import logging
import os
import re
-from portage.util import apply_permissions, ensure_dirs, grabfile, \
- grablines, normalize_path, write_atomic
+from portage.util import apply_secpass_permissions, ensure_dirs, \
+ grabfile, normalize_path, write_atomic, writemsg_level
from portage.data import portage_gid
+from portage.dep import isvalidatom
from portage.locks import lockfile, unlockfile
-from portage.exception import OperationNotPermitted
+from portage.exception import InvalidLocation, OperationNotPermitted, \
+ PermissionDenied
class NewsManager(object):
"""
self.vdb = vardb
self.portdb = portdb
+ # GLEP 42 says:
+ # All news item related files should be root owned and in the
+ # portage group with the group write (and, for directories,
+ # execute) bits set. News files should be world readable.
+ self._uid = int(self.config["PORTAGE_INST_UID"])
+ self._gid = portage_gid
+ self._file_mode = 00064
+ self._dir_mode = 00074
+ self._mode_mask = 00000
+
portdir = portdb.porttree_root
profiles_base = os.path.join(portdir, 'profiles') + os.path.sep
profile_path = None
profile_path = profile_path[len(profiles_base):]
self._profile_path = profile_path
- # Ensure that the unread path exists and is writable.
- dirmode = 02070
- modemask = 02
- try:
- ensure_dirs(self.unread_path, mode=dirmode,
- mask=modemask, gid=portage_gid)
- except OperationNotPermitted:
- pass
+ def _unread_filename(self, repoid):
+ return os.path.join(self.unread_path, 'news-%s.unread' % repoid)
+
+ def _skip_filename(self, repoid):
+ return os.path.join(self.unread_path, 'news-%s.skip' % repoid)
+
+ def _news_dir(self, repoid):
+ repo_path = self.portdb.getRepositoryPath(repoid)
+ if repo_path is None:
+ raise AssertionError("Invalid repoID: %s" % repoid)
+ return os.path.join(repo_path, self.news_path)
def updateItems(self, repoid):
"""
items into the news.repoid.unread file.
"""
- repos = self.portdb.getRepositories()
- if repoid not in repos:
- raise ValueError("Invalid repoID: %s" % repoid)
+ # Ensure that the unread path exists and is writable.
- path = os.path.join(self.portdb.getRepositoryPath(repoid), self.news_path)
+ try:
+ ensure_dirs(self.unread_path, uid=self._uid, gid=self._gid,
+ mode=self._dir_mode, mask=self._mode_mask)
+ except (OperationNotPermitted, PermissionDenied):
+ return
- # Skip reading news for repoid if the news dir does not exist. Requested by
- # NightMorph :)
- if not os.path.exists(path):
- return None
- news = os.listdir(path)
+ if not os.access(self.unread_path, os.W_OK):
+ return
- skipfile = os.path.join(self.unread_path, "news-%s.skip" % repoid)
- skiplist = grabfile(skipfile)
- updates = []
- for itemid in news:
- if itemid in skiplist:
- continue
- try:
- filename = os.path.join(path, itemid, itemid + "." + self.language_id + ".txt")
- item = NewsItem(filename, itemid)
- except (TypeError):
- continue
- if item.isRelevant(profile=self._profile_path,
- config=self.config, vardb=self.vdb):
- updates.append(item)
- del path
-
- path = os.path.join(self.unread_path, 'news-%s.unread' % repoid)
- unread_lock = None
+ news_dir = self._news_dir(repoid)
try:
- unread_lock = lockfile(path)
- if not os.path.exists(path):
- #create the file if it does not exist
- open(path, "w")
- # Ensure correct perms on the unread file.
- apply_permissions( filename=path,
- uid=int(self.config['PORTAGE_INST_UID']), gid=portage_gid, mode=0664)
- # Make sure we have the correct permissions when created
- unread_file = open(path, 'a')
-
- for item in updates:
- unread_file.write(item.name + "\n")
- skiplist.append(item.name)
- unread_file.close()
- finally:
- if unread_lock:
- unlockfile(unread_lock)
- write_atomic(skipfile, "\n".join(skiplist)+"\n")
+ news = os.listdir(news_dir)
+ except OSError:
+ return
+
+ skip_filename = self._skip_filename(repoid)
+ unread_filename = self._unread_filename(repoid)
+ unread_lock = lockfile(unread_filename, wantnewlockfile=1)
try:
- apply_permissions(filename=skipfile,
- uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=0664)
- except OperationNotPermitted, e:
- import errno
- # skip "permission denied" errors as we're likely running in pretend mode
- # with reduced priviledges
- if e.errno == errno.EPERM:
- pass
- else:
- raise
+ unread = set(grabfile(unread_filename))
+ unread_orig = unread.copy()
+ skip = set(grabfile(skip_filename))
+ skip_orig = skip.copy()
+
+ updates = []
+ for itemid in news:
+ if itemid in skip:
+ continue
+ filename = os.path.join(news_dir, itemid,
+ itemid + "." + self.language_id + ".txt")
+ if not os.path.isfile(filename):
+ continue
+ item = NewsItem(filename, itemid)
+ if not item.isValid():
+ continue
+ if item.isRelevant(profile=self._profile_path,
+ config=self.config, vardb=self.vdb):
+ unread.add(item.name)
+ skip.add(item.name)
+
+ if unread != unread_orig:
+ write_atomic(unread_filename,
+ "".join("%s\n" % x for x in sorted(unread)))
+ apply_secpass_permissions(unread_filename,
+ uid=self._uid, gid=self._gid,
+ mode=self._file_mode, mask=self._mode_mask)
+
+ if skip != skip_orig:
+ write_atomic(skip_filename,
+ "".join("%s\n" % x for x in sorted(skip)))
+ apply_secpass_permissions(skip_filename,
+ uid=self._uid, gid=self._gid,
+ mode=self._file_mode, mask=self._mode_mask)
+
+ finally:
+ unlockfile(unread_lock)
def getUnreadItems(self, repoid, update=False):
"""
if update:
self.updateItems(repoid)
- unreadfile = os.path.join(self.unread_path, 'news-%s.unread' % repoid)
+ unread_filename = self._unread_filename(repoid)
unread_lock = None
try:
- if os.access(os.path.dirname(unreadfile), os.W_OK):
- # TODO: implement shared readonly locks
- unread_lock = lockfile(unreadfile)
-
- return len(grablines(unreadfile))
-
+ unread_lock = lockfile(unread_filename, wantnewlockfile=1)
+ except (InvalidLocation, OperationNotPermitted, PermissionDenied):
+ return 0
+ try:
+ return len(grabfile(unread_filename))
finally:
if unread_lock:
unlockfile(unread_lock)
"""
For a given news item we only want if it path is a file.
"""
- if not os.path.isfile(path):
- raise TypeError("%s is no regular file" % path)
self.path = path
self.name = name
self._parsed = False
+ self._valid = True
def isRelevant(self, vardb, config, profile):
"""
or raise a ValueError exception if the required object is not present.
"""
+ if not self._parsed:
+ self.parse()
+
if not len(self.restrictions):
return True # no restrictions to match means everyone should see it
return False # No restrictions were met; thus we aren't relevant :(
+ def isValid(self):
+ if not self._parsed:
+ self.parse()
+ return self._valid
+
def parse(self):
lines = open(self.path).readlines()
self.restrictions = []
- for line in lines:
+ invalids = []
+ for i, line in enumerate(lines):
#Optimization to ignore regex matchines on lines that
#will never match
if not line.startswith('D'):
match = regex.match(line)
if match:
self.restrictions.append(restriction(match.groups()[0].strip()))
+ if not self.restrictions[-1].isValid():
+ invalids.append((i + 1, line.rstrip("\n")))
continue
- self._parsed = True
+ if invalids:
+ self._valid = False
+ msg = []
+ msg.append("Invalid news item: %s" % (self.path,))
+ for lineno, line in invalids:
+ msg.append(" line %d: %s" % (lineno, line))
+ writemsg_level("".join("!!! %s\n" % x for x in msg),
+ level=logging.ERROR, noiselevel=-1)
- def __getattr__(self, attr):
- if not self._parsed:
- self.parse()
- return self.__dict__[attr]
+ self._parsed = True
class DisplayRestriction(object):
"""
are met, then it is displayed
"""
+ def isValid(self):
+ return True
+
def checkRestriction(self, **kwargs):
raise NotImplementedError('Derived class should over-ride this method')
if the user has that item installed.
"""
- def __init__(self, cpv):
- self.cpv = cpv
+ def __init__(self, atom):
+ self.atom = atom
+
+ def isValid(self):
+ return isvalidatom(self.atom)
def checkRestriction(self, **kwargs):
vdb = kwargs['vardb']
- if vdb.match(self.cpv):
+ if vdb.match(self.atom):
return True
return False