1#!/usr/bin/python 2 3# Copyright 2002, 2003 Dave Abrahams 4# Copyright 2002, 2003, 2004, 2005 Vladimir Prus 5# Copyright 2012 Jurko Gospodnetic 6# Distributed under the Boost Software License, Version 1.0. 7# (See accompanying file LICENSE_1_0.txt or copy at 8# http://www.boost.org/LICENSE_1_0.txt) 9 10import BoostBuild 11import re 12 13 14def test_basic(): 15 t = BoostBuild.Tester() 16 __write_appender(t, "appender.jam") 17 t.write("a.cpp", "") 18 t.write("b.cxx", "") 19 t.write("c.tui", "") 20 t.write("d.wd", "") 21 t.write("e.cpp", "") 22 t.write("x.l", "") 23 t.write("y.x_pro", "") 24 t.write("z.cpp", "") 25 t.write("lib/c.cpp", "int bar() { return 0; }\n") 26 t.write("lib/jamfile.jam", "my-lib auxilliary : c.cpp ;") 27 t.write("jamroot.jam", 28r"""import appender ; 29 30import "class" : new ; 31import generators ; 32import type ; 33 34 35################################################################################ 36# 37# We use our own custom EXE, LIB & OBJ target generators as using the regular 38# ones would force us to have to deal with different compiler/linker specific 39# 'features' that really have nothing to do with this test. For example, IBM XL 40# C/C++ for AIX, V12.1 (Version: 12.01.0000.0000) compiler exits with a non-zero 41# exit code and thus fails our build when run with a source file using an 42# unknown suffix like '.marked_cpp'. 43# 44################################################################################ 45 46type.register MY_EXE : my_exe ; 47type.register MY_LIB : my_lib ; 48type.register MY_OBJ : my_obj ; 49 50appender.register compile-c : C : MY_OBJ ; 51appender.register compile-cpp : CPP : MY_OBJ ; 52appender.register link-lib composing : MY_OBJ : MY_LIB ; 53appender.register link-exe composing : MY_OBJ MY_LIB : MY_EXE ; 54 55 56################################################################################ 57# 58# LEX --> C 59# 60################################################################################ 61 62type.register LEX : l ; 63 64appender.register lex-to-c : LEX : C ; 65 66 67################################################################################ 68# 69# /--> tUI_H --\ 70# tUI --< >--> CPP 71# \------------/ 72# 73################################################################################ 74 75type.register tUI : tui ; 76type.register tUI_H : tui_h ; 77 78appender.register ui-to-cpp : tUI tUI_H : CPP ; 79appender.register ui-to-h : tUI : tUI_H ; 80 81 82################################################################################ 83# 84# /--> X1 --\ 85# X_PRO --< >--> CPP 86# \--> X2 --/ 87# 88################################################################################ 89 90type.register X1 : x1 ; 91type.register X2 : x2 ; 92type.register X_PRO : x_pro ; 93 94appender.register x1-x2-to-cpp : X1 X2 : CPP ; 95appender.register x-pro-to-x1-x2 : X_PRO : X1 X2 ; 96 97 98################################################################################ 99# 100# When the main target type is NM_EXE, build OBJ from CPP-MARKED and not from 101# anything else, e.g. directly from CPP. 102# 103################################################################################ 104 105type.register CPP_MARKED : marked_cpp : CPP ; 106type.register POSITIONS : positions ; 107type.register NM.TARGET.CPP : target_cpp : CPP ; 108type.register NM_EXE : : MY_EXE ; 109 110appender.register marked-to-target-cpp : CPP_MARKED : NM.TARGET.CPP ; 111appender.register cpp-to-marked-positions : CPP : CPP_MARKED POSITIONS ; 112 113class "nm::target::cpp-obj-generator" : generator 114{ 115 rule __init__ ( id ) 116 { 117 generator.__init__ $(id) : NM.TARGET.CPP : MY_OBJ ; 118 generator.set-rule-name appender.appender ; 119 } 120 121 rule requirements ( ) 122 { 123 return <main-target-type>NM_EXE ; 124 } 125 126 rule run ( project name ? : properties * : source : multiple ? ) 127 { 128 if [ $(source).type ] = CPP 129 { 130 local converted = [ generators.construct $(project) : NM.TARGET.CPP 131 : $(properties) : $(source) ] ; 132 if $(converted) 133 { 134 return [ generators.construct $(project) : MY_OBJ : 135 $(properties) : $(converted[2]) ] ; 136 } 137 } 138 } 139} 140generators.register [ new "nm::target::cpp-obj-generator" target-obj ] ; 141generators.override target-obj : all ; 142 143 144################################################################################ 145# 146# A more complex test case scenario with the following generators: 147# 1. WHL --> CPP, WHL_LR0, H, H(%_symbols) 148# 2. DLP --> CPP 149# 3. WD --> WHL(%_parser) DLP(%_lexer) 150# 4. A custom generator of higher priority than generators 1. & 2. that helps 151# disambiguate between them when generating CPP files from WHL and DLP 152# sources. 153# 154################################################################################ 155 156type.register WHL : whl ; 157type.register DLP : dlp ; 158type.register WHL_LR0 : lr0 ; 159type.register WD : wd ; 160 161local whale-generator-id = [ appender.register whale : WHL : CPP WHL_LR0 H 162 H(%_symbols) ] ; 163local dolphin-generator-id = [ appender.register dolphin : DLP : CPP ] ; 164appender.register wd : WD : WHL(%_parser) DLP(%_lexer) ; 165 166class wd-to-cpp : generator 167{ 168 rule __init__ ( id : sources * : targets * ) 169 { 170 generator.__init__ $(id) : $(sources) : $(targets) ; 171 } 172 173 rule run ( project name ? : property-set : source ) 174 { 175 local new-sources = $(source) ; 176 if ! [ $(source).type ] in WHL DLP 177 { 178 local r1 = [ generators.construct $(project) $(name) : WHL : 179 $(property-set) : $(source) ] ; 180 local r2 = [ generators.construct $(project) $(name) : DLP : 181 $(property-set) : $(source) ] ; 182 new-sources = [ sequence.unique $(r1[2-]) $(r2[2-]) ] ; 183 } 184 185 local result ; 186 for local i in $(new-sources) 187 { 188 local t = [ generators.construct $(project) $(name) : CPP : 189 $(property-set) : $(i) ] ; 190 result += $(t[2-]) ; 191 } 192 return $(result) ; 193 } 194} 195generators.override $(__name__).wd-to-cpp : $(whale-generator-id) ; 196generators.override $(__name__).wd-to-cpp : $(dolphin-generator-id) ; 197generators.register [ new wd-to-cpp $(__name__).wd-to-cpp : : CPP ] ; 198 199 200################################################################################ 201# 202# Declare build targets. 203# 204################################################################################ 205 206# This should not cause two CPP --> MY_OBJ constructions for a.cpp or b.cpp. 207my-exe a : a.cpp b.cxx obj_1 obj_2 c.tui d.wd x.l y.x_pro lib//auxilliary ; 208my-exe f : a.cpp b.cxx obj_1 obj_2 lib//auxilliary ; 209 210# This should cause two CPP --> MY_OBJ constructions for z.cpp. 211my-obj obj_1 : z.cpp ; 212my-obj obj_2 : z.cpp ; 213 214nm-exe e : e.cpp ; 215""") 216 217 t.run_build_system() 218 t.expect_addition("bin/" * BoostBuild.List("a.my_exe " 219 "a.my_obj b.my_obj c.tui_h c.cpp c.my_obj d_parser.whl d_lexer.dlp " 220 "d_parser.cpp d_lexer.cpp d_lexer.my_obj d_parser.lr0 d_parser.h " 221 "d_parser.my_obj d_parser_symbols.h x.c x.my_obj y.x1 y.x2 y.cpp " 222 "y.my_obj e.marked_cpp e.positions e.target_cpp e.my_obj e.my_exe " 223 "f.my_exe obj_1.my_obj obj_2.my_obj")) 224 t.expect_addition("lib/bin/" * BoostBuild.List("c.my_obj " 225 "auxilliary.my_lib")) 226 t.expect_nothing_more() 227 228 folder = "bin" 229 t.expect_content_lines("%s/obj_1.my_obj" % folder, " Sources: 'z.cpp'") 230 t.expect_content_lines("%s/obj_2.my_obj" % folder, " Sources: 'z.cpp'") 231 t.expect_content_lines("%s/a.my_obj" % folder, " Sources: 'a.cpp'") 232 233 lines = t.stdout().splitlines() 234 source_lines = [x for x in lines if re.match("^ Sources: '", x)] 235 if not __match_count_is(source_lines, "'z.cpp'", 2): 236 BoostBuild.annotation("failure", "z.cpp must be compiled exactly " 237 "twice.") 238 t.fail_test(1) 239 if not __match_count_is(source_lines, "'a.cpp'", 1): 240 BoostBuild.annotation("failure", "a.cpp must be compiled exactly " 241 "once.") 242 t.fail_test(1) 243 t.cleanup() 244 245 246def test_generated_target_names(): 247 """ 248 Test generator generated target names. Unless given explicitly, target 249 names should be determined based on their specified source names. All 250 sources for generating a target need to have matching names in order for 251 Boost Build to be able to implicitly determine the target's name. 252 253 We use the following target generation structure with differently named 254 BBX targets: 255 /---> BB1 ---\ 256 AAA --<----> BB2 ---->--> CCC --(composing)--> DDD 257 \---> BB3 ---/ 258 259 The extra generator at the end is needed because generating a top-level 260 CCC target directly would requires us to explicitly specify a name for it. 261 The extra generator needs to be composing in order not to explicitly 262 request a specific name for its CCC source target based on its own target 263 name. 264 265 We also check for a regression where only the first two sources were 266 checked to see if their names match. Note that we need to try out all file 267 renaming combinations as we do not know what ordering Boost Build is going 268 to use when passing in those files as generator sources. 269 270 """ 271 jamfile_template = """\ 272import type ; 273type.register AAA : _a ; 274type.register BB1 : _b1 ; 275type.register BB2 : _b2 ; 276type.register BB3 : _b3 ; 277type.register CCC : _c ; 278type.register DDD : _d ; 279 280import appender ; 281appender.register aaa-to-bbX : AAA : BB1%s BB2%s BB3%s ; 282appender.register bbX-to-ccc : BB1 BB2 BB3 : CCC ; 283appender.register ccc-to-ddd composing : CCC : DDD ; 284 285ddd _xxx : _xxx._a ; 286""" 287 288 t = BoostBuild.Tester() 289 __write_appender(t, "appender.jam") 290 t.write("_xxx._a", "") 291 292 def test_one(t, rename1, rename2, rename3, status): 293 def f(rename): 294 if rename: return "(%_x)" 295 return "" 296 297 jamfile = jamfile_template % (f(rename1), f(rename2), f(rename3)) 298 t.write("jamroot.jam", jamfile, wait=False) 299 300 # Remove any preexisting targets left over from a previous test run 301 # so we do not have to be careful about tracking which files have been 302 # newly added and which preexisting ones have only been modified. 303 t.rm("bin") 304 305 t.run_build_system(status=status) 306 307 if status: 308 t.expect_output_lines("*.bbX-to-ccc: source targets have " 309 "different names: cannot determine target name") 310 else: 311 def suffix(rename): 312 if rename: return "_x" 313 return "" 314 name = "bin/_xxx" 315 e = t.expect_addition 316 e("%s%s._b1" % (name, suffix(rename1))) 317 e("%s%s._b2" % (name, suffix(rename2))) 318 e("%s%s._b3" % (name, suffix(rename3))) 319 e("%s%s._c" % (name, suffix(rename1 and rename2 and rename3))) 320 e("%s._d" % name) 321 t.expect_nothing_more() 322 323 test_one(t, False, False, False, status=0) 324 test_one(t, True , False, False, status=1) 325 test_one(t, False, True , False, status=1) 326 test_one(t, False, False, True , status=1) 327 test_one(t, True , True , False, status=1) 328 test_one(t, True , False, True , status=1) 329 test_one(t, False, True , True , status=1) 330 test_one(t, True , True , True , status=0) 331 t.cleanup() 332 333 334def __match_count_is(lines, pattern, expected): 335 count = 0 336 for x in lines: 337 if re.search(pattern, x): 338 count += 1 339 if count > expected: 340 return False 341 return count == expected 342 343 344def __write_appender(t, name): 345 t.write(name, 346r"""# Copyright 2012 Jurko Gospodnetic 347# Distributed under the Boost Software License, Version 1.0. 348# (See accompanying file LICENSE_1_0.txt or copy at 349# http://www.boost.org/LICENSE_1_0.txt) 350 351# Support for registering test generators that construct their targets by 352# simply appending their given input data, e.g. list of sources & targets. 353 354import "class" : new ; 355import generators ; 356import modules ; 357import sequence ; 358 359rule register ( id composing ? : source-types + : target-types + ) 360{ 361 local caller-module = [ CALLER_MODULE ] ; 362 id = $(caller-module).$(id) ; 363 local g = [ new generator $(id) $(composing) : $(source-types) : 364 $(target-types) ] ; 365 $(g).set-rule-name $(__name__).appender ; 366 generators.register $(g) ; 367 return $(id) ; 368} 369 370if [ modules.peek : NT ] 371{ 372 X = ")" ; 373 ECHO_CMD = (echo. ; 374} 375else 376{ 377 X = \" ; 378 ECHO_CMD = "echo $(X)" ; 379} 380 381local appender-runs ; 382 383# We set up separate actions for building each target in order to avoid having 384# to iterate over them in action (i.e. shell) code. We have to be extra careful 385# though to achieve the exact same effect as if doing all the work in just one 386# action. Otherwise Boost Jam might, under some circumstances, run only some of 387# our actions. To achieve this we register a series of actions for all the 388# targets (since they all have the same target list - either all or none of them 389# get run independent of which target actually needs to get built), each 390# building only a single target. Since all our actions use the same targets, we 391# can not use 'on-target' parameters to pass data to a specific action so we 392# pass them using the second 'sources' parameter which our actions then know how 393# to interpret correctly. This works well since Boost Jam does not automatically 394# add dependency relations between specified action targets & sources and so the 395# second argument, even though most often used to pass in a list of sources, can 396# actually be used for passing in any type of information. 397rule appender ( targets + : sources + : properties * ) 398{ 399 appender-runs = [ CALC $(appender-runs:E=0) + 1 ] ; 400 local target-index = 0 ; 401 local target-count = [ sequence.length $(targets) ] ; 402 local original-targets ; 403 for t in $(targets) 404 { 405 target-index = [ CALC $(target-index) + 1 ] ; 406 local appender-run = $(appender-runs) ; 407 if $(targets[2])-defined 408 { 409 appender-run += "[$(target-index)/$(target-count)]" ; 410 } 411 append $(targets) : $(appender-run:J=" ") $(t) $(sources) ; 412 } 413} 414 415actions append 416{ 417 $(ECHO_CMD)-------------------------------------------------$(X) 418 $(ECHO_CMD)Appender run: $(>[1])$(X) 419 $(ECHO_CMD)Appender run: $(>[1])$(X)>> "$(>[2])" 420 $(ECHO_CMD)Target group: $(<:J=' ')$(X) 421 $(ECHO_CMD)Target group: $(<:J=' ')$(X)>> "$(>[2])" 422 $(ECHO_CMD) Target: '$(>[2])'$(X) 423 $(ECHO_CMD) Target: '$(>[2])'$(X)>> "$(>[2])" 424 $(ECHO_CMD) Sources: '$(>[3-]:J=' ')'$(X) 425 $(ECHO_CMD) Sources: '$(>[3-]:J=' ')'$(X)>> "$(>[2])" 426 $(ECHO_CMD)=================================================$(X) 427 $(ECHO_CMD)-------------------------------------------------$(X)>> "$(>[2])" 428} 429""") 430 431 432test_basic() 433test_generated_target_names() 434