1 |
I'm cooking up some eclass functions and it occurred to me that perhaps |
2 |
I had some responsibility regarding EAPI4's new error handling |
3 |
semantics. After looking into it, it seems that, superficially, the |
4 |
answer to my question is "no, the EAPI4 changes only apply to ebuild |
5 |
helpers." |
6 |
|
7 |
Even so, this got me thinking: if ebuild helpers are going to warn(*) in |
8 |
EAPI<4 and die in EAPI>=4 (unless somebody used "nonfatal"), then, those |
9 |
error-handling semantics are going to bubble up through my eclass to its |
10 |
heirs (assuming my eclass uses helpers). |
11 |
|
12 |
The problem I see with that is that a consumer of my eclass function is |
13 |
effectively required to anticipate these EAPI helper-function outcomes, |
14 |
which is fair-enough if s/he's consuming them directly, but pretty |
15 |
unreasonable if they're just consuming my eclass.... In effect, this |
16 |
seems to create a situation where they can't know what to expect, |
17 |
without wading through my code, unless I go in and document "in EAPI4+ |
18 |
it does X, but in EAPI3, it does Y". Which, let's face it, most eclass |
19 |
authors probably won't get around to doing. |
20 |
|
21 |
For example, suppose poop.eclass has a function "epoop" which performs |
22 |
the following steps: |
23 |
|
24 |
o Downloads the latest "post" from the |
25 |
http://www.unnecessaryquotes.com/ "website" using "wget" |
26 |
o Strips out the top 3 posts from piped wget output using |
27 |
a bunch of sed and awk magic. Saves each post-processed post as |
28 |
"${S}/$(mktemp -u $( printf %100s |tr " " "X" )).xml" and |
29 |
remembers these filenames in three hard-coded variables. |
30 |
o Installs the posts as documentation using dodoc statements |
31 |
o Returns "$(( $(date +'%s') % 3 ))" |
32 |
|
33 |
Let's say I started writing this code before I knew anything about EAPI |
34 |
4, and the current epoop documentation states that: |
35 |
|
36 |
o errors in the wget and postprocessing steps will result in a return |
37 |
code of '3' |
38 |
o errors from dodoc will result in a return code of '3' + the |
39 |
return-code of dodoc |
40 |
o epoop() succeeds every third second and otherwise fails, returning |
41 |
the number of seconds ago that the API could have been invoked |
42 |
successfully as the error code (WTF: this is tounge-in-cheek: |
43 |
$(( $(date +'%s') % 3 )) is the current UNIX time in |
44 |
seconds, modulo 3, an extremely pointless and arbitrary success |
45 |
criterion for my hypothetical function that will cause it to |
46 |
randomly fail 66% of the times it's invoked and runs to completion. |
47 |
If you don't find this amusing, that's OK; it's not terribly |
48 |
important to the matter at hand.) |
49 |
|
50 |
Well, now I have an awkward situation. In EAPI's 0-3, the function |
51 |
works as documented. In EAPI 4+, however, epoop() will die if any error |
52 |
occurs in dodoc. I can fix this by replacing the dodoc code -- let's |
53 |
say it originally was coded like: |
54 |
|
55 |
dodoc ${docfile1} || { r=$?; eerror dodoc1; return $((r+3)); } |
56 |
dodoc ${docfile2} || { r=$?; eerror dodoc2; return $((r+3)); } |
57 |
dodoc ${docfile3} || { r=$?; eerror dodoc3; return $((r+3)); } |
58 |
|
59 |
-- with code like: |
60 |
|
61 |
. |
62 |
. |
63 |
if has "${EAPI:-0}" 0 1 2 3 ; then |
64 |
nonfatal dodoc ${docfile2} |
65 |
else |
66 |
dodoc ${docfile2} |
67 |
fi || { r=$?; eerror dodoc2; return $((r+3)); } |
68 |
. |
69 |
. |
70 |
|
71 |
but I will have to do something like this for each of the three dodoc |
72 |
statements. Seems like a real PITA. |
73 |
|
74 |
Obviously, I've chosen a very silly example, and perhaps I've |
75 |
cherry-picked a solution that illustrates my point -- but it's not hard |
76 |
to imagine having to make fairly significant changes to an eclass in |
77 |
order to preserve its pre-EAPI4 semantics in EAPI4+. |
78 |
|
79 |
The conclusion I'm tempted to draw is that it makes more sense to bring |
80 |
my eclass error-handling behavior into line with the EAPI-specific |
81 |
behavior of helper-functions than it does to gum up helper usage in my |
82 |
eclass with a bunch of EAPI-specific error-handling spaghetti-code. |
83 |
|
84 |
i.e., returning to epoop: instead of the above solution, I might leave |
85 |
the dodoc code alone, and instead implement something vaguely like |
86 |
__helpers_die, i.e.: |
87 |
|
88 |
# note: stupidly clever -- not how i'd implement this irl |
89 |
_poop_die_maybe() { |
90 |
local ret="$?" eapi="${EAPI:-0}" |
91 |
eapi=${eapi::1} |
92 |
(( (3-eapi)*(PORTAGE_NONFATAL-1) > 0 )) && die "$@" |
93 |
eerror "$@" |
94 |
return ${ret} # pass through pre-invocation $? unchanged |
95 |
} |
96 |
|
97 |
With this in place, it's not terribly difficult to bring the |
98 |
error-handling/EAPI semantics of epoop() into line with those of the |
99 |
helper functions across all EAPIs. For example, where we had |
100 |
|
101 |
return $(( $(date +'%s') % 3 )) |
102 |
|
103 |
we might now put |
104 |
|
105 |
ret="$(( $(date +'%s') % 3 ))" |
106 |
(( ret == 0 )) || \ |
107 |
_poop_die_maybe "epoop: invoked ${ret} seconds too late" |
108 |
return "${ret}" |
109 |
|
110 |
Or, if it was the last statement in the epoop function, just: |
111 |
|
112 |
( exit $(( $(date +'%s') % 3 )) ) || \ |
113 |
_poop_die_maybe "epoop: invoked $? seconds too late" |
114 |
|
115 |
I hope my gist isn't getting lost in the particulars of this silly |
116 |
example. My point is: EAPI4 changes the behavior of eclasses if those |
117 |
eclasses consume helper functions, which could lead to some pretty |
118 |
muddled error-handling semantics without some kind of clarification or |
119 |
reimplementation. Rather than dance around EAPI4 in our eclasses, |
120 |
perhaps it's more elegant to try to approximate the helper-function |
121 |
error handling semantics at the eclass level, across all EAPI's, which |
122 |
would mean dying for "any kind of error" in EAPI4+ and warning and |
123 |
returning failure codes in EAPI3-. |
124 |
|
125 |
Stated more strongly, perhaps EAPI auto-die effectively sets us on a |
126 |
course where eventually "everything else" really ought to work the same |
127 |
way as the PMS mandates for helpers, avoiding an otherwise confusing |
128 |
situation where ebuild authors constantly need to second-guess eclass |
129 |
behavior: "yes, but specifically how does it behave in EAPI4+/3-, so I |
130 |
can put die() in the right places?" |
131 |
|
132 |
If so, this raises a second question: should portage provide some kind |
133 |
of facility to uh.. facilitate the needs of eclass authors looking to |
134 |
implement these error-handling patterns, rather than leave the correct |
135 |
implementation of something like _epoop_die_maybe() "as an exercise to |
136 |
the coder"? |
137 |
|
138 |
... does anyone see a better approach? Perhaps this a non-issue for |
139 |
some reason I'm not grokking? I'd love to hear your thoughts -- the |
140 |
exact "right" answer isn't so clear to me, and, frankly, I'd like to |
141 |
move on to my eclass writing. |
142 |
|
143 |
Thanks, |
144 |
|
145 |
-gmt |
146 |
|
147 |
-------- |
148 |
|
149 |
* Hey, can someone explain why, when __helpers_die decides die()ing is |
150 |
not called-for (due either to EAPI<4 or PORTAGE_NONFATAL==1), it |
151 |
unceremoniously dumps the error message to stderr, rather than, i.e., |
152 |
calling one of the __elog_base-consuming functions? I really don't get |
153 |
that. |