blob: 63b11e867446e2ffdc33760d882986f4875e7367 [file] [log] [blame]
# Copyright 2003 Dave Abrahams
# Copyright 2004 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
# Print a stack backtrace leading to this rule's caller. Each argument
# represents a line of output to be printed after the first line of the
# backtrace.
#
rule backtrace ( skip-frames prefix messages * : * )
{
local frame-skips = 5 9 13 17 21 25 29 33 37 41 45 49 53 57 61 65 69 73 77 81 ;
local drop-elements = $(frame-skips[$(skip-frames)]) ;
if ! ( $(skip-frames) in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 )
{
ECHO "warning: backtrace doesn't support skipping $(skip-frames) frames;"
"using 1 instead." ;
drop-elements = 5 ;
}
local args = $(.args) ;
if $(.user-modules-only)
{
local bt = [ nearest-user-location ] ;
ECHO "$(prefix) at $(bt) " ;
for local n in $(args)
{
if $($(n))-is-not-empty
{
ECHO $(prefix) $($(n)) ;
}
}
}
else
{
# Get the whole backtrace, then drop the initial quadruples
# corresponding to the frames that must be skipped.
local bt = [ BACKTRACE ] ;
bt = $(bt[$(drop-elements)-]) ;
while $(bt)
{
local m = [ MATCH ^(.+)\\.$ : $(bt[3]) ] ;
ECHO $(bt[1]):$(bt[2]): "in" $(bt[4]) "from module" $(m) ;
# The first time through, print each argument on a separate line.
for local n in $(args)
{
if $($(n))-is-not-empty
{
ECHO $(prefix) $($(n)) ;
}
}
args = ; # Kill args so that this never happens again.
# Move on to the next quadruple.
bt = $(bt[5-]) ;
}
}
}
.args ?= messages 2 3 4 5 6 7 8 9 ;
.disabled ?= ;
.last-error-$(.args) ?= ;
# try-catch --
#
# This is not really an exception-handling mechanism, but it does allow us to
# perform some error-checking on our error-checking. Errors are suppressed after
# a try, and the first one is recorded. Use catch to check that the error
# message matched expectations.
# Begin looking for error messages.
#
rule try ( )
{
.disabled += true ;
.last-error-$(.args) = ;
}
# Stop looking for error messages; generate an error if an argument of messages
# is not found in the corresponding argument in the error call.
#
rule catch ( messages * : * )
{
.disabled = $(.disabled[2-]) ; # Pop the stack.
import sequence ;
if ! $(.last-error-$(.args))-is-not-empty
{
error-skip-frames 3 expected an error, but none occurred ;
}
else
{
for local n in $(.args)
{
if ! $($(n)) in $(.last-error-$(n))
{
local v = [ sequence.join $($(n)) : " " ] ;
v ?= "" ;
local joined = [ sequence.join $(.last-error-$(n)) : " " ] ;
.last-error-$(.args) = ;
error-skip-frames 3 expected \"$(v)\" in argument $(n) of error
: got \"$(joined)\" instead ;
}
}
}
}
rule error-skip-frames ( skip-frames messages * : * )
{
if ! $(.disabled)
{
backtrace $(skip-frames) error: $(messages) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
EXIT ;
}
else if ! $(.last-error-$(.args))
{
for local n in $(.args)
{
# Add an extra empty string so that we always have
# something in the event of an error
.last-error-$(n) = $($(n)) "" ;
}
}
}
if --no-error-backtrace in [ modules.peek : ARGV ]
{
.no-error-backtrace = true ;
}
# Print an error message with a stack backtrace and exit.
#
rule error ( messages * : * )
{
if $(.no-error-backtrace)
{
# Print each argument on a separate line.
for local n in $(.args)
{
if $($(n))-is-not-empty
{
if ! $(first-printed)
{
ECHO error: $($(n)) ;
first-printed = true ;
}
else
{
ECHO $($(n)) ;
}
}
}
EXIT ;
}
else
{
error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}
}
# Same as 'error', but the generated backtrace will include only user files.
#
rule user-error ( messages * : * )
{
.user-modules-only = 1 ;
error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}
# Print a warning message with a stack backtrace and exit.
#
rule warning
{
backtrace 2 warning: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}
# Convert an arbitrary argument list into a list with ":" separators and quoted
# elements representing the same information. This is mostly useful for
# formatting descriptions of arguments with which a rule was called when
# reporting an error.
#
rule lol->list ( * )
{
local result ;
local remaining = 1 2 3 4 5 6 7 8 9 ;
while $($(remaining))
{
local n = $(remaining[1]) ;
remaining = $(remaining[2-]) ;
if $(n) != 1
{
result += ":" ;
}
result += \"$($(n))\" ;
}
return $(result) ;
}
# Return the file:line for the nearest entry in backtrace which correspond to a
# user module.
#
rule nearest-user-location ( )
{
local bt = [ BACKTRACE ] ;
local result ;
while $(bt) && ! $(result)
{
local m = [ MATCH ^(.+)\\.$ : $(bt[3]) ] ;
local user-modules = ([Jj]amroot(.jam|.v2|)|([Jj]amfile(.jam|.v2|)|user-config.jam|site-config.jam|project-root.jam) ;
if [ MATCH $(user-modules) : $(bt[1]:D=) ]
{
result = $(bt[1]):$(bt[2]) ;
}
bt = $(bt[5-]) ;
}
return $(result) ;
}
# If optimized rule is available in Jam, use it.
if NEAREST_USER_LOCATION in [ RULENAMES ]
{
rule nearest-user-location ( )
{
local r = [ NEAREST_USER_LOCATION ] ;
return $(r[1]):$(r[2]) ;
}
}
rule __test__ ( )
{
# Show that we can correctly catch an expected error.
try ;
{
error an error occurred : somewhere ;
}
catch an error occurred : somewhere ;
# Show that unexpected errors generate real errors.
try ;
{
try ;
{
error an error occurred : somewhere ;
}
catch an error occurred : nowhere ;
}
catch expected \"nowhere\" in argument 2 ;
# Show that not catching an error where one was expected is an error.
try ;
{
try ;
{
}
catch ;
}
catch expected an error, but none occurred ;
}