1# Status: ported.
2# Base revision: 45462.
3
4#  Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
5#  distribute this software is granted provided this copyright notice appears in
6#  all copies. This software is provided "as is" without express or implied
7#  warranty, and with no claim as to its suitability for any purpose.
8
9
10
11import re
12import os
13import os.path
14from b2.util.utility import replace_grist, os_name
15from b2.exceptions import *
16from b2.build import feature, property, scanner
17from b2.util import bjam_signature
18
19
20__re_hyphen = re.compile ('-')
21
22def __register_features ():
23    """ Register features need by this module.
24    """
25    # The feature is optional so that it is never implicitly added.
26    # It's used only for internal purposes, and in all cases we
27    # want to explicitly use it.
28    feature.feature ('target-type', [], ['composite', 'optional'])
29    feature.feature ('main-target-type', [], ['optional', 'incidental'])
30    feature.feature ('base-target-type', [], ['composite', 'optional', 'free'])
31
32def reset ():
33    """ Clear the module state. This is mainly for testing purposes.
34        Note that this must be called _after_ resetting the module 'feature'.
35    """
36    global __prefixes_suffixes, __suffixes_to_types, __types, __rule_names_to_types, __target_suffixes_cache
37
38    __register_features ()
39
40    # Stores suffixes for generated targets.
41    __prefixes_suffixes = [property.PropertyMap(), property.PropertyMap()]
42
43    # Maps suffixes to types
44    __suffixes_to_types = {}
45
46    # A map with all the registered types, indexed by the type name
47    # Each entry is a dictionary with following values:
48    # 'base': the name of base type or None if type has no base
49    # 'derived': a list of names of type which derive from this one
50    # 'scanner': the scanner class registered for this type, if any
51    __types = {}
52
53    # Caches suffixes for targets with certain properties.
54    __target_suffixes_cache = {}
55
56reset ()
57
58@bjam_signature((["type"], ["suffixes", "*"], ["base_type", "?"]))
59def register (type, suffixes = [], base_type = None):
60    """ Registers a target type, possibly derived from a 'base-type'.
61        If 'suffixes' are provided, they list all the suffixes that mean a file is of 'type'.
62        Also, the first element gives the suffix to be used when constructing and object of
63        'type'.
64        type: a string
65        suffixes: None or a sequence of strings
66        base_type: None or a string
67    """
68    # Type names cannot contain hyphens, because when used as
69    # feature-values they will be interpreted as composite features
70    # which need to be decomposed.
71    if __re_hyphen.search (type):
72        raise BaseException ('type name "%s" contains a hyphen' % type)
73
74    if __types.has_key (type):
75        raise BaseException ('Type "%s" is already registered.' % type)
76
77    entry = {}
78    entry ['base'] = base_type
79    entry ['derived'] = []
80    entry ['scanner'] = None
81    __types [type] = entry
82
83    if base_type:
84        __types.setdefault(base_type, {}).setdefault('derived', []).append(type)
85
86    if len (suffixes) > 0:
87        # Generated targets of 'type' will use the first of 'suffixes'
88        # (this may be overriden)
89        set_generated_target_suffix (type, [], suffixes [0])
90
91        # Specify mapping from suffixes to type
92        register_suffixes (suffixes, type)
93
94    feature.extend('target-type', [type])
95    feature.extend('main-target-type', [type])
96    feature.extend('base-target-type', [type])
97
98    if base_type:
99        feature.compose ('<target-type>' + type, replace_grist (base_type, '<base-target-type>'))
100        feature.compose ('<base-target-type>' + type, '<base-target-type>' + base_type)
101
102    import b2.build.generators as generators
103    # Adding a new derived type affects generator selection so we need to
104    # make the generator selection module update any of its cached
105    # information related to a new derived type being defined.
106    generators.update_cached_information_with_a_new_type(type)
107
108    # FIXME: resolving recursive dependency.
109    from b2.manager import get_manager
110    get_manager().projects().project_rules().add_rule_for_type(type)
111
112# FIXME: quick hack.
113def type_from_rule_name(rule_name):
114    return rule_name.upper().replace("-", "_")
115
116
117def register_suffixes (suffixes, type):
118    """ Specifies that targets with suffix from 'suffixes' have the type 'type'.
119        If a different type is already specified for any of syffixes, issues an error.
120    """
121    for s in suffixes:
122        if __suffixes_to_types.has_key (s):
123            old_type = __suffixes_to_types [s]
124            if old_type != type:
125                raise BaseException ('Attempting to specify type for suffix "%s"\nOld type: "%s", New type "%s"' % (s, old_type, type))
126        else:
127            __suffixes_to_types [s] = type
128
129def registered (type):
130    """ Returns true iff type has been registered.
131    """
132    return __types.has_key (type)
133
134def validate (type):
135    """ Issues an error if 'type' is unknown.
136    """
137    if not registered (type):
138        raise BaseException ("Unknown target type '%s'" % type)
139
140def set_scanner (type, scanner):
141    """ Sets a scanner class that will be used for this 'type'.
142    """
143    validate (type)
144    __types [type]['scanner'] = scanner
145
146def get_scanner (type, prop_set):
147    """ Returns a scanner instance appropriate to 'type' and 'property_set'.
148    """
149    if registered (type):
150        scanner_type = __types [type]['scanner']
151        if scanner_type:
152            return scanner.get (scanner_type, prop_set.raw ())
153            pass
154
155    return None
156
157def base(type):
158    """Returns a base type for the given type or nothing in case the given type is
159    not derived."""
160
161    return __types[type]['base']
162
163def all_bases (type):
164    """ Returns type and all of its bases, in the order of their distance from type.
165    """
166    result = []
167    while type:
168        result.append (type)
169        type = __types [type]['base']
170
171    return result
172
173def all_derived (type):
174    """ Returns type and all classes that derive from it, in the order of their distance from type.
175    """
176    result = [type]
177    for d in __types [type]['derived']:
178        result.extend (all_derived (d))
179
180    return result
181
182def is_derived (type, base):
183    """ Returns true if 'type' is 'base' or has 'base' as its direct or indirect base.
184    """
185    # TODO: this isn't very efficient, especially for bases close to type
186    if base in all_bases (type):
187        return True
188    else:
189        return False
190
191def is_subtype (type, base):
192    """ Same as is_derived. Should be removed.
193    """
194    # TODO: remove this method
195    return is_derived (type, base)
196
197@bjam_signature((["type"], ["properties", "*"], ["suffix"]))
198def set_generated_target_suffix (type, properties, suffix):
199    """ Sets a target suffix that should be used when generating target
200        of 'type' with the specified properties. Can be called with
201        empty properties if no suffix for 'type' was specified yet.
202        This does not automatically specify that files 'suffix' have
203        'type' --- two different types can use the same suffix for
204        generating, but only one type should be auto-detected for
205        a file with that suffix. User should explicitly specify which
206        one.
207
208        The 'suffix' parameter can be empty string ("") to indicate that
209        no suffix should be used.
210    """
211    set_generated_target_ps(1, type, properties, suffix)
212
213
214
215def change_generated_target_suffix (type, properties, suffix):
216    """ Change the suffix previously registered for this type/properties
217        combination. If suffix is not yet specified, sets it.
218    """
219    change_generated_target_ps(1, type, properties, suffix)
220
221def generated_target_suffix(type, properties):
222    return generated_target_ps(1, type, properties)
223
224# Sets a target prefix that should be used when generating targets of 'type'
225# with the specified properties. Can be called with empty properties if no
226# prefix for 'type' has been specified yet.
227#
228# The 'prefix' parameter can be empty string ("") to indicate that no prefix
229# should be used.
230#
231# Usage example: library names use the "lib" prefix on unix.
232@bjam_signature((["type"], ["properties", "*"], ["suffix"]))
233def set_generated_target_prefix(type, properties, prefix):
234    set_generated_target_ps(0, type, properties, prefix)
235
236# Change the prefix previously registered for this type/properties combination.
237# If prefix is not yet specified, sets it.
238def change_generated_target_prefix(type, properties, prefix):
239    change_generated_target_ps(0, type, properties, prefix)
240
241def generated_target_prefix(type, properties):
242    return generated_target_ps(0, type, properties)
243
244def set_generated_target_ps(is_suffix, type, properties, val):
245    properties.append ('<target-type>' + type)
246    __prefixes_suffixes[is_suffix].insert (properties, val)
247
248def change_generated_target_ps(is_suffix, type, properties, val):
249    properties.append ('<target-type>' + type)
250    prev = __prefixes_suffixes[is_suffix].find_replace(properties, val)
251    if not prev:
252        set_generated_target_ps(is_suffix, type, properties, val)
253
254# Returns either prefix or suffix (as indicated by 'is_suffix') that should be used
255# when generating a target of 'type' with the specified properties.
256# If no prefix/suffix is specified for 'type', returns prefix/suffix for
257# base type, if any.
258def generated_target_ps_real(is_suffix, type, properties):
259
260    result = ''
261    found = False
262    while type and not found:
263        result = __prefixes_suffixes[is_suffix].find (['<target-type>' + type] + properties)
264
265        # Note that if the string is empty (""), but not null, we consider
266        # suffix found.  Setting prefix or suffix to empty string is fine.
267        if result is not None:
268            found = True
269
270        type = __types [type]['base']
271
272    if not result:
273        result = ''
274    return result
275
276def generated_target_ps(is_suffix, type, prop_set):
277    """ Returns suffix that should be used when generating target of 'type',
278        with the specified properties. If not suffix were specified for
279        'type', returns suffix for base type, if any.
280    """
281    key = (is_suffix, type, prop_set)
282    v = __target_suffixes_cache.get(key, None)
283
284    if not v:
285        v = generated_target_ps_real(is_suffix, type, prop_set.raw())
286        __target_suffixes_cache [key] = v
287
288    return v
289
290def type(filename):
291    """ Returns file type given it's name. If there are several dots in filename,
292        tries each suffix. E.g. for name of "file.so.1.2" suffixes "2", "1", and
293        "so"  will be tried.
294    """
295    while 1:
296        filename, suffix = os.path.splitext (filename)
297        if not suffix: return None
298        suffix = suffix[1:]
299
300        if __suffixes_to_types.has_key(suffix):
301            return __suffixes_to_types[suffix]
302
303# NOTE: moved from tools/types/register
304def register_type (type, suffixes, base_type = None, os = []):
305    """ Register the given type on the specified OSes, or on remaining OSes
306        if os is not specified.  This rule is injected into each of the type
307        modules for the sake of convenience.
308    """
309    if registered (type):
310        return
311
312    if not os or os_name () in os:
313        register (type, suffixes, base_type)
314