Imported Upstream version 1.72.0
[platform/upstream/boost.git] / tools / build / src / tools / common.jam
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
11 import os ;
12 import modules ;
13 import utility ;
14 import print ;
15 import type ;
16 import feature ;
17 import errors ;
18 import path ;
19 import sequence ;
20 import toolset ;
21 import virtual-target ;
22 import numbers ;
23
24 if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ]
25 {
26     .debug-configuration = true ;
27 }
28 if [ 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
55 class 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 #
156 rule 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 #
270 rule 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 #
303 rule 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 #
327 rule 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 #
348 rule 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 #
388 local 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 #
416 local 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 #
436 rule 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 #
471 rule 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
486 if [ 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 }
499 else 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 }
508 else
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
517 NULL_OUT = ">$(NULL_DEVICE)" ;
518
519 rule null-device ( )
520 {
521     return $(NULL_DEVICE) ;
522 }
523
524
525 rule rm-command ( )
526 {
527     return $(RM) ;
528 }
529
530
531 rule copy-command ( )
532 {
533     return $(CP) ;
534 }
535
536
537 if "\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 }
545 else
546 {
547     nl = "\n" ;
548     q = "\"" ;
549 }
550
551
552 rule 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 #
564 rule 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 #
589 rule 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 #
599 rule 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 #
610 rule 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 #
648 rule 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
665 rule 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
721 if [ os.name ] = NT
722 {
723     actions quietly mkdir
724     {
725         if not exist "$(<)\\" mkdir "$(<)"
726     }
727 }
728 else
729 {
730     actions quietly mkdir
731     {
732         mkdir -p "$(<)"
733     }
734 }
735
736
737 actions piecemeal together existing Clean
738 {
739     $(RM) "$(>)"
740 }
741
742
743 rule copy
744 {
745 }
746
747
748 actions copy
749 {
750     $(CP) "$(>)" $(WINDOWS-CP-HACK) "$(<)"
751 }
752
753
754 rule RmTemps
755 {
756 }
757
758
759 actions quietly updated piecemeal together RmTemps
760 {
761     $(RM) "$(>)" $(IGNORE)
762 }
763
764
765 actions hard-link
766 {
767     $(RM) "$(<)" 2$(NULL_OUT) $(NULL_OUT)
768     $(LN) "$(>)" "$(<)" $(NULL_OUT)
769 }
770
771
772 if [ 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 #
842 rule 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
904 local rule join-tag ( joiner ? : tag ? )
905 {
906     if ! $(joiner) { joiner = - ; }
907     return $(joiner)$(tag) ;
908 }
909
910
911 local 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 && $(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
1008 local rule threading-tag ( name : type ? : property-set )
1009 {
1010     if <threading>multi in [ $(property-set).raw ]
1011     {
1012         return mt ;
1013     }
1014 }
1015
1016
1017 local 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     {
1033         if <runtime-debugging>on in $(properties) { tag += g ; }
1034     }
1035
1036     if <python-debugging>on in $(properties) { tag += y ; }
1037     if <variant>debug in $(properties) { tag += d ; }
1038     if <stdlib>stlport in $(properties) { tag += p ; }
1039     if <stdlib-stlport:iostream>hostios in $(properties) { tag += n ; }
1040
1041     return $(tag:J=) ;
1042 }
1043
1044
1045 # Create a tag for the Qt library version
1046 # "<qt>4.6.0" will result in tag "qt460"
1047 local rule qt-tag ( name : type ? : property-set )
1048 {
1049     local v = [ MATCH "([0123456789]+)[.]?([0123456789]*)[.]?([0123456789]*)" :
1050         [ $(property-set).get <qt> ] ] ;
1051     return qt$(v:J=) ;
1052 }
1053
1054
1055 # Create a tag for the address-model
1056 # <address-model>64 will simply generate "64"
1057 local rule address-model-tag ( name : type ? : property-set )
1058 {
1059     return [ $(property-set).get <address-model> ] ;
1060 }
1061
1062 # Create a tag for the architecture and model
1063 # <architecture>x86 <address-model>32 would generate "x32"
1064 # This relies on the fact that all architectures start with
1065 # unique letters.
1066 local rule arch-and-model-tag ( name : type ? : property-set )
1067 {
1068     local architecture = [ $(property-set).get <architecture> ] ;
1069     local address-model = [ $(property-set).get <address-model> ] ;
1070
1071     local arch = [ MATCH ^(.) : $(architecture) ] ;
1072
1073     return $(arch)$(address-model) ;
1074 }
1075
1076 rule __test__ ( )
1077 {
1078     import assert ;
1079
1080     local save-os = [ modules.peek os : .name ] ;
1081
1082     modules.poke os : .name : LINUX ;
1083     assert.result "PATH=\"foo:bar:baz\"\nexport PATH\n"
1084         : path-variable-setting-command PATH : foo bar baz ;
1085     assert.result "PATH=\"foo:bar:$PATH\"\nexport PATH\n"
1086         : prepend-path-variable-command PATH : foo bar ;
1087
1088     modules.poke os : .name : NT ;
1089     assert.result "set PATH=foo;bar;baz\n"
1090         : path-variable-setting-command PATH : foo bar baz ;
1091     assert.result "set PATH=foo;bar;%PATH%\n"
1092         : prepend-path-variable-command PATH : foo bar ;
1093
1094     modules.poke os : .name : $(save-os) ;
1095 }