1#!/usr/local/bin/python3.8
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