• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, is_iterable_typed
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    # it's possible for a type to be registered with a
75    # base type that hasn't been registered yet. in the
76    # check for base_type below and the following calls to setdefault()
77    # the key `type` will be added to __types. When the base type
78    # actually gets registered, it would fail after the simple check
79    # of "type in __types"; thus the check for "'base' in __types[type]"
80    if type in __types and 'base' in __types[type]:
81        raise BaseException ('Type "%s" is already registered.' % type)
82
83    entry = __types.setdefault(type, {})
84    entry['base'] = base_type
85    entry.setdefault('derived', [])
86    entry.setdefault('scanner', None)
87
88    if base_type:
89        __types.setdefault(base_type, {}).setdefault('derived', []).append(type)
90
91    if len (suffixes) > 0:
92        # Generated targets of 'type' will use the first of 'suffixes'
93        # (this may be overridden)
94        set_generated_target_suffix (type, [], suffixes [0])
95
96        # Specify mapping from suffixes to type
97        register_suffixes (suffixes, type)
98
99    feature.extend('target-type', [type])
100    feature.extend('main-target-type', [type])
101    feature.extend('base-target-type', [type])
102
103    if base_type:
104        feature.compose ('<target-type>' + type, [replace_grist (base_type, '<base-target-type>')])
105        feature.compose ('<base-target-type>' + type, ['<base-target-type>' + base_type])
106
107    import b2.build.generators as generators
108    # Adding a new derived type affects generator selection so we need to
109    # make the generator selection module update any of its cached
110    # information related to a new derived type being defined.
111    generators.update_cached_information_with_a_new_type(type)
112
113    # FIXME: resolving recursive dependency.
114    from b2.manager import get_manager
115    get_manager().projects().project_rules().add_rule_for_type(type)
116
117# FIXME: quick hack.
118def type_from_rule_name(rule_name):
119    assert isinstance(rule_name, basestring)
120    return rule_name.upper().replace("-", "_")
121
122
123def register_suffixes (suffixes, type):
124    """ Specifies that targets with suffix from 'suffixes' have the type 'type'.
125        If a different type is already specified for any of syffixes, issues an error.
126    """
127    assert is_iterable_typed(suffixes, basestring)
128    assert isinstance(type, basestring)
129    for s in suffixes:
130        if s in __suffixes_to_types:
131            old_type = __suffixes_to_types [s]
132            if old_type != type:
133                raise BaseException ('Attempting to specify type for suffix "%s"\nOld type: "%s", New type "%s"' % (s, old_type, type))
134        else:
135            __suffixes_to_types [s] = type
136
137def registered (type):
138    """ Returns true iff type has been registered.
139    """
140    assert isinstance(type, basestring)
141    return type in __types
142
143def validate (type):
144    """ Issues an error if 'type' is unknown.
145    """
146    assert isinstance(type, basestring)
147    if not registered (type):
148        raise BaseException ("Unknown target type '%s'" % type)
149
150def set_scanner (type, scanner):
151    """ Sets a scanner class that will be used for this 'type'.
152    """
153    if __debug__:
154        from .scanner import Scanner
155        assert isinstance(type, basestring)
156        assert issubclass(scanner, Scanner)
157    validate (type)
158    __types [type]['scanner'] = scanner
159
160def get_scanner (type, prop_set):
161    """ Returns a scanner instance appropriate to 'type' and 'property_set'.
162    """
163    if __debug__:
164        from .property_set import PropertySet
165        assert isinstance(type, basestring)
166        assert isinstance(prop_set, PropertySet)
167    if registered (type):
168        scanner_type = __types [type]['scanner']
169        if scanner_type:
170            return scanner.get (scanner_type, prop_set.raw ())
171            pass
172
173    return None
174
175def base(type):
176    """Returns a base type for the given type or nothing in case the given type is
177    not derived."""
178    assert isinstance(type, basestring)
179    return __types[type]['base']
180
181def all_bases (type):
182    """ Returns type and all of its bases, in the order of their distance from type.
183    """
184    assert isinstance(type, basestring)
185    result = []
186    while type:
187        result.append (type)
188        type = __types [type]['base']
189
190    return result
191
192def all_derived (type):
193    """ Returns type and all classes that derive from it, in the order of their distance from type.
194    """
195    assert isinstance(type, basestring)
196    result = [type]
197    for d in __types [type]['derived']:
198        result.extend (all_derived (d))
199
200    return result
201
202def is_derived (type, base):
203    """ Returns true if 'type' is 'base' or has 'base' as its direct or indirect base.
204    """
205    assert isinstance(type, basestring)
206    assert isinstance(base, basestring)
207    # TODO: this isn't very efficient, especially for bases close to type
208    if base in all_bases (type):
209        return True
210    else:
211        return False
212
213def is_subtype (type, base):
214    """ Same as is_derived. Should be removed.
215    """
216    assert isinstance(type, basestring)
217    assert isinstance(base, basestring)
218    # TODO: remove this method
219    return is_derived (type, base)
220
221@bjam_signature((["type"], ["properties", "*"], ["suffix"]))
222def set_generated_target_suffix (type, properties, suffix):
223    """ Sets a target suffix that should be used when generating target
224        of 'type' with the specified properties. Can be called with
225        empty properties if no suffix for 'type' was specified yet.
226        This does not automatically specify that files 'suffix' have
227        'type' --- two different types can use the same suffix for
228        generating, but only one type should be auto-detected for
229        a file with that suffix. User should explicitly specify which
230        one.
231
232        The 'suffix' parameter can be empty string ("") to indicate that
233        no suffix should be used.
234    """
235    assert isinstance(type, basestring)
236    assert is_iterable_typed(properties, basestring)
237    assert isinstance(suffix, basestring)
238    set_generated_target_ps(1, type, properties, suffix)
239
240
241
242def change_generated_target_suffix (type, properties, suffix):
243    """ Change the suffix previously registered for this type/properties
244        combination. If suffix is not yet specified, sets it.
245    """
246    assert isinstance(type, basestring)
247    assert is_iterable_typed(properties, basestring)
248    assert isinstance(suffix, basestring)
249    change_generated_target_ps(1, type, properties, suffix)
250
251def generated_target_suffix(type, properties):
252    if __debug__:
253        from .property_set import PropertySet
254        assert isinstance(type, basestring)
255        assert isinstance(properties, PropertySet)
256    return generated_target_ps(1, type, properties)
257
258
259@bjam_signature((["type"], ["properties", "*"], ["prefix"]))
260def set_generated_target_prefix(type, properties, prefix):
261    """
262    Sets a file prefix to be used when generating a target of 'type' with the
263    specified properties. Can be called with no properties if no prefix has
264    already been specified for the 'type'. The 'prefix' parameter can be an empty
265    string ("") to indicate that no prefix should be used.
266
267    Note that this does not cause files with 'prefix' to be automatically
268    recognized as being of 'type'. Two different types can use the same prefix for
269    their generated files but only one type can be auto-detected for a file with
270    that prefix. User should explicitly specify which one using the
271    register-prefixes rule.
272
273    Usage example: library names use the "lib" prefix on unix.
274    """
275    set_generated_target_ps(0, type, properties, prefix)
276
277# Change the prefix previously registered for this type/properties combination.
278# If prefix is not yet specified, sets it.
279def change_generated_target_prefix(type, properties, prefix):
280    assert isinstance(type, basestring)
281    assert is_iterable_typed(properties, basestring)
282    assert isinstance(prefix, basestring)
283    change_generated_target_ps(0, type, properties, prefix)
284
285def generated_target_prefix(type, properties):
286    if __debug__:
287        from .property_set import PropertySet
288        assert isinstance(type, basestring)
289        assert isinstance(properties, PropertySet)
290    return generated_target_ps(0, type, properties)
291
292def set_generated_target_ps(is_suffix, type, properties, val):
293    assert isinstance(is_suffix, (int, bool))
294    assert isinstance(type, basestring)
295    assert is_iterable_typed(properties, basestring)
296    assert isinstance(val, basestring)
297    properties.append ('<target-type>' + type)
298    __prefixes_suffixes[is_suffix].insert (properties, val)
299
300def change_generated_target_ps(is_suffix, type, properties, val):
301    assert isinstance(is_suffix, (int, bool))
302    assert isinstance(type, basestring)
303    assert is_iterable_typed(properties, basestring)
304    assert isinstance(val, basestring)
305    properties.append ('<target-type>' + type)
306    prev = __prefixes_suffixes[is_suffix].find_replace(properties, val)
307    if not prev:
308        set_generated_target_ps(is_suffix, type, properties, val)
309
310# Returns either prefix or suffix (as indicated by 'is_suffix') that should be used
311# when generating a target of 'type' with the specified properties.
312# If no prefix/suffix is specified for 'type', returns prefix/suffix for
313# base type, if any.
314def generated_target_ps_real(is_suffix, type, properties):
315    assert isinstance(is_suffix, (int, bool))
316    assert isinstance(type, basestring)
317    assert is_iterable_typed(properties, basestring)
318    result = ''
319    found = False
320    while type and not found:
321        result = __prefixes_suffixes[is_suffix].find (['<target-type>' + type] + properties)
322
323        # Note that if the string is empty (""), but not null, we consider
324        # suffix found.  Setting prefix or suffix to empty string is fine.
325        if result is not None:
326            found = True
327
328        type = __types [type]['base']
329
330    if not result:
331        result = ''
332    return result
333
334def generated_target_ps(is_suffix, type, prop_set):
335    """ Returns suffix that should be used when generating target of 'type',
336        with the specified properties. If not suffix were specified for
337        'type', returns suffix for base type, if any.
338    """
339    if __debug__:
340        from .property_set import PropertySet
341        assert isinstance(is_suffix, (int, bool))
342        assert isinstance(type, basestring)
343        assert isinstance(prop_set, PropertySet)
344    key = (is_suffix, type, prop_set)
345    v = __target_suffixes_cache.get(key, None)
346
347    if not v:
348        v = generated_target_ps_real(is_suffix, type, prop_set.raw())
349        __target_suffixes_cache [key] = v
350
351    return v
352
353def type(filename):
354    """ Returns file type given it's name. If there are several dots in filename,
355        tries each suffix. E.g. for name of "file.so.1.2" suffixes "2", "1", and
356        "so"  will be tried.
357    """
358    assert isinstance(filename, basestring)
359    while 1:
360        filename, suffix = os.path.splitext (filename)
361        if not suffix: return None
362        suffix = suffix[1:]
363
364        if suffix in __suffixes_to_types:
365            return __suffixes_to_types[suffix]
366
367# NOTE: moved from tools/types/register
368def register_type (type, suffixes, base_type = None, os = []):
369    """ Register the given type on the specified OSes, or on remaining OSes
370        if os is not specified.  This rule is injected into each of the type
371        modules for the sake of convenience.
372    """
373    assert isinstance(type, basestring)
374    assert is_iterable_typed(suffixes, basestring)
375    assert isinstance(base_type, basestring) or base_type is None
376    assert is_iterable_typed(os, basestring)
377    if registered (type):
378        return
379
380    if not os or os_name () in os:
381        register (type, suffixes, base_type)
382