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 (+78 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, they will either be executed before
7
		or after that particular stage. The hooks inside each directory
8
		will be executed by bash. Each one will receive the environment
9
		of an ebuild, so they are capable of inherit, einfo, and other
10
		common commands (if you find them useful). Avoid commands that
11
		may trigger changes in the filesystem!
12
		</para>
13
		
14
		<para>
15
		A hook is presently not allowed to alter portage's execution,
16
		but they can supplement it with additional functionality. Since
17
		hooks execute in a bash environment, they are told the parent
18
		process ID, which can be used to kill the parent (nicely,
19
		please) if absolutely needed. This might be useful in a pre-sync
20
		script.
21
		</para>
22
		
23
		<para>
24
		When a hook is called, any of the following arguments are
25
		passed:
26
		<cmdsynopsis>
27
			<command>/bin/bash <replaceable>...</replaceable></command><sbr/>
28
29
			<arg>--opt <replaceable>portage arguments, always translated to long form, given by user at the prompt, such as "--verbose" or "--newuse"</replaceable></arg><sbr/>
30
31
			<arg>--action <replaceable>a single action being performed by portage, such as "depclean", "sync", or an ebuild phase</replaceable></arg><sbr/>
32
33
			<arg>--target <replaceable>the thing to perform the action with or on</replaceable></arg>
34
		</cmdsynopsis>
35
		</para>
36
		
37
		<para>
38
		As of this writing, the following hook directories are
39
		supported. It can be assumed that the above arguments apply
40
		except wherever described differently.
41
		</para>
42
		
43
		<itemizedlist>
44
			<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>
45
			<listitem><para><filename>/etc/portage/hooks/post-ebuild.d/</filename> - yet to be implemented</para></listitem>
46
			<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>
47
			<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>
48
			<listitem><para><filename>/etc/portage/hooks/pre-sync.d/</filename> - executed before portage synchronizes the portage tree.</para></listitem>
49
			<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>
50
		</itemizedlist>
51
	</sect1>
52
	<sect1 id='config-hooks-skeleton-hook'>
53
		<title>Skeleton Hook</title>
54
		<para>
55
		Most hooks will parse the options at the beginning and look for
56
		specific things. This skeleton hook provides that functionality
57
		to get you started. Replace the colons with actual code where
58
		desired.
59
		</para>
60
		<programlisting>
61
		#!/bin/bash
62
63
		einfo "This is an example hook."
64
		while [[ "$1" != "" ]]; do
65
			if [[ "$1" == "--opt" ]]; then
66
				:
67
			elif [[ "$1" == "--action" ]]; then
68
				:
69
			elif [[ "$1" == "--target" ]]; then
70
				:
71
			else
72
				ewarn "Unknown hook option: $1 $2"
73
			fi
74
			shift 2
75
		done
76
		</programlisting>
77
	</sect1>
78
</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 (+7 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 1233-1238 def emerge_main(): Link Here
1233
	os.umask(0o22)
1235
	os.umask(0o22)
1234
	settings, trees, mtimedb = load_emerge_config()
1236
	settings, trees, mtimedb = load_emerge_config()
1235
	portdb = trees[settings["ROOT"]]["porttree"].dbapi
1237
	portdb = trees[settings["ROOT"]]["porttree"].dbapi
1238
1239
	# Portage configured; let's let a hook set everything up before we do anything more
1240
	portage.process.atexit_register(portage.hooks.HookDirectory(phase='post-run', settings=settings, myopts=myopts, myaction=myaction, mytargets=myfiles).execute)
1241
	portage.hooks.HookDirectory(phase='pre-run', settings=settings, myopts=myopts, myaction=myaction, mytargets=myfiles).execute()
1242
1236
	rval = profile_check(trees, myaction)
1243
	rval = profile_check(trees, myaction)
1237
	if rval != os.EX_OK:
1244
	if rval != os.EX_OK:
1238
		return rval
1245
		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 (+93 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
# TODO: following may be harmful, but helpful for debugging
6
#import os, sys
7
#import os.path as osp
8
#sys.path.insert(0, osp.dirname(osp.dirname(osp.abspath(__file__))))
9
10
from portage.const import BASH_BINARY, HOOKS_PATH, PORTAGE_BIN_PATH
11
from portage import os
12
from portage import check_config_instance
13
from portage import normalize_path
14
from portage.exception import PortageException
15
from portage.exception import InvalidLocation
16
from portage.output import EOutput
17
from process import spawn
18
19
class HookDirectory(object):
20
21
	def __init__ (self, phase, settings, myopts=None, myaction=None, mytargets=None):
22
		self.myopts = myopts
23
		self.myaction = myaction
24
		self.mytargets = mytargets
25
		check_config_instance(settings)
26
		self.settings = settings
27
		self.path = os.path.join(settings["PORTAGE_CONFIGROOT"], HOOKS_PATH, phase + '.d')
28
		self.output = EOutput()
29
30
	def execute (self, path=None):
31
		if not path:
32
			path = self.path
33
		
34
		path = normalize_path(path)
35
		
36
		if not os.path.exists(path):
37
			if self.myopts and "--debug" in self.myopts:
38
				self.output.ewarn('This hook path could not be found; ignored: ' + path)
39
			return
40
		
41
		if os.path.isdir(path):
42
			for parent, dirs, files in os.walk(path):
43
				for dir in dirs:
44
					if self.myopts and "--debug" in self.myopts:
45
						self.output.ewarn('Directory within hook directory not allowed; ignored: ' + path+'/'+dir)
46
				for filename in files:
47
					HookFile(os.path.join(path, filename), self.settings, self.myopts, self.myaction, self.mytargets).execute()
48
		
49
		else:
50
			raise InvalidLocation('This hook path ought to be a directory: ' + path)
51
52
class HookFile (object):
53
	
54
	def __init__ (self, path, settings, myopts=None, myaction=None, mytargets=None):
55
		self.myopts = myopts
56
		self.myaction = myaction
57
		self.mytargets = mytargets
58
		check_config_instance(settings)
59
		self.path = normalize_path(path)
60
		self.settings = settings
61
		self.output = EOutput()
62
	
63
	def execute (self):
64
		if "hooks" not in self.settings['FEATURES']:
65
			return
66
		
67
		if not os.path.exists(self.path):
68
			raise InvalidLocation('This hook path could not be found: ' + self.path)
69
		
70
		if os.path.isfile(self.path):
71
			command=[self.path]
72
			if self.myopts:
73
				for myopt in self.myopts:
74
					command.extend(['--opt', myopt])
75
			if self.myaction:
76
				command.extend(['--action', self.myaction])
77
			if self.mytargets:
78
				for mytarget in self.mytargets:
79
					command.extend(['--target', mytarget])
80
			
81
			command=[BASH_BINARY, '-c', 'source ' + PORTAGE_BIN_PATH + '/isolated-functions.sh && source ' + ' '.join(command)]
82
			if self.myopts and "--verbose" in self.myopts:
83
				self.output.einfo('Executing hook "' + self.path + '"...')
84
			code = spawn(mycommand=command, env=self.settings.environ())
85
			if code: # if failure
86
				raise PortageException('!!! Hook %s failed with exit code %s' % (self.path, code))
87
		
88
		else:
89
			raise InvalidLocation('This hook path ought to be a file: ' + self.path)
90
91
if __name__ == "__main__": # TODO: debug
92
	from portage.package.ebuild.config import config
93
	HookDirectory('run', config()).execute()
(-)a/pym/portage/package/ebuild/doebuild.py (-5 / +13 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 601-609 def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, Link Here
601
				mysettings["dbkey"] = \
604
				mysettings["dbkey"] = \
602
					os.path.join(mysettings.depcachedir, "aux_db_key_temp")
605
					os.path.join(mysettings.depcachedir, "aux_db_key_temp")
603
606
604
			return spawn(_shell_quote(ebuild_sh_binary) + " depend",
607
			retval = spawn(_shell_quote(ebuild_sh_binary) + " depend",
605
				mysettings,
608
				mysettings,
606
				droppriv=droppriv)
609
				droppriv=droppriv)
610
			return retval
607
611
608
		# Validate dependency metadata here to ensure that ebuilds with invalid
612
		# Validate dependency metadata here to ensure that ebuilds with invalid
609
		# data are never installed via the ebuild command. Don't bother when
613
		# data are never installed via the ebuild command. Don't bother when
Lines 650-657 def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, Link Here
650
		del checkdir
654
		del checkdir
651
655
652
		if mydo == "unmerge":
656
		if mydo == "unmerge":
653
			return unmerge(mysettings["CATEGORY"],
657
			retval = unmerge(mysettings["CATEGORY"],
654
				mysettings["PF"], myroot, mysettings, vartree=vartree)
658
				mysettings["PF"], myroot, mysettings, vartree=vartree)
659
			return retval
655
660
656
		# Build directory creation isn't required for any of these.
661
		# Build directory creation isn't required for any of these.
657
		# In the fetch phase, the directory is needed only for RESTRICT=fetch
662
		# In the fetch phase, the directory is needed only for RESTRICT=fetch
Lines 741-748 def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, Link Here
741
		# if any of these are being called, handle them -- running them out of
746
		# if any of these are being called, handle them -- running them out of
742
		# the sandbox -- and stop now.
747
		# the sandbox -- and stop now.
743
		if mydo == "help":
748
		if mydo == "help":
744
			return spawn(_shell_quote(ebuild_sh_binary) + " " + mydo,
749
			retval = spawn(_shell_quote(ebuild_sh_binary) + " " + mydo,
745
				mysettings, debug=debug, free=1, logfile=logfile)
750
				mysettings, debug=debug, free=1, logfile=logfile)
751
			return retval
746
		elif mydo == "setup":
752
		elif mydo == "setup":
747
			retval = spawn(
753
			retval = spawn(
748
				_shell_quote(ebuild_sh_binary) + " " + mydo, mysettings,
754
				_shell_quote(ebuild_sh_binary) + " " + mydo, mysettings,
Lines 869-877 def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, Link Here
869
875
870
		try:
876
		try:
871
			if mydo == "manifest":
877
			if mydo == "manifest":
872
				return not digestgen(mysettings=mysettings, myportdb=mydbapi)
878
				retval = digestgen(mysettings=mysettings, myportdb=mydbapi)
879
				return not retval
873
			elif mydo == "digest":
880
			elif mydo == "digest":
874
				return not digestgen(mysettings=mysettings, myportdb=mydbapi)
881
				retval = digestgen(mysettings=mysettings, myportdb=mydbapi)
882
				return not retval
875
			elif mydo != 'fetch' and not emerge_skip_digest and \
883
			elif mydo != 'fetch' and not emerge_skip_digest and \
876
				"digest" in mysettings.features:
884
				"digest" in mysettings.features:
877
				# Don't do this when called by emerge or when called just
885
				# Don't do this when called by emerge or when called just
(-)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