Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
View | Details | Raw Unified | Return to bug 272988 | Differences between
and this patch

Collapse All | Expand All

(-)a/doc/config.docbook (+1 lines)
Lines 2-5 Link Here
2
<title>Configuration</title>
2
<title>Configuration</title>
3
&config_bashrc;
3
&config_bashrc;
4
&config_set;
4
&config_set;
5
&config_hooks;
5
</part>
6
</part>
(-)a/doc/config/hooks.docbook (+97 lines)
Line 0 Link Here
1
<chapter id='config-hooks'>
2
	<title>Hooks Configuration</title>
3
	<sect1 id='config-hooks-locations'>
4
		<title>Hooks Locations</title>
5
		<para>
6
		If a hook directory exists, the bash scripts within each one
7
		wil either be executed before or after that particular stage, in
8
		alphabetical order. Each one will receive the environment of an
9
		ebuild, so they are capable of inherit, einfo, and other common
10
		commands (if you find them useful). Avoid commands that may
11
		trigger changes in the filesystem!
12
		</para>
13
		
14
		<para>
15
		All hooks are not allowed to directly alter portage's execution,
16
		but they can accomplish certain extra tasks at various points,
17
		which might indrectly alter portage's execution. Since hooks
18
		execute in a bash environment, they are told the parent process
19
		ID, which can be used to kill portage if absolutely needed. This
20
		might be useful if a hook handled the rest of a certain job,
21
		such as syncing, and portage's default behavior is undesired, or
22
		if a hook caught potential problems with the rest of portage's
23
		execution.
24
		</para>
25
		
26
		<para>
27
		A hook script is expected to understand the following usage:
28
		<cmdsynopsis>
29
			<command>/bin/bash <replaceable>...</replaceable></command><sbr/>
30
31
			<arg>--opt <replaceable>portage arguments, always translated to long form, given by user at the prompt, such as "--verbose" or "--newuse"</replaceable></arg><sbr/>
32
33
			<arg>--action <replaceable>a single action being performed by portage, such as "depclean", "sync", or an ebuild phase</replaceable></arg><sbr/>
34
35
			<arg>--target <replaceable>the thing to perform the action with or on</replaceable></arg>
36
		</cmdsynopsis>
37
		</para>
38
		
39
		<para>
40
		The following hook directories are supported. The standard hook
41
		script usage applies, except wherever described differently.
42
		</para>
43
		
44
		<itemizedlist>
45
			<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>
46
			<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>
47
			<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>
48
			<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>
49
			<listitem><para><filename>/etc/portage/hooks/pre-sync.d/</filename> - executed before portage synchronizes the portage tree.</para></listitem>
50
			<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>
51
		</itemizedlist>
52
	</sect1>
53
	<sect1 id='config-hooks-skeleton-hook'>
54
		<title>Skeleton Hook</title>
55
		<para>
56
		Most hooks will parse the options at the beginning and look for
57
		specific things. This skeleton hook provides that functionality
58
		to get you started. Replace the colons with actual code where
59
		desired.
60
		</para>
61
		<para>
62
		It's highly recommended that --verbose, --debug, and --quiet be
63
		utilized for suppressing or adding to "regular" output. The
64
		following skeleton hook already has example code in place to
65
		handle these flags.
66
		</para>
67
		<programlisting>
68
		#!/bin/bash
69
70
		verbose_redirect="/dev/null"
71
		debug_redirect="/dev/null"
72
		while [[ "$1" != "" ]]; do
73
			if [[ "$1" == "--opt" ]]; then
74
				if [[ "$2" == "--verbose" ]]; then
75
					verbose_redirect="/dev/tty"
76
				fi
77
				if [[ "$2" == "--debug" ]]; then
78
					debug_redirect="/dev/tty"
79
				fi
80
				if [[ "$2" == "--quiet" ]]; then
81
					verbose_redirect="/dev/null"
82
					debug_redirect="/dev/null"
83
				fi
84
			elif [[ "$1" == "--action" ]]; then
85
				:
86
			elif [[ "$1" == "--target" ]]; then
