1# Copyright 2002 Dave Abrahams
2# Distributed under the Boost Software License, Version 1.0.
3# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
4
5import "class" : new ;
6import sequence ;
7import set ;
8import regex ;
9import feature ;
10import property ;
11import container ;
12import string ;
13
14
15# Transform property-set by applying f to each component property.
16#
17local rule apply-to-property-set ( f property-set )
18{
19    local properties = [ feature.split $(property-set) ] ;
20    return [ string.join [ $(f) $(properties) ] : / ] ;
21}
22
23
24# Expand the given build request by combining all property-sets which do not
25# specify conflicting non-free features. Expects all the project files to
26# already be loaded.
27#
28rule expand-no-defaults ( property-sets * )
29{
30    # First make all features and subfeatures explicit.
31    local expanded-property-sets = [ sequence.transform apply-to-property-set
32        feature.expand-subfeatures : $(property-sets) ] ;
33
34    # Now combine all of the expanded property-sets
35    local product = [ x-product $(expanded-property-sets) : $(feature-space) ] ;
36
37    return $(product) ;
38}
39
40
41# Implementation of x-product, below. Expects all the project files to already
42# be loaded.
43#
44local rule x-product-aux ( property-sets + )
45{
46    local result ;
47    local p = [ feature.split $(property-sets[1]) ] ;
48    local f = [ set.difference $(p:G) : [ feature.free-features ] ] ;
49    local seen ;
50    # No conflict with things used at a higher level?
51    if ! [ set.intersection $(f) : $(x-product-used) ]
52    {
53        local x-product-seen ;
54        {
55            # Do not mix in any conflicting features.
56            local x-product-used = $(x-product-used) $(f) ;
57
58            if $(property-sets[2])
59            {
60                local rest = [ x-product-aux $(property-sets[2-]) : $(feature-space) ] ;
61                result = $(property-sets[1])/$(rest) ;
62            }
63
64            result ?= $(property-sets[1]) ;
65        }
66
67        # If we did not encounter a conflicting feature lower down, do not
68        # recurse again.
69        if ! [ set.intersection $(f) : $(x-product-seen) ]
70        {
71            property-sets = ;
72        }
73
74        seen = $(x-product-seen) ;
75    }
76
77    if $(property-sets[2])
78    {
79        result += [ x-product-aux $(property-sets[2-]) : $(feature-space) ] ;
80    }
81
82    # Note that we have seen these features so that higher levels will recurse
83    # again without them set.
84    x-product-seen += $(f) $(seen) ;
85    return $(result) ;
86}
87
88
89# Return the cross-product of all elements of property-sets, less any that would
90# contain conflicting values for single-valued features. Expects all the project
91# files to already be loaded.
92#
93local rule x-product ( property-sets * )
94{
95    if $(property-sets).non-empty
96    {
97        # Prepare some "scoped globals" that can be used by the implementation
98        # function, x-product-aux.
99        local x-product-seen x-product-used ;
100        return [ x-product-aux $(property-sets) : $(feature-space) ] ;
101    }
102    # Otherwise return empty.
103}
104
105
106# Returns true if either 'v' or the part of 'v' before the first '-' symbol is
107# an implicit value. Expects all the project files to already be loaded.
108#
109local rule looks-like-implicit-value ( v )
110{
111    if [ feature.is-implicit-value $(v) ]
112    {
113        return true ;
114    }
115    else
116    {
117        local split = [ regex.split $(v) - ] ;
118        if [ feature.is-implicit-value $(split[1]) ]
119        {
120            return true ;
121        }
122    }
123}
124
125
126# Takes the command line tokens (such as taken from the ARGV rule) and
127# constructs a build request from them. Returns a vector of two vectors (where
128# "vector" means container.jam's "vector"). First is the set of targets
129# specified in the command line, and second is the set of requested build
130# properties. Expects all the project files to already be loaded.
131#
132rule from-command-line ( command-line * )
133{
134    local targets ;
135    local properties ;
136
137    command-line = $(command-line[2-]) ;
138    local skip-next = ;
139    for local e in $(command-line)
140    {
141        if $(skip-next)
142        {
143            skip-next = ;
144        }
145        else if ! [ MATCH ^(-) : $(e) ]
146        {
147            # Build request spec either has "=" in it or completely consists of
148            # implicit feature values.
149            local fs = feature-space ;
150            if [ MATCH "(.*=.*)" : $(e) ]
151                || [ looks-like-implicit-value $(e:D=) : $(feature-space) ]
152            {
153                properties += [ convert-command-line-element $(e) :
154                    $(feature-space) ] ;
155            }
156            else if $(e)
157            {
158                targets += $(e) ;
159            }
160        }
161        else if [ MATCH "^(-[-ldjfsto])$" : $(e) ]
162        {
163            skip-next = true ;
164        }
165    }
166    return [ new vector
167        [ new vector $(targets) ]
168        [ new vector $(properties) ] ] ;
169}
170
171
172# Converts one element of command line build request specification into internal
173# form. Expects all the project files to already be loaded.
174#
175local rule convert-command-line-element ( e )
176{
177    local result ;
178    local parts = [ regex.split $(e) "/" ] ;
179    while $(parts)
180    {
181        local p = $(parts[1]) ;
182        local m = [ MATCH "([^=]*)=(.*)" : $(p) ] ;
183        local lresult ;
184        local feature ;
185        local values ;
186        if $(m)
187        {
188            feature = $(m[1]) ;
189            values = [ regex.split $(m[2]) "," ] ;
190            lresult = <$(feature)>$(values) ;
191        }
192        else
193        {
194            lresult = [ regex.split $(p) "," ] ;
195        }
196
197        if $(feature) && free in [ feature.attributes <$(feature)> ]
198        {
199            # If we have free feature, then the value is everything
200            # until the end of the command line token. Slashes in
201            # the following string are not taked to mean separation
202            # of properties. Commas are also not interpreted specially.
203            values = $(values:J=,) ;
204            values = $(values) $(parts[2-]) ;
205            values = $(values:J=/) ;
206            lresult = <$(feature)>$(values) ;
207            parts = ;
208        }
209
210        if ! [ MATCH (.*-.*) : $(p) ]
211        {
212            # property.validate cannot handle subfeatures, so we avoid the check
213            # here.
214            for local p in $(lresult)
215            {
216                property.validate $(p) : $(feature-space) ;
217            }
218        }
219
220        if ! $(result)
221        {
222            result = $(lresult) ;
223        }
224        else
225        {
226            result = $(result)/$(lresult) ;
227        }
228
229        parts = $(parts[2-]) ;
230    }
231
232    return $(result) ;
233}
234
235
236rule __test__ ( )
237{
238    import assert ;
239    import feature ;
240
241    feature.prepare-test build-request-test-temp ;
242
243    import build-request ;
244    import build-request : expand-no-defaults : build-request.expand-no-defaults ;
245    import errors : try catch ;
246    import feature : feature subfeature ;
247
248    feature toolset : gcc msvc borland : implicit ;
249    subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
250      3.0 3.0.1 3.0.2 : optional ;
251
252    feature variant : debug release : implicit composite ;
253    feature inlining : on off ;
254    feature "include" : : free ;
255
256    feature stdlib : native stlport : implicit ;
257
258    feature runtime-link : dynamic static : symmetric ;
259
260    # Empty build requests should expand to empty.
261    assert.result
262        : build-request.expand-no-defaults ;
263
264    assert.result
265        <toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug
266        <toolset>msvc/<stdlib>stlport/<variant>debug
267        <toolset>msvc/<variant>debug
268        : build-request.expand-no-defaults gcc-3.0.1/stlport msvc/stlport msvc debug ;
269
270    assert.result
271        <toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug
272        <toolset>msvc/<variant>debug
273        <variant>debug/<toolset>msvc/<stdlib>stlport
274        : build-request.expand-no-defaults gcc-3.0.1/stlport msvc debug msvc/stlport ;
275
276    assert.result
277        <toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug/<inlining>off
278        <toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>release/<inlining>off
279        : build-request.expand-no-defaults gcc-3.0.1/stlport debug release <inlining>off ;
280
281    assert.result
282        <include>a/b/c/<toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug/<include>x/y/z
283        <include>a/b/c/<toolset>msvc/<stdlib>stlport/<variant>debug/<include>x/y/z
284        <include>a/b/c/<toolset>msvc/<variant>debug/<include>x/y/z
285        : build-request.expand-no-defaults <include>a/b/c gcc-3.0.1/stlport msvc/stlport msvc debug  <include>x/y/z ;
286
287    local r ;
288
289    r = [ build-request.from-command-line bjam debug runtime-link=dynamic ] ;
290    assert.equal [ $(r).get-at 1 ] : ;
291    assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic ;
292
293    try ;
294    {
295        build-request.from-command-line bjam gcc/debug runtime-link=dynamic/static ;
296    }
297    catch \"static\" is not an implicit feature value ;
298
299    r = [ build-request.from-command-line bjam -d2 --debug debug target runtime-link=dynamic ] ;
300    assert.equal [ $(r).get-at 1 ] : target ;
301    assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic ;
302
303    r = [ build-request.from-command-line bjam debug runtime-link=dynamic,static ] ;
304    assert.equal [ $(r).get-at 1 ] : ;
305    assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic <runtime-link>static ;
306
307    r = [ build-request.from-command-line bjam debug gcc/runtime-link=dynamic,static ] ;
308    assert.equal [ $(r).get-at 1 ] : ;
309    assert.equal [ $(r).get-at 2 ] : debug gcc/<runtime-link>dynamic
310        gcc/<runtime-link>static ;
311
312    r = [ build-request.from-command-line bjam msvc gcc,borland/runtime-link=static ] ;
313    assert.equal [ $(r).get-at 1 ] : ;
314    assert.equal [ $(r).get-at 2 ] : msvc gcc/<runtime-link>static
315        borland/<runtime-link>static ;
316
317    r = [ build-request.from-command-line bjam gcc-3.0 ] ;
318    assert.equal [ $(r).get-at 1 ] : ;
319    assert.equal [ $(r).get-at 2 ] : gcc-3.0 ;
320
321    feature.finish-test build-request-test-temp ;
322}
323