• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#  Status: being ported by Steven Watanabe
2#  Base revision: 47174
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""" Provides actions common to all toolsets, such as creating directories and
10    removing files.
11"""
12
13import re
14import bjam
15import os
16import os.path
17import sys
18
19# for some reason this fails on Python 2.7(r27:82525)
20# from b2.build import virtual_target
21import b2.build.virtual_target
22from b2.build import feature, type
23from b2.util.utility import *
24from b2.util import path, is_iterable_typed
25
26__re__before_first_dash = re.compile ('([^-]*)-')
27
28def reset ():
29    """ Clear the module state. This is mainly for testing purposes.
30        Note that this must be called _after_ resetting the module 'feature'.
31    """
32    global __had_unspecified_value, __had_value, __declared_subfeature
33    global __init_loc
34    global __all_signatures, __debug_configuration, __show_configuration
35
36    # Stores toolsets without specified initialization values.
37    __had_unspecified_value = {}
38
39    # Stores toolsets with specified initialization values.
40    __had_value = {}
41
42    # Stores toolsets with declared subfeatures.
43    __declared_subfeature = {}
44
45    # Stores all signatures of the toolsets.
46    __all_signatures = {}
47
48    # Stores the initialization locations of each toolset
49    __init_loc = {}
50
51    __debug_configuration = '--debug-configuration' in bjam.variable('ARGV')
52    __show_configuration = '--show-configuration' in bjam.variable('ARGV')
53
54    global __executable_path_variable
55    OS = bjam.call("peek", [], "OS")[0]
56    if OS == "NT":
57        # On Windows the case and capitalization of PATH is not always predictable, so
58        # let's find out what variable name was really set.
59        for n in os.environ:
60            if n.lower() == "path":
61                __executable_path_variable = n
62                break
63    else:
64        __executable_path_variable = "PATH"
65
66    m = {"NT": __executable_path_variable,
67         "CYGWIN": "PATH",
68         "MACOSX": "DYLD_LIBRARY_PATH",
69         "AIX": "LIBPATH",
70         "HAIKU": "LIBRARY_PATH"}
71    global __shared_library_path_variable
72    __shared_library_path_variable = m.get(OS, "LD_LIBRARY_PATH")
73
74reset()
75
76def shared_library_path_variable():
77    return __shared_library_path_variable
78
79# ported from trunk@47174
80class Configurations(object):
81    """
82        This class helps to manage toolset configurations. Each configuration
83        has a unique ID and one or more parameters. A typical example of a unique ID
84        is a condition generated by 'common.check-init-parameters' rule. Other kinds
85        of IDs can be used. Parameters may include any details about the configuration
86        like 'command', 'path', etc.
87
88        A toolset configuration may be in one of the following states:
89
90        - registered
91              Configuration has been registered (e.g. by autodetection code) but has
92              not yet been marked as used, i.e. 'toolset.using' rule has not yet been
93              called for it.
94          - used
95              Once called 'toolset.using' rule marks the configuration as 'used'.
96
97        The main difference between the states above is that while a configuration is
98        'registered' its options can be freely changed. This is useful in particular
99        for autodetection code - all detected configurations may be safely overwritten
100        by user code.
101    """
102
103    def __init__(self):
104        self.used_ = set()
105        self.all_ = set()
106        self.params_ = {}
107
108    def register(self, id):
109        """
110            Registers a configuration.
111
112            Returns True if the configuration has been added and False if
113            it already exists. Reports an error if the configuration is 'used'.
114        """
115        assert isinstance(id, basestring)
116        if id in self.used_:
117            #FIXME
118            errors.error("common: the configuration '$(id)' is in use")
119
120        if id not in self.all_:
121            self.all_.add(id)
122
123            # Indicate that a new configuration has been added.
124            return True
125        else:
126            return False
127
128    def use(self, id):
129        """
130            Mark a configuration as 'used'.
131
132            Returns True if the state of the configuration has been changed to
133            'used' and False if it the state wasn't changed. Reports an error
134            if the configuration isn't known.
135        """
136        assert isinstance(id, basestring)
137        if id not in self.all_:
138            #FIXME:
139            errors.error("common: the configuration '$(id)' is not known")
140
141        if id not in self.used_:
142            self.used_.add(id)
143
144            # indicate that the configuration has been marked as 'used'
145            return True
146        else:
147            return False
148
149    def all(self):
150        """ Return all registered configurations. """
151        return self.all_
152
153    def used(self):
154        """ Return all used configurations. """
155        return self.used_
156
157    def get(self, id, param):
158        """ Returns the value of a configuration parameter. """
159        assert isinstance(id, basestring)
160        assert isinstance(param, basestring)
161        return self.params_.get(param, {}).get(id)
162
163    def set (self, id, param, value):
164        """ Sets the value of a configuration parameter. """
165        assert isinstance(id, basestring)
166        assert isinstance(param, basestring)
167        assert is_iterable_typed(value, basestring)
168        self.params_.setdefault(param, {})[id] = value
169
170# Ported from trunk@47174
171def check_init_parameters(toolset, requirement, *args):
172    """ The rule for checking toolset parameters. Trailing parameters should all be
173        parameter name/value pairs. The rule will check that each parameter either has
174        a value in each invocation or has no value in each invocation. Also, the rule
175        will check that the combination of all parameter values is unique in all
176        invocations.
177
178        Each parameter name corresponds to a subfeature. This rule will declare a
179        subfeature the first time a non-empty parameter value is passed and will
180        extend it with all the values.
181
182        The return value from this rule is a condition to be used for flags settings.
183    """
184    assert isinstance(toolset, basestring)
185    assert is_iterable_typed(requirement, basestring) or requirement is None
186    from b2.build import toolset as b2_toolset
187    if requirement is None:
188        requirement = []
189    sig = toolset
190    condition = replace_grist(toolset, '<toolset>')
191    subcondition = []
192
193    for arg in args:
194        assert(isinstance(arg, tuple))
195        assert(len(arg) == 2)
196        name = arg[0]
197        value = arg[1]
198        assert(isinstance(name, str))
199        assert(isinstance(value, str) or value is None)
200
201        str_toolset_name = str((toolset, name))
202
203        # FIXME: is this the correct translation?
204        ### if $(value)-is-not-empty
205        if value is not None:
206            condition = condition + '-' + value
207            if str_toolset_name in __had_unspecified_value:
208                raise BaseException("'%s' initialization: parameter '%s' inconsistent\n" \
209                "no value was specified in earlier initialization\n" \
210                "an explicit value is specified now" % (toolset, name))
211
212            # The logic below is for intel compiler. It calls this rule
213            # with 'intel-linux' and 'intel-win' as toolset, so we need to
214            # get the base part of toolset name.
215            # We can't pass 'intel' as toolset, because it that case it will
216            # be impossible to register versionles intel-linux and
217            # intel-win of specific version.
218            t = toolset
219            m = __re__before_first_dash.match(toolset)
220            if m:
221                t = m.group(1)
222
223            if str_toolset_name not in __had_value:
224                if str((t, name)) not in __declared_subfeature:
225                    feature.subfeature('toolset', t, name, [], ['propagated'])
226                    __declared_subfeature[str((t, name))] = True
227
228                __had_value[str_toolset_name] = True
229
230            feature.extend_subfeature('toolset', t, name, [value])
231            subcondition += ['<toolset-' + t + ':' + name + '>' + value ]
232
233        else:
234            if str_toolset_name in __had_value:
235                raise BaseException ("'%s' initialization: parameter '%s' inconsistent\n" \
236                "an explicit value was specified in an earlier initialization\n" \
237                "no value is specified now" % (toolset, name))
238
239            __had_unspecified_value[str_toolset_name] = True
240
241        if value == None: value = ''
242
243        sig = sig + value + '-'
244
245    # if a requirement is specified, the signature should be unique
246    # with that requirement
247    if requirement:
248        sig += '-' + '-'.join(requirement)
249
250    if sig in __all_signatures:
251        message = "duplicate initialization of '%s' with the following parameters: " % toolset
252
253        for arg in args:
254            name = arg[0]
255            value = arg[1]
256            if value == None: value = '<unspecified>'
257
258            message += "'%s' = '%s'\n" % (name, value)
259
260        raise BaseException(message)
261
262    __all_signatures[sig] = True
263    # FIXME
264    __init_loc[sig] = "User location unknown" #[ errors.nearest-user-location ] ;
265
266    # If we have a requirement, this version should only be applied under that
267    # condition. To accomplish this we add a toolset requirement that imposes
268    # the toolset subcondition, which encodes the version.
269    if requirement:
270        r = ['<toolset>' + toolset] + requirement
271        r = ','.join(r)
272        b2_toolset.add_requirements([r + ':' + c for c in subcondition])
273
274    # We add the requirements, if any, to the condition to scope the toolset
275    # variables and options to this specific version.
276    condition = [condition]
277    if requirement:
278        condition += requirement
279
280    if __show_configuration:
281        print "notice:", condition
282    return ['/'.join(condition)]
283
284# Ported from trunk@47077
285def get_invocation_command_nodefault(
286    toolset, tool, user_provided_command=[], additional_paths=[], path_last=False):
287    """
288        A helper rule to get the command to invoke some tool. If
289        'user-provided-command' is not given, tries to find binary named 'tool' in
290        PATH and in the passed 'additional-path'. Otherwise, verifies that the first
291        element of 'user-provided-command' is an existing program.
292
293        This rule returns the command to be used when invoking the tool. If we can't
294        find the tool, a warning is issued. If 'path-last' is specified, PATH is
295        checked after 'additional-paths' when searching for 'tool'.
296    """
297    assert isinstance(toolset, basestring)
298    assert isinstance(tool, basestring)
299    assert is_iterable_typed(user_provided_command, basestring)
300    assert is_iterable_typed(additional_paths, basestring) or additional_paths is None
301    assert isinstance(path_last, (int, bool))
302
303    if not user_provided_command:
304        command = find_tool(tool, additional_paths, path_last)
305        if not command and __debug_configuration:
306            print "warning: toolset", toolset, "initialization: can't find tool, tool"
307            #FIXME
308            #print "warning: initialized from" [ errors.nearest-user-location ] ;
309    else:
310        command = check_tool(user_provided_command)
311        if not command and __debug_configuration:
312            print "warning: toolset", toolset, "initialization:"
313            print "warning: can't find user-provided command", user_provided_command
314            #FIXME
315            #ECHO "warning: initialized from" [ errors.nearest-user-location ]
316            command = []
317        if command:
318            command = ' '.join(command)
319
320    return command
321
322# ported from trunk@47174
323def get_invocation_command(toolset, tool, user_provided_command = [],
324                           additional_paths = [], path_last = False):
325    """ Same as get_invocation_command_nodefault, except that if no tool is found,
326        returns either the user-provided-command, if present, or the 'tool' parameter.
327    """
328    assert isinstance(toolset, basestring)
329    assert isinstance(tool, basestring)
330    assert is_iterable_typed(user_provided_command, basestring)
331    assert is_iterable_typed(additional_paths, basestring) or additional_paths is None
332    assert isinstance(path_last, (int, bool))
333
334    result = get_invocation_command_nodefault(toolset, tool,
335                                              user_provided_command,
336                                              additional_paths,
337                                              path_last)
338
339    if not result:
340        if user_provided_command:
341            result = user_provided_command[0]
342        else:
343            result = tool
344
345    assert(isinstance(result, str))
346
347    return result
348
349# ported from trunk@47281
350def get_absolute_tool_path(command):
351    """
352        Given an invocation command,
353        return the absolute path to the command. This works even if command
354        has not path element and is present in PATH.
355    """
356    assert isinstance(command, basestring)
357    if os.path.dirname(command):
358        return os.path.dirname(command)
359    else:
360        programs = path.programs_path()
361        m = path.glob(programs, [command, command + '.exe' ])
362        if not len(m):
363            if __debug_configuration:
364                print "Could not find:", command, "in", programs
365            return None
366        return os.path.dirname(m[0])
367
368# ported from trunk@47174
369def find_tool(name, additional_paths = [], path_last = False):
370    """ Attempts to find tool (binary) named 'name' in PATH and in
371        'additional-paths'.  If found in path, returns 'name'.  If
372        found in additional paths, returns full name.  If the tool
373        is found in several directories, returns the first path found.
374        Otherwise, returns the empty string.  If 'path_last' is specified,
375        path is checked after 'additional_paths'.
376    """
377    assert isinstance(name, basestring)
378    assert is_iterable_typed(additional_paths, basestring)
379    assert isinstance(path_last, (int, bool))
380
381    programs = path.programs_path()
382    match = path.glob(programs, [name, name + '.exe'])
383    additional_match = path.glob(additional_paths, [name, name + '.exe'])
384
385    result = []
386    if path_last:
387        result = additional_match
388        if not result and match:
389            result = match
390
391    else:
392        if match:
393            result = match
394
395        elif additional_match:
396            result = additional_match
397
398    if result:
399        return path.native(result[0])
400    else:
401        return ''
402
403#ported from trunk@47281
404def check_tool_aux(command):
405    """ Checks if 'command' can be found either in path
406        or is a full name to an existing file.
407    """
408    assert isinstance(command, basestring)
409    dirname = os.path.dirname(command)
410    if dirname:
411        if os.path.exists(command):
412            return command
413        # Both NT and Cygwin will run .exe files by their unqualified names.
414        elif on_windows() and os.path.exists(command + '.exe'):
415            return command
416        # Only NT will run .bat files by their unqualified names.
417        elif os_name() == 'NT' and os.path.exists(command + '.bat'):
418            return command
419    else:
420        paths = path.programs_path()
421        if path.glob(paths, [command]):
422            return command
423
424# ported from trunk@47281
425def check_tool(command):
426    """ Checks that a tool can be invoked by 'command'.
427        If command is not an absolute path, checks if it can be found in 'path'.
428        If command is absolute path, check that it exists. Returns 'command'
429        if ok and empty string otherwise.
430    """
431    assert is_iterable_typed(command, basestring)
432    #FIXME: why do we check the first and last elements????
433    if check_tool_aux(command[0]) or check_tool_aux(command[-1]):
434        return command
435
436# ported from trunk@47281
437def handle_options(tool, condition, command, options):
438    """ Handle common options for toolset, specifically sets the following
439        flag variables:
440        - CONFIG_COMMAND to 'command'
441        - OPTIOns for compile to the value of <compileflags> in options
442        - OPTIONS for compile.c to the value of <cflags> in options
443        - OPTIONS for compile.c++ to the value of <cxxflags> in options
444        - OPTIONS for compile.asm to the value of <asmflags> in options
445        - OPTIONS for compile.fortran to the value of <fflags> in options
446        - OPTIONs for link to the value of <linkflags> in options
447    """
448    from b2.build import toolset
449
450    assert isinstance(tool, basestring)
451    assert is_iterable_typed(condition, basestring)
452    assert command and isinstance(command, basestring)
453    assert is_iterable_typed(options, basestring)
454    toolset.flags(tool, 'CONFIG_COMMAND', condition, [command])
455    toolset.flags(tool + '.compile', 'OPTIONS', condition, feature.get_values('<compileflags>', options))
456    toolset.flags(tool + '.compile.c', 'OPTIONS', condition, feature.get_values('<cflags>', options))
457    toolset.flags(tool + '.compile.c++', 'OPTIONS', condition, feature.get_values('<cxxflags>', options))
458    toolset.flags(tool + '.compile.asm', 'OPTIONS', condition, feature.get_values('<asmflags>', options))
459    toolset.flags(tool + '.compile.fortran', 'OPTIONS', condition, feature.get_values('<fflags>', options))
460    toolset.flags(tool + '.link', 'OPTIONS', condition, feature.get_values('<linkflags>', options))
461
462# ported from trunk@47281
463def get_program_files_dir():
464    """ returns the location of the "program files" directory on a windows
465        platform
466    """
467    ProgramFiles = bjam.variable("ProgramFiles")
468    if ProgramFiles:
469        ProgramFiles = ' '.join(ProgramFiles)
470    else:
471        ProgramFiles = "c:\\Program Files"
472    return ProgramFiles
473
474# ported from trunk@47281
475def rm_command():
476    return __RM
477
478# ported from trunk@47281
479def copy_command():
480    return __CP
481
482# ported from trunk@47281
483def variable_setting_command(variable, value):
484    """
485        Returns the command needed to set an environment variable on the current
486        platform. The variable setting persists through all following commands and is
487        visible in the environment seen by subsequently executed commands. In other
488        words, on Unix systems, the variable is exported, which is consistent with the
489        only possible behavior on Windows systems.
490    """
491    assert isinstance(variable, basestring)
492    assert isinstance(value, basestring)
493
494    if os_name() == 'NT':
495        return "set " + variable + "=" + value + os.linesep
496    else:
497        # (todo)
498        #   The following does not work on CYGWIN and needs to be fixed. On
499        # CYGWIN the $(nl) variable holds a Windows new-line \r\n sequence that
500        # messes up the executed export command which then reports that the
501        # passed variable name is incorrect. This is most likely due to the
502        # extra \r character getting interpreted as a part of the variable name.
503        #
504        #   Several ideas pop to mind on how to fix this:
505        #     * One way would be to separate the commands using the ; shell
506        #       command separator. This seems like the quickest possible
507        #       solution but I do not know whether this would break code on any
508        #       platforms I I have no access to.
509        #     * Another would be to not use the terminating $(nl) but that would
510        #       require updating all the using code so it does not simply
511        #       prepend this variable to its own commands.
512        #     * I guess the cleanest solution would be to update Boost Jam to
513        #       allow explicitly specifying \n & \r characters in its scripts
514        #       instead of always relying only on the 'current OS native newline
515        #       sequence'.
516        #
517        #   Some code found to depend on this behaviour:
518        #     * This Boost Build module.
519        #         * __test__ rule.
520        #         * path-variable-setting-command rule.
521        #     * python.jam toolset.
522        #     * xsltproc.jam toolset.
523        #     * fop.jam toolset.
524        #                                     (todo) (07.07.2008.) (Jurko)
525        #
526        # I think that this works correctly in python -- Steven Watanabe
527        return variable + "=" + value + os.linesep + "export " + variable + os.linesep
528
529def path_variable_setting_command(variable, paths):
530    """
531        Returns a command to sets a named shell path variable to the given NATIVE
532        paths on the current platform.
533    """
534    assert isinstance(variable, basestring)
535    assert is_iterable_typed(paths, basestring)
536    sep = os.path.pathsep
537    return variable_setting_command(variable, sep.join(paths))
538
539def prepend_path_variable_command(variable, paths):
540    """
541        Returns a command that prepends the given paths to the named path variable on
542        the current platform.
543    """
544    assert isinstance(variable, basestring)
545    assert is_iterable_typed(paths, basestring)
546    return path_variable_setting_command(
547        variable, paths + [expand_variable(variable)])
548
549
550def expand_variable(variable):
551    """Produce a string that expands the shell variable."""
552    if os.name == 'nt':
553        return '%{}%'.format(variable)
554    return '${%s}' % variable
555
556
557def file_creation_command():
558    """
559        Return a command which can create a file. If 'r' is result of invocation, then
560        'r foobar' will create foobar with unspecified content. What happens if file
561        already exists is unspecified.
562    """
563    if os_name() == 'NT':
564        return "echo. > "
565    else:
566        return "touch "
567
568#FIXME: global variable
569__mkdir_set = set()
570__re_windows_drive = re.compile(r'^.*:\$')
571
572def mkdir(engine, target):
573    assert isinstance(target, basestring)
574    # If dir exists, do not update it. Do this even for $(DOT).
575    bjam.call('NOUPDATE', target)
576
577    global __mkdir_set
578
579    # FIXME: Where is DOT defined?
580    #if $(<) != $(DOT) && ! $($(<)-mkdir):
581    if target != '.' and target not in __mkdir_set:
582        # Cheesy gate to prevent multiple invocations on same dir.
583        __mkdir_set.add(target)
584
585        # Schedule the mkdir build action.
586        engine.set_update_action("common.MkDir", target, [])
587
588        # Prepare a Jam 'dirs' target that can be used to make the build only
589        # construct all the target directories.
590        engine.add_dependency('dirs', target)
591
592        # Recursively create parent directories. $(<:P) = $(<)'s parent & we
593        # recurse until root.
594
595        s = os.path.dirname(target)
596        if os_name() == 'NT':
597            if(__re_windows_drive.match(s)):
598                s = ''
599
600        if s:
601            if s != target:
602                engine.add_dependency(target, s)
603                mkdir(engine, s)
604            else:
605                bjam.call('NOTFILE', s)
606
607__re_version = re.compile(r'^([^.]+)[.]([^.]+)[.]?([^.]*)')
608
609def format_name(format, name, target_type, prop_set):
610    """ Given a target, as given to a custom tag rule, returns a string formatted
611        according to the passed format. Format is a list of properties that is
612        represented in the result. For each element of format the corresponding target
613        information is obtained and added to the result string. For all, but the
614        literal, the format value is taken as the as string to prepend to the output
615        to join the item to the rest of the result. If not given "-" is used as a
616        joiner.
617
618        The format options can be:
619
620          <base>[joiner]
621              ::  The basename of the target name.
622          <toolset>[joiner]
623              ::  The abbreviated toolset tag being used to build the target.
624          <threading>[joiner]
625              ::  Indication of a multi-threaded build.
626          <runtime>[joiner]
627              ::  Collective tag of the build runtime.
628          <version:/version-feature | X.Y[.Z]/>[joiner]
629              ::  Short version tag taken from the given "version-feature"
630                  in the build properties. Or if not present, the literal
631                  value as the version number.
632          <property:/property-name/>[joiner]
633              ::  Direct lookup of the given property-name value in the
634                  build properties. /property-name/ is a regular expression.
635                  e.g. <property:toolset-.*:flavor> will match every toolset.
636          /otherwise/
637              ::  The literal value of the format argument.
638
639        For example this format:
640
641          boost_ <base> <toolset> <threading> <runtime> <version:boost-version>
642
643        Might return:
644
645          boost_thread-vc80-mt-gd-1_33.dll, or
646          boost_regex-vc80-gd-1_33.dll
647
648        The returned name also has the target type specific prefix and suffix which
649        puts it in a ready form to use as the value from a custom tag rule.
650    """
651    if __debug__:
652        from ..build.property_set import PropertySet
653        assert is_iterable_typed(format, basestring)
654        assert isinstance(name, basestring)
655        assert isinstance(target_type, basestring)
656        assert isinstance(prop_set, PropertySet)
657    # assert(isinstance(prop_set, property_set.PropertySet))
658    if type.is_derived(target_type, 'LIB'):
659        result = "" ;
660        for f in format:
661            grist = get_grist(f)
662            if grist == '<base>':
663                result += os.path.basename(name)
664            elif grist == '<toolset>':
665                result += join_tag(get_value(f),
666                    toolset_tag(name, target_type, prop_set))
667            elif grist == '<threading>':
668                result += join_tag(get_value(f),
669                    threading_tag(name, target_type, prop_set))
670            elif grist == '<runtime>':
671                result += join_tag(get_value(f),
672                    runtime_tag(name, target_type, prop_set))
673            elif grist.startswith('<version:'):
674                key = grist[len('<version:'):-1]
675                version = prop_set.get('<' + key + '>')
676                if not version:
677                    version = key
678                version = __re_version.match(version)
679                result += join_tag(get_value(f), version[1] + '_' + version[2])
680            elif grist.startswith('<property:'):
681                key = grist[len('<property:'):-1]
682                property_re = re.compile('<(' + key + ')>')
683                p0 = None
684                for prop in prop_set.raw():
685                    match = property_re.match(prop)
686                    if match:
687                        p0 = match[1]
688                        break
689                if p0:
690                    p = prop_set.get('<' + p0 + '>')
691                    if p:
692                        assert(len(p) == 1)
693                        result += join_tag(ungrist(f), p)
694            else:
695                result += f
696
697        result = b2.build.virtual_target.add_prefix_and_suffix(
698            ''.join(result), target_type, prop_set)
699        return result
700
701def join_tag(joiner, tag):
702    assert isinstance(joiner, basestring)
703    assert isinstance(tag, basestring)
704    if tag:
705        if not joiner: joiner = '-'
706        return joiner + tag
707    return ''
708
709__re_toolset_version = re.compile(r"<toolset.*version>(\d+)[.](\d*)")
710
711def toolset_tag(name, target_type, prop_set):
712    if __debug__:
713        from ..build.property_set import PropertySet
714        assert isinstance(name, basestring)
715        assert isinstance(target_type, basestring)
716        assert isinstance(prop_set, PropertySet)
717    tag = ''
718
719    properties = prop_set.raw()
720    tools = prop_set.get('<toolset>')
721    assert(len(tools) == 1)
722    tools = tools[0]
723    if tools.startswith('borland'): tag += 'bcb'
724    elif tools.startswith('como'): tag += 'como'
725    elif tools.startswith('cw'): tag += 'cw'
726    elif tools.startswith('darwin'): tag += 'xgcc'
727    elif tools.startswith('edg'): tag += 'edg'
728    elif tools.startswith('gcc'):
729        flavor = prop_set.get('<toolset-gcc:flavor>')
730        ''.find
731        if flavor.find('mingw') != -1:
732            tag += 'mgw'
733        else:
734            tag += 'gcc'
735    elif tools == 'intel':
736        if prop_set.get('<toolset-intel:platform>') == ['win']:
737            tag += 'iw'
738        else:
739            tag += 'il'
740    elif tools.startswith('kcc'): tag += 'kcc'
741    elif tools.startswith('kylix'): tag += 'bck'
742    #case metrowerks* : tag += cw ;
743    #case mingw* : tag += mgw ;
744    elif tools.startswith('mipspro'): tag += 'mp'
745    elif tools.startswith('msvc'): tag += 'vc'
746    elif tools.startswith('sun'): tag += 'sw'
747    elif tools.startswith('tru64cxx'): tag += 'tru'
748    elif tools.startswith('vacpp'): tag += 'xlc'
749
750    for prop in properties:
751        match = __re_toolset_version.match(prop)
752        if(match):
753            version = match
754            break
755    version_string = None
756    # For historical reasons, vc6.0 and vc7.0 use different naming.
757    if tag == 'vc':
758        if version.group(1) == '6':
759            # Cancel minor version.
760            version_string = '6'
761        elif version.group(1) == '7' and version.group(2) == '0':
762            version_string = '7'
763
764    # On intel, version is not added, because it does not matter and it's the
765    # version of vc used as backend that matters. Ideally, we'd encode the
766    # backend version but that would break compatibility with V1.
767    elif tag == 'iw':
768        version_string = ''
769
770    # On borland, version is not added for compatibility with V1.
771    elif tag == 'bcb':
772        version_string = ''
773
774    if version_string is None:
775        version = version.group(1) + version.group(2)
776
777    tag += version
778
779    return tag
780
781
782def threading_tag(name, target_type, prop_set):
783    if __debug__:
784        from ..build.property_set import PropertySet
785        assert isinstance(name, basestring)
786        assert isinstance(target_type, basestring)
787        assert isinstance(prop_set, PropertySet)
788    tag = ''
789    properties = prop_set.raw()
790    if '<threading>multi' in properties: tag = 'mt'
791
792    return tag
793
794
795def runtime_tag(name, target_type, prop_set ):
796    if __debug__:
797        from ..build.property_set import PropertySet
798        assert isinstance(name, basestring)
799        assert isinstance(target_type, basestring)
800        assert isinstance(prop_set, PropertySet)
801    tag = ''
802
803    properties = prop_set.raw()
804    if '<runtime-link>static' in properties: tag += 's'
805
806    # This is an ugly thing. In V1, there's a code to automatically detect which
807    # properties affect a target. So, if <runtime-debugging> does not affect gcc
808    # toolset, the tag rules won't even see <runtime-debugging>. Similar
809    # functionality in V2 is not implemented yet, so we just check for toolsets
810    # which are known to care about runtime debug.
811    if '<toolset>msvc' in properties \
812       or '<stdlib>stlport' in properties \
813       or '<toolset-intel:platform>win' in properties:
814        if '<runtime-debugging>on' in properties: tag += 'g'
815
816    if '<python-debugging>on' in properties: tag += 'y'
817    if '<variant>debug' in properties: tag += 'd'
818    if '<stdlib>stlport' in properties: tag += 'p'
819    if '<stdlib-stlport:iostream>hostios' in properties: tag += 'n'
820
821    return tag
822
823
824def init(manager):
825    global __RM, __CP, __IGNORE, __LN
826    engine = manager.engine()
827
828    # register the make() and alias() rules globally
829    import b2.tools.make
830    import b2.build.alias
831
832    windows_hack = ''
833    # ported from trunk@47281
834    if os_name() == 'NT':
835        __RM = 'del /f /q'
836        __CP = 'copy /b'
837        windows_hack = '+ this-file-does-not-exist-A698EE7806899E69'
838        __IGNORE = '2>nul >nul & setlocal'
839        __LN = __CP
840        #if not __LN:
841        #    __LN = CP
842        MKDIR = 'if not exist "$(<)\\" mkdir "$(<)"'
843    else:
844        __RM = 'rm -f'
845        __CP = 'cp'
846        __IGNORE = ''
847        __LN = 'ln'
848        MKDIR = 'mkdir -p "$(<)"'
849
850    engine.register_action("common.MkDir", MKDIR + __IGNORE)
851
852    engine.register_action(
853        "common.Clean", __RM + ' "$(>)"', flags=['piecemeal', 'together', 'existing'])
854    engine.register_action("common.copy", '{} "$(>)" {}  "$(<)"'.format(__CP, windows_hack))
855    engine.register_action("common.RmTemps", __RM + ' "$(>)" ' + __IGNORE,
856                           flags=['quietly', 'updated', 'piecemeal', 'together'])
857
858    engine.register_action("common.hard-link",
859        __RM + ' "$(<)" 2$(NULL_OUT) $(NULL_OUT)' + os.linesep +
860        __LN + ' "$(>)" "$(<)" $(NULL_OUT)')
861