Ticket #33: type.jam

File type.jam, 11.5 KB (added by mse102950, 20 years ago)

Proposed implementation of feature

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