1# Copyright Rene Rivera 2015
2# Distributed under the Boost Software License, Version 1.0.
3# (See accompanying file LICENSE_1_0.txt or copy at
4# http://www.boost.org/LICENSE_1_0.txt)
5
6# Defines rules that provide requirements based on checking
7# conditions using Boost Predef definitions and version numbers.
8
9import modules ;
10import project ;
11import feature ;
12import string ;
13import toolset ;
14import modules ;
15import path ;
16import "class" : new ;
17
18# Create a project for our targets.
19project.extension predef check ;
20
21# Feature to pass check expressions to check programs.
22feature.feature predef-expression : : free ;
23
24# Check programs. Each needs to be compiled for different languages
25# even though they are all the same source code.
26local rule check_target ( language : ext )
27{
28    # Need to use absolute paths because we don't know the
29    # context of the invocation which affects where the paths
30    # originate from.
31    local predef_jam
32        = [ modules.binding $(__name__) ] ;
33        local source_path
34            = $(predef_jam:D)/predef_check_as_$(language).$(ext) ;
35        local include_path
36            = $(predef_jam:D)/../include ;
37        _check_exe_($(language)) = [
38            exe predef_check_as_$(language)
39                : $(source_path)
40                : <include>$(include_path) ] ;
41    explicit predef_check_as_$(language) ;
42}
43check_target c : c ;
44check_target cpp : cpp ;
45check_target objc : m ;
46check_target objcpp : mm ;
47
48# Checks the expressions and when used evaluates to the true-properties
49# if the expressions are all true. Otherwise evaluates to the
50# false-properties.
51rule check ( expressions + : language ? : true-properties * : false-properties * )
52{
53    # Default to C++ on the check context.
54    language ?= cpp ;
55
56    local project_target = [ project.target $(__name__) ] ;
57	project.push-current $(project_target) ;
58    local terms ;
59    local result ;
60    for expression in $(expressions)
61    {
62        if $(expression:L) in "and" "or"
63        {
64            terms += $(expression:L) ;
65        }
66        else
67        {
68            # The check program to use.
69            local exe_target = [ $(_check_exe_($(language))).name ] ;
70            exe_target = /check/predef//$(exe_target) ;
71
72            # Create the check run if we don't have one yet.
73            local key = [ MD5 $(language)::$(expression) ] ;
74            if ! ( $(key) in $(_checks_) )
75            {
76                _checks_ += $(key) ;
77                _message_(/check/predef//$(key).txt) = $(expression) ;
78                make
79                    $(key).txt :
80                    $(exe_target) :
81                    @$(__name__).predef_check_action :
82                    <predef-expression>$(expression) ;
83                explicit
84                    $(key).txt ;
85            }
86
87            terms += /check/predef//$(key).txt ;
88        }
89    }
90    local instance = [ new check-expression-evaluator
91        $(terms) : $(true-properties) : $(false-properties) ] ;
92    result = <conditional>@$(instance).check ;
93    project.pop-current ;
94    return $(result) ;
95}
96
97# Checks the expressions and when used evaluates to <build>no
98# if the expressions are all false. Otherwise evaluates to the
99# nothing.
100rule require ( expressions + : language ? )
101{
102    return [ check $(expressions) : $(language) : : <build>no ] ;
103}
104
105rule predef_check_action ( targets + : sources + : props * )
106{
107    PREDEF_CHECK_EXPRESSION on $(targets)
108        = [ feature.get-values <predef-expression> : $(props) ] ;
109}
110
111actions predef_check_action bind PREDEF_CHECK_EXPRESSION
112{
113    $(>) "$(PREDEF_CHECK_EXPRESSION)" > $(<)
114}
115
116class check-expression-evaluator
117{
118    import configure ;
119
120    rule __init__ ( expression + : true-properties * : false-properties * )
121    {
122        self.expression = $(expression) ;
123        self.true-properties = $(true-properties) ;
124        self.false-properties = $(false-properties) ;
125    }
126
127    rule check ( properties * )
128    {
129        local to-eval ;
130        local tokens = "and" "or" ;
131        # Go through the expression and: eval the target values,
132        # and normalize to a full expression.
133        for local term in $(self.expression)
134        {
135            if ! ( $(term:L) in $(tokens) )
136            {
137                # A value is a target reference that will evan to "true"
138                # or "false".
139                if $(to-eval[-1]:L) && ! ( $(to-eval[-1]:L) in $(tokens) )
140                {
141                    # Default to "and" operation.
142                    to-eval += "and" ;
143                }
144                local message = [ modules.peek predef : _message_($(term)) ] ;
145                if [ configure.builds $(term) : $(properties) : $(message) ]
146                {
147                    to-eval += "true" ;
148                }
149                else
150                {
151                    to-eval += "false" ;
152                }
153            }
154            else
155            {
156                to-eval += $(term) ;
157            }
158        }
159        # Eval full the expression.
160        local eval-result = [ eval $(to-eval) ] ;
161        # And resolve true/false properties.
162        if $(eval-result) = "true"
163        {
164            return $(self.true-properties) ;
165        }
166        else
167        {
168            return $(self.false-properties) ;
169        }
170    }
171
172    rule eval ( e * )
173    {
174        local r ;
175        if $(e[1]) && $(e[2]) && $(e[3])
176        {
177            if $(e[2]) = "and"
178            {
179                if $(e[1]) = "true" && $(e[3]) = "true"
180                {
181                    r = [ eval "true" $(e[4-]) ] ;
182                }
183                else
184                {
185                    r = [ eval "false" $(e[4-]) ] ;
186                }
187            }
188            else if $(e[2]) = "or"
189            {
190                if $(e[1]) = "true" || $(e[3]) = "true"
191                {
192                    r = [ eval "true" $(e[4-]) ] ;
193                }
194                else
195                {
196                    r = [ eval "false" $(e[4-]) ] ;
197                }
198            }
199        }
200        else
201        {
202            r = $(e[1]) ;
203        }
204        return $(r) ;
205    }
206}
207