Go to:
Gentoo Home
Documentation
Forums
Lists
Bugs
Planet
Store
Wiki
Get Gentoo!
Gentoo's Bugzilla – Attachment 233889 Details for
Bug 272988
New /etc/portage/hooks/{post,pre}-{run,sync,ebuild,configure,compile,install}.d/ functionality
Home
|
New
–
[Ex]
|
Browse
|
Search
|
Privacy Policy
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Final hooks patch, ready for comments and commit
hooks-bug272988.patch (text/plain), 15.54 KB, created by
Jacob Godserv
on 2010-06-02 21:01:41 UTC
(
hide
)
Description:
Final hooks patch, ready for comments and commit
Filename:
MIME Type:
Creator:
Jacob Godserv
Created:
2010-06-02 21:01:41 UTC
Size:
15.54 KB
patch
obsolete
>diff --git a/doc/config.docbook b/doc/config.docbook >index 88009df..c2bd0ed 100644 >--- a/doc/config.docbook >+++ b/doc/config.docbook >@@ -2,4 +2,5 @@ > <title>Configuration</title> > &config_bashrc; > &config_set; >+&config_hooks; > </part> >diff --git a/doc/config/hooks.docbook b/doc/config/hooks.docbook >new file mode 100644 >index 0000000..ca62f5f >--- /dev/null >+++ b/doc/config/hooks.docbook >@@ -0,0 +1,97 @@ >+<chapter id='config-hooks'> >+ <title>Hooks Configuration</title> >+ <sect1 id='config-hooks-locations'> >+ <title>Hooks Locations</title> >+ <para> >+ If a hook directory exists, the bash scripts within each one >+ wil either be executed before or after that particular stage, in >+ alphabetical order. Each one will receive the environment of an >+ ebuild, so they are capable of inherit, einfo, and other common >+ commands (if you find them useful). Avoid commands that may >+ trigger changes in the filesystem! >+ </para> >+ >+ <para> >+ All hooks are not allowed to directly alter portage's execution, >+ but they can accomplish certain extra tasks at various points, >+ which might indrectly alter portage's execution. Since hooks >+ execute in a bash environment, they are told the parent process >+ ID, which can be used to kill portage if absolutely needed. This >+ might be useful if a hook handled the rest of a certain job, >+ such as syncing, and portage's default behavior is undesired, or >+ if a hook caught potential problems with the rest of portage's >+ execution. >+ </para> >+ >+ <para> >+ A hook script is expected to understand the following usage: >+ <cmdsynopsis> >+ <command>/bin/bash <replaceable>...</replaceable></command><sbr/> >+ >+ <arg>--opt <replaceable>portage arguments, always translated to long form, given by user at the prompt, such as "--verbose" or "--newuse"</replaceable></arg><sbr/> >+ >+ <arg>--action <replaceable>a single action being performed by portage, such as "depclean", "sync", or an ebuild phase</replaceable></arg><sbr/> >+ >+ <arg>--target <replaceable>the thing to perform the action with or on</replaceable></arg> >+ </cmdsynopsis> >+ </para> >+ >+ <para> >+ The following hook directories are supported. The standard hook >+ script usage applies, except wherever described differently. >+ </para> >+ >+ <itemizedlist> >+ <listitem><para><filename>/etc/portage/hooks/pre-ebuild.d/</filename> - executed before every ebuild execution. Never receives --opt, and --target is set to the full path of the ebuild.</para></listitem> >+ <listitem><para><filename>/etc/portage/hooks/post-ebuild.d/</filename> - executed after every ebuild execution. Never receives --opt, and --target is set to the full path of the ebuild.</para></listitem> >+ <listitem><para><filename>/etc/portage/hooks/pre-run.d/</filename> - executed before portage considers most things, including proper permissions and validity of arguments.</para></listitem> >+ <listitem><para><filename>/etc/portage/hooks/post-run.d/</filename> - executed after portage is done. It should run regardless of any errors or signals sent, but this cannot be guaranteed for certain scenarios (such as when the KILL signal is received). No information is available concerning the reason portage is exiting. This is a limitation of python itself.</para></listitem> >+ <listitem><para><filename>/etc/portage/hooks/pre-sync.d/</filename> - executed before portage synchronizes the portage tree.</para></listitem> >+ <listitem><para><filename>/etc/portage/hooks/post-sync.d/</filename> - executed after portage has successfully synchronized the portage tree. Presently you must use a combination of pre-sync and post-run to catch sync failures if desired.</para></listitem> >+ </itemizedlist> >+ </sect1> >+ <sect1 id='config-hooks-skeleton-hook'> >+ <title>Skeleton Hook</title> >+ <para> >+ Most hooks will parse the options at the beginning and look for >+ specific things. This skeleton hook provides that functionality >+ to get you started. Replace the colons with actual code where >+ desired. >+ </para> >+ <para> >+ It's highly recommended that --verbose, --debug, and --quiet be >+ utilized for suppressing or adding to "regular" output. The >+ following skeleton hook already has example code in place to >+ handle these flags. >+ </para> >+ <programlisting> >+ #!/bin/bash >+ >+ verbose_redirect="/dev/null" >+ debug_redirect="/dev/null" >+ while [[ "$1" != "" ]]; do >+ if [[ "$1" == "--opt" ]]; then >+ if [[ "$2" == "--verbose" ]]; then >+ verbose_redirect="/dev/tty" >+ fi >+ if [[ "$2" == "--debug" ]]; then >+ debug_redirect="/dev/tty" >+ fi >+ if [[ "$2" == "--quiet" ]]; then >+ verbose_redirect="/dev/null" >+ debug_redirect="/dev/null" >+ fi >+ elif [[ "$1" == "--action" ]]; then >+ : >+ elif [[ "$1" == "--target" ]]; then >+ : >+ else >+ ewarn "Unknown hook option: $1 $2" > "${verbose_redirect}" 2>&1 >+ fi >+ shift 2 >+ done >+ einfo "This is an example hook." > "${verbose_redirect}" 2>&1 >+ einfo "This is debug output." > "${debug_redirect}" 2>&1 >+ </programlisting> >+ </sect1> >+</chapter> >diff --git a/doc/portage.docbook b/doc/portage.docbook >index 999103a..c754352 100644 >--- a/doc/portage.docbook >+++ b/doc/portage.docbook >@@ -23,6 +23,7 @@ > <!ENTITY config SYSTEM "config.docbook"> > <!ENTITY config_bashrc SYSTEM "config/bashrc.docbook"> > <!ENTITY config_set SYSTEM "config/sets.docbook"> >+ <!ENTITY config_hooks SYSTEM "config/hooks.docbook"> > ]> > > <book id="portage" lang="en"> >diff --git a/man/portage.5 b/man/portage.5 >index 6c78cbd..b918411 100644 >--- a/man/portage.5 >+++ b/man/portage.5 >@@ -62,6 +62,9 @@ repos.conf > .BR /etc/portage/env/ > package-specific bashrc files > .TP >+.BR /etc/portage/hooks/ >+portage pre/post hooks >+.TP > .BR /etc/portage/profile/ > site-specific overrides of \fB/etc/make.profile/\fR > .TP >@@ -637,6 +640,14 @@ order: > /etc/portage/env/${CATEGORY}/${PF} > .RE > .TP >+.BR /etc/portage/hooks/ >+.RS >+In this directory, portage hooks are executed before each ebuild phase, >+before and after synchronization, and before and after portage runs >+themselves. Please see the DocBook documentation for detailed >+information. >+.RE >+.TP > .BR /usr/portage/metadata/ > .RS > .TP >diff --git a/pym/_emerge/actions.py b/pym/_emerge/actions.py >index 148b8c3..052455c 100644 >--- a/pym/_emerge/actions.py >+++ b/pym/_emerge/actions.py >@@ -33,6 +33,7 @@ from portage.output import blue, bold, colorize, create_color_func, darkgreen, \ > red, yellow > good = create_color_func("GOOD") > bad = create_color_func("BAD") >+from portage.hooks import HookDirectory > from portage.sets import load_default_config, SETPREFIX > from portage.sets.base import InternalPackageSet > from portage.util import cmp_sort_key, writemsg, writemsg_level >@@ -1817,6 +1818,7 @@ def action_sync(settings, trees, mtimedb, myopts, myaction): > os.umask(0o022) > dosyncuri = syncuri > updatecache_flg = False >+ HookDirectory(phase='pre-sync', settings=settings, myopts=myopts, myaction=myaction).execute() > if myaction == "metadata": > print("skipping sync") > updatecache_flg = True >@@ -2260,6 +2262,8 @@ def action_sync(settings, trees, mtimedb, myopts, myaction): > if retval != os.EX_OK: > print(red(" * ") + bold("spawn failed of " + postsync)) > >+ HookDirectory(phase='post-sync', settings=settings, myopts=myopts, myaction=myaction).execute() >+ > if(mybestpv != mypvs) and not "--quiet" in myopts: > print() > print(red(" * ")+bold("An update to portage is available.")+" It is _highly_ recommended") >diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py >index 9e91ee9..baf7779 100644 >--- a/pym/_emerge/main.py >+++ b/pym/_emerge/main.py >@@ -27,6 +27,8 @@ bad = create_color_func("BAD") > import portage.elog > import portage.dep > portage.dep._dep_check_strict = True >+import portage.hooks >+import portage.process > import portage.util > import portage.locks > import portage.exception >@@ -1233,6 +1235,12 @@ def emerge_main(): > os.umask(0o22) > settings, trees, mtimedb = load_emerge_config() > portdb = trees[settings["ROOT"]]["porttree"].dbapi >+ >+ # Have post-run hooks executed whenever portage quits >+ portage.process.atexit_register(portage.hooks.HookDirectory(phase='post-run', settings=settings, myopts=myopts, myaction=myaction, mytargets=myfiles).execute) >+ # Portage configured; let's let hooks run before we do anything more >+ portage.hooks.HookDirectory(phase='pre-run', settings=settings, myopts=myopts, myaction=myaction, mytargets=myfiles).execute() >+ > rval = profile_check(trees, myaction) > if rval != os.EX_OK: > return rval >diff --git a/pym/portage/const.py b/pym/portage/const.py >index 445677b..1fd48c3 100644 >--- a/pym/portage/const.py >+++ b/pym/portage/const.py >@@ -35,6 +35,7 @@ CUSTOM_PROFILE_PATH = USER_CONFIG_PATH + "/profile" > USER_VIRTUALS_FILE = USER_CONFIG_PATH + "/virtuals" > EBUILD_SH_ENV_FILE = USER_CONFIG_PATH + "/bashrc" > EBUILD_SH_ENV_DIR = USER_CONFIG_PATH + "/env" >+HOOKS_PATH = USER_CONFIG_PATH + "/hooks" > CUSTOM_MIRRORS_FILE = USER_CONFIG_PATH + "/mirrors" > COLOR_MAP_FILE = USER_CONFIG_PATH + "/color.map" > PROFILE_PATH = "etc/make.profile" >diff --git a/pym/portage/hooks.py b/pym/portage/hooks.py >new file mode 100644 >index 0000000..ec4436d >--- /dev/null >+++ b/pym/portage/hooks.py >@@ -0,0 +1,84 @@ >+# Copyright 1998-2010 Gentoo Foundation >+# Distributed under the terms of the GNU General Public License v2 >+# $Id$ >+ >+from portage.const import BASH_BINARY, HOOKS_PATH, PORTAGE_BIN_PATH >+from portage import os >+from portage import check_config_instance >+from portage import normalize_path >+from portage.exception import PortageException >+from portage.exception import InvalidLocation >+from portage.output import EOutput >+from process import spawn >+ >+class HookDirectory(object): >+ >+ def __init__ (self, phase, settings, myopts=None, myaction=None, mytargets=None): >+ self.myopts = myopts >+ self.myaction = myaction >+ self.mytargets = mytargets >+ check_config_instance(settings) >+ self.settings = settings >+ self.path = os.path.join(settings["PORTAGE_CONFIGROOT"], HOOKS_PATH, phase + '.d') >+ self.output = EOutput() >+ >+ def execute (self, path=None): >+ if not path: >+ path = self.path >+ >+ path = normalize_path(path) >+ >+ if not os.path.exists(path): >+ if self.myopts and "--debug" in self.myopts: >+ self.output.ewarn('This hook path could not be found; ignored: ' + path) >+ return >+ >+ if os.path.isdir(path): >+ for parent, dirs, files in os.walk(path): >+ for dir in dirs: >+ if self.myopts and "--debug" in self.myopts: >+ self.output.ewarn('Directory within hook directory not allowed; ignored: ' + path+'/'+dir) >+ for filename in files: >+ HookFile(os.path.join(path, filename), self.settings, self.myopts, self.myaction, self.mytargets).execute() >+ >+ else: >+ raise InvalidLocation('This hook path ought to be a directory: ' + path) >+ >+class HookFile (object): >+ >+ def __init__ (self, path, settings, myopts=None, myaction=None, mytargets=None): >+ self.myopts = myopts >+ self.myaction = myaction >+ self.mytargets = mytargets >+ check_config_instance(settings) >+ self.path = normalize_path(path) >+ self.settings = settings >+ self.output = EOutput() >+ >+ def execute (self): >+ if "hooks" not in self.settings['FEATURES']: >+ return >+ >+ if not os.path.exists(self.path): >+ raise InvalidLocation('This hook path could not be found: ' + self.path) >+ >+ if os.path.isfile(self.path): >+ command=[self.path] >+ if self.myopts: >+ for myopt in self.myopts: >+ command.extend(['--opt', myopt]) >+ if self.myaction: >+ command.extend(['--action', self.myaction]) >+ if self.mytargets: >+ for mytarget in self.mytargets: >+ command.extend(['--target', mytarget]) >+ >+ command=[BASH_BINARY, '-c', 'source ' + PORTAGE_BIN_PATH + '/isolated-functions.sh && source ' + ' '.join(command)] >+ if self.myopts and "--verbose" in self.myopts: >+ self.output.einfo('Executing hook "' + self.path + '"...') >+ code = spawn(mycommand=command, env=self.settings.environ()) >+ if code: # if failure >+ raise PortageException('!!! Hook %s failed with exit code %s' % (self.path, code)) >+ >+ else: >+ raise InvalidLocation('This hook path ought to be a file: ' + self.path) >diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py >index 531cb23..9bb2b88 100644 >--- a/pym/portage/package/ebuild/doebuild.py >+++ b/pym/portage/package/ebuild/doebuild.py >@@ -44,6 +44,7 @@ from portage.elog.messages import eerror, eqawarn > from portage.exception import DigestException, FileNotFound, \ > IncorrectParameter, InvalidAtom, InvalidDependString, PermissionDenied, \ > UnsupportedAPIException >+from portage.hooks import HookDirectory > from portage.localization import _ > from portage.manifest import Manifest > from portage.output import style_to_ansi_code >@@ -547,6 +548,8 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, > doebuild_environment(myebuild, mydo, myroot, mysettings, debug, > use_cache, mydbapi) > >+ HookDirectory(phase='pre-ebuild', settings=mysettings, myopts=None, myaction=mydo, mytargets=[mysettings["EBUILD"]]).execute() >+ > if mydo in clean_phases: > retval = spawn(_shell_quote(ebuild_sh_binary) + " clean", > mysettings, debug=debug, fd_pipes=fd_pipes, free=1, >@@ -1048,6 +1051,8 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, > # If necessary, depend phase has been triggered by aux_get calls > # and the exemption is no longer needed. > portage._doebuild_manifest_exempt_depend -= 1 >+ >+ HookDirectory(phase='post-ebuild', settings=mysettings, myopts=None, myaction=mydo, mytargets=[mysettings["EBUILD"]]).execute() > > def _validate_deps(mysettings, myroot, mydo, mydbapi): > >diff --git a/pym/portage/tests/hooks/__init__.py b/pym/portage/tests/hooks/__init__.py >new file mode 100644 >index 0000000..95dfcfc >--- /dev/null >+++ b/pym/portage/tests/hooks/__init__.py >@@ -0,0 +1,5 @@ >+# tests/portage/hooks/__init__.py -- Portage Unit Test functionality >+# Copyright 2010 Gentoo Foundation >+# Distributed under the terms of the GNU General Public License v2 >+# $Id$ >+ >diff --git a/pym/portage/tests/hooks/__test__ b/pym/portage/tests/hooks/__test__ >new file mode 100644 >index 0000000..e69de29 >diff --git a/pym/portage/tests/hooks/test_HookDirectory.py b/pym/portage/tests/hooks/test_HookDirectory.py >new file mode 100644 >index 0000000..d19c47b >--- /dev/null >+++ b/pym/portage/tests/hooks/test_HookDirectory.py >@@ -0,0 +1,49 @@ >+# test_HookDirectory.py -- Portage Unit Testing Functionality >+# Copyright 2010 Gentoo Foundation >+# Distributed under the terms of the GNU General Public License v2 >+# $Id$ >+ >+from portage import os >+from portage.hooks import HookDirectory >+from portage.package.ebuild.config import config >+from portage.tests import TestCase >+from tempfile import mkdtemp >+from shutil import rmtree >+ >+# http://stackoverflow.com/questions/845058/how-to-get-line-count-cheaply-in-python >+def file_len(fname): >+ with open(fname) as f: >+ for i, l in enumerate(f): >+ pass >+ return i + 1 >+ >+class HookDirectoryTestCase(TestCase): >+ >+ def testHookDirectory(self): >+ """ >+ Tests to be sure a hook loads and reads the right settings >+ Based on test_PackageKeywordsFile.py >+ """ >+ >+ tmp_dir_path = self.BuildTmp('/etc/portage/hooks/test.d') >+ try: >+ settings = config() >+ settings["PORTAGE_CONFIGROOT"] = tmp_dir_path >+ settings["FEATURES"] += " hooks" >+ hooks = HookDirectory('test', settings) >+ hooks.execute() >+ self.assert_(file_len(tmp_dir_path+'/output') == 1) >+ finally: >+ rmtree(tmp_dir_path) >+ >+ def BuildTmp(self, tmp_subdir): >+ tmp_dir = mkdtemp() >+ hooks_dir = tmp_dir + '/' + tmp_subdir >+ os.makedirs(hooks_dir) >+ >+ f = open(hooks_dir+'/testhook', 'w') >+ f.write('#!/bin/bash\n') >+ f.write('echo hi > '+tmp_dir+'/output && exit 0\n') >+ f.close() >+ >+ return tmp_dir
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 272988
:
233213
|
233889
|
233891
|
233991
|
233993
|
238177
|
238181
|
238191
|
238193
|
299457
|
299463