summaryrefslogtreecommitdiff
path: root/tools/build/v2/tools/common.jam
blob: 53e91b428fd7b5c942f7d1d433c2702fba3604f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
# Copyright 2003, 2005 Dave Abrahams
# Copyright 2005, 2006 Rene Rivera
# Copyright 2005 Toon Knapen
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)

#   Provides actions common to all toolsets, such as creating directories and
# removing files.

import os ;
import modules ;
import utility ;
import print ;
import type ;
import feature ;
import errors ;
import path ;
import sequence ;
import toolset ;
import virtual-target ;

if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ]
{
    .debug-configuration = true ;
}
if [ MATCH (--show-configuration) : [ modules.peek : ARGV ] ]
{
    .show-configuration = true ;
}

# Configurations
#
# The following class helps to manage toolset configurations. Each configuration
# has a unique ID and one or more parameters. A typical example of a unique ID
# is a condition generated by 'common.check-init-parameters' rule. Other kinds
# of IDs can be used. Parameters may include any details about the configuration
# like 'command', 'path', etc.
#
# A toolset configuration may be in one of the following states:
#
#   - registered
#       Configuration has been registered (e.g. explicitly or by auto-detection
#       code) but has not yet been marked as used, i.e. 'toolset.using' rule has
#       not yet been called for it.
#   - used
#       Once called 'toolset.using' rule marks the configuration as 'used'.
#
# The main difference between the states above is that while a configuration is
# 'registered' its options can be freely changed. This is useful in particular
# for autodetection code - all detected configurations may be safely overwritten
# by user code.

class configurations
{
    import errors ;

    rule __init__ ( )
    {
    }

    # Registers a configuration.
    #
    # Returns 'true' if the configuration has been added and an empty value if
    # it already exists. Reports an error if the configuration is 'used'.
    #
    rule register ( id )
    {
        if $(id) in $(self.used)
        {
            errors.error "common: the configuration '$(id)' is in use" ;
        }

        local retval ;

        if ! $(id) in $(self.all)
        {
            self.all += $(id) ;

            # Indicate that a new configuration has been added.
            retval = true ;
        }

        return $(retval) ;
    }

    # Mark a configuration as 'used'.
    #
    # Returns 'true' if the state of the configuration has been changed to
    # 'used' and an empty value if it the state has not been changed. Reports an
    # error if the configuration is not known.
    #
    rule use ( id )
    {
        if ! $(id) in $(self.all)
        {
            errors.error "common: the configuration '$(id)' is not known" ;
        }

        local retval ;

        if ! $(id) in $(self.used)
        {
            self.used += $(id) ;

            # Indicate that the configuration has been marked as 'used'.
            retval = true ;
        }

        return $(retval) ;
    }

    # Return all registered configurations.
    #
    rule all ( )
    {
        return $(self.all) ;
    }

    # Return all used configurations.
    #
    rule used ( )
    {
        return $(self.used) ;
    }

    # Returns the value of a configuration parameter.
    #
    rule get ( id : param )
    {
        return $(self.$(param).$(id)) ;
    }

    # Sets the value of a configuration parameter.
    #
    rule set ( id : param : value * )
    {
        self.$(param).$(id) = $(value) ;
    }
}