87
				:
88
			else
89
				ewarn "Unknown hook option: $1 $2" > "${verbose_redirect}" 2>&1
90
			fi
91
			shift 2
92
		done
93
		einfo "This is an example hook." > "${verbose_redirect}" 2>&1
94
		einfo "This is debug output." > "${debug_redirect}" 2>&1
95
		</programlisting>
96
	</sect1>
97
</chapter>
(-)a/doc/portage.docbook (+1 lines)
Lines 23-28 Link Here
23
	<!ENTITY config SYSTEM "config.docbook">
23
	<!ENTITY config SYSTEM "config.docbook">
24
	<!ENTITY config_bashrc SYSTEM "config/bashrc.docbook">
24
	<!ENTITY config_bashrc SYSTEM "config/bashrc.docbook">
25
	<!ENTITY config_set SYSTEM "config/sets.docbook">
25
	<!ENTITY config_set SYSTEM "config/sets.docbook">
26
	<!ENTITY config_hooks SYSTEM "config/hooks.docbook">
26
]>
27
]>
27
28
28
<book id="portage" lang="en">
29
<book id="portage" lang="en">
(-)a/man/portage.5 (+11 lines)
Lines 62-67 repos.conf Link Here
62
.BR /etc/portage/env/
62
.BR /etc/portage/env/
63
package-specific bashrc files
63
package-specific bashrc files
64
.TP
64
.TP
65
.BR /etc/portage/hooks/
66
portage pre/post hooks
67
.TP
65
.BR /etc/portage/profile/
68
.BR /etc/portage/profile/
66
site-specific overrides of \fB/etc/make.profile/\fR
69
site-specific overrides of \fB/etc/make.profile/\fR
67
.TP
70
.TP
Lines 637-642 order: Link Here
637
/etc/portage/env/${CATEGORY}/${PF}
640
/etc/portage/env/${CATEGORY}/${PF}
638
.RE
641
.RE
639
.TP
642
.TP
643
.BR /etc/portage/hooks/
644
.RS
645
In this directory, portage hooks are executed before each ebuild phase,
646
before and after synchronization, and before and after portage runs
647
themselves. Please see the DocBook documentation for detailed
648
information.
649
.RE
650
.TP
640
.BR /usr/portage/metadata/
651
.BR /usr/portage/metadata/
641
.RS
652
.RS
642
.TP
653
.TP
(-)a/pym/_emerge/actions.py (+4 lines)
Lines 33-38 from portage.output import blue, bold, colorize, create_color_func, darkgreen, \ Link Here
33
	red, yellow
33
	red, yellow
34
good = create_color_func("GOOD")
34
good = create_color_func("GOOD")
35
bad = create_color_func("BAD")
35
bad = create_color_func("BAD")
36
from portage.hooks import HookDirectory
36
from portage.sets import load_default_config, SETPREFIX
37
from portage.sets import load_default_config, SETPREFIX
37
from portage.sets.base import InternalPackageSet
38
from portage.sets.base import InternalPackageSet
38
from portage.util import cmp_sort_key, writemsg, writemsg_level
39
from portage.util import cmp_sort_key, writemsg, writemsg_level
Lines 1817-1822 def action_sync(settings, trees, mtimedb, myopts, myaction): Link Here
1817
	os.umask(0o022)
1818
	os.umask(0o022)
1818
	dosyncuri = syncuri
1819
	dosyncuri = syncuri
1819
	updatecache_flg = False
1820
	updatecache_flg = False
1821
	HookDirectory(phase='pre-sync', settings=settings, myopts=myopts, myaction=myaction).execute()
1820
	if myaction == "metadata":
1822
	if myaction == "metadata":
1821
		print("skipping sync")
1823
		print("skipping sync")
1822
		updatecache_flg = True
1824
		updatecache_flg = True
Lines 2260-2265 def action_sync(settings, trees, mtimedb, myopts, myaction): Link Here
2260
			if retval != os.EX_OK:
2262
			if retval != os.EX_OK:
