diff --git a/bin/ebuild.sh b/bin/ebuild.sh index 9c599c0..89fca48 100755 --- a/bin/ebuild.sh +++ b/bin/ebuild.sh @@ -136,7 +136,7 @@ useq() { # TODO: Add a registration interface for eclasses to register # any number of phase hooks, so that global scope eclass # initialization can by migrated to phase hooks in new EAPIs. - # Example: add_phase_hook before pkg_setup $ECLASS_pre_pkg_setup + # Example: register_phase_hook before pkg_setup $ECLASS_pre_pkg_setup #if [[ -n $EAPI ]] && ! hasq "$EAPI" 0 1 2 3 ; then # die "use() called during invalid phase: $EBUILD_PHASE" #fi @@ -283,6 +283,51 @@ register_success_hook() { done } +register_phase_hook() { + if [[ -z "$3" ]]; then + echo "!!! register_phase_hook() called without enough parameters." >&2 + echo "!!! register_phase_hook " >&2 + return 1 + fi + local x when phase cmd cmdargs phase_hooks + when="$(echo $1 | tr 'a-z' 'A-Z')"; shift # uppercase when + phase="$(echo $1 | tr 'A-Z' 'a-z')"; shift # lowercase phase name (to match real phase names) + + case "${when}" in + BEFORE|AFTER) + : # allowed + ;; + *) + echo "!!! register_phase_hook() called with invalid when parameter: $when" >&2 + return 1 + ;; + esac + + phase_hooks="$(eval 'echo $EBUILD_PHASE_HOOKS_'"${when}"'_'"${phase}")" + + if [[ -z "${phase_hooks}" ]]; then + phase_hooks="0 " + elif ! _is_phase_hook_at_version "${phase_hooks}" 0; then + echo "!!! Unsupported ebuild phase hook version" + return $? + fi + + for x in $* ; do + hasq $x ${phase_hooks} || \ + phase_hooks+="${x} " + done + + export EBUILD_PHASE_HOOKS_"${when}"_"${phase}"="${phase_hooks}" +} + +_is_phase_hook_at_version() { + if [[ "${1:0:1}" == "$2" ]]; then + return 0 + else + return 1 + fi +} + # Ensure that $PWD is sane whenever possible, to protect against # exploitation of insecure search path for python -c in ebuilds. # See bug #239560. @@ -655,17 +700,82 @@ _eapi4_src_install() { } ebuild_phase() { - declare -F "$1" >/dev/null && qa_call $1 + local x phase_name=${1} pre_phase_hooks post_phase_hooks + + # only run new-style hooks if this function isn't being used to + # execute an old-style phase hook (which causes duplicate new-style + # hook calls) + if [[ "$(expr match $1 '^pre_\|^post_')" == "0" ]]; then + # Loop through new-style ebuild phase hooks with version check + for x in \ + EBUILD_PHASE_HOOKS_BEFORE_"${phase_name}" \ + EBUILD_PHASE_HOOKS_BEFORE_all \ + EBUILD_PHASE_HOOKS_AFTER_"${phase_name}" \ + EBUILD_PHASE_HOOKS_AFTER_all + do + x="$(eval 'echo $'${x})" + if [[ "${x}" == "" ]]; then + continue + fi + if ! _is_phase_hook_at_version "${x}" 0; then + echo "!!! Unsupported ebuild phase hook version" + return 1 + fi + done + pre_phase_hooks="$(eval 'echo $EBUILD_PHASE_HOOKS_BEFORE_'"${phase_name}") $EBUILD_PHASE_HOOKS_BEFORE_all" + post_phase_hooks="$(eval 'echo $EBUILD_PHASE_HOOKS_AFTER_'"${phase_name}") $EBUILD_PHASE_HOOKS_AFTER_all" + fi + + for x in \ + $pre_phase_hooks \ + ${phase_name} \ + $post_phase_hooks + do + exec_ebuild_phase ${x} + done } +# TODO: deprecate this function? Should be easy to provide backwards +# compatibility: +# register_phase_hook before pre_ +# register_phase_hook after post_ ebuild_phase_with_hooks() { local x phase_name=${1} [ -n "$EBUILD_PHASE" ] && rm -f "$T/logging/$EBUILD_PHASE" - for x in {pre_,,post_}${phase_name} ; do - ebuild_phase ${x} + + # Loop through new-style ebuild phase hooks with version check + for x in \ + EBUILD_PHASE_HOOKS_BEFORE_"${phase_name}" \ + EBUILD_PHASE_HOOKS_BEFORE_all \ + EBUILD_PHASE_HOOKS_AFTER_"${phase_name}" \ + EBUILD_PHASE_HOOKS_AFTER_all + do + x="$(eval 'echo $'${x})" + if [[ "${x}" == "" ]]; then + continue + fi + if ! _is_phase_hook_at_version "${x}" 0; then + echo "!!! Unsupported ebuild phase hook version" + return 1 + fi + done + + # Loop through all hooks and the actual phase + for x in \ + $(eval 'echo $EBUILD_PHASE_HOOKS_BEFORE_'"${phase_name}") \ + $EBUILD_PHASE_HOOKS_BEFORE_all \ + {pre_,,post_}${phase_name} \ + $(eval 'echo $EBUILD_PHASE_HOOKS_AFTER_'"${phase_name}") \ + $EBUILD_PHASE_HOOKS_AFTER_all + do + exec_ebuild_phase ${x} done } +exec_ebuild_phase() { + declare -F "$1" >/dev/null && qa_call $1 +} + dyn_pretend() { ebuild_phase_with_hooks pkg_pretend } @@ -1760,6 +1870,16 @@ preprocess_ebuild_env() { return ${retval} } +# Portage hooks +portage_hooks_pre_ebuild() { + source "${HOOKS_SH_BINARY}" --do-pre-ebuild || return $? +} +portage_hooks_post_ebuild() { + source "${HOOKS_SH_BINARY}" --do-post-ebuild || return $? +} +register_phase_hook before all portage_hooks_pre_ebuild +register_phase_hook after all portage_hooks_post_ebuild + # === === === === === === === === === === === === === === === === === === # === === === === === functions end, main part begins === === === === === # === === === === === functions end, main part begins === === === === === diff --git a/bin/hooks.sh b/bin/hooks.sh new file mode 100755 index 0000000..915ee31 --- /dev/null +++ b/bin/hooks.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# Copyright 1999-2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +# @MAINTAINER: +# jacobgodserv@gmail.com +# @BLURB: Executes hooks in the current directory. +# @DESCRIPTION: +# Part of the portage hooks system, this script is responsible for executing +# hooks within a prepared environment + +# Only run hooks if it's requested in $FEATURES +if ! (source "${PORTAGE_BIN_PATH}/isolated-functions.sh" && hasq hooks $FEATURES) ; then + return +fi + +# TODO: unit testing does not cover this portion of hooks.sh +# This code is put here so it's easier to do one-liners elsewhere. +# This section is meant to be run by ebuild.sh +if [[ "$1" == "--do-pre-ebuild" || "$1" == "--do-post-ebuild" ]]; then + if [[ "${EBUILD_PHASE}" == "" ]]; then + # an in-between-phases moment; useless to hooks + return + fi + + + oldwd="$(pwd)" + if [[ "$1" == "--do-pre-ebuild" ]]; then + hooks_dir="${PORTAGE_CONFIGROOT}/${HOOKS_PATH}/pre-ebuild.d" + else + hooks_dir="${PORTAGE_CONFIGROOT}/${HOOKS_PATH}/post-ebuild.d" + fi + + [ -d "${hooks_dir}" ] && cd "${hooks_dir}" + exit_code="$?" + if [[ "${exit_code}" != "0" ]]; then + # mimicks behavior in hooks.py + # TODO: --verbose detection? + : + #debug-print "This hook path could not be found; ignored: ${hooks_dir}" + else + # Execute the hooks + source "${HOOKS_SH_BINARY}" --action "${EBUILD_PHASE}" --target "${EBUILD}" + exit_code="$?" + if [[ "${exit_code}" != "0" ]]; then + # mimicks behavior in hooks.py + die "Hook directory ${HOOKS_PATH}/pre-ebuild.d failed with exit code ${exit_code}" + fi + fi + cd "${oldwd}" || die "Could not return to the old ebuild directory after pre-ebuild hooks: ${oldwd}" + + return +fi + +# Local variables listed here. +# Using the local keyword makes no difference since this script is being sourced +# so we'll have to unset them manually later. Be sure to keep the local_vars +# array up-to-date. +hook_files=( * ) +hook_args=( "$@" ) +hook_verbosity="0" + +hook_local_vars=( "hook_files" "hook_args" "hook_verbosity" ) # variables unset for hooks + +for (( i = 0 ; i < ${#hook_args[@]} ; i++ )); do + if [[ "${hook_args[$i]}" == "--verbose" ]]; then + hook_verbosity="1" + fi +done + +for (( i = 0 ; i < ${#hook_files[@]} ; i++ )); do + hook="${hook_files[$i]}" + if [[ ! -e "${hook}" ]]; then + continue + elif [[ ! -f "${hook}" ]]; then + [ "${hook_verbosity}" -gt 0 ] && ewarn "Only files are recognized in a hook directory: ${hook}" + continue + fi + + [ "${hook_verbosity}" -gt 0 ] && einfo "Executing hook ${hook}..." + # We use eval so the hook_args gets expanded before it is unset + ( eval unset "${hook_local_vars[@]}" '&&' source "${hook}" "${hook_args[@]}" ) + + exit_code="$?" + if [[ "${exit_code}" != "0" ]]; then + eerror "Hook $(pwd)/${hook} returned with exit code ${exit_code}" + exit "${exit_code}" + fi +done + +unset "${hook_local_vars[@]}" 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 @@ Configuration &config_bashrc; &config_set; +&config_hooks; diff --git a/doc/config/hooks.docbook b/doc/config/hooks.docbook new file mode 100644 index 0000000..aa37c75 --- /dev/null +++ b/doc/config/hooks.docbook @@ -0,0 +1,108 @@ + + Hooks Configuration + + + Hooks Execution + + Hooks are only executed if hooks is set in + FEATURES. + + + If a hook directory exists, the bash scripts within each one + wil either be executed before or after that particular phase, 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). For non-ebuild hooks, avoid + commands that may trigger changes in the filesystem! + + + + Ebuild hooks are executed within ebuild.sh, so they receive the + same sandbox limitations as ebuilds. + + + + A hook script is expected to understand the following usage: + + /bin/bash ... + + --opt portage arguments, always translated to long form, given by user at the prompt, such as "--verbose" or "--newuse" + + --action a single action being performed by portage, such as "depclean", "sync", or an ebuild phase + + --target the thing to perform the action with or on + + + + + Some hook types have slightly different usage. See + for more + information. + + + + + + Hooks Locations + + The following hook directories are supported. Each directory + corresponds to a specific type, such as ebuild or + run. The standard hook script usage applies given + in , + except wherever described differently below. + + + + /etc/portage/hooks/pre-ebuild.d/ - executed before every ebuild phase execution, within ebuild.sh itself. Never receives --opt, and --target is set to the full path of the ebuild. + /etc/portage/hooks/post-ebuild.d/ - executed after every ebuild phase execution. Never receives --opt, and --target is set to the full path of the ebuild. + /etc/portage/hooks/pre-run.d/ - executed before portage considers most things, including proper permissions and validity of parsed arguments. + /etc/portage/hooks/post-run.d/ - 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. + /etc/portage/hooks/pre-sync.d/ - executed before portage synchronizes the portage tree. + /etc/portage/hooks/post-sync.d/ - executed after portage has successfully synchronized the portage tree. If you want to catch a sync failure, use post-run. + + + + + Skeleton Hook + + Most hooks will parse the options at the beginning and look for + specific things. This skeleton hook provides that functionality + to get you started. + + + 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. + + + #!/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 + : # do nothing + elif [[ "$1" == "--target" ]]; then + : # do nothing + else + ewarn "Unknown hook option: $1 $2" &> "${verbose_redirect}" + fi + shift 2 + done + einfo "This is an example hook." &> "${verbose_redirect}" + einfo "This is debug output." &> "${debug_redirect}" + + + 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 @@ + ]> 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 a4b9f76..3d8bc5e 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 @@ -1836,6 +1837,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 @@ -2279,6 +2281,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..91b2b05 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 @@ -1232,7 +1234,16 @@ def emerge_main(): # Portage needs to ensure a sane umask for the files it creates. os.umask(0o22) settings, trees, mtimedb = load_emerge_config() + + # 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() + + settings, trees, mtimedb = load_emerge_config() # once more, since pre-run might've done something 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) + 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 0865c02..1179cf5 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" @@ -60,6 +61,7 @@ PORTAGE_PYM_PATH = PORTAGE_BASE_PATH + "/pym" LOCALE_DATA_PATH = PORTAGE_BASE_PATH + "/locale" # FIXME: not used EBUILD_SH_BINARY = PORTAGE_BIN_PATH + "/ebuild.sh" MISC_SH_BINARY = PORTAGE_BIN_PATH + "/misc-functions.sh" +HOOKS_SH_BINARY = PORTAGE_BIN_PATH + "/hooks.sh" SANDBOX_BINARY = "/usr/bin/sandbox" FAKEROOT_BINARY = "/usr/bin/fakeroot" BASH_BINARY = "/bin/bash" @@ -85,7 +87,7 @@ EBUILD_PHASES = ("pretend", "setup", "unpack", "prepare", "configure" SUPPORTED_FEATURES = frozenset([ "assume-digests", "buildpkg", "buildsyspkg", "ccache", "collision-protect", "digest", "distcc", "distlocks", - "fakeroot", "fail-clean", "fixpackages", "getbinpkg", + "fakeroot", "fail-clean", "fixpackages", "hooks", "getbinpkg", "installsources", "keeptemp", "keepwork", "lmirror", "metadata-transfer", "mirror", "multilib-strict", "news", "noauto", "noclean", "nodoc", "noinfo", "noman", "nostrip" diff --git a/pym/portage/hooks.py b/pym/portage/hooks.py new file mode 100644 index 0000000..29c41e1 --- /dev/null +++ b/pym/portage/hooks.py @@ -0,0 +1,78 @@ +# 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, HOOKS_SH_BINARY, 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 +from shutil import rmtree +from tempfile import mkdtemp + +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 "hooks" not in self.settings['FEATURES']: + return + + if not path: + path = self.path + + path = normalize_path(path) + + if not os.path.exists(path): + if self.myopts and "--debug" in self.myopts: + # behavior mimicked by hook.sh + self.output.ewarn('This hook path could not be found; ignored: ' + path) + return + + if os.path.isdir(path): + command=[HOOKS_SH_BINARY] + 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', 'cd "'+path+'" && source "' + PORTAGE_BIN_PATH + '/isolated-functions.sh" && source ' + ' '.join(command)] + if self.myopts and "--verbose" in self.myopts: + self.output.einfo('Executing hooks directory "' + self.path + '"...') + code = spawn(mycommand=command, env=self.settings.environ()) + if code: # if failure + # behavior mimicked by hook.sh + raise PortageException('!!! Hook directory %s failed with exit code %s' % (self.path, code)) + + else: + raise InvalidLocation('This hook path ought to be a directory: ' + path) + + def merge_to_env (self, existingenv, path): + path = normalize_path(path) + + if not os.path.isdir(path): + raise InvalidLocation('This environment path is not a directory: ' + path) + + for parent, dirs, files in os.walk(path): + for varname in files: + file = open(os.path.join(path, varname), 'r') + # read the file, remove the very last newline, and make the escaped double-quotes just plain double-quotes (since only bash needs them to be escaped, not python) + vardata = file.read()[:-1].replace('\"','"').strip('"') + existingenv[varname] = vardata + existingenv.backup_changes(varname) + + return existingenv diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py index a3a372e..003c0ea 100644 --- a/pym/portage/package/ebuild/config.py +++ b/pym/portage/package/ebuild/config.py @@ -22,10 +22,10 @@ import portage from portage import bsd_chflags, eapi_is_supported, \ load_mod, os, selinux, _encodings, _unicode_encode, _unicode_decode from portage.const import CACHE_PATH, CUSTOM_PROFILE_PATH, \ - DEPCACHE_PATH, GLOBAL_CONFIG_PATH, INCREMENTALS, MAKE_CONF_FILE, \ - MODULES_FILE_PATH, PORTAGE_BIN_PATH, PORTAGE_PYM_PATH, \ - PRIVATE_PATH, PROFILE_PATH, SUPPORTED_FEATURES, USER_CONFIG_PATH, \ - USER_VIRTUALS_FILE + DEPCACHE_PATH, GLOBAL_CONFIG_PATH, HOOKS_PATH, HOOKS_SH_BINARY, \ + INCREMENTALS, MAKE_CONF_FILE, MODULES_FILE_PATH, PORTAGE_BIN_PATH, \ + PORTAGE_PYM_PATH, PRIVATE_PATH, PROFILE_PATH, SUPPORTED_FEATURES, \ + USER_CONFIG_PATH, USER_VIRTUALS_FILE from portage.data import portage_gid from portage.dbapi import dbapi from portage.dbapi.porttree import portdbapi @@ -762,6 +762,8 @@ class config(object): self["PORTAGE_CONFIGROOT"] = config_root self.backup_changes("PORTAGE_CONFIGROOT") + self["HOOKS_PATH"] = HOOKS_PATH + self.backup_changes("HOOKS_PATH") self["ROOT"] = target_root self.backup_changes("ROOT") @@ -987,6 +989,8 @@ class config(object): self["PORTAGE_BIN_PATH"] = PORTAGE_BIN_PATH self.backup_changes("PORTAGE_BIN_PATH") + self["HOOKS_SH_BINARY"] = HOOKS_SH_BINARY + self.backup_changes("HOOKS_SH_BINARY") self["PORTAGE_PYM_PATH"] = PORTAGE_PYM_PATH self.backup_changes("PORTAGE_PYM_PATH") 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..09949e5 --- /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 + +class HookDirectoryTestCase(TestCase): + + def testHookDirectory(self): + """ + Tests to be sure a hook loads and reads the right settings + Based on test_PackageKeywordsFile.py + """ + + self.tmp_dir_path = self.BuildTmp('/etc/portage/hooks/test.d') + try: + settings = config() + settings["PORTAGE_CONFIGROOT"] = self.tmp_dir_path + settings["FEATURES"] += " hooks" + hooks = HookDirectory(phase='test', settings=settings) + hooks.execute() + self.assert_(settings["hookonlytest"] == "") + finally: + rmtree(self.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+'/1-testhook', 'w') + f.write('#!/bin/bash\n') + f.write('export hookonlytest="portage cannot see me!"\n') + f.write('exit 0\n') + f.close() + + f = open(hooks_dir+'/2-testhook', 'w') + f.write('#!/bin/bash\n') + f.write('if [[ "${hookonlytest}" != "" ]]; then echo "Unexpected hookonlytest value: ${hookonlytest}"; exit 1; fi\n'); + f.write('exit 0\n') + f.close() + + return tmp_dir