# The rule for checking toolset parameters. Trailing parameters should all be
# parameter name/value pairs. The rule will check that each parameter either has
# a value in each invocation or has no value in each invocation. Also, the rule
# will check that the combination of all parameter values is unique in all
# invocations.
#
# Each parameter name corresponds to a subfeature. This rule will declare a
# subfeature the first time a non-empty parameter value is passed and will
# extend it with all the values.
#
# The return value from this rule is a condition to be used for flags settings.
#
rule check-init-parameters ( toolset requirement * : * )
{
    local sig = $(toolset) ;
    local condition = <toolset>$(toolset) ;
    local subcondition ;
    for local index in 2 3 4 5 6 7 8 9
    {
        local name = $($(index)[1]) ;
        local value = $($(index)[2]) ;

        if $(value)-is-not-empty
        {
            condition = $(condition)-$(value) ;
            if $(.had-unspecified-value.$(toolset).$(name))
            {
                errors.user-error
                    "$(toolset) initialization: parameter '$(name)'"
                    "inconsistent" : "no value was specified in earlier"
                    "initialization" : "an explicit value is specified now" ;
            }
            # The below logic is for intel compiler. It calls this rule with
            # 'intel-linux' and 'intel-win' as toolset, so we need to get the
            # base part of toolset name. We can not pass 'intel' as toolset
            # because in that case it will be impossible to register versionless
            # intel-linux and intel-win toolsets of a specific version.
            local t = $(toolset) ;
            local m = [ MATCH ([^-]*)- : $(toolset) ] ;
            if $(m)
            {
                t = $(m[1]) ;
            }
            if ! $(.had-value.$(toolset).$(name))
            {
                if ! $(.declared-subfeature.$(t).$(name))
                {
                    feature.subfeature toolset $(t) : $(name) : : propagated ;
                    .declared-subfeature.$(t).$(name) = true ;
                }
                .had-value.$(toolset).$(name) = true ;
            }
            feature.extend-subfeature toolset $(t) : $(name) : $(value) ;
            subcondition += <toolset-$(t):$(name)>$(value) ;
        }
        else
        {
            if $(.had-value.$(toolset).$(name))
            {
                errors.user-error
                    "$(toolset) initialization: parameter '$(name)'"
                    "inconsistent" : "an explicit value was specified in an"
                    "earlier initialization" : "no value is specified now" ;
            }
            .had-unspecified-value.$(toolset).$(name) = true ;
        }
        sig = $(sig)$(value:E="")- ;
    }
    if $(sig) in $(.all-signatures)
    {
        local message =
            "duplicate initialization of $(toolset) with the following parameters: " ;
        for local index in 2 3 4 5 6 7 8 9
        {
            local p = $($(index)) ;
            if $(p)
            {
                message += "$(p[1]) = $(p[2]:E=<unspecified>)" ;
            }
        }
        message += "previous initialization at $(.init-loc.$(sig))" ;
        errors.user-error
            $(message[1]) : $(message[2]) : $(message[3]) : $(message[4]) :
            $(message[5]) : $(message[6]) : $(message[7]) : $(message[8]) ;
    }
    .all-signatures += $(sig) ;
    .init-loc.$(sig) = [ errors.nearest-user-location ] ;

    # If we have a requirement, this version should only be applied under that
    # condition. To accomplish this we add a toolset requirement that imposes
    # the toolset subcondition, which encodes the version.
    if $(requirement)
    {
        local r = <toolset>$(toolset) $(requirement) ;
        r = $(r:J=,) ;
        toolset.add-requirements $(r):$(subcondition) ;
    }

    # We add the requirements, if any, to the condition to scope the toolset
    # variables and options to this specific version.
    condition += $(requirement) ;

    if $(.show-configuration)
    {
        ECHO notice: $(condition) ;
    }
    return $(condition:J=/) ;
}


# A helper rule to get the command to invoke some tool. If
# 'user-provided-command' is not given, tries to find binary named 'tool' in
# PATH and in the passed 'additional-path'. Otherwise, verifies that the first
# element of 'user-provided-command' is an existing program.
#
# This rule returns the command to be used when invoking the tool. If we can not
# find the tool, a warning is issued. If 'path-last' is specified, PATH is
# checked after 'additional-paths' when searching for 'tool'.
#
rule get-invocation-command-nodefault ( toolset : tool :
    user-provided-command * : additional-paths * : path-last ? )
{
    local command ;
    if ! $(user-provided-command)
    {
        command = [ find-tool $(tool) : $(additional-paths) : $(path-last) ] ;
        if ! $(command) && $(.debug-configuration)
        {
            ECHO "warning: toolset $(toolset) initialization: can not find tool $(tool)" ;
            ECHO "warning: initialized from" [ errors.nearest-user-location ] ;
        }
    }
    else
    {
        command = [ check-tool $(user-provided-command) ] ;
        if ! $(command) && $(.debug-configuration)
        {
            ECHO "warning: toolset $(toolset) initialization: " ;
            ECHO "warning: can not find user-provided command " '$(user-provided-command)' ;
            ECHO "warning: initialized from" [ errors.nearest-user-location ] ;
        }
    }

    return $(command) ;
}