2261
				print(red(" * ") + bold("spawn failed of " + postsync))
2263
				print(red(" * ") + bold("spawn failed of " + postsync))
2262
2264
2265
	HookDirectory(phase='post-sync', settings=settings, myopts=myopts, myaction=myaction).execute()
2266
2263
	if(mybestpv != mypvs) and not "--quiet" in myopts:
2267
	if(mybestpv != mypvs) and not "--quiet" in myopts:
2264
		print()
2268
		print()
2265
		print(red(" * ")+bold("An update to portage is available.")+" It is _highly_ recommended")
2269
		print(red(" * ")+bold("An update to portage is available.")+" It is _highly_ recommended")
(-)a/pym/_emerge/main.py (+11 lines)
Lines 27-32 bad = create_color_func("BAD") Link Here
27
import portage.elog
27
import portage.elog
28
import portage.dep
28
import portage.dep
29
portage.dep._dep_check_strict = True
29
portage.dep._dep_check_strict = True
30
import portage.hooks
31
import portage.process
30
import portage.util
32
import portage.util
31
import portage.locks
33
import portage.locks
32
import portage.exception
34
import portage.exception
Lines 1232-1238 def emerge_main(): Link Here
1232
	# Portage needs to ensure a sane umask for the files it creates.
1234
	# Portage needs to ensure a sane umask for the files it creates.
1233
	os.umask(0o22)
1235
	os.umask(0o22)
1234
	settings, trees, mtimedb = load_emerge_config()
1236
	settings, trees, mtimedb = load_emerge_config()
1237
1238
	# Portage configured; let's let hooks run before we do anything more
1239
	portage.hooks.HookDirectory(phase='pre-run', settings=settings, myopts=myopts, myaction=myaction, mytargets=myfiles).execute()
1240
1241
	settings, trees, mtimedb = load_emerge_config() # once more, since pre-run might've done something
1235
	portdb = trees[settings["ROOT"]]["porttree"].dbapi
1242
	portdb = trees[settings["ROOT"]]["porttree"].dbapi
1243
1244
	# Have post-run hooks executed whenever portage quits
1245
	portage.process.atexit_register(portage.hooks.HookDirectory(phase='post-run', settings=settings, myopts=myopts, myaction=myaction, mytargets=myfiles).execute)
1246
1236
	rval = profile_check(trees, myaction)
1247
	rval = profile_check(trees, myaction)
1237
	if rval != os.EX_OK:
1248
	if rval != os.EX_OK:
1238
		return rval
1249
		return rval
(-)a/pym/portage/const.py (+1 lines)
Lines 35-40 CUSTOM_PROFILE_PATH = USER_CONFIG_PATH + "/profile" Link Here
35
USER_VIRTUALS_FILE       = USER_CONFIG_PATH + "/virtuals"
35
USER_VIRTUALS_FILE       = USER_CONFIG_PATH + "/virtuals"
36
EBUILD_SH_ENV_FILE       = USER_CONFIG_PATH + "/bashrc"
36
EBUILD_SH_ENV_FILE       = USER_CONFIG_PATH + "/bashrc"
37
EBUILD_SH_ENV_DIR        = USER_CONFIG_PATH + "/env"
37
EBUILD_SH_ENV_DIR        = USER_CONFIG_PATH + "/env"
38
HOOKS_PATH               = USER_CONFIG_PATH + "/hooks"
38
CUSTOM_MIRRORS_FILE      = USER_CONFIG_PATH + "/mirrors"
39
CUSTOM_MIRRORS_FILE      = USER_CONFIG_PATH + "/mirrors"
39
COLOR_MAP_FILE           = USER_CONFIG_PATH + "/color.map"
40
COLOR_MAP_FILE           = USER_CONFIG_PATH + "/color.map"
40
PROFILE_PATH             = "etc/make.profile"
41
PROFILE_PATH             = "etc/make.profile"
(-)a/pym/portage/hooks.py (+84 lines)
Line 0 Link Here
1
# Copyright 1998-2010 Gentoo Foundation
2
# Distributed under the terms of the GNU General Public License v2
3
# $Id$
4
5
from portage.const import BASH_BINARY, HOOKS_PATH, PORTAGE_BIN_PATH
6
from portage import os
7
from portage import check_config_instance
8
from portage import normalize_path
9
from portage.exception import PortageException
10
from portage.exception import InvalidLocation
11
from portage.output import EOutput
12
from process import spawn
13
14
class HookDirectory(object):
15
16
	def __init__ (self, phase, settings, myopts=None, myaction=None, mytargets=None):
