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