# Same as get-invocation-command-nodefault, except that if no tool is found,
# returns either the user-provided-command, if present, or the 'tool' parameter.
#
rule get-invocation-command ( toolset : tool : user-provided-command * :
    additional-paths * : path-last ? )
{
    local result = [ get-invocation-command-nodefault $(toolset) : $(tool) :
        $(user-provided-command) : $(additional-paths) : $(path-last) ] ;

    if ! $(result)
    {
        if $(user-provided-command)
        {
            result = $(user-provided-command) ;
        }
        else
        {
            result = $(tool) ;
        }
    }
    return $(result) ;
}


# Given an invocation command return the absolute path to the command. This
# works even if command has no path element and was found on the PATH.
#
rule get-absolute-tool-path ( command )
{
    if $(command:D)
    {
        return $(command:D) ;
    }
    else
    {
        local m = [ GLOB [ modules.peek : PATH Path path ] : $(command) $(command).exe ] ;
        return $(m[1]:D) ;
    }
}


# Attempts to find tool (binary) named 'name' in PATH and in 'additional-paths'.
# If found in PATH, returns 'name' and if found in additional paths, returns
# absolute name. If the tool is found in several directories, returns the
# first path found. Otherwise, returns an empty string. If 'path-last' is
# specified, PATH is searched after 'additional-paths'.
#
rule find-tool ( name : additional-paths * : path-last ? )
{
    local path = [ path.programs-path ] ;
    local match = [ path.glob $(path) : $(name) $(name).exe ] ;
    local additional-match = [ path.glob $(additional-paths) : $(name) $(name).exe ] ;

    local result ;
    if $(path-last)
    {
        result = $(additional-match) ;
        if ! $(result) && $(match)
        {
            result = $(name) ;
        }
    }
    else
    {
        if $(match)
        {
            result = $(name) ;
        }
        else
        {
            result = $(additional-match) ;
        }
    }
    if $(result)
    {
        return [ path.native $(result[1]) ] ;
    }
}


# Checks if 'command' can be found either in path or is a full name to an
# existing file.
#
local rule check-tool-aux ( command )
{
    if $(command:D)
    {
        if [ path.exists $(command) ]
            # Both NT and Cygwin will run .exe files by their unqualified names.
            || ( [ os.on-windows ] && [ path.exists $(command).exe ] )
            # Only NT will run .bat & .cmd files by their unqualified names.
            || ( ( [ os.name ] = NT ) && ( [ path.exists $(command).bat ] ||
                [ path.exists $(command).cmd ] ) )
        {
            return $(command) ;
        }
    }
    else
    {
        if [ GLOB [ modules.peek : PATH Path path ] : $(command) ]
        {
            return $(command) ;
        }
    }
}


# Checks that a tool can be invoked by 'command'. If command is not an absolute
# path, checks if it can be found in 'path'. If comand is an absolute path,
# check that it exists. Returns 'command' if ok or empty string otherwise.
#
local rule check-tool ( xcommand + )
{
    if [ check-tool-aux $(xcommand[1]) ] ||
       [ check-tool-aux $(xcommand[-1]) ]
    {
        return $(xcommand) ;
    }
}