17
		self.myopts = myopts
18
		self.myaction = myaction
19
		self.mytargets = mytargets
20
		check_config_instance(settings)
21
		self.settings = settings
22
		self.path = os.path.join(settings["PORTAGE_CONFIGROOT"], HOOKS_PATH, phase + '.d')
23
		self.output = EOutput()
24
25
	def execute (self, path=None):
26
		if not path:
27
			path = self.path
28
		
29
		path = normalize_path(path)
30
		
31
		if not os.path.exists(path):
32
			if self.myopts and "--debug" in self.myopts:
33
				self.output.ewarn('This hook path could not be found; ignored: ' + path)
34
			return
35
		
36
		if os.path.isdir(path):
37
			for parent, dirs, files in os.walk(path):
38
				for dir in dirs:
39
					if self.myopts and "--debug" in self.myopts:
40
						self.output.ewarn('Directory within hook directory not allowed; ignored: ' + path+'/'+dir)
41
				for filename in files:
42
					HookFile(os.path.join(path, filename), self.settings, self.myopts, self.myaction, self.mytargets).execute()
43
		
44
		else:
45
			raise InvalidLocation('This hook path ought to be a directory: ' + path)
46
47
class HookFile (object):
48
	
49
	def __init__ (self, path, settings, myopts=None, myaction=None, mytargets=None):
50
		self.myopts = myopts
51
		self.myaction = myaction
52
		self.mytargets = mytargets
53
		check_config_instance(settings)
54
		self.path = normalize_path(path)
55
		self.settings = settings
56
		self.output = EOutput()
57
	
58
	def execute (self):
59
		if "hooks" not in self.settings['FEATURES']:
60
			return
61
		
62
		if not os.path.exists(self.path):
63
			raise InvalidLocation('This hook path could not be found: ' + self.path)
64
		
65
		if os.path.isfile(self.path):
66
			command=[self.path]
67
			if self.myopts:
68
				for myopt in self.myopts:
69
					command.extend(['--opt', myopt])
70
			if self.myaction:
71
				command.extend(['--action', self.myaction])
72
			if self.mytargets:
73
				for mytarget in self.mytargets:
74
					command.extend(['--target', mytarget])
75
			
76
			command=[BASH_BINARY, '-c', 'source ' + PORTAGE_BIN_PATH + '/isolated-functions.sh && source ' + ' '.join(command)]
77
			if self.myopts and "--verbose" in self.myopts:
78
				self.output.einfo('Executing hook "' + self.path + '"...')
79
			code = spawn(mycommand=command, env=self.settings.environ())
80
			if code: # if failure
81
				raise PortageException('!!! Hook %s failed with exit code %s' % (self.path, code))
82
		
83
		else:
84
			raise InvalidLocation('This hook path ought to be a file: ' + self.path)
(-)a/pym/portage/package/ebuild/doebuild.py (+5 lines)
Lines 44-49 from portage.elog.messages import eerror, eqawarn Link Here
44
from portage.exception import DigestException, FileNotFound, \
44
from portage.exception import DigestException, FileNotFound, \
45
	IncorrectParameter, InvalidAtom, InvalidDependString, PermissionDenied, \
45
	IncorrectParameter, InvalidAtom, InvalidDependString, PermissionDenied, \
46
	UnsupportedAPIException
46
	UnsupportedAPIException
