Skip to content

Commit c9ba695

Browse files
committed
Introduce 'pipestatus'
Starting with EAPI 9, we decided to phase out 'assert', because, for example, its name is confusing. Instead 'assert' is replaced with 'pipestatus'. This also introduces the portage internal __pipestatus helper that can be used in bash code for all EAPIs. The __pipestatus in eapi9-pipestatus.sh was taken from eapi9-pipestatus.eclass, written by Ulrich Müller. Thanks ulm! Bug: https://bugs.gentoo.org/566342 Signed-off-by: Florian Schmaus <[email protected]>
1 parent 237be7c commit c9ba695

9 files changed

+189
-8
lines changed

Diff for: bin/eapi.sh

+4
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ ___eapi_has_assert() {
144144
[[ ${1-${EAPI-0}} =~ (0|1|2|3|4|5|6|7|8)$ ]]
145145
}
146146

147+
___eapi_has_pipestatus() {
148+
[[ ${1-${EAPI-0}} =~ ^(0|1|2|3|4|5|6|7|8)$ ]]
149+
}
150+
147151
# HELPERS BEHAVIOR
148152

149153
___eapi_best_version_and_has_version_support_--host-root() {

Diff for: bin/eapi9-pipestatus.sh

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2024-2025 Gentoo Authors
3+
# Distributed under the terms of the GNU General Public License v2
4+
5+
# @FUNCTION: __pipestatus
6+
# @USAGE: [-v]
7+
# @RETURN: last non-zero element of PIPESTATUS, or zero if all are zero
8+
# @DESCRIPTION:
9+
# Check the PIPESTATUS array, i.e. the exit status of the command(s)
10+
# in the most recently executed foreground pipeline. If called with
11+
# option -v, also output the PIPESTATUS array.
12+
__pipestatus() {
13+
local status=( "${PIPESTATUS[@]}" )
14+
local s ret=0 verbose=""
15+
16+
[[ ${1} == -v ]] && { verbose=1; shift; }
17+
[[ $# -ne 0 ]] && die "usage: ${FUNCNAME} [-v]"
18+
19+
for s in "${status[@]}"; do
20+
[[ ${s} -ne 0 ]] && ret=${s}
21+
done
22+
23+
[[ ${verbose} ]] && echo "${status[@]}"
24+
25+
return "${ret}"
26+
}

Diff for: bin/isolated-functions.sh

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ fi
1212
# It _must_ preceed all the calls to die and assert.
1313
shopt -s expand_aliases
1414

15+
source "${PORTAGE_BIN_PATH}/eapi9-pipestatus.sh" || exit 1
16+
if ___eapi_has_pipestatus; then
17+
pipestatus() {
18+
__pipestatus "$@"
19+
}
20+
fi
21+
1522
if ___eapi_has_assert; then
1623
assert() {
1724
local x pipestatus=${PIPESTATUS[*]}

Diff for: bin/misc-functions.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ __dyn_package() {
540540

541541
tar ${tar_options} -cf - ${PORTAGE_BINPKG_TAR_OPTS} -C "${D}" . | \
542542
${PORTAGE_COMPRESSION_COMMAND} > "${PORTAGE_BINPKG_TMPFILE}"
543-
assert "failed to pack binary package: '${PORTAGE_BINPKG_TMPFILE}'"
543+
__pipestatus || die "failed to pack binary package: '${PORTAGE_BINPKG_TMPFILE}'"
544544

545545
PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \
546546
"${PORTAGE_PYTHON:-/usr/bin/python}" "${PORTAGE_BIN_PATH}"/xpak-helper.py recompose \

Diff for: bin/phase-functions.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ __dyn_install() {
716716
__save_ebuild_env --exclude-init-phases | __filter_readonly_variables \
717717
--filter-path --filter-sandbox --allow-extra-vars > \
718718
"${PORTAGE_BUILDDIR}"/build-info/environment
719-
assert "__save_ebuild_env failed"
719+
__pipestatus || die "__save_ebuild_env failed"
720720
cd "${PORTAGE_BUILDDIR}"/build-info || die
721721

722722
${PORTAGE_BZIP2_COMMAND} -f9 environment
@@ -1022,7 +1022,7 @@ __ebuild_main() {
10221022
__filter_readonly_variables --filter-path \
10231023
--filter-sandbox --allow-extra-vars \
10241024
| ${PORTAGE_BZIP2_COMMAND} -c -f9 > "${PORTAGE_UPDATE_ENV}"
1025-
assert "__save_ebuild_env failed"
1025+
__pipestatus || die "__save_ebuild_env failed"
10261026
fi
10271027
;;
10281028
unpack|prepare|configure|compile|test|clean|install)
@@ -1114,7 +1114,7 @@ __ebuild_main() {
11141114
cd "${PORTAGE_PYM_PATH}"
11151115
__save_ebuild_env | __filter_readonly_variables \
11161116
--filter-features > "${T}/environment"
1117-
assert "__save_ebuild_env failed"
1117+
__pipestatus || die "__save_ebuild_env failed"
11181118

11191119
chgrp "${PORTAGE_GRPNAME:-portage}" "${T}/environment"
11201120
chmod g+w "${T}/environment"

Diff for: bin/save-ebuild-env.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ __save_ebuild_env() {
5050
done
5151
unset x
5252

53-
unset -f assert __assert_sigpipe_ok \
53+
unset -f assert __assert_sigpipe_ok __pipestatus \
5454
__dump_trace die \
5555
__quiet_mode __vecho __elog_base eqawarn elog \
5656
einfo einfon ewarn eerror ebegin __eend eend KV_major \
@@ -91,6 +91,7 @@ __save_ebuild_env() {
9191
___eapi_has_eapply && unset -f eapply eapply_user
9292
___eapi_has_in_iuse && unset -f in_iuse
9393
___eapi_has_version_functions && unset -f ver_cut ver_rs ver_test
94+
___eapi_has_pipestatus && unset -f pipestatus
9495

9596
# Clear out the triple underscore namespace as it is reserved by the PM.
9697
while IFS=' ' read -r _ _ REPLY; do

Diff for: lib/portage/tests/bin/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ py.install_sources(
55
'test_dodir.py',
66
'test_doins.py',
77
'test_eapi7_ver_funcs.py',
8+
'test_eapi9_pipestatus.py',
89
'test_filter_bash_env.py',
910
'__init__.py',
1011
'__test__.py',

Diff for: lib/portage/tests/bin/test_eapi9_pipestatus.py

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright 2018 Gentoo Foundation
2+
# Distributed under the terms of the GNU General Public License v2
3+
4+
import subprocess
5+
import tempfile
6+
import itertools
7+
8+
from portage.const import PORTAGE_BIN_PATH
9+
from portage.tests import TestCase
10+
11+
12+
class TestEAPI9Pipestatus(TestCase):
13+
test_script_prelude = """\
14+
pipestatus() {
15+
__pipestatus "$@"
16+
}
17+
18+
tps() {
19+
local cmd=${1}
20+
eval "${cmd}; pipestatus"
21+
echo $?
22+
}
23+
24+
tpsv() {
25+
local cmd=${1}
26+
local out ret
27+
out=$(eval "${cmd}; pipestatus -v")
28+
ret=$?
29+
echo $ret $out
30+
}
31+
32+
ret() {
33+
return ${1}
34+
}
35+
36+
die() {
37+
if [[ $@ ]]; then
38+
2>&1 echo "$@"
39+
fi
40+
exit 42
41+
}
42+
"""
43+
44+
def _test_pipestatus(self, test_cases):
45+
with tempfile.NamedTemporaryFile("w") as test_script:
46+
test_script.write(f'source "{PORTAGE_BIN_PATH}"/eapi9-pipestatus.sh\n')
47+
test_script.write(self.test_script_prelude)
48+
for cmd, _ in test_cases:
49+
test_script.write(f"{cmd}\n")
50+
51+
test_script.flush()
52+
53+
s = subprocess.Popen(
54+
["bash", test_script.name],
55+
stdout=subprocess.PIPE,
56+
stderr=subprocess.PIPE,
57+
)
58+
sout, serr = s.communicate()
59+
self.assertEqual(s.returncode, 0)
60+
for test_case, result in zip(test_cases, sout.decode().splitlines()):
61+
cmd, exp = test_case
62+
self.assertEqual(
63+
result, exp, f"{cmd} -> '{result}' but expected: {exp}"
64+
)
65+
66+
def test_pipestatus(self):
67+
test_cases = [
68+
# (command, expected exit status)
69+
("true", 0),
70+
("false", 1),
71+
("true | true", 0),
72+
("false | true", 1),
73+
("true | false", 1),
74+
("ret 2 | true", 2),
75+
("true | false | true", 1),
76+
("true |false | ret 5 | true", 5),
77+
]
78+
test_cases = itertools.starmap(lambda a, b: (f"tps {a}", f"{b}"), test_cases)
79+
self._test_pipestatus(test_cases)
80+
81+
def test_pipestatus_v(self):
82+
test_cases = [
83+
# (command, expected exit status, expected output)
84+
("true | true | true", 0, "0 0 O"),
85+
("false | true", 1, "1 0"),
86+
("ret 3 | ret 2 | true", 2, "3 2 0"),
87+
]
88+
test_cases = itertools.starmap(
89+
lambda a, b, c: (f"tpsv {a}", f"{b} {c}"), test_cases
90+
)
91+
self._test_pipestatus(test_cases)
92+
93+
def test_pipestatus_xfail(self):
94+
test_cases = [
95+
"pipestatus bad_arg",
96+
"pipestatus -v extra_arg",
97+
]
98+
for cmd in test_cases:
99+
with tempfile.NamedTemporaryFile("w") as test_script:
100+
test_script.write(f'source "{PORTAGE_BIN_PATH}"/eapi9-pipestatus.sh\n')
101+
test_script.write(self.test_script_prelude)
102+
test_script.write(f"{cmd}\n")
103+
104+
test_script.flush()
105+
s = subprocess.Popen(
106+
["bash", test_script.name],
107+
stdout=subprocess.PIPE,
108+
stderr=subprocess.PIPE,
109+
)
110+
s.wait()
111+
self.assertEqual(s.returncode, 42)

Diff for: man/ebuild.5

+34-3
Original file line numberDiff line numberDiff line change
@@ -1085,10 +1085,41 @@ default_src_test
10851085

10861086
.SS "General:"
10871087
.TP
1088+
.B pipestatus\fR \fI[-v]
1089+
Since \fBEAPI 9\fR: Checks the PIPESTATUS array, i.e., the exit status
1090+
of the command(s) in the most recently executed foreground pipeline.
1091+
Returns the last non-zero-element of PIPESTATUS, or zero if all are
1092+
zero. If called with option -v, also outputs the PIPESTATUS array.
1093+
In its simplest form it can be called like this:
1094+
.RS
1095+
.TP
1096+
.I Example:
1097+
.nf
1098+
foo | bar
1099+
pipestatus || die
1100+
.fi
1101+
.RE
1102+
.IP
1103+
Using the -v option allows to save the value of the PIPESTATUS array
1104+
for later diagnostics:
1105+
.RS
1106+
.TP
1107+
.I Example:
1108+
.nf
1109+
local status
1110+
foo | bar
1111+
status=$(pipestatus -v) || die "foo | bar failed, PIPESTATUS: ${status}"
1112+
.fi
1113+
.RE
1114+
.IP
1115+
Note that pipestatus must be the next command following the pipeline.
1116+
In particular, the "local status" declaration must be before the
1117+
pipeline, otherwise it would reset PIPESTATUS.
1118+
.TP
10881119
.B assert\fR \fI[reason]
1089-
Checks the value of the shell's PIPESTATUS array variable, and if any
1090-
component is non-zero (indicating failure), calls die with \fIreason\fR
1091-
as a failure message.
1120+
Until \fBEAPI 8\fR. Checks the value of the shell's PIPESTATUS array
1121+
variable, and if any component is non-zero (indicating failure), calls
1122+
die with \fIreason\fR as a failure message.
10921123
.TP
10931124
.B die\fR \fI[reason]
10941125
Causes the current emerge process to be aborted. The final display will

0 commit comments

Comments
 (0)