# Handle common options for toolset, specifically sets the following flag
# variables:
# - CONFIG_COMMAND to $(command)
# - OPTIONS for compile         to the value of <compileflags> in $(options)
# - OPTIONS for compile.c       to the value of <cflags>       in $(options)
# - OPTIONS for compile.c++     to the value of <cxxflags>     in $(options)
# - OPTIONS for compile.fortran to the value of <fflags>       in $(options)
# - OPTIONS for link            to the value of <linkflags>    in $(options)
#
rule handle-options ( toolset : condition * : command * : options * )
{
    if $(.debug-configuration)
    {
        ECHO "notice: will use '$(command)' for $(toolset), condition $(condition:E=(empty))" ;
    }

    #   The last parameter ('unchecked') says it is OK to set flags for another
    # module.
    toolset.flags $(toolset) CONFIG_COMMAND $(condition) : $(command)
        : unchecked ;

    toolset.flags $(toolset).compile         OPTIONS $(condition) :
        [ feature.get-values <compileflags> : $(options) ] : unchecked ;

    toolset.flags $(toolset).compile.c       OPTIONS $(condition) :
        [ feature.get-values <cflags>       : $(options) ] : unchecked ;

    toolset.flags $(toolset).compile.c++     OPTIONS $(condition) :
        [ feature.get-values <cxxflags>     : $(options) ] : unchecked ;

    toolset.flags $(toolset).compile.fortran OPTIONS $(condition) :
        [ feature.get-values <fflags>       : $(options) ] : unchecked ;

    toolset.flags $(toolset).link            OPTIONS $(condition) :
        [ feature.get-values <linkflags>    : $(options) ] : unchecked ;
}


# Returns the location of the "program files" directory on a Windows platform.
#
rule get-program-files-dir ( )
{
    local ProgramFiles = [ modules.peek : ProgramFiles ] ;
    if $(ProgramFiles)
    {
        ProgramFiles = "$(ProgramFiles:J= )" ;
    }
    else
    {
        ProgramFiles = "c:\\Program Files" ;
    }
    return $(ProgramFiles) ;
}


if [ os.name ] = NT
{
    RM = del /f /q ;
    CP = copy /b ;
    IGNORE = "2>nul >nul & setlocal" ;
    LN ?= $(CP) ;
    # Ugly hack to convince copy to set the timestamp of the
    # destination to the current time by concatenating the
    # source with a nonexistent file.  Note that this requires
    # /b (binary) as the default when concatenating files is /a (ascii).
    WINDOWS-CP-HACK = "+ this-file-does-not-exist-A698EE7806899E69" ;
}
else
{
    RM = rm -f ;
    CP = cp ;
    LN = ln ;
}


rule rm-command ( )
{
    return $(RM) ;
}


rule copy-command ( )
{
    return $(CP) ;
}


if "\n" = "n" 
{    
    # Escape characters are not supported. Use ugly hacks that won't work,
    # see below.
    nl = "
" ;
    q = "" ;
}
else
{
    nl = "\n" ; 
    q = "\"" ;
}

# Returns the command needed to set an environment variable on the current
# platform. The variable setting persists through all following commands and is
# visible in the environment seen by subsequently executed commands. In other
# words, on Unix systems, the variable is exported, which is consistent with the
# only possible behavior on Windows systems.
#
rule variable-setting-command ( variable : value )
{
    if [ os.name ] = NT
    {
        return "set $(variable)=$(value)$(nl)" ;
    }
    else
    {
        # If we don't have escape characters support in bjam, the below blows
        # up on CYGWIN, since the $(nl) variable holds a Windows new-line \r\n 
        # sequence that messes up the executed export command which then reports
        # that the passed variable name is incorrect.
        # But we have a check for cygwin in kernel/bootstrap.jam already.
        return "$(variable)=$(q)$(value)$(q)$(nl)export $(variable)$(nl)" ;
    }
}


# Returns a command to sets a named shell path variable to the given NATIVE
# paths on the current platform.
#
rule path-variable-setting-command ( variable : paths * )
{
    local sep = [ os.path-separator ] ;
    return [ variable-setting-command $(variable) : $(paths:J=$(sep)) ] ;
}


# Returns a command that prepends the given paths to the named path variable on
# the current platform.
#
rule prepend-path-variable-command ( variable : paths * )
{
    return [ path-variable-setting-command $(variable)
        : $(paths) [ os.expand-variable $(variable) ] ] ;
}


