• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2003 Dave Abrahams
2# Copyright 2004 Vladimir Prus
3# Distributed under the Boost Software License, Version 1.0.
4# (See accompanying file LICENSE_1_0.txt or copy at
5# http://www.boost.org/LICENSE_1_0.txt)
6
7# Print a stack backtrace leading to this rule's caller. Each argument
8# represents a line of output to be printed after the first line of the
9# backtrace.
10#
11rule backtrace ( skip-frames prefix messages * : * )
12{
13    local frame-skips = 5 9 13 17 21 25 29 33 37 41 45 49 53 57 61 65 69 73 77 81 ;
14    local drop-elements = $(frame-skips[$(skip-frames)]) ;
15    if ! ( $(skip-frames) in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 )
16    {
17        ECHO "warning: backtrace doesn't support skipping $(skip-frames) "
18            "frames; using 1 instead." ;
19        drop-elements = 5 ;
20    }
21
22    local args = $(.args) ;
23    if $(.user-modules-only)
24    {
25        local bt = [ nearest-user-location ] ;
26        if $(bt)
27        {
28            ECHO $(prefix) at $(bt) ;
29        }
30        for local n in $(args)
31        {
32            if $($(n))-is-defined
33            {
34                ECHO $(prefix) $($(n)) ;
35            }
36        }
37    }
38    else
39    {
40        # Get the whole backtrace, then drop the initial quadruples
41        # corresponding to the frames that must be skipped.
42        local bt = [ BACKTRACE ] ;
43        bt = $(bt[$(drop-elements)-]) ;
44
45        while $(bt)
46        {
47            local m = [ MATCH ^(.+)\\.$ : $(bt[3]) ] ;
48            ECHO "$(bt[1]):$(bt[2]):" "in" $(bt[4]) "from module" $(m) ;
49
50            # The first time through, print each argument on a separate line.
51            for local n in $(args)
52            {
53                if $($(n))-is-defined
54                {
55                    ECHO $(prefix) $($(n)) ;
56                }
57            }
58            args = ;  # Kill args so that this never happens again.
59
60            # Move on to the next quadruple.
61            bt = $(bt[5-]) ;
62        }
63    }
64}
65
66.args ?= messages 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;
67.disabled ?= ;
68.last-error-$(.args) ?= ;
69
70
71# try-catch --
72#
73# This is not really an exception-handling mechanism, but it does allow us to
74# perform some error-checking on our error-checking. Errors are suppressed after
75# a try, and the first one is recorded. Use catch to check that the error
76# message matched expectations.
77
78# Begin looking for error messages.
79#
80rule try ( )
81{
82    .disabled += true ;
83    .last-error-$(.args) = ;
84}
85
86
87# Stop looking for error messages; generate an error if an argument of messages
88# is not found in the corresponding argument in the error call.
89#
90rule catch ( messages * : * )
91{
92    .disabled = $(.disabled[2-]) ;  # Pop the stack.
93
94    import sequence ;
95
96    if ! $(.last-error-$(.args))-is-defined
97    {
98        error-skip-frames 3 expected an error, but none occurred ;
99    }
100    else
101    {
102        for local n in $(.args)
103        {
104            if ! $($(n)) in $(.last-error-$(n))
105            {
106                local v = [ sequence.join $($(n)) : " " ] ;
107                v ?= "" ;
108                local joined = [ sequence.join $(.last-error-$(n)) : " " ] ;
109
110                .last-error-$(.args) = ;
111                error-skip-frames 3 expected \"$(v)\" in argument $(n) of error
112                    : got \"$(joined)\" instead ;
113            }
114        }
115    }
116}
117
118
119rule error-skip-frames ( skip-frames messages * : * )
120{
121    if ! $(.disabled)
122    {
123        backtrace $(skip-frames) "error:" $(messages) : $(2) : $(3) : $(4) : $(5)
124            : $(6) : $(7) : $(8) : $(9) : $(10) : $(11) : $(12) : $(13) : $(14)
125            : $(15) : $(16) : $(17) : $(18) : $(19) ;
126        EXIT ;
127    }
128    else if ! $(.last-error-$(.args))
129    {
130        for local n in $(.args)
131        {
132            # Add an extra empty string so that we always have something in the
133            # event of an error.
134            .last-error-$(n) = $($(n)) "" ;
135        }
136    }
137}
138
139if --no-error-backtrace in [ modules.peek : ARGV ]
140{
141    .no-error-backtrace = true ;
142}
143
144
145# Print an error message with a stack backtrace and exit.
146#
147rule error ( messages * : * )
148{
149    if $(.no-error-backtrace)
150    {
151        local first-printed ;
152        # Print each argument on a separate line.
153        for local n in $(.args)
154        {
155            if $($(n))-is-defined
156            {
157                if ! $(first-printed)
158                {
159                    ECHO "error:" $($(n)) ;
160                    first-printed = true ;
161                }
162                else
163                {
164                    ECHO $($(n)) ;
165                }
166            }
167        }
168        EXIT ;
169    }
170    else
171    {
172        error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) :
173            $(8) : $(9) : $(10) : $(11) : $(12) : $(13) : $(14) : $(15) : $(16)
174            : $(17) : $(18) : $(19) ;
175    }
176}
177
178
179# Same as 'error', but the generated backtrace will include only user files.
180#
181rule user-error ( messages * : * )
182{
183    .user-modules-only = 1 ;
184    error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) :
185        $(9) : $(10) : $(11) : $(12) : $(13) : $(14) : $(15) : $(16) : $(17) :
186        $(18) : $(19) ;
187}
188
189
190# Print a warning message with a stack backtrace and exit.
191#
192rule warning
193{
194    backtrace 2 "warning:" $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) :
195        $(9) : $(10) : $(11) : $(12) : $(13) : $(14) : $(15) : $(16) : $(17) :
196        $(18) : $(19) ;
197}
198
199
200# Convert an arbitrary argument list into a list with ":" separators and quoted
201# elements representing the same information. This is mostly useful for
202# formatting descriptions of arguments with which a rule was called when
203# reporting an error.
204#
205rule lol->list ( * )
206{
207    local result ;
208    local remaining = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;
209    while $($(remaining))
210    {
211        local n = $(remaining[1]) ;
212        remaining = $(remaining[2-]) ;
213
214        if $(n) != 1
215        {
216            result += ":" ;
217        }
218        result += \"$($(n))\" ;
219    }
220    return $(result) ;
221}
222
223
224# Return the file:line for the nearest entry in backtrace which correspond to a
225# user module.
226#
227rule nearest-user-location ( )
228{
229    local bt = [ BACKTRACE ] ;
230
231    local result ;
232    while $(bt) && ! $(result)
233    {
234        local m = [ MATCH ^(.+)\\.$ : $(bt[3]) ] ;
235        local user-modules = "([Jj]amroot(.jam|.v2|)|([Jj]amfile(.jam|.v2|)|user-config.jam|site-config.jam|project-config.jam|project-root.jam)" ;
236
237        if [ MATCH $(user-modules) : $(bt[1]:D=) ]
238        {
239            result = "$(bt[1]):$(bt[2])" ;
240        }
241        bt = $(bt[5-]) ;
242    }
243    return $(result) ;
244}
245
246
247# If optimized rule is available in Jam, use it.
248if NEAREST_USER_LOCATION in [ RULENAMES ]
249{
250    rule nearest-user-location ( )
251    {
252        local r = [ NEAREST_USER_LOCATION ] ;
253        return "$(r[1]):$(r[2])" ;
254    }
255}
256
257
258rule __test__ ( )
259{
260    # Show that we can correctly catch an expected error.
261    try ;
262    {
263        error an error occurred : somewhere ;
264    }
265    catch an error occurred : somewhere ;
266
267    # Show that unexpected errors generate real errors.
268    try ;
269    {
270        try ;
271        {
272            error an error occurred : somewhere ;
273        }
274        catch an error occurred : nowhere ;
275    }
276    catch expected \"nowhere\" in argument 2 ;
277
278    # Show that not catching an error where one was expected is an error.
279    try ;
280    {
281        try ;
282        {
283        }
284        catch ;
285    }
286    catch expected an error, but none occurred ;
287}
288