1# Copyright 2003, 2005 Dave Abrahams
2# Copyright 2005, 2006 Rene Rivera
3# Copyright 2005 Toon Knapen
4# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
5# Distributed under the Boost Software License, Version 1.0.
6# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
7
8#   Provides actions common to all toolsets, such as creating directories and
9# removing files.
10
11import os ;
12import modules ;
13import utility ;
14import print ;
15import type ;
16import feature ;
17import errors ;
18import path ;
19import sequence ;
20import toolset ;
21import virtual-target ;
22import numbers ;
23
24if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ]
25{
26    .debug-configuration = true ;
27}
28if [ MATCH (--show-configuration) : [ modules.peek : ARGV ] ]
29{
30    .show-configuration = true ;
31}
32
33# Configurations
34#
35# The following class helps to manage toolset configurations. Each configuration
36# has a unique ID and one or more parameters. A typical example of a unique ID
37# is a condition generated by 'common.check-init-parameters' rule. Other kinds
38# of IDs can be used. Parameters may include any details about the configuration
39# like 'command', 'path', etc.
40#
41# A toolset configuration may be in one of the following states:
42#
43#   - registered
44#       Configuration has been registered (e.g. explicitly or by auto-detection
45#       code) but has not yet been marked as used, i.e. 'toolset.using' rule has
46#       not yet been called for it.
47#   - used
48#       Once called 'toolset.using' rule marks the configuration as 'used'.
49#
50# The main difference between the states above is that while a configuration is
51# 'registered' its options can be freely changed. This is useful in particular
52# for autodetection code - all detected configurations may be safely overwritten
53# by user code.
54
55class configurations
56{
57    import errors ;
58
59    rule __init__ ( )
60    {
61    }
62
63    # Registers a configuration.
64    #
65    # Returns 'true' if the configuration has been added and an empty value if
66    # it already exists. Reports an error if the configuration is 'used'.
67    #
68    rule register ( id )
69    {
70        if $(id) in $(self.used)
71        {
72            errors.error "common: the configuration '$(id)' is in use" ;
73        }
74
75        local retval ;
76
77        if ! $(id) in $(self.all)
78        {
79            self.all += $(id) ;
80
81            # Indicate that a new configuration has been added.
82            retval = true ;
83        }
84
85        return $(retval) ;
86    }
87
88    # Mark a configuration as 'used'.
89    #
90    # Returns 'true' if the state of the configuration has been changed to
91    # 'used' and an empty value if it the state has not been changed. Reports an
92    # error if the configuration is not known.
93    #
94    rule use ( id )
95    {
96        if ! $(id) in $(self.all)
97        {
98            errors.error "common: the configuration '$(id)' is not known" ;
99        }
100
101        local retval ;
102
103        if ! $(id) in $(self.used)
104        {
105            self.used += $(id) ;
106
107            # Indicate that the configuration has been marked as 'used'.
108            retval = true ;
109        }
110
111        return $(retval) ;
112    }
113
114    # Return all registered configurations.
115    #
116    rule all ( )
117    {
118        return $(self.all) ;
119    }
120
121    # Return all used configurations.
122    #
123    rule used ( )
124    {
125        return $(self.used) ;
126    }
127
128    # Returns the value of a configuration parameter.
129    #
130    rule get ( id : param )
131    {
132        return $(self.$(param).$(id)) ;
133    }
134
135    # Sets the value of a configuration parameter.
136    #
137    rule set ( id : param : value * )
138    {
139        self.$(param).$(id) = $(value) ;
140    }
141}
142
143
144# The rule for checking toolset parameters. Trailing parameters should all be
145# parameter name/value pairs. The rule will check that each parameter either has
146# a value in each invocation or has no value in each invocation. Also, the rule
147# will check that the combination of all parameter values is unique in all
148# invocations.
149#
150# Each parameter name corresponds to a subfeature. This rule will declare a
151# subfeature the first time a non-empty parameter value is passed and will
152# extend it with all the values.
153#
154# The return value from this rule is a condition to be used for flags settings.
155#
156rule check-init-parameters ( toolset requirement * : * )
157{
158    local sig = $(toolset) ;
159    local condition = <toolset>$(toolset) ;
160    local subcondition ;
161    for local index in 2 3 4 5 6 7 8 9
162    {
163        local name = $($(index)[1]) ;
164        local value = $($(index)[2]) ;
165
166        if $(value)-is-not-empty
167        {
168            condition = $(condition)-$(value) ;
169            if $(.had-unspecified-value.$(toolset).$(name))
170            {
171                errors.user-error
172                    "$(toolset) initialization: parameter '$(name)'"
173                    "inconsistent" : "no value was specified in earlier"
174                    "initialization" : "an explicit value is specified now" ;
175            }
176            # The below logic is for intel compiler. It calls this rule with
177            # 'intel-linux' and 'intel-win' as toolset, so we need to get the
178            # base part of toolset name. We can not pass 'intel' as toolset
179            # because in that case it will be impossible to register versionless
180            # intel-linux and intel-win toolsets of a specific version.
181            local t = $(toolset) ;
182            local m = [ MATCH "([^-]*)-" : $(toolset) ] ;
183            if $(m)
184            {
185                t = $(m[1]) ;
186            }
187            if ! $(.had-value.$(toolset).$(name))
188            {
189                if ! $(.declared-subfeature.$(t).$(name))
190                {
191                    feature.subfeature toolset $(t) : $(name) : : propagated ;
192                    .declared-subfeature.$(t).$(name) = true ;
193                }
194                .had-value.$(toolset).$(name) = true ;
195            }
196            feature.extend-subfeature toolset $(t) : $(name) : $(value) ;
197            subcondition += <toolset-$(t):$(name)>$(value) ;
198        }
199        else
200        {
201            if $(.had-value.$(toolset).$(name))
202            {
203                errors.user-error
204                    "$(toolset) initialization: parameter '$(name)'"
205                    "inconsistent" : "an explicit value was specified in an"
206                    "earlier initialization" : "no value is specified now" ;
207            }
208            .had-unspecified-value.$(toolset).$(name) = true ;
209        }
210        sig = $(sig)$(value:E="")- ;
211    }
212    # We also need to consider requirements on the toolset as we can
213    # configure the same toolset multiple times with different options that
214    # are selected with the requirements.
215    if $(requirement)
216    {
217        sig = $(sig)$(requirement:J=,) ;
218    }
219    if $(sig) in $(.all-signatures)
220    {
221        local message =
222            "duplicate initialization of $(toolset) with the following parameters: " ;
223        for local index in 2 3 4 5 6 7 8 9
224        {
225            local p = $($(index)) ;
226            if $(p)
227            {
228                message += "$(p[1]) = $(p[2]:E=<unspecified>)" ;
229            }
230        }
231        message += "previous initialization at $(.init-loc.$(sig))" ;
232        errors.user-error
233            $(message[1]) : $(message[2]) : $(message[3]) : $(message[4]) :
234            $(message[5]) : $(message[6]) : $(message[7]) : $(message[8]) ;
235    }
236    .all-signatures += $(sig) ;
237    .init-loc.$(sig) = [ errors.nearest-user-location ] ;
238
239    # If we have a requirement, this version should only be applied under that
240    # condition. To accomplish this we add a toolset requirement that imposes
241    # the toolset subcondition, which encodes the version.
242    if $(requirement)
243    {
244        local r = <toolset>$(toolset) $(requirement) ;
245        r = $(r:J=,) ;
246        toolset.add-requirements "$(r):$(subcondition)" ;
247    }
248
249    # We add the requirements, if any, to the condition to scope the toolset
250    # variables and options to this specific version.
251    condition += $(requirement) ;
252
253    if $(.show-configuration)
254    {
255        ECHO "notice:" $(condition) ;
256    }
257    return $(condition:J=/) ;
258}
259
260
261# A helper rule to get the command to invoke some tool. If
262# 'user-provided-command' is not given, tries to find binary named 'tool' in
263# PATH and in the passed 'additional-path'. Otherwise, verifies that the first
264# element of 'user-provided-command' is an existing program.
265#
266# This rule returns the command to be used when invoking the tool. If we can not
267# find the tool, a warning is issued. If 'path-last' is specified, PATH is
268# checked after 'additional-paths' when searching for 'tool'.
269#
270rule get-invocation-command-nodefault ( toolset : tool :
271    user-provided-command * : additional-paths * : path-last ? )
272{
273    local command ;
274    if ! $(user-provided-command)
275    {
276        command = [ find-tool $(tool) : $(additional-paths) : $(path-last) ] ;
277        if ! $(command) && $(.debug-configuration)
278        {
279            ECHO "warning:" toolset $(toolset) "initialization:" can not find tool
280                $(tool) ;
281            ECHO "warning:" initialized from [ errors.nearest-user-location ] ;
282        }
283    }
284    else
285    {
286        command = [ check-tool $(user-provided-command) ] ;
287        if ! $(command) && $(.debug-configuration)
288        {
289            ECHO "warning:" toolset $(toolset) "initialization:" ;
290            ECHO "warning:" can not find user-provided command
291                '$(user-provided-command)' ;
292            ECHO "warning:" initialized from [ errors.nearest-user-location ] ;
293        }
294    }
295
296    return $(command) ;
297}
298
299
300# Same as get-invocation-command-nodefault, except that if no tool is found,
301# returns either the user-provided-command, if present, or the 'tool' parameter.
302#
303rule get-invocation-command ( toolset : tool : user-provided-command * :
304    additional-paths * : path-last ? )
305{
306    local result = [ get-invocation-command-nodefault $(toolset) : $(tool) :
307        $(user-provided-command) : $(additional-paths) : $(path-last) ] ;
308
309    if ! $(result)
310    {
311        if $(user-provided-command)
312        {
313            result = $(user-provided-command) ;
314        }
315        else
316        {
317            result = $(tool) ;
318        }
319    }
320    return $(result) ;
321}
322
323
324# Given an invocation command return the absolute path to the command. This
325# works even if command has no path element and was found on the PATH.
326#
327rule get-absolute-tool-path ( command )
328{
329    if $(command:D)
330    {
331        return $(command:D) ;
332    }
333    else
334    {
335        local m = [ GLOB [ modules.peek : PATH Path path ] : $(command)
336            $(command).exe ] ;
337        return $(m[1]:D) ;
338    }
339}
340
341
342# Attempts to find tool (binary) named 'name' in PATH and in 'additional-paths'.
343# If found in PATH, returns 'name' and if found in additional paths, returns
344# absolute name. If the tool is found in several directories, returns the first
345# path found. Otherwise, returns an empty string. If 'path-last' is specified,
346# PATH is searched after 'additional-paths'.
347#
348rule find-tool ( name : additional-paths * : path-last ? )
349{
350    if $(name:D)
351    {
352        return [ check-tool-aux $(name) ] ;
353    }
354    local path = [ path.programs-path ] ;
355    local match = [ path.glob $(path) : $(name) $(name).exe ] ;
356    local additional-match = [ path.glob $(additional-paths) : $(name)
357        $(name).exe ] ;
358
359    local result ;
360    if $(path-last)
361    {
362        result = $(additional-match) ;
363        if ! $(result) && $(match)
364        {
365            result = $(name) ;
366        }
367    }
368    else
369    {
370        if $(match)
371        {
372            result = $(name) ;
373        }
374        else
375        {
376            result = $(additional-match) ;
377        }
378    }
379    if $(result)
380    {
381        return [ path.native $(result[1]) ] ;
382    }
383}
384
385# Checks if 'command' can be found either in path or is a full name to an
386# existing file.
387#
388local rule check-tool-aux ( command )
389{
390    if $(command:D)
391    {
392        if [ path.exists $(command) ]
393            # Both NT and Cygwin will run .exe files by their unqualified names.
394            || ( [ os.on-windows ] && [ path.exists $(command).exe ] )
395            # Only NT will run .bat & .cmd files by their unqualified names.
396            || ( ( [ os.name ] = NT ) && ( [ path.exists $(command).bat ] ||
397                [ path.exists $(command).cmd ] ) )
398        {
399            return $(command) ;
400        }
401    }
402    else
403    {
404        if [ GLOB [ modules.peek : PATH Path path ] : $(command) ]
405        {
406            return $(command) ;
407        }
408    }
409}
410
411
412# Checks that a tool can be invoked by 'command'. If command is not an absolute
413# path, checks if it can be found in 'path'. If command is an absolute path,
414# check that it exists. Returns 'command' if ok or empty string otherwise.
415#
416local rule check-tool ( xcommand + )
417{
418    if [ check-tool-aux $(xcommand[1]) ] ||
419       [ check-tool-aux $(xcommand[-1]) ]
420    {
421        return $(xcommand) ;
422    }
423}
424
425
426# Handle common options for toolset, specifically sets the following flag
427# variables:
428# - CONFIG_COMMAND to $(command)
429# - OPTIONS for compile         to the value of <compileflags> in $(options)
430# - OPTIONS for compile.c       to the value of <cflags>       in $(options)
431# - OPTIONS for compile.c++     to the value of <cxxflags>     in $(options)
432# - OPTIONS for compile.asm     to the value of <asmflags>     in $(options)
433# - OPTIONS for compile.fortran to the value of <fflags>       in $(options)
434# - OPTIONS for link            to the value of <linkflags>    in $(options)
435#
436rule handle-options ( toolset : condition * : command * : options * )
437{
438    if $(.debug-configuration)
439    {
440        ECHO "notice:" will use '$(command)' for $(toolset), condition
441            $(condition:E=(empty)) ;
442    }
443
444    #   The last parameter ('unchecked') says it is OK to set flags for another
445    # module.
446    toolset.flags $(toolset) CONFIG_COMMAND $(condition) : $(command)
447        : unchecked ;
448
449    toolset.flags $(toolset).compile         OPTIONS $(condition) :
450        [ feature.get-values <compileflags> : $(options) ] : unchecked ;
451
452    toolset.flags $(toolset).compile.c       OPTIONS $(condition) :
453        [ feature.get-values <cflags>       : $(options) ] : unchecked ;
454
455    toolset.flags $(toolset).compile.c++     OPTIONS $(condition) :
456        [ feature.get-values <cxxflags>     : $(options) ] : unchecked ;
457
458    toolset.flags $(toolset).compile.asm     OPTIONS $(condition) :
459        [ feature.get-values <asmflags>     : $(options) ] : unchecked ;
460
461    toolset.flags $(toolset).compile.fortran OPTIONS $(condition) :
462        [ feature.get-values <fflags>       : $(options) ] : unchecked ;
463
464    toolset.flags $(toolset).link            OPTIONS $(condition) :
465        [ feature.get-values <linkflags>    : $(options) ] : unchecked ;
466}
467
468
469# Returns the location of the "program files" directory on a Windows platform.
470#
471rule get-program-files-dir ( )
472{
473    local ProgramFiles = [ modules.peek : ProgramFiles ] ;
474    if $(ProgramFiles)
475    {
476        ProgramFiles = "$(ProgramFiles:J= )" ;
477    }
478    else
479    {
480        ProgramFiles = "c:\\Program Files" ;
481    }
482    return $(ProgramFiles) ;
483}
484
485
486if [ os.name ] = NT
487{
488    NULL_DEVICE = "NUL" ;
489    IGNORE = "2>$(NULL_DEVICE) >$(NULL_DEVICE) & setlocal" ;
490    RM = del /f /q ;
491    CP = copy /b ;
492    LN ?= $(CP) ;
493    # Ugly hack to convince copy to set the timestamp of the destination to the
494    # current time by concatenating the source with a nonexistent file. Note
495    # that this requires /b (binary) as the default when concatenating files is
496    # /a (ascii).
497    WINDOWS-CP-HACK = "+ this-file-does-not-exist-A698EE7806899E69" ;
498}
499else if [ os.name ] = VMS
500{
501    NULL_DEVICE = "NL:" ;
502    PIPE = PIPE ;
503    IGNORE = "2>$(NULL_DEVICE) >$(NULL_DEVICE)" ;
504    RM = DELETE /NOCONF ;
505    CP = COPY /OVERWRITE ;
506    LN = $(CP) ;
507}
508else
509{
510    NULL_DEVICE = "/dev/null" ;
511    IGNORE = "2>$(NULL_DEVICE) >$(NULL_DEVICE)" ;
512    RM = rm -f ;
513    CP = cp ;
514    LN = ln ;
515}
516
517NULL_OUT = ">$(NULL_DEVICE)" ;
518
519rule null-device ( )
520{
521    return $(NULL_DEVICE) ;
522}
523
524
525rule rm-command ( )
526{
527    return $(RM) ;
528}
529
530
531rule copy-command ( )
532{
533    return $(CP) ;
534}
535
536
537if "\n" = "n"
538{
539    # Escape characters not supported so use ugly hacks. Will not work on Cygwin
540    # - see below.
541    nl = "
542" ;
543    q = "" ;
544}
545else
546{
547    nl = "\n" ;
548    q = "\"" ;
549}
550
551
552rule newline-char ( )
553{
554    return $(nl) ;
555}
556
557
558# Returns the command needed to set an environment variable on the current
559# platform. The variable setting persists through all following commands and is
560# visible in the environment seen by subsequently executed commands. In other
561# words, on Unix systems, the variable is exported, which is consistent with the
562# only possible behavior on Windows systems.
563#
564rule variable-setting-command ( variable : value )
565{
566    if [ os.name ] = NT
567    {
568        return "set $(variable)=$(value)$(nl)" ;
569    }
570    else if [ os.name ] = VMS
571    {
572        return "$(variable) == $(q)$(value)$(q)$(nl)" ;
573    }
574    else
575    {
576        # If we do not have escape character support in bjam, the cod below
577        # blows up on CYGWIN, since the $(nl) variable holds a Windows new-line
578        # \r\n sequence that messes up the executed export command which then
579        # reports that the passed variable name is incorrect.
580        # But we have a check for cygwin in kernel/bootstrap.jam already.
581        return "$(variable)=$(q)$(value)$(q)$(nl)export $(variable)$(nl)" ;
582    }
583}
584
585
586# Returns a command to sets a named shell path variable to the given NATIVE
587# paths on the current platform.
588#
589rule path-variable-setting-command ( variable : paths * )
590{
591    local sep = [ os.path-separator ] ;
592    return [ variable-setting-command $(variable) : $(paths:J=$(sep)) ] ;
593}
594
595
596# Returns a command that prepends the given paths to the named path variable on
597# the current platform.
598#
599rule prepend-path-variable-command ( variable : paths * )
600{
601    return [ path-variable-setting-command $(variable)
602        : $(paths) [ os.expand-variable $(variable) ] ] ;
603}
604
605
606# Return a command which can create a file. If 'r' is result of invocation, then
607# 'r foobar' will create foobar with unspecified content. What happens if file
608# already exists is unspecified.
609#
610rule file-creation-command ( )
611{
612    if [ os.name ] = NT
613    {
614        # A few alternative implementations on Windows:
615        #
616        #   'type NUL >> '
617        #        That would construct an empty file instead of a file containing
618        #      a space and an end-of-line marker but it would also not change
619        #      the target's timestamp in case the file already exists.
620        #
621        #   'type NUL > '
622        #        That would construct an empty file instead of a file containing
623        #      a space and an end-of-line marker but it would also destroy an
624        #      already existing file by overwriting it with an empty one.
625        #
626        #   I guess the best solution would be to allow Boost Jam to define
627        # built-in functions such as 'create a file', 'touch a file' or 'copy a
628        # file' which could be used from inside action code. That would allow
629        # completely portable operations without this kind of kludge.
630        #                                            (22.02.2009.) (Jurko)
631        return "echo. > " ;
632    }
633    else if [ os.name ] = VMS
634    {
635        return "APPEND /NEW NL: " ;
636    }
637    else
638    {
639        return "touch " ;
640    }
641}
642
643
644# Returns a command that may be used for 'touching' files. It is not a real
645# 'touch' command on NT because it adds an empty line at the end of file but it
646# works with source files.
647#
648rule file-touch-command ( )
649{
650    if [ os.name ] = NT
651    {
652        return "echo. >> " ;
653    }
654    else if [ os.name ] = VMS
655    {
656        return "APPEND /NEW NL: " ;
657    }
658    else
659    {
660        return "touch " ;
661    }
662}
663
664
665rule MkDir
666{
667    # If dir exists, do not update it. Do this even for $(DOT).
668    NOUPDATE $(<) ;
669
670    if $(<) != $(DOT) && ! $($(<)-mkdir)
671    {
672        # Cheesy gate to prevent multiple invocations on same dir.
673        $(<)-mkdir = true ;
674
675        # Schedule the mkdir build action.
676        common.mkdir $(<) ;
677
678        # Prepare a Jam 'dirs' target that can be used to make the build only
679        # construct all the target directories.
680        DEPENDS dirs : $(<) ;
681
682        # Recursively create parent directories. $(<:P) = $(<)'s parent & we
683        # recurse until root.
684
685        local s = $(<:P) ;
686        if [ os.name ] = NT
687        {
688            switch $(s)
689            {
690                case "*:"   : s = ;
691                case "*:\\" : s = ;
692            }
693        }
694
695        if $(s)
696        {
697            if $(s) != $(<)
698            {
699                DEPENDS $(<) : $(s) ;
700                MkDir $(s) ;
701            }
702            else
703            {
704                NOTFILE $(s) ;
705            }
706        }
707    }
708}
709
710
711#actions MkDir1
712#{
713#    mkdir "$(<)"
714#}
715
716#   The following quick-fix actions should be replaced using the original MkDir1
717# action once Boost Jam gets updated to correctly detect different paths leading
718# up to the same filesystem target and triggers their build action only once.
719#                                             (todo) (04.07.2008.) (Jurko)
720
721if [ os.name ] = NT
722{
723    actions quietly mkdir
724    {
725        if not exist "$(<)\\" mkdir "$(<)"
726    }
727}
728else
729{
730    actions quietly mkdir
731    {
732        mkdir -p "$(<)"
733    }
734}
735
736
737actions piecemeal together existing Clean
738{
739    $(RM) "$(>)"
740}
741
742
743rule copy
744{
745}
746
747
748actions copy
749{
750    $(CP) "$(>)" $(WINDOWS-CP-HACK) "$(<)"
751}
752
753
754rule RmTemps
755{
756}
757
758
759actions quietly updated piecemeal together RmTemps
760{
761    $(RM) "$(>)" $(IGNORE)
762}
763
764
765actions hard-link
766{
767    $(RM) "$(<)" 2$(NULL_OUT) $(NULL_OUT)
768    $(LN) "$(>)" "$(<)" $(NULL_OUT)
769}
770
771
772if [ os.name ] = VMS
773{
774    actions mkdir
775    {
776        IF F$PARSE("$(<:W)") .EQS. "" THEN CREATE /DIR $(<:W)
777    }
778
779    actions piecemeal together existing Clean
780    {
781        $(RM) $(>:WJ=;*,);*
782    }
783
784    actions copy
785    {
786        $(CP) $(>:WJ=,) $(<:W)
787    }
788
789    actions quietly updated piecemeal together RmTemps
790    {
791        $(PIPE) $(RM) $(>:WJ=;*,);* $(IGNORE)
792    }
793
794    actions hard-link
795    {
796        $(PIPE) $(RM) $(>[1]:W);* $(IGNORE)
797        $(PIPE) $(LN) $(>[1]:W) $(<:W) $(NULL_OUT)
798    }
799}
800
801# Given a target, as given to a custom tag rule, returns a string formatted
802# according to the passed format. Format is a list of properties that is
803# represented in the result. For each element of format the corresponding target
804# information is obtained and added to the result string. For all, but the
805# literal, the format value is taken as the as string to prepend to the output
806# to join the item to the rest of the result. If not given "-" is used as a
807# joiner.
808#
809# The format options can be:
810#
811#   <base>[joiner]
812#       ::  The basename of the target name.
813#   <toolset>[joiner]
814#       ::  The abbreviated toolset tag being used to build the target.
815#   <threading>[joiner]
816#       ::  Indication of a multi-threaded build.
817#   <runtime>[joiner]
818#       ::  Collective tag of the build runtime.
819#   <version:/version-feature | X.Y[.Z]/>[joiner]
820#       ::  Short version tag taken from the given "version-feature" in the
821#           build properties. Or if not present, the literal value as the
822#           version number.
823#   <property:/property-name/>[joiner]
824#       ::  Direct lookup of the given property-name value in the build
825#           properties. /property-name/ is a regular expression. E.g.
826#           <property:toolset-.*:flavor> will match every toolset.
827#   /otherwise/
828#       ::  The literal value of the format argument.
829#
830# For example this format:
831#
832#   boost_ <base> <toolset> <threading> <runtime> <version:boost-version>
833#
834# Might return:
835#
836#   boost_thread-vc80-mt-gd-1_33.dll, or
837#   boost_regex-vc80-gd-1_33.dll
838#
839# The returned name also has the target type specific prefix and suffix which
840# puts it in a ready form to use as the value from a custom tag rule.
841#
842rule format-name ( format * : name : type ? : property-set )
843{
844    local result = "" ;
845    for local f in $(format)
846    {
847        switch $(f:G)
848        {
849            case <base> :
850                result += $(name:B) ;
851
852            case <toolset> :
853                result += [ join-tag $(f:G=) : [ toolset-tag $(name) : $(type) :
854                    $(property-set) ] ] ;
855
856            case <threading> :
857                result += [ join-tag $(f:G=) : [ threading-tag $(name) : $(type)
858                    : $(property-set) ] ] ;
859
860            case <runtime> :
861                result += [ join-tag $(f:G=) : [ runtime-tag $(name) : $(type) :
862                    $(property-set) ] ] ;
863
864            case <qt> :
865                result += [ join-tag $(f:G=) : [ qt-tag $(name) : $(type) :
866                    $(property-set) ] ] ;
867
868            case <address-model> :
869                result += [ join-tag $(f:G=) : [ address-model-tag $(name) :
870                    $(type) : $(property-set) ] ] ;
871
872            case <arch-and-model> :
873                result += [ join-tag $(f:G=) : [ arch-and-model-tag $(name) :
874                    $(type) : $(property-set) ] ] ;
875
876            case <version:*> :
877                local key = [ MATCH <version:(.*)> : $(f:G) ] ;
878                local version = [ $(property-set).get <$(key)> ] ;
879                version ?= $(key) ;
880                version = [ MATCH "^([^.]+)[.]([^.]+)[.]?([^.]*)" : $(version) ] ;
881                result += [ join-tag $(f:G=) : $(version[1])_$(version[2]) ] ;
882
883            case <property:*> :
884                local key = [ MATCH <property:(.*)> : $(f:G) ] ;
885                local p0 = [ MATCH <($(key))> : [ $(property-set).raw ] ] ;
886                if $(p0)
887                {
888                    local p = [ $(property-set).get <$(p0)> ] ;
889                    if $(p)
890                    {
891                        result += [ join-tag $(f:G=) : $(p) ] ;
892                    }
893                }
894
895            case * :
896                result += $(f:G=) ;
897        }
898    }
899    return [ virtual-target.add-prefix-and-suffix $(result:J=) : $(type) :
900        $(property-set) ] ;
901}
902
903
904local rule join-tag ( joiner ? : tag ? )
905{
906    if ! $(joiner) { joiner = - ; }
907    return $(joiner)$(tag) ;
908}
909
910
911local rule toolset-tag ( name : type ? : property-set )
912{
913    local tag = ;
914
915    local properties = [ $(property-set).raw ] ;
916    switch [ $(property-set).get <toolset> ]
917    {
918        case borland* : tag += bcb ;
919        case clang* :
920        {
921            switch [ $(property-set).get <toolset-clang:platform> ]
922            {
923               case darwin : tag += clang-darwin ;
924               case linux  : tag += clang ;
925               case win    : tag += clangw ;
926            }
927        }
928        case como* : tag += como ;
929        case cw : tag += cw ;
930        case darwin* : tag += xgcc ;
931        case edg* : tag += edg ;
932        case gcc* :
933        {
934            switch [ $(property-set).get <target-os> ]
935            {
936                case *windows* : tag += mgw ;
937                case * : tag += gcc ;
938            }
939        }
940        case intel :
941        if [ $(property-set).get <toolset-intel:platform> ] = win
942        {
943            tag += iw ;
944        }
945        else
946        {
947            tag += il ;
948        }
949        case kcc* : tag += kcc ;
950        case kylix* : tag += bck ;
951        #case metrowerks* : tag += cw ;
952        #case mingw* : tag += mgw ;
953        case mipspro* : tag += mp ;
954        case msvc* : tag += vc ;
955        case qcc* : tag += qcc ;
956        case sun* : tag += sw ;
957        case tru64cxx* : tag += tru ;
958        case vacpp* : tag += xlc ;
959    }
960    local version = [ MATCH "<toolset.*version>([0123456789]+)[.]?([0123456789]*)"
961        : $(properties) ] ;
962    # For historical reasons, vc6.0 and vc7.0 use different naming.
963    if $(tag) = vc
964    {
965        if $(version[1]) = 6
966        {
967            # Cancel minor version.
968            version = 6 ;
969        }
970        else if $(version[1]) = 7 && $(version[2]) = 0
971        {
972            version = 7 ;
973        }
974    }
975
976    # From GCC 5, versioning changes and minor becomes patch
977    if ( $(tag) = gcc || $(tag) = mgw ) && $(version[1]) && [ numbers.less 4 $(version[1]) ]
978    {
979        version = $(version[1]) ;
980    }
981
982    # Ditto, from Clang 4
983    if ( $(tag) = clang || $(tag) = clangw ) && $(version[1]) && [ numbers.less 3 $(version[1]) ]
984    {
985        version = $(version[1]) ;
986    }
987
988    # On intel, version is not added, because it does not matter and it is the
989    # version of vc used as backend that matters. Ideally, we should encode the
990    # backend version but that would break compatibility with V1.
991    if $(tag) = iw
992    {
993        version = ;
994    }
995
996    # On borland, version is not added for compatibility with V1.
997    if $(tag) = bcb
998    {
999        version = ;
1000    }
1001
1002    tag += $(version) ;
1003
1004    return $(tag:J=) ;
1005}
1006
1007
1008local rule threading-tag ( name : type ? : property-set )
1009{
1010    if <threading>multi in [ $(property-set).raw ]
1011    {
1012        return mt ;
1013    }
1014}
1015
1016
1017local rule runtime-tag ( name : type ? : property-set )
1018{
1019    local tag = ;
1020
1021    local properties = [ $(property-set).raw ] ;
1022    if <runtime-link>static in $(properties) { tag += s ; }
1023
1024    # This is an ugly thing. In V1, there is code to automatically detect which
1025    # properties affect a target. So, if <runtime-debugging> does not affect gcc
1026    # toolset, the tag rules will not even see <runtime-debugging>. Similar
1027    # functionality in V2 is not implemented yet, so we just check for toolsets
1028    # known to care about runtime debugging.
1029    if ( <toolset>msvc in $(properties) ) ||
1030        ( <stdlib>stlport in $(properties) ) ||
1031        ( <toolset-intel:platform>win in $(properties) ) ||
1032        ( <toolset-clang:platform>win in $(properties) )
1033    {
1034        if <runtime-debugging>on in $(properties) { tag += g ; }
1035    }
1036
1037    if <python-debugging>on in $(properties) { tag += y ; }
1038    if <variant>debug in $(properties) { tag += d ; }
1039    if <stdlib>stlport in $(properties) { tag += p ; }
1040    if <stdlib-stlport:iostream>hostios in $(properties) { tag += n ; }
1041
1042    return $(tag:J=) ;
1043}
1044
1045
1046# Create a tag for the Qt library version
1047# "<qt>4.6.0" will result in tag "qt460"
1048local rule qt-tag ( name : type ? : property-set )
1049{
1050    local v = [ MATCH "([0123456789]+)[.]?([0123456789]*)[.]?([0123456789]*)" :
1051        [ $(property-set).get <qt> ] ] ;
1052    return qt$(v:J=) ;
1053}
1054
1055
1056# Create a tag for the address-model
1057# <address-model>64 will simply generate "64"
1058local rule address-model-tag ( name : type ? : property-set )
1059{
1060    return [ $(property-set).get <address-model> ] ;
1061}
1062
1063# Create a tag for the architecture and model
1064# <architecture>x86 <address-model>32 would generate "x32"
1065# This relies on the fact that all architectures start with
1066# unique letters.
1067local rule arch-and-model-tag ( name : type ? : property-set )
1068{
1069    local architecture = [ $(property-set).get <architecture> ] ;
1070    local address-model = [ $(property-set).get <address-model> ] ;
1071
1072    local arch = [ MATCH ^(.) : $(architecture) ] ;
1073
1074    return $(arch)$(address-model) ;
1075}
1076
1077rule __test__ ( )
1078{
1079    import assert ;
1080
1081    local save-os = [ modules.peek os : .name ] ;
1082
1083    modules.poke os : .name : LINUX ;
1084    assert.result "PATH=\"foo:bar:baz\"\nexport PATH\n"
1085        : path-variable-setting-command PATH : foo bar baz ;
1086    assert.result "PATH=\"foo:bar:$PATH\"\nexport PATH\n"
1087        : prepend-path-variable-command PATH : foo bar ;
1088
1089    modules.poke os : .name : NT ;
1090    assert.result "set PATH=foo;bar;baz\n"
1091        : path-variable-setting-command PATH : foo bar baz ;
1092    assert.result "set PATH=foo;bar;%PATH%\n"
1093        : prepend-path-variable-command PATH : foo bar ;
1094
1095    modules.poke os : .name : $(save-os) ;
1096}
1097