1# Copyright 2002, 2003 Dave Abrahams
2# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
3# Distributed under the Boost Software License, Version 1.0.
4# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
5
6#  Deals with target type declaration and defines target class which supports
7#  typed targets.
8
9import "class" : new ;
10import feature ;
11import generators : * ;
12import os ;
13import param ;
14import project ;
15import property ;
16import scanner ;
17
18# The following import would create a circular dependency:
19# project -> project-root -> builtin -> type -> targets -> project
20# import targets ;
21
22# The feature is optional so it would never get added implicitly. It is used
23# only for internal purposes and in all cases we want to use it explicitly.
24feature.feature target-type : : composite optional ;
25
26feature.feature main-target-type : : optional incidental ;
27feature.feature base-target-type : : composite optional free ;
28
29
30# Registers a target type, possible derived from a 'base-type'. Providing a list
31# of 'suffixes' here is a shortcut for separately calling the register-suffixes
32# rule with the given suffixes and the set-generated-target-suffix rule with the
33# first given suffix.
34#
35rule register ( type : suffixes * : base-type ? )
36{
37    # Type names cannot contain hyphens, because when used as feature-values
38    # they would be interpreted as composite features which need to be
39    # decomposed.
40    switch $(type)
41    {
42        case *-* :
43            import errors ;
44            errors.error "type name \"$(type)\" contains a hyphen" ;
45    }
46
47    if $(type) in $(.types)
48    {
49        import errors ;
50        errors.error "Type $(type) is already registered." ;
51    }
52
53    {
54        .types += $(type) ;
55        .base.$(type) = $(base-type) ;
56        .derived.$(base-type) += $(type) ;
57        .bases.$(type) = $(type) $(.bases.$(base-type)) ;
58
59        # Store suffixes for generated targets.
60        .suffixes.$(type) = [ new property-map ] ;
61
62        # Store prefixes for generated targets (e.g. "lib" for library).
63        .prefixes.$(type) = [ new property-map ] ;
64
65        if $(suffixes)-is-defined
66        {
67            # Specify mapping from suffixes to type.
68            register-suffixes $(suffixes) : $(type) ;
69            # By default generated targets of 'type' will use the first of
70            #'suffixes'. This may be overridden.
71            set-generated-target-suffix $(type) : : $(suffixes[1]) ;
72        }
73
74        feature.extend target-type      : $(type) ;
75        feature.extend main-target-type : $(type) ;
76        feature.extend base-target-type : $(type) ;
77
78        feature.compose <target-type>$(type) : $(base-type:G=<base-target-type>) ;
79        feature.compose <base-target-type>$(type) : <base-target-type>$(base-type) ;
80
81        # We used to declare the main target rule only when a 'main' parameter
82        # has been specified. However, it is hard to decide that a type will
83        # *never* need a main target rule and so from time to time we needed to
84        # make yet another type 'main'. So now a main target rule is defined for
85        # each type.
86        main-rule-name = [ type-to-rule-name $(type) ] ;
87        .main-target-type.$(main-rule-name) = $(type) ;
88        IMPORT $(__name__) : main-target-rule : : $(main-rule-name) ;
89
90        # Adding a new derived type affects generator selection so we need to
91        # make the generator selection module update any of its cached
92        # information related to a new derived type being defined.
93        generators.update-cached-information-with-a-new-type $(type) ;
94    }
95}
96
97
98# Given a type, returns the name of the main target rule which creates targets
99# of that type.
100#
101rule type-to-rule-name ( type )
102{
103    # Lowercase everything. Convert underscores to dashes.
104    import regex ;
105    local n = [ regex.split $(type:L) "_" ] ;
106    return $(n:J=-) ;
107}
108
109
110# Given a main target rule name, returns the type for which it creates targets.
111#
112rule type-from-rule-name ( rule-name )
113{
114    return $(.main-target-type.$(rule-name)) ;
115}
116
117
118# Specifies that files with suffix from 'suffixes' be recognized as targets of
119# type 'type'. Issues an error if a different type is already specified for any
120# of the suffixes.
121#
122rule register-suffixes ( suffixes + : type )
123{
124    for local s in $(suffixes)
125    {
126        if ! $(.type.$(s))
127        {
128            .type.$(s) = $(type) ;
129        }
130        else if $(.type.$(s)) != $(type)
131        {
132            import errors ;
133            errors.error Attempting to specify multiple types for suffix
134                \"$(s)\" : "Old type $(.type.$(s)), New type $(type)" ;
135        }
136    }
137}
138
139
140# Returns true iff type has been registered.
141#
142rule registered ( type )
143{
144    if $(type) in $(.types)
145    {
146        return true ;
147    }
148}
149
150
151# Issues an error if 'type' is unknown.
152#
153rule validate ( type )
154{
155    if ! [ registered $(type) ]
156    {
157        import errors ;
158        errors.error "Unknown target type $(type)" ;
159    }
160}
161
162
163# Sets a scanner class that will be used for this 'type'.
164#
165rule set-scanner ( type : scanner )
166{
167    validate $(type) ;
168    .scanner.$(type) = $(scanner) ;
169}
170
171
172# Returns a scanner instance appropriate to 'type' and 'properties'.
173#
174rule get-scanner ( type : property-set )
175{
176    if $(.scanner.$(type))
177    {
178        return [ scanner.get $(.scanner.$(type)) : $(property-set) ] ;
179    }
180}
181
182
183# Returns a base type for the given type or nothing in case the given type is
184# not derived.
185#
186rule base ( type )
187{
188    return $(.base.$(type)) ;
189}
190
191
192# Returns the given type and all of its base types in order of their distance
193# from type.
194#
195rule all-bases ( type )
196{
197    return $(.bases.$(type)) ;
198}
199
200
201# Returns the given type and all of its derived types in order of their distance
202# from type.
203#
204rule all-derived ( type )
205{
206    local result = $(type) ;
207    for local d in $(.derived.$(type))
208    {
209        result += [ all-derived $(d) ] ;
210    }
211    return $(result) ;
212}
213
214
215# Returns true if 'type' is equal to 'base' or has 'base' as its direct or
216# indirect base.
217#
218rule is-derived ( type base )
219{
220    if $(base) in $(.bases.$(type))
221    {
222        return true ;
223    }
224}
225
226# Returns true if 'type' is either derived from or is equal to 'base'.
227#
228# TODO: It might be that is-derived and is-subtype were meant to be different
229# rules - one returning true for type = base and one not, but as currently
230# implemented they are actually the same. Clean this up.
231#
232rule is-subtype ( type base )
233{
234    return [ is-derived $(type) $(base) ] ;
235}
236
237
238
239
240# Sets a file suffix to be used when generating a target of 'type' with the
241# specified properties. Can be called with no properties if no suffix has
242# already been specified for the 'type'. The 'suffix' parameter can be an empty
243# string ("") to indicate that no suffix should be used.
244#
245# Note that this does not cause files with 'suffix' to be automatically
246# recognized as being of 'type'. Two different types can use the same suffix for
247# their generated files but only one type can be auto-detected for a file with
248# that suffix. User should explicitly specify which one using the
249# register-suffixes rule.
250#
251rule set-generated-target-suffix ( type : properties * : suffix )
252{
253    set-generated-target-ps suffix : $(type) : $(properties) : $(suffix) ;
254}
255
256
257# Change the suffix previously registered for this type/properties combination.
258# If suffix is not yet specified, sets it.
259#
260rule change-generated-target-suffix ( type : properties * : suffix )
261{
262    change-generated-target-ps suffix : $(type) : $(properties) : $(suffix) ;
263}
264
265
266# Returns the suffix used when generating a file of 'type' with the given
267# properties.
268#
269rule generated-target-suffix ( type : property-set )
270{
271    return [ generated-target-ps suffix : $(type) : $(property-set) ] ;
272}
273
274
275# Sets a target prefix that should be used when generating targets of 'type'
276# with the specified properties. Can be called with empty properties if no
277# prefix for 'type' has been specified yet.
278#
279# The 'prefix' parameter can be empty string ("") to indicate that no prefix
280# should be used.
281#
282# Usage example: library names use the "lib" prefix on unix.
283#
284rule set-generated-target-prefix ( type : properties * : prefix )
285{
286    set-generated-target-ps prefix : $(type) : $(properties) : $(prefix) ;
287}
288
289
290# Change the prefix previously registered for this type/properties combination.
291# If prefix is not yet specified, sets it.
292#
293rule change-generated-target-prefix ( type : properties * : prefix )
294{
295    change-generated-target-ps prefix : $(type) : $(properties) : $(prefix) ;
296}
297
298
299rule generated-target-prefix ( type : property-set )
300{
301    return [ generated-target-ps prefix : $(type) : $(property-set) ] ;
302}
303
304
305# Common rules for prefix/suffix provisioning follow.
306
307local rule set-generated-target-ps ( ps : type : properties * : psval )
308{
309    $(.$(ps)es.$(type)).insert $(properties) : $(psval) ;
310}
311
312
313local rule change-generated-target-ps ( ps : type : properties * : psval )
314{
315    local prev = [ $(.$(ps)es.$(type)).find-replace $(properties) : $(psval) ] ;
316    if ! $(prev)
317    {
318        set-generated-target-ps $(ps) : $(type) : $(properties) : $(psval) ;
319    }
320}
321
322
323# Returns either prefix or suffix (as indicated by 'ps') that should be used
324# when generating a target of 'type' with the specified properties. Parameter
325# 'ps' can be either "prefix" or "suffix".  If no prefix/suffix is specified for
326# 'type', returns prefix/suffix for base type, if any.
327#
328local rule generated-target-ps ( ps : type : property-set )
329{
330    local result ;
331    local found ;
332    while $(type) && ! $(found)
333    {
334        result = [ $(.$(ps)es.$(type)).find $(property-set) ] ;
335        # If the prefix/suffix is explicitly set to an empty string, we consider
336        # prefix/suffix to be found. If we were not to compare with "", there
337        # would be no way to specify an empty prefix/suffix.
338        if $(result)-is-defined
339        {
340            found = true ;
341        }
342        type = $(.base.$(type)) ;
343    }
344    if $(result) = ""
345    {
346        result = ;
347    }
348    return $(result) ;
349}
350
351
352# Returns file type given its name. If there are several dots in filename, tries
353# each suffix. E.g. for name of "file.so.1.2" suffixes "2", "1", and "so" will
354# be tried.
355#
356rule type ( filename )
357{
358    if [ os.name ] in NT CYGWIN
359    {
360        filename = $(filename:L) ;
361    }
362    local type ;
363    while ! $(type) && $(filename:S)
364    {
365        local suffix = $(filename:S) ;
366        type = $(.type$(suffix)) ;
367        filename = $(filename:S=) ;
368    }
369    return $(type) ;
370}
371
372
373# Rule used to construct all main targets. Note that this rule gets imported
374# into the global namespace under different alias names and the exact target
375# type to construct is selected based on the alias used to actually invoke this
376# rule.
377#
378rule main-target-rule ( name : sources * : requirements * : default-build * :
379    usage-requirements * )
380{
381    param.handle-named-params
382        sources requirements default-build usage-requirements ;
383    # First discover the required target type based on the exact alias used to
384    # invoke this rule.
385    local bt = [ BACKTRACE 1 ] ;
386    local rulename = $(bt[4]) ;
387    local target-type = [ type-from-rule-name $(rulename) ] ;
388
389    # This is a circular module dependency and so must be imported here.
390    import targets ;
391
392    return [ targets.create-typed-target $(target-type) : [ project.current ] :
393        $(name) : $(sources) : $(requirements) : $(default-build) :
394        $(usage-requirements) ] ;
395}
396
397
398rule __test__ ( )
399{
400    import assert ;
401
402    # TODO: Add tests for all the is-derived, is-base & related type relation
403    # checking rules.
404}
405