# Return a command which can create a file. If 'r' is result of invocation, then
# 'r foobar' will create foobar with unspecified content. What happens if file
# already exists is unspecified.
#
rule file-creation-command ( )
{
    if [ os.name ] = NT
    {
        # A few alternative implementations on Windows:
        #
        #   'type NUL >> '
        #        That would construct an empty file instead of a file containing
        #      a space and an end-of-line marker but it would also not change
        #      the target's timestamp in case the file already exists.
        #
        #   'type NUL > '
        #        That would construct an empty file instead of a file containing
        #      a space and an end-of-line marker but it would also destroy an
        #      already existing file by overwriting it with an empty one.
        #
        #   I guess the best solution would be to allow Boost Jam to define
        # built-in functions such as 'create a file', 'touch a file' or 'copy a
        # file' which could be used from inside action code. That would allow
        # completely portable operations without this kind of kludge.
        #                                            (22.02.2009.) (Jurko)
        return "echo. > " ;
    }
    else
    {
        return "touch " ;
    }
}


# Returns a command that may be used for 'touching' files. It is not a real
# 'touch' command on NT because it adds an empty line at the end of file but it
# works with source files.
#
rule file-touch-command ( )
{
    if [ os.name ] = NT
    {
        return "echo. >> " ;
    }
    else
    {
        return "touch " ;
    }
}


rule MkDir
{
    # If dir exists, do not update it. Do this even for $(DOT).
    NOUPDATE $(<) ;

    if $(<) != $(DOT) && ! $($(<)-mkdir)
    {
        # Cheesy gate to prevent multiple invocations on same dir.
        $(<)-mkdir = true ;

        # Schedule the mkdir build action.
        common.mkdir $(<) ;

        # Prepare a Jam 'dirs' target that can be used to make the build only
        # construct all the target directories.
        DEPENDS dirs : $(<) ;

        # Recursively create parent directories. $(<:P) = $(<)'s parent & we
        # recurse until root.

        local s = $(<:P) ;
        if [ os.name ] = NT
        {
            switch $(s)
            {
                case *:   : s = ;
                case *:\\ : s = ;
            }
        }

        if $(s)
        {
            if $(s) != $(<)
            {
                DEPENDS $(<) : $(s) ;
                MkDir $(s) ;
            }
            else
            {
                NOTFILE $(s) ;
            }
        }
    }
}


#actions MkDir1
#{
#    mkdir "$(<)"
#}

#   The following quick-fix actions should be replaced using the original MkDir1
# action once Boost Jam gets updated to correctly detect different paths leading
# up to the same filesystem target and triggers their build action only once.
#                                             (todo) (04.07.2008.) (Jurko)

if [ os.name ] = NT
{
    actions mkdir
    {
        if not exist "$(<)\\" mkdir "$(<)"
    }
}
else
{
    actions mkdir
    {
        mkdir -p "$(<)"
    }
}

actions piecemeal together existing Clean
{
    $(RM) "$(>)"
}


rule copy
{
}


actions copy
{
    $(CP) "$(>)" $(WINDOWS-CP-HACK) "$(<)"
}


rule RmTemps
{
}


actions quietly updated piecemeal together RmTemps
{
    $(RM) "$(>)" $(IGNORE)
}


actions hard-link
{
    $(RM) "$(<)" 2$(NULL_OUT) $(NULL_OUT)
    $(LN) "$(>)" "$(<)" $(NULL_OUT)
}


