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