47
from portage.hooks import HookDirectory
47
from portage.localization import _
48
from portage.localization import _
48
from portage.manifest import Manifest
49
from portage.manifest import Manifest
49
from portage.output import style_to_ansi_code
50
from portage.output import style_to_ansi_code
Lines 547-552 def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, Link Here
547
		doebuild_environment(myebuild, mydo, myroot, mysettings, debug,
548
		doebuild_environment(myebuild, mydo, myroot, mysettings, debug,
548
			use_cache, mydbapi)
549
			use_cache, mydbapi)
549
550
551
		HookDirectory(phase='pre-ebuild', settings=mysettings, myopts=None, myaction=mydo, mytargets=[mysettings["EBUILD"]]).execute()
552
550
		if mydo in clean_phases:
553
		if mydo in clean_phases:
551
			retval = spawn(_shell_quote(ebuild_sh_binary) + " clean",
554
			retval = spawn(_shell_quote(ebuild_sh_binary) + " clean",
552
				mysettings, debug=debug, fd_pipes=fd_pipes, free=1,
555
				mysettings, debug=debug, fd_pipes=fd_pipes, free=1,
Lines 1048-1053 def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, Link Here
1048
			# If necessary, depend phase has been triggered by aux_get calls
1051
			# If necessary, depend phase has been triggered by aux_get calls
1049
			# and the exemption is no longer needed.
1052
			# and the exemption is no longer needed.
1050
			portage._doebuild_manifest_exempt_depend -= 1
1053
			portage._doebuild_manifest_exempt_depend -= 1
1054
		
1055
		HookDirectory(phase='post-ebuild', settings=mysettings, myopts=None, myaction=mydo, mytargets=[mysettings["EBUILD"]]).execute()
1051
1056
1052
def _validate_deps(mysettings, myroot, mydo, mydbapi):
1057
def _validate_deps(mysettings, myroot, mydo, mydbapi):
1053
1058
(-)a/pym/portage/tests/hooks/__init__.py (+5 lines)
Line 0 Link Here
1
# tests/portage/hooks/__init__.py -- Portage Unit Test functionality
2
# Copyright 2010 Gentoo Foundation
3
# Distributed under the terms of the GNU General Public License v2
4
# $Id$
5
(-)a/pym/portage/tests/hooks/test_HookDirectory.py (+49 lines)
Line 0 Link Here
1
# test_HookDirectory.py -- Portage Unit Testing Functionality
2
# Copyright 2010 Gentoo Foundation
3
# Distributed under the terms of the GNU General Public License v2
4
# $Id$
5
6
from portage import os
7
from portage.hooks import HookDirectory
8
from portage.package.ebuild.config import config
9
from portage.tests import TestCase
10
from tempfile import mkdtemp
11
from shutil import rmtree
12
13
# http://stackoverflow.com/questions/845058/how-to-get-line-count-cheaply-in-python
14
def file_len(fname):
15
    with open(fname) as f:
16
        for i, l in enumerate(f):
17
            pass
18
    return i + 1
19
20
class HookDirectoryTestCase(TestCase):
21
	
22
	def testHookDirectory(self):
23
		"""
24
		Tests to be sure a hook loads and reads the right settings
25
		Based on test_PackageKeywordsFile.py
26
		"""
27
28
		tmp_dir_path = self.BuildTmp('/etc/portage/hooks/test.d')
29
		try:
30
			settings = config()
31
			settings["PORTAGE_CONFIGROOT"] = tmp_dir_path
32
			settings["FEATURES"] += " hooks"
33
			hooks = HookDirectory('test', settings)
34
			hooks.execute()
35
			self.assert_(file_len(tmp_dir_path+'/output') == 1)
36
		finally:
37
			rmtree(tmp_dir_path)
38
	
39
	def BuildTmp(self, tmp_subdir):
40
		tmp_dir = mkdtemp()
41
		hooks_dir = tmp_dir + '/' + tmp_subdir
42
		os.makedirs(hooks_dir)
43
		
44
		f = open(hooks_dir+'/testhook', 'w')
45
		f.write('#!/bin/bash\n')
46
		f.write('echo hi > '+tmp_dir+'/output && exit 0\n')
47
		f.close()
48
		
49
		return tmp_dir

Return to bug 272988