Skip to content

Commit

Permalink
build: print a backtrace with the original metadata locations of Bash…
Browse files Browse the repository at this point in the history
… shell funcs

Leverage the comments that emit_var writes and the backtrace that
the shell func writes to generate an additional metadata-relative
backtrace. This will help the user troubleshoot shell funcs much
more easily.

E.g.:

| /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873: line 168: no_exist: command not found
| WARNING: /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873:168 exit 127 from 'no_exist'
| WARNING: Backtrace (BB generated script):
| 	#0: myclass_do_something, /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873, line 168
| 	#1: do_something, /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873, line 157
| 	#2: actually_fail, /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873, line 150
| 	#3: my_compile_extra, /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873, line 152
| 	#4: do_compile, /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873, line 138
| 	#5: main, /home/laplante/repos/oe-core/build/tmp-glibc/work/core2-64-oe-linux/libsolv/0.7.14-r0/temp/run.do_compile.87873, line 181
|
| Backtrace (metadata-relative locations):
| 	#0: myclass_do_something, /home/laplante/repos/oe-core/meta/classes/myclass.bbclass, line 2
| 	#1: do_something, autogenerated, line 2
| 	#2: actually_fail, /home/laplante/repos/oe-core/meta/recipes-extended/libsolv/libsolv_0.7.14.bb, line 36
| 	#3: my_compile_extra, /home/laplante/repos/oe-core/meta/recipes-extended/libsolv/libsolv_0.7.14.bb, line 38
| 	#4: do_compile, autogenerated, line 3

Signed-off-by: Chris Laplante <[email protected]>
Signed-off-by: Richard Purdie <[email protected]>
  • Loading branch information
chris-laplante authored and rpurdie committed Aug 7, 2020
1 parent 81098a1 commit e686794
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 1 deletion.
58 changes: 58 additions & 0 deletions lib/bb/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import sys
import logging
import glob
import itertools
import time
import re
import stat
import bb
import bb.msg
Expand Down Expand Up @@ -470,6 +472,62 @@ def readfifo(data):
bb.debug(2, "Executing shell function %s" % func)
with open(os.devnull, 'r+') as stdin, logfile:
bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
except bb.process.ExecutionError as exe:
# Find the backtrace that the shell trap generated
backtrace_marker_regex = re.compile(r"WARNING: Backtrace \(BB generated script\)")
stdout_lines = (exe.stdout or "").split("\n")
backtrace_start_line = None
for i, line in enumerate(reversed(stdout_lines)):
if backtrace_marker_regex.search(line):
backtrace_start_line = len(stdout_lines) - i
break

# Read the backtrace frames, starting at the location we just found
backtrace_entry_regex = re.compile(r"#(?P<frameno>\d+): (?P<funcname>[^\s]+), (?P<file>.+?), line ("
r"?P<lineno>\d+)")
backtrace_frames = []
if backtrace_start_line:
for line in itertools.islice(stdout_lines, backtrace_start_line, None):
match = backtrace_entry_regex.search(line)
if match:
backtrace_frames.append(match.groupdict())

with open(runfile, "r") as script:
script_lines = [line.rstrip() for line in script.readlines()]

# For each backtrace frame, search backwards in the script (from the line number called out by the frame),
# to find the comment that emit_vars injected when it wrote the script. This will give us the metadata
# filename (e.g. .bb or .bbclass) and line number where the shell function was originally defined.
script_metadata_comment_regex = re.compile(r"# line: (?P<lineno>\d+), file: (?P<file>.+)")
better_frames = []
# Skip the very last frame since it's just the call to the shell task in the body of the script
for frame in backtrace_frames[:-1]:
# Check whether the frame corresponds to a function defined in the script vs external script.
if os.path.samefile(frame["file"], runfile):
# Search backwards from the frame lineno to locate the comment that BB injected
i = int(frame["lineno"]) - 1
while i >= 0:
match = script_metadata_comment_regex.match(script_lines[i])
if match:
# Calculate the relative line in the function itself
relative_line_in_function = int(frame["lineno"]) - i - 2
# Calculate line in the function as declared in the metadata
metadata_function_line = relative_line_in_function + int(match["lineno"])
better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(
frameno=frame["frameno"],
funcname=frame["funcname"],
file=match["file"],
lineno=metadata_function_line
))
break
i -= 1
else:
better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(**frame))

if better_frames:
better_frames = ("\t{0}".format(frame) for frame in better_frames)
exe.extra_message = "\nBacktrace (metadata-relative locations):\n{0}".format("\n".join(better_frames))
raise
finally:
os.unlink(fifopath)

Expand Down
3 changes: 2 additions & 1 deletion lib/bb/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, command, exitcode, stdout = None, stderr = None):
self.exitcode = exitcode
self.stdout = stdout
self.stderr = stderr
self.extra_message = None

def __str__(self):
message = ""
Expand All @@ -51,7 +52,7 @@ def __str__(self):
if message:
message = ":\n" + message
return (CmdError.__str__(self) +
" with exit code %s" % self.exitcode + message)
" with exit code %s" % self.exitcode + message + (self.extra_message or ""))

class Popen(subprocess.Popen):
defaults = {
Expand Down

0 comments on commit e686794

Please sign in to comment.