# Given a target, as given to a custom tag rule, returns a string formatted
# according to the passed format. Format is a list of properties that is
# represented in the result. For each element of format the corresponding target
# information is obtained and added to the result string. For all, but the
# literal, the format value is taken as the as string to prepend to the output
# to join the item to the rest of the result. If not given "-" is used as a
# joiner.
#
# The format options can be:
#
#   <base>[joiner]
#       ::  The basename of the target name.
#   <toolset>[joiner]
#       ::  The abbreviated toolset tag being used to build the target.
#   <threading>[joiner]
#       ::  Indication of a multi-threaded build.
#   <runtime>[joiner]
#       ::  Collective tag of the build runtime.
#   <version:/version-feature | X.Y[.Z]/>[joiner]
#       ::  Short version tag taken from the given "version-feature" in the
#           build properties. Or if not present, the literal value as the
#           version number.
#   <property:/property-name/>[joiner]
#       ::  Direct lookup of the given property-name value in the build
#           properties. /property-name/ is a regular expression. E.g.
#           <property:toolset-.*:flavor> will match every toolset.
#   /otherwise/
#       ::  The literal value of the format argument.
#
# For example this format:
#
#   boost_ <base> <toolset> <threading> <runtime> <version:boost-version>
#
# Might return:
#
#   boost_thread-vc80-mt-gd-1_33.dll, or
#   boost_regex-vc80-gd-1_33.dll
#
# The returned name also has the target type specific prefix and suffix which
# puts it in a ready form to use as the value from a custom tag rule.
#
rule format-name ( format * : name : type ? : property-set )
{
    local result = "" ;
    for local f in $(format)
    {
        switch $(f:G)
        {
            case <base> :
                result += $(name:B) ;
            
            case <toolset> :
                result += [ join-tag $(f:G=) : [ toolset-tag $(name) : $(type) :
                $(property-set) ] ] ;
            
            case <threading> :
                result += [ join-tag $(f:G=) : [ threading-tag $(name) : $(type)
                : $(property-set) ] ] ;
            
            case <runtime> :
                result += [ join-tag $(f:G=) : [ runtime-tag $(name) : $(type) :
                $(property-set) ] ] ;
            
            case <qt> :
            result += [ join-tag $(f:G=) : [ qt-tag $(name) : $(type) :
                $(property-set) ] ] ;

            case <address-model> :
            result += [ join-tag $(f:G=) : [ address-model-tag $(name) : $(type) :
                $(property-set) ] ] ;

            case <version:*> :
                local key = [ MATCH <version:(.*)> : $(f:G) ] ;
                local version = [ $(property-set).get <$(key)> ] ;
                version ?= $(key) ;
                version = [ MATCH "^([^.]+)[.]([^.]+)[.]?([^.]*)" : $(version) ] ;
                result += [ join-tag $(f:G=) : $(version[1])_$(version[2]) ] ;

            case <property:*> :
                local key = [ MATCH <property:(.*)> : $(f:G) ] ;
                local p0 = [ MATCH <($(key))> : [ $(property-set).raw ] ] ;
                if $(p0)
                {
                    local p = [ $(property-set).get <$(p0)> ] ;
                    if $(p)
                    {
                        result += [ join-tag $(f:G=) : $(p) ] ;
                    }
                }

            case * :
                result += $(f:G=) ;
        }
    }
    result = [ virtual-target.add-prefix-and-suffix $(result:J=) : $(type) :
      $(property-set) ] ;
    return $(result) ;
}


local rule join-tag ( joiner ? : tag ? )
{
    if ! $(joiner) { joiner = - ; }
    return $(joiner)$(tag) ;
}


local rule toolset-tag ( name : type ? : property-set )
{
    local tag = ;

    local properties = [ $(property-set).raw ] ;
    switch [ $(property-set).get <toolset> ]
    {
        case borland* : tag += bcb ;
        case clang* :
        {
            switch [ $(property-set).get <toolset-clang:platform> ]
            {
               case darwin : tag += clang-darwin ;
               case linux  : tag += clang ;
            }
        }
        case como* : tag += como ;
        case cw : tag += cw ;
        case darwin* : tag += xgcc ;
        case edg* : tag += edg ;
        case gcc* :
        {
            switch [ $(property-set).get <toolset-gcc:flavor> ]
            {
                case *mingw* : tag += mgw ;
                case * : tag += gcc ;
            }
        }
        case intel :
        if [ $(property-set).get <toolset-intel:platform> ] = win
        {
            tag += iw ;
        }
        else
        {
            tag += il ;
        }
        case kcc* : tag += kcc ;
        case kylix* : tag += bck ;
        #case metrowerks* : tag += cw ;
        #case mingw* : tag += mgw ;
        case mipspro* : tag += mp ;
        case msvc* : tag += vc ;
        case qcc* : tag += qcc ;
        case sun* : tag += sw ;
        case tru64cxx* : tag += tru ;
        case vacpp* : tag += xlc ;
    }
    local version = [ MATCH "<toolset.*version>([0123456789]+)[.]([0123456789]*)"
        : $(properties) ] ;
    # For historical reasons, vc6.0 and vc7.0 use different naming.
    if $(tag) = vc
    {
        if $(version[1]) = 6
        {
            # Cancel minor version.
            version = 6 ;
        }
        else if $(version[1]) = 7 && $(version[2]) = 0
        {
            version = 7 ;
        }
    }
    # On intel, version is not added, because it does not matter and it is the
    # version of vc used as backend that matters. Ideally, we should encode the
    # backend version but that would break compatibility with V1.
    if $(tag) = iw
    {
        version = ;
    }

    # On borland, version is not added for compatibility with V1.
    if $(tag) = bcb
    {
        version = ;
    }

    tag += $(version) ;

    return $(tag:J=) ;
}


local rule threading-tag ( name : type ? : property-set )
{
    local tag = ;
    local properties = [ $(property-set).raw ] ;
    if <threading>multi in $(properties) { tag = mt ; }

    return $(tag:J=) ;
}


local rule runtime-tag ( name : type ? : property-set )
{
    local tag = ;

    local properties = [ $(property-set).raw ] ;
    if <runtime-link>static in $(properties) { tag += s ; }

    # This is an ugly thing. In V1, there is code to automatically detect which
    # properties affect a target. So, if <runtime-debugging> does not affect gcc
    # toolset, the tag rules will not even see <runtime-debugging>. Similar
    # functionality in V2 is not implemented yet, so we just check for toolsets
    # known to care about runtime debugging.
    if ( <toolset>msvc in $(properties) ) ||
        ( <stdlib>stlport in $(properties) ) ||
        ( <toolset-intel:platform>win in $(properties) )
    {
        if <runtime-debugging>on in $(properties) { tag += g ; }
    }

    if <python-debugging>on in $(properties) { tag += y ; }
    if <variant>debug in $(properties) { tag += d ; }
    if <stdlib>stlport in $(properties) { tag += p ; }
    if <stdlib-stlport:iostream>hostios in $(properties) { tag += n ; }

    return $(tag:J=) ;
}

# Create a tag for the Qt library version
# "<qt>4.6.0" will result in tag "qt460"
local rule qt-tag ( name : type ? : property-set )
{
    local properties = [ $(property-set).get <qt> ] ;
    local version = [ MATCH "([0123456789]+)[.]?([0123456789]*)[.]?([0123456789]*)"
        : $(properties) ] ;
    local tag = "qt"$(version:J=) ;
    return $(tag) ;
}

# Create a tag for the address-model
# <address-model>64 will simply generate "64"
local rule address-model-tag ( name : type ? : property-set )
{
    local tag = ;
    local version = [ $(property-set).get <address-model> ] ;
    return $(version) ;
}

rule __test__ ( )
{
    import assert ;

    local save-os = [ modules.peek os : .name ] ;

    modules.poke os : .name : LINUX ;

    assert.result "PATH=\"foo:bar:baz\"\nexport PATH\n"
        : path-variable-setting-command PATH : foo bar baz ;

    assert.result "PATH=\"foo:bar:$PATH\"\nexport PATH\n"
        : prepend-path-variable-command PATH : foo bar ;

    modules.poke os : .name : NT ;

    assert.result "set PATH=foo;bar;baz\n"
        : path-variable-setting-command PATH : foo bar baz ;

    assert.result "set PATH=foo;bar;%PATH%\n"
        : prepend-path-variable-command PATH : foo bar ;

    modules.poke os : .name : $(save-os) ;
}