Blob Blame History Raw
# Copyright (c) 2003 David Abrahams
# Copyright (c) 2005 Vladimir Prus
# Copyright (c) 2005 Alexey Pakhunov
# Copyright (c) 2006 Bojan Resnik
# Copyright (c) 2006 Ilya Sokolov
# Copyright (c) 2007-2017 Rene Rivera
# Copyright (c) 2008 Jurko Gospodnetic
# Copyright (c) 2014 Microsoft Corporation
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

################################################################################
#
# MSVC Boost Build toolset module.
# --------------------------------
#
# All toolset versions need to have their location either auto-detected or
# explicitly specified except for the special 'default' version that expects the
# environment to find the needed tools or report an error.
#
################################################################################

import "class" : new ;
import common ;
import feature ;
import generators ;
import mc ;
import midl ;
import os ;
import path ;
import pch ;
import property ;
import rc ;
import set ;
import toolset ;
import type ;


type.register MANIFEST : manifest ;
feature.feature embed-manifest : on off : incidental propagated ;
feature.feature embed-manifest-file : : free dependency ;

type.register PDB : pdb ;


################################################################################
#
# Public rules.
#
################################################################################

# Initialize a specific toolset version configuration. As the result, path to
# compiler and, possible, program names are set up, and will be used when that
# version of compiler is requested. For example, you might have:
#
#    using msvc : 6.5 : cl.exe ;
#    using msvc : 7.0 : Y:/foo/bar/cl.exe ;
#
# The version parameter may be omitted:
#
#    using msvc : : Z:/foo/bar/cl.exe ;
#
# The following keywords have special meanings when specified as versions:
#   - all     - all detected but not yet used versions will be marked as used
#               with their default options.
#   - default - this is an equivalent to an empty version.
#
# Depending on a supplied version, detected configurations and presence 'cl.exe'
# in the path different results may be achieved. The following table describes
# the possible scenarios:
#
#                                      Nothing            "x.y"
# Passed   Nothing       "x.y"         detected,          detected,
# version  detected      detected      cl.exe in path     cl.exe in path
#
# default  Error         Use "x.y"     Create "default"   Use "x.y"
# all      None          Use all       None               Use all
# x.y      -             Use "x.y"     -                  Use "x.y"
# a.b      Error         Error         Create "a.b"       Create "a.b"
#
# "x.y" - refers to a detected version;
# "a.b" - refers to an undetected version.
#
# FIXME: Currently the command parameter and the <compiler> property parameter
# seem to overlap in duties. Remove this duplication. This seems to be related
# to why someone started preparing to replace init with configure rules.
#
rule init (
    # The msvc version being configured. When omitted the tools invoked when no
    # explicit version is given will be configured.
    version ?

    # The command used to invoke the compiler. If not specified:
    #   - if version is given, default location for that version will be
    #     searched
    #
    #   - if version is not given, default locations for MSVC 9.0, 8.0, 7.1, 7.0
    #     and 6.* will be searched
    #
    #   - if compiler is not found in the default locations, PATH will be
    #     searched.
    : command *

    # Options may include:
    #
    #     All options shared by multiple toolset types as handled by the
    #   common.handle-options() rule, e.g. <cflags>, <compileflags>, <cxxflags>,
    #   <fflags> & <linkflags>.
    #
    #   <assembler>
    #   <compiler>
    #   <idl-compiler>
    #   <linker>
    #   <mc-compiler>
    #   <resource-compiler>
    #       Exact tool names to be used by this msvc toolset configuration.
    #
    #   <compiler-filter>
    #       Command through which to pipe the output of running the compiler.
    #     For example to pass the output to STLfilt.
    #
    #   <setup>
    #       Global setup command to invoke before running any of the msvc tools.
    #     It will be passed additional option parameters depending on the actual
    #     target platform.
    #
    #   <setup-amd64>
    #   <setup-i386>
    #   <setup-ia64>
    #   <setup-arm>
    #   <setup-phone-i386>
    #   <setup-phone-arm>
    #       Platform specific setup command to invoke before running any of the
    #     msvc tools used when builing a target for a specific platform, e.g.
    #     when building a 32 or 64 bit executable.
    #
    #   <rewrite-setup-scripts>
    #       Whether to rewrite setup scripts. New scripts will be output in
    #     TEMP directory and will be used instead of originals in build actions.
    #     Possible values:
    #       * on - rewrite scripts, if they do not already exist (default)
    #       * always - always rewrite scripts, even if they already exist
    #       * off - use original setup scripts
    : options *
)
{
    if $(command)
    {
        options += <command>$(command) ;
    }
    configure $(version) : $(options) ;
}


# 'configure' is a newer version of 'init'. The parameter 'command' is passed as
# a part of the 'options' list. See the 'init' rule comment for more detailed
# information.
#
rule configure ( version ? : options * )
{
    switch $(version)
    {
        case "all" :
            if $(options)
            {
                import errors ;
                errors.error "MSVC toolset configuration: options should be"
                    "empty when '$(version)' is specified." ;
            }

            # Configure (i.e. mark as used) all registered versions.
            local all-versions = [ $(.versions).all ] ;
            if ! $(all-versions)
            {
                if $(.debug-configuration)
                {
                    ECHO "notice: [msvc-cfg] Asked to configure all registered"
                        "msvc toolset versions when there are none currently"
                        "registered." ;
                }
            }
            else
            {
                for local v in $(all-versions)
                {
                    # Note that there is no need to skip already configured
                    # versions here as this will request configure-really rule
                    # to configure the version using default options which will
                    # in turn cause it to simply do nothing in case the version
                    # has already been configured.
                    configure-really $(v) ;
                }
            }

        case "default" :
            configure-really : $(options) ;

        case * :
            configure-really $(version) : $(options) ;
    }
}


# Sets up flag definitions dependent on the compiler version used.
# - 'version' is the version of compiler in N.M format.
# - 'conditions' is the property set to be used as flag conditions.
# - 'toolset' is the toolset for which flag settings are to be defined.
#   This makes the rule reusable for other msvc-option-compatible compilers.
#
rule configure-version-specific ( toolset : version : conditions )
{
    toolset.push-checking-for-flags-module unchecked ;
    # Starting with versions 7.0, the msvc compiler have the /Zc:forScope and
    # /Zc:wchar_t options that improve C++ standard conformance, but those
    # options are off by default. If we are sure that the msvc version is at
    # 7.*, add those options explicitly. We can be sure either if user specified
    # version 7.* explicitly or if we auto-detected the version ourselves.
    if ! [ MATCH ^(6\\.) : $(version) ]
    {
        toolset.flags $(toolset).compile CFLAGS $(conditions) : /Zc:forScope /Zc:wchar_t ;
        toolset.flags $(toolset).compile.c++ C++FLAGS $(conditions) : /wd4675 ;

        # Explicitly disable the 'function is deprecated' warning. Some msvc
        # versions have a bug, causing them to emit the deprecation warning even
        # with /W0.
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<warnings>off : /wd4996 ;

        if [ MATCH ^([78]\\.) : $(version) ]
        {
            # 64-bit compatibility warning deprecated since 9.0, see
            # http://msdn.microsoft.com/en-us/library/yt4xw8fh.aspx
            toolset.flags $(toolset).compile CFLAGS $(conditions)/<warnings>all : /Wp64 ;
        }
    }

    #
    # Processor-specific optimization.
    #

    if [ MATCH ^([67]) : $(version) ]
    {
        # 8.0 deprecates some of the options.
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<optimization>speed $(conditions)/<optimization>space : /Ogiy /Gs ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<optimization>speed : /Ot ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<optimization>space : /Os ;

        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-i386)/<instruction-set> : /GB ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-i386)/<instruction-set>i486 : /G4 ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-i386)/<instruction-set>$(.cpu-type-g5) : /G5 ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-i386)/<instruction-set>$(.cpu-type-g6) : /G6 ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-i386)/<instruction-set>$(.cpu-type-g7) : /G7 ;

        # Improve floating-point accuracy. Otherwise, some of C++ Boost's "math"
        # tests will fail.
        toolset.flags $(toolset).compile CFLAGS $(conditions) : /Op ;

        # 7.1 and below have single-threaded static RTL.
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<runtime-debugging>off/<runtime-link>static/<threading>single : /ML ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<runtime-debugging>on/<runtime-link>static/<threading>single : /MLd ;
    }
    else
    {
        # 8.0 and above adds some more options.
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-amd64)/<instruction-set> : /favor:blend ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-amd64)/<instruction-set>$(.cpu-type-em64t) : /favor:EM64T ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/$(.cpu-arch-amd64)/<instruction-set>$(.cpu-type-amd64) : /favor:AMD64 ;

        # 8.0 and above only has multi-threaded static RTL.
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<runtime-debugging>off/<runtime-link>static/<threading>single : /MT ;
        toolset.flags $(toolset).compile CFLAGS $(conditions)/<runtime-debugging>on/<runtime-link>static/<threading>single : /MTd ;

        # Specify target machine type so the linker will not need to guess.
        toolset.flags $(toolset).link LINKFLAGS $(conditions)/$(.cpu-arch-amd64) : /MACHINE:X64 ;
        toolset.flags $(toolset).link LINKFLAGS $(conditions)/$(.cpu-arch-i386)  : /MACHINE:X86 ;
        toolset.flags $(toolset).link LINKFLAGS $(conditions)/$(.cpu-arch-ia64)  : /MACHINE:IA64 ;
        toolset.flags $(toolset).link LINKFLAGS $(conditions)/$(.cpu-arch-arm)   : /MACHINE:ARM ;

        # Make sure that manifest will be generated even if there is no
        # dependencies to put there.
        toolset.flags $(toolset).link LINKFLAGS $(conditions) : /MANIFEST ;
    }

    toolset.pop-checking-for-flags-module ;
}

# Feature for handling targeting different Windows API sets.
feature.feature windows-api : desktop store phone : propagated composite link-incompatible ;
feature.compose <windows-api>store : <define>WINAPI_FAMILY=WINAPI_FAMILY_APP <define>_WIN32_WINNT=0x0602 
    <linkflags>/APPCONTAINER ;
feature.compose <windows-api>phone : <define>WINAPI_FAMILY=WINAPI_FAMILY_PHONE_APP <define>_WIN32_WINNT=0x0602 
    <linkflags>/APPCONTAINER <linkflags>/NODEFAULTLIB:ole32.lib <linkflags>/NODEFAULTLIB:kernel32.lib <linkflags>WindowsPhoneCore.lib ;
feature.set-default windows-api : desktop ;


# Registers this toolset including all of its flags, features & generators. Does
# nothing on repeated calls.
#
rule register-toolset ( )
{
    if ! msvc in [ feature.values toolset ]
    {
        register-toolset-really ;
    }
}

rule resolve-possible-msvc-version-alias ( version )
{
    if $(.version-alias-$(version))
    {
        version = $(.version-alias-$(version)) ;
    }
    return $(version) ;
}


# Declare action for creating static libraries. If library exists, remove it
# before adding files. See
# http://article.gmane.org/gmane.comp.lib.boost.build/4241 for rationale.
if [ os.name ] in NT
{
    # The 'DEL' command would issue a message to stdout if the file does not
    # exist, so need a check.
    actions archive
    {
        if exist "$(<[1])" DEL "$(<[1])"
        $(.LD) $(AROPTIONS) /out:"$(<[1])" @"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib" $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")"
    }
}
else
{
    actions archive
    {
        $(.RM) "$(<[1])"
        $(.LD) $(AROPTIONS) /out:"$(<[1])" @"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib" $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")"
    }
}


# For the assembler the following options are turned on by default:
#
#   -Zp4   align structures to 4 bytes
#   -Cp    preserve case of user identifiers
#   -Cx    preserve case in publics, externs
#
actions compile.asm
{
    $(.ASM) -c -Zp4 -Cp -Cx -D$(DEFINES) $(ASMFLAGS) $(USER_ASMFLAGS) -Fo "$(<:W)" "$(>:W)"
}


rule compile.c ( targets + : sources * : properties * )
{
    C++FLAGS on $(targets[1]) = ;
    get-rspline $(targets) : -TC ;
    compile-c-c++ $(<) : $(>) [ on $(<) return $(PCH_FILE) ] [ on $(<) return $(PCH_HEADER) ] ;
}


rule compile.c.preprocess ( targets + : sources * : properties * )
{
    C++FLAGS on $(targets[1]) = ;
    get-rspline $(targets) : -TC ;
    preprocess-c-c++ $(<) : $(>) [ on $(<) return $(PCH_FILE) ] [ on $(<) return $(PCH_HEADER) ] ;
}


rule compile.c.pch ( targets + : sources * : properties * )
{
    C++FLAGS on $(targets[1]) = ;
    get-rspline $(targets[1]) : -TC ;
    get-rspline $(targets[2]) : -TC ;
    local pch-source = [ on $(<) return $(PCH_SOURCE) ] ;
    if $(pch-source)
    {
        DEPENDS $(<) : $(pch-source) ;
        compile-c-c++-pch-s $(targets) : $(sources) $(pch-source) ;
    }
    else
    {
        compile-c-c++-pch $(targets) : $(sources) ;
    }
}

toolset.flags msvc YLOPTION : "-Yl" ;

# Action for running the C/C++ compiler without using precompiled headers.
#
# WARNING: Synchronize any changes this in action with intel-win
#
# Notes regarding PDB generation, for when we use
# <debug-symbols>on/<debug-store>database:
#
# 1. PDB_CFLAG is only set for <debug-symbols>on/<debug-store>database, ensuring
#    that the /Fd flag is dropped if PDB_CFLAG is empty.
#
# 2. When compiling executables's source files, PDB_NAME is set on a per-source
#    file basis by rule compile-c-c++. The linker will pull these into the
#    executable's PDB.
#
# 3. When compiling library's source files, PDB_NAME is updated to <libname>.pdb
#    for each source file by rule archive, as in this case compiler must be used
#    to create a single PDB for our library.
#
actions compile-c-c++ bind PDB_NAME
{
    $(.CC) @"@($(<[1]:W).rsp:E="$(>[1]:W)" -Fo"$(<[1]:W)" $(PDB_CFLAG)"$(PDB_NAME)" -Yu"$(>[3]:D=)" -Fp"$(>[2]:W)" $(CC_RSPLINE))" $(.CC.FILTER)
}

actions preprocess-c-c++ bind PDB_NAME
{
    $(.CC) @"@($(<[1]:W).rsp:E="$(>[1]:W)" -E $(PDB_CFLAG)"$(PDB_NAME)" -Yu"$(>[3]:D=)" -Fp"$(>[2]:W)" $(CC_RSPLINE))" >"$(<[1]:W)"
}

rule compile-c-c++ ( targets + : sources * )
{
    DEPENDS $(<[1]) : [ on $(<[1]) return $(PCH_HEADER) ] ;
    DEPENDS $(<[1]) : [ on $(<[1]) return $(PCH_FILE) ] ;
    PDB_NAME on $(<) = $(<[1]:S=.pdb) ;
    LOCATE on $(<[1]:S=.pdb) = [ on $(<[1]) return $(LOCATE) ] ;
}

rule preprocess-c-c++ ( targets + : sources * )
{
    DEPENDS $(<[1]) : [ on $(<[1]) return $(PCH_HEADER) ] ;
    DEPENDS $(<[1]) : [ on $(<[1]) return $(PCH_FILE) ] ;
    PDB_NAME on $(<) = $(<:S=.pdb) ;
    LOCATE on $(<[1]:S=.pdb) = [ on $(<[1]) return $(LOCATE) ] ;
}

# Action for running the C/C++ compiler using precompiled headers. In addition
# to whatever else it needs to compile, this action also adds a temporary source
# .cpp file used to compile the precompiled headers themselves.
#
# The global .escaped-double-quote variable is used to avoid messing up Emacs
# syntax highlighting in the messy N-quoted code below.
actions compile-c-c++-pch
{
    $(.CC) @"@($(<[1]:W).rsp:E="$(>[2]:W)" -Fo"$(<[2]:W)" -Yc"$(>[1]:D=)" $(YLOPTION)"__bjam_pch_symbol_$(>[1]:D=)" -Fp"$(<[1]:W)" $(CC_RSPLINE))" "@($(<[1]:W).cpp:E=#include $(.escaped-double-quote)$(>[1]:D=)$(.escaped-double-quote)$(.nl))" $(.CC.FILTER)
}


# Action for running the C/C++ compiler using precompiled headers. An already
# built source file for compiling the precompiled headers is expected to be
# given as one of the source parameters.
actions compile-c-c++-pch-s
{
    $(.CC) @"@($(<[1]:W).rsp:E="$(>[2]:W)" -Fo"$(<[2]:W)" -Yc"$(>[1]:D=)" $(YLOPTION)"__bjam_pch_symbol_$(>[1]:D=)" -Fp"$(<[1]:W)" $(CC_RSPLINE))" $(.CC.FILTER)
}


rule compile.c++ ( targets + : sources * : properties * )
{
    get-rspline $(targets) : -TP ;
    compile-c-c++ $(<) : $(>) [ on $(<) return $(PCH_FILE) ] [ on $(<) return $(PCH_HEADER) ] ;
}

rule compile.c++.preprocess ( targets + : sources * : properties * )
{
    get-rspline $(targets) : -TP ;
    preprocess-c-c++ $(<) : $(>) [ on $(<) return $(PCH_FILE) ] [ on $(<) return $(PCH_HEADER) ] ;
}


rule compile.c++.pch ( targets + : sources * : properties * )
{
    get-rspline $(targets[1]) : -TP ;
    get-rspline $(targets[2]) : -TP ;
    local pch-source = [ on $(<) return $(PCH_SOURCE) ] ;
    if $(pch-source)
    {
        DEPENDS $(<) : $(pch-source) ;
        compile-c-c++-pch-s $(targets) : $(sources) $(pch-source) ;
    }
    else
    {
        compile-c-c++-pch $(targets) : $(sources) ;
    }
}


# See midl.jam for details.
#
actions compile.idl
{
    $(.IDL) /nologo @"@($(<[1]:W).rsp:E=$(.nl)"$(>:W)" $(.nl)-D$(DEFINES) $(.nl)"-I$(INCLUDES:W)" $(.nl)-U$(UNDEFS) $(.nl)$(MIDLFLAGS) $(.nl)/tlb "$(<[1]:W)" $(.nl)/h "$(<[2]:W)" $(.nl)/iid "$(<[3]:W)" $(.nl)/proxy "$(<[4]:W)" $(.nl)/dlldata "$(<[5]:W)")"
    $(.TOUCH_FILE) "$(<[4]:W)"
    $(.TOUCH_FILE) "$(<[5]:W)"
}


actions compile.mc
{
    $(.MC) $(MCFLAGS) -h "$(<[1]:DW)" -r "$(<[2]:DW)" "$(>:W)"
}


actions compile.rc
{
    $(.RC) -l 0x409 -U$(UNDEFS) -D$(DEFINES) -I"$(INCLUDES:W)" -fo "$(<:W)" "$(>:W)"
}


rule link ( targets + : sources * : properties * )
{
    if <embed-manifest>on in $(properties)
    {
        if [ feature.get-values <embed-manifest-file> : $(properties) ]
        {
            DEPENDS $(<) : [ on $(<) return $(EMBED_MANIFEST_FILE) ] ;
            msvc.manifest.user $(targets) $(EMBED_MANIFEST_FILE) : $(sources) : $(properties) ;
        }
        else 
        {
            msvc.manifest $(targets) : $(sources) : $(properties) ;
        }
    }
}

rule link.dll ( targets + : sources * : properties * )
{
    DEPENDS $(<) : [ on $(<) return $(DEF_FILE) ] ;
    if <embed-manifest>on in $(properties)
    {
        if [ feature.get-values <embed-manifest-file> : $(properties) ]
        {
            DEPENDS $(<) : [ on $(<) return $(EMBED_MANIFEST_FILE) ] ;
            msvc.manifest.dll.user $(targets) $(EMBED_MANIFEST_FILE) : $(sources) : $(properties) ;
        }
        else 
        {
            msvc.manifest.dll $(targets) : $(sources) : $(properties) ;
        }
    }
}

# Incremental linking a DLL causes no end of problems: if the actual exports do
# not change, the import .lib file is never updated. Therefore, the .lib is
# always out-of-date and gets rebuilt every time. I am not sure that incremental
# linking is such a great idea in general, but in this case I am sure we do not
# want it.

# Windows manifest is a new way to specify dependencies on managed DotNet
# assemblies and Windows native DLLs. The manifests are embedded as resources
# and are useful in any PE target (both DLL and EXE).

if [ os.name ] in NT
{
    actions link bind DEF_FILE LIBRARIES_MENTIONED_BY_FILE
    {
        $(.LD) $(LINKFLAGS) /out:"$(<[1]:W)" /LIBPATH:"$(LINKPATH:W)" $(OPTIONS) @"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)$(LIBRARIES) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib" $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")"
        if %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL%
    }

    actions manifest
    {
        if exist "$(<[1]).manifest" (
            $(.MT) -manifest "$(<[1]).manifest" "-outputresource:$(<[1]);1"
        )
    }

    actions manifest.user bind EMBED_MANIFEST_FILE
    {
        $(.MT) -manifest "$(EMBED_MANIFEST_FILE)" "-outputresource:$(<[1]);1"
    }

    actions link.dll bind DEF_FILE LIBRARIES_MENTIONED_BY_FILE
    {
        $(.LD) /DLL $(LINKFLAGS) /out:"$(<[1]:W)" /IMPLIB:"$(<[2]:W)" /LIBPATH:"$(LINKPATH:W)" /def:"$(DEF_FILE)" $(OPTIONS) @"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)$(LIBRARIES) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib" $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")"
        if %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL%
    }

    actions manifest.dll
    {
        if exist "$(<[1]).manifest" (
            $(.MT) -manifest "$(<[1]).manifest" "-outputresource:$(<[1]);2"
        )
    }
    actions manifest.dll.user bind EMBED_MANIFEST_FILE
    {
        $(.MT) -manifest "$(EMBED_MANIFEST_FILE)" "-outputresource:$(<[1]);2"
    }
}
else
{
    actions link bind DEF_FILE LIBRARIES_MENTIONED_BY_FILE
    {
        $(.LD) $(LINKFLAGS) /out:"$(<[1]:W)" /LIBPATH:"$(LINKPATH:W)" $(OPTIONS) @"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)$(LIBRARIES) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib" $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")"
    }

    actions manifest
    {
        if test -e "$(<[1]).manifest"; then
            $(.MT) -manifest "$(<[1]:W).manifest" "-outputresource:$(<[1]:W);1"
        fi
    }

    actions link.dll bind DEF_FILE LIBRARIES_MENTIONED_BY_FILE
    {
        $(.LD) /DLL $(LINKFLAGS) /out:"$(<[1]:W)" /IMPLIB:"$(<[2]:W)" /LIBPATH:"$(LINKPATH:W)" /def:"$(DEF_FILE)" $(OPTIONS) @"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)$(LIBRARIES) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib" $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")"
    }

    actions manifest.dll
    {
        if test -e "$(<[1]).manifest"; then
            $(.MT) -manifest "$(<[1]:W).manifest" "-outputresource:$(<[1]:W);2"
        fi
    }

    actions manifest.dll.user bind EMBED_MANIFEST_FILE
    {
        $(.MT) -manifest "$(EMBED_MANIFEST_FILE)" "-outputresource:$(<[1]);2"
    }
}

# This rule sets up the pdb file that will be used when generating static
# libraries and the debug-store option is database, so that the compiler puts
# all the debug info into a single .pdb file named after the library.
#
# Poking at source targets this way is probably not clean, but it is the
# easiest approach.
#
rule archive ( targets + : sources * : properties * )
{
    PDB_NAME on $(>) = $(<[1]:S=.pdb) ;
    LOCATE on $(<[1]:S=.pdb) = [ on $(<[1]) return $(LOCATE) ] ;
}


################################################################################
#
# Classes.
#
################################################################################

class msvc-pch-generator : pch-generator
{
    import property-set ;

    rule run-pch ( project name ? : property-set : sources * )
    {
        # Searching for the header and source file in the sources.
        local pch-header ;
        local pch-source ;
        for local s in $(sources)
        {
            if [ type.is-derived [ $(s).type ] H ]
            {
                pch-header = $(s) ;
            }
            else if
                [ type.is-derived [ $(s).type ] CPP ] ||
                [ type.is-derived [ $(s).type ] C ]
            {
                pch-source = $(s) ;
            }
        }

        if ! $(pch-header)
        {
            import errors : user-error : errors.user-error ;
            errors.user-error "can not build pch without pch-header" ;
        }

        # If we do not have the PCH source - that is fine. We will just create a
        # temporary .cpp file in the action.

        local generated = [ generator.run $(project) $(name)
            : [ property-set.create
                # Passing of <pch-source> is a dirty trick, needed because
                # non-composing generators with multiple inputs are subtly
                # broken. For more detailed information see:
                # https://zigzag.cs.msu.su:7813/boost.build/ticket/111
                <pch-source>$(pch-source)
                [ $(property-set).raw ] ]
            : $(pch-header) ] ;

        local pch-file ;
        for local g in $(generated)
        {
            if [ type.is-derived [ $(g).type ] PCH ]
            {
                pch-file = $(g) ;
            }
        }

        return [ property-set.create <pch-header>$(pch-header)
            <pch-file>$(pch-file) ] $(generated) ;
    }
}


################################################################################
#
# Local rules.
#
################################################################################

# Detects versions listed as '.known-versions' by checking registry information,
# environment variables & default paths. Supports both native Windows and
# Cygwin.
#
local rule auto-detect-toolset-versions ( )
{
    if [ os.name ] in NT CYGWIN
    {
        # Get installation paths from the registry.
        for local i in $(.known-versions)
        {
            if $(.version-$(i)-reg)
            {
                local vc-path ;
                for local x in "" "Wow6432Node\\"
                {
                    vc-path += [ W32_GETREG
                        "HKEY_LOCAL_MACHINE\\SOFTWARE\\"$(x)"\\Microsoft\\"$(.version-$(i)-reg)
                        : "ProductDir" ] ;
                }

                if $(vc-path)
                {
                    vc-path = [ path.join [ path.make-NT $(vc-path[1]) ] "bin" ] ;
                    register-configuration $(i) : [ path.native $(vc-path[1]) ] ;
                }
            }
        }
    }

    # Check environment and default installation paths.
    for local i in $(.known-versions)
    {
        if ! $(i) in [ $(.versions).all ]
        {
            register-configuration $(i) : [ default-path $(i) ] ;
        }
    }
}

# Helper rule to generate a faster alternative to MSVC setup scripts.
# We used to call MSVC setup scripts directly in every action, however in
# newer MSVC versions (10.0+) they make long-lasting registry queries 
# which have a significant impact on build time.
rule maybe-rewrite-setup ( toolset : setup-script : setup-options : version : rewrite-setup ? )
{
    local result = $(setup-script)" "$(setup-options) ;
    # At the moment we only know how to rewrite scripts with cmd shell.
    if ( [ os.name ] in NT ) && ( $(rewrite-setup) != off )
    {
        setup-script-id = b2_$(toolset)_$(version)_$(setup-script:B) ;
        if $(setup-options)-is-not-empty
        {
            setup-script-id = $(setup-script-id)_$(setup-options) ;
        }

        if $(.$(setup-script-id))
        {
            errors.error rewriting setup script for the second time ;
        }

        local tmpdir = [ os.environ TEMP ] ;
        local replacement = [ path.native $(tmpdir)/$(setup-script-id).cmd ] ;
        if ( $(rewrite-setup) = always ) || ( ! [ path.exists $(replacement) ] )
        {
            local original-vars = [ SPLIT_BY_CHARACTERS [ SHELL set ] : "\n" ] ;
            local new-vars = [ SPLIT_BY_CHARACTERS [ SHELL "$(setup-script) $(setup-options)>nul && set" ] : "\n" ] ;
            local diff-vars = [ set.difference $(new-vars) : $(original-vars) ] ;
            if $(diff-vars)
            {
                local target = <new-setup-script>$(replacement) ;
                FILE_CONTENTS on $(target) = "SET "$(diff-vars) ;
                ALWAYS $(target) ;
                msvc.write-setup-script $(target) ;
                UPDATE_NOW $(target) : : ignore-minus-n ;
                .$(setup-script-id) = $(replacement) ;
                result = "\""$(replacement)"\"" ;
            }
        }
        else
        {
            result = "\""$(replacement)"\"" ;
        }
    }
    return $(result) ;
}

actions write-setup-script
{
    @($(STDOUT):E=$(FILE_CONTENTS:J=$(.nl))) > "$(<)"
}


# Local helper rule to create the vcvars setup command for given architecture
# and options.
#
local rule generate-setup-cmd ( version : command : parent : options * : cpu : global-setup ? : default-global-setup-options : default-setup )
{
    local setup-prefix = "call " ;
    local setup-suffix = " >nul"$(.nl) ;
    if ! [ os.name ] in NT
    {
        setup-prefix = "cmd.exe /S /C call " ;
        setup-suffix = " \">nul\" \"&&\" " ;
    }

    local setup-options ;
    local setup = [ feature.get-values <setup-$(cpu)> : $(options) ] ;
    
    if ! $(setup)-is-defined
    {
        if $(global-setup)-is-defined
        {
            setup = $(global-setup) ;
                        
            # If needed we can easily add using configuration flags
            # here for overriding which options get passed to the
            # global setup command for which target platform:
            # setup-options = [ feature.get-values <setup-options-$(c)> : $(options) ] ;
            setup-options ?= $(default-global-setup-options) ;
        }
        else
        {
            if [ MATCH "(14.1)" : $(version) ]
            {
                if $(.debug-configuration)
                {
                    ECHO 'notice: [generate-setup-cmd] $(version) is 14.1' ;
                }
                parent = [ path.native [ path.join  $(parent) "..\\..\\..\\..\\..\\Auxiliary\\Build" ] ] ;
            }
            setup = [ locate-default-setup $(command) : $(parent) : $(default-setup) ] ;
            setup ?= [ path.join  $(parent) "vcvarsall.bat" ] ;
        }
    }
    
    # Cygwin to Windows path translation.
    setup = "\""$(setup:W)"\"" ;

    # Append setup options to the setup name and add the final setup
    # prefix & suffix.
    setup-options ?= "" ;
    local rewrite = [ feature.get-values <rewrite-setup-scripts> : $(options) ] ;
    setup = [ maybe-rewrite-setup msvc : $(setup:J=" ") : $(setup-options:J=" ") : $(version) : $(rewrite) ] ;
    setup = $(setup-prefix)$(setup)$(setup-suffix) ;
    
    return $(setup) ;
}


# Worker rule for toolset version configuration. Takes an explicit version id or
# nothing in case it should configure the default toolset version (the first
# registered one or a new 'default' one in case no toolset versions have been
# registered yet).
#
local rule configure-really ( version ? : options * )
{
    local v = $(version) ;

    # Decide what the 'default' version is.
    if ! $(v)
    {
        # Take the best registered (i.e. auto-detected) version.
        version = [ $(.versions).all ] ;
        for local known in $(.known-versions)
        {
            if $(known) in $(version)
            {
                version = $(known) ;
                break ;
            }
        }
        version = $(version[1]) ;
        v = $(version) ;

        # Note: 'version' can still be empty at this point if no versions have
        # been auto-detected.
        version ?= "default" ;
    }

    # Version alias -> real version number.
    version = [ resolve-possible-msvc-version-alias $(version) ] ;

    # Check whether the selected configuration is already in use.
    if $(version) in [ $(.versions).used ]
    {
        # Allow multiple 'toolset.using' calls for the same configuration if the
        # identical sets of options are used.
        if $(options) && ( $(options) != [ $(.versions).get $(version) : options ] )
        {
            import errors ;
            errors.error "MSVC toolset configuration: Toolset version"
                "'$(version)' already configured." ;
        }
    }
    else
    {
        # Register a new configuration.
        $(.versions).register $(version) ;

        # Add user-supplied to auto-detected options.
        options = [ $(.versions).get $(version) : options ] $(options) ;

        # Mark the configuration as 'used'.
        $(.versions).use $(version) ;

        # Generate conditions and save them.
        local conditions = [ common.check-init-parameters msvc : version $(v) ]
            ;

        $(.versions).set $(version) : conditions : $(conditions) ;

        local command = [ feature.get-values <command> : $(options) ] ;

        # For 14.1 we need the exact version as MS is planning rolling updates
        # that will cause our `setup-cmd` to become invalid
        exact-version = [ MATCH "(14\.10\.[0-9\.]+)" : $(command) ] ;

        # If version is specified, we try to search first in default paths, and
        # only then in PATH.
        command = [ common.get-invocation-command msvc : cl.exe : $(command) :
            [ default-paths $(version) ] : $(version) ] ;

        if ( ! $(version) || $(version) = "default" ) && ! $(command:D)
        {
            ECHO ;
            ECHO warning:
                "Did not find command for MSVC toolset."
                "If you have Visual Studio 2017 installed you will need to"
                "specify the full path to the command,"
                "set VS150COMNTOOLS for your installation,"
                "or"
                "build from the 'Visual Studio Command Prompt for VS 2017'."
                ;
            ECHO ;
        }

        common.handle-options msvc : $(conditions) : $(command) : $(options) ;

        if ! $(version)
        {
            # Even if version is not explicitly specified, try to detect the
            # version from the path.
            # FIXME: We currently detect both Microsoft Visual Studio 9.0 and
            # 9.0express as 9.0 here.
            if [ MATCH "(MSVC\\14.1)" : $(command) ]
            {
                version = 14.1 ;
            }
            else if [ MATCH "(Microsoft Visual Studio 14)" : $(command) ]
            {
                version = 14.0 ;
            }
            else if [ MATCH "(Microsoft Visual Studio 12)" : $(command) ]
            {
                version = 12.0 ;
            }
            else if [ MATCH "(Microsoft Visual Studio 11)" : $(command) ]
            {
                version = 11.0 ;
            }
            else if [ MATCH "(Microsoft Visual Studio 10)" : $(command) ]
            {
                version = 10.0 ;
            }
            else if [ MATCH "(Microsoft Visual Studio 9)" : $(command) ]
            {
                version = 9.0 ;
            }
            else if [ MATCH "(Microsoft Visual Studio 8)" : $(command) ]
            {
                version = 8.0 ;
            }
            else if [ MATCH "(NET 2003[\/\\]VC7)" : $(command) ]
            {
                version = 7.1 ;
            }
            else if [ MATCH "(Microsoft Visual C\\+\\+ Toolkit 2003)" :
                $(command) ]
            {
                version = 7.1toolkit ;
            }
            else if [ MATCH "(.NET[\/\\]VC7)" : $(command) ]
            {
                version = 7.0 ;
            }
            else
            {
                version = 6.0 ;
            }
        }

        # Generate and register setup command.

        local below-8.0 = [ MATCH ^([67]\\.) : $(version) ] ;
        local below-11.0 = [ MATCH ^([6789]\\.|10\\.) : $(version) ] ;
        
        local cpu = i386 amd64 ia64 arm ;
        if $(below-8.0)
        {
            cpu = i386 ;
        }
        else if $(below-11.0)
        {
            cpu = i386 amd64 ia64 ;
        }
        
        local setup-amd64 ;
        local setup-i386 ;
        local setup-ia64 ;
        local setup-arm ;
        local setup-phone-i386 ;
        local setup-phone-arm ;

        if $(command)
        {
            # TODO: Note that if we specify a non-existant toolset version then
            # this rule may find and use a corresponding compiler executable
            # belonging to an incorrect toolset version. For example, if you
            # have only MSVC 7.1 installed, have its executable on the path and
            # specify you want Boost Build to use MSVC 9.0, then you want Boost
            # Build to report an error but this may cause it to silently use the
            # MSVC 7.1 compiler even though it thinks it is using the msvc-9.0
            # toolset version.
            command = [ common.get-absolute-tool-path $(command[-1]) ] ;
        }

        if $(command)
        {
            local parent = [ path.make $(command) ] ;
            parent = [ path.parent $(parent) ] ;
            parent = [ path.native $(parent) ] ;

            # Setup will be used if the command name has been specified. If
            # setup is not specified explicitly then a default setup script will
            # be used instead. Setup scripts may be global or architecture/
            # /platform/cpu specific. Setup options are used only in case of
            # global setup scripts.

            # Default setup scripts provided with different VC distributions:
            #
            #   VC 7.1 had only the vcvars32.bat script specific to 32 bit i386
            # builds. It was located in the bin folder for the regular version
            # and in the root folder for the free VC 7.1 tools.
            #
            #   Later 8.0 & 9.0 versions introduce separate platform specific
            # vcvars*.bat scripts (e.g. 32 bit, 64 bit AMD or 64 bit Itanium)
            # located in or under the bin folder. Most also include a global
            # vcvarsall.bat helper script located in the root folder which runs
            # one of the aforementioned vcvars*.bat scripts based on the options
            # passed to it. So far only the version coming with some PlatformSDK
            # distributions does not include this top level script but to
            # support those we need to fall back to using the worker scripts
            # directly in case the top level script can not be found.

            local global-setup = [ feature.get-values <setup> : $(options) ] ;
            global-setup = $(global-setup[1]) ;
            local global-setup-phone = $(global-setup) ;
            if ! $(below-8.0)
            {
                global-setup ?= [ locate-default-setup $(command) : $(parent) :
                    vcvarsall.bat ] ;
            }

            local default-setup-amd64 = vcvarsx86_amd64.bat ;
            local default-setup-i386  = vcvars32.bat ;
            local default-setup-ia64  = vcvarsx86_ia64.bat ;
            local default-setup-arm   = vcvarsx86_arm.bat ;
            local default-setup-phone-i386 = vcvarsphonex86.bat ;
            local default-setup-phone-arm = vcvarsphonex86_arm.bat ;

            # http://msdn2.microsoft.com/en-us/library/x4d2c09s(VS.80).aspx and
            # http://msdn2.microsoft.com/en-us/library/x4d2c09s(vs.90).aspx
            # mention an x86_IPF option, that seems to be a documentation bug
            # and x86_ia64 is the correct option.
            local default-global-setup-options-amd64 = x86_amd64 ;
            local default-global-setup-options-i386  = x86 ;
            local default-global-setup-options-ia64  = x86_ia64 ;
            local default-global-setup-options-arm   = x86_arm ;

            # When using 64-bit Windows, and targeting 64-bit, it is possible to
            # use a native 64-bit compiler, selected by the "amd64" & "ia64"
            # parameters to vcvarsall.bat. There are two variables we can use --
            # PROCESSOR_ARCHITECTURE and PROCESSOR_IDENTIFIER. The first is
            # 'x86' when running 32-bit Windows, no matter which processor is
            # used, and 'AMD64' on 64-bit windows on x86 (either AMD64 or EM64T)
            # Windows.
            #
            if [ MATCH ^(AMD64) : [ os.environ PROCESSOR_ARCHITECTURE ] ]
            {
                default-global-setup-options-amd64 = amd64 ;
            }
            # When Boost.Build itself is running as a 32-bit process on 64-bit
            # Windows, the above test will fail (since WOW64 simulates a 32-bit
            # environment, including environment values).  So check the WOW64
            # variable PROCESSOR_ARCHITEW6432 as well.
            if [ MATCH ^(AMD64) : [ os.environ PROCESSOR_ARCHITEW6432 ] ]
            {
                default-global-setup-options-amd64 = amd64 ;
            }
            # TODO: The same 'native compiler usage' should be implemented for
            # the Itanium platform by using the "ia64" parameter. For this
            # though we need someone with access to this platform who can find
            # out how to correctly detect this case.
            else if $(somehow-detect-the-itanium-platform)
            {
                default-global-setup-options-ia64 = ia64 ;
            }

            for local c in $(cpu)
            {
                exact-version ?= $(version) ;
                setup-$(c) = [ generate-setup-cmd $(exact-version) : $(command) : $(parent) : $(options) : $(c) : $(global-setup) : $(default-global-setup-options-$(c)) : $(default-setup-$(c)) ] ;
            }
            
            # Windows phone has different setup scripts, located in a different directory hierarchy.
            # The 11.0 toolset can target Windows Phone 8.0 and the 12.0 toolset can target Windows Phone 8.1,
            # each of which have a different directory for their vcvars setup scripts.
            local phone-parent = [ path.native [ path.join $(parent) WPSDK ] ] ;
            local phone-directory = $(phone-parent) ;
            if [ MATCH "(11.0)" : $(version) ]
            {
                phone-directory = [ path.native [ path.join $(phone-directory) WP80 ] ] ;
            }
            else if [ MATCH "(12.0)" : $(version) ]
            {
                phone-directory = [ path.native [ path.join $(phone-directory) WP81 ] ] ;
            }
            global-setup-phone ?= [ locate-default-setup $(phone-directory) : $(phone-parent) : vcvarsphoneall.bat ] ;
                
            # If can't locate default phone setup script then this VS version doesn't support Windows Phone.
            if $(global-setup-phone)-is-defined
            {
                # i386 CPU is for the Windows Phone emulator in Visual Studio.
                local phone-cpu = i386 arm ;
                for local c in $(phone-cpu)
                {
                    setup-phone-$(c) = [ generate-setup-cmd $(version) : $(phone-directory) : $(phone-parent) : $(options) : $(c) : $(global-setup-phone) : $(default-global-setup-options-$(c)) : $(default-setup-phone-$(c)) ] ;
                }
            }
        }

        # Get tool names (if any) and finish setup.

        compiler = [ feature.get-values <compiler> : $(options) ] ;
        compiler ?= cl ;

        linker = [ feature.get-values <linker> : $(options) ] ;
        linker ?= link ;

        resource-compiler = [ feature.get-values <resource-compiler> : $(options) ] ;
        resource-compiler ?= rc ;

        # Turn on some options for i386 assembler
        #  -coff  generate COFF format object file (compatible with cl.exe output)
        local default-assembler-amd64 = ml64 ;
        local default-assembler-i386  = "ml -coff" ;
        local default-assembler-ia64  = ias ;
        local default-assembler-ia64  = armasm ;

        assembler = [ feature.get-values <assembler> : $(options) ] ;

        idl-compiler = [ feature.get-values <idl-compiler> : $(options) ] ;
        idl-compiler ?= midl ;

        mc-compiler = [ feature.get-values <mc-compiler> : $(options) ] ;
        mc-compiler ?= mc ;

        manifest-tool = [ feature.get-values <manifest-tool> : $(options) ] ;
        manifest-tool ?= mt ;

        local cc-filter = [ feature.get-values <compiler-filter> : $(options) ]
            ;

        for local c in $(cpu)
        {
            # Setup script is not required in some configurations.
            setup-$(c) ?= "" ;

            local cpu-conditions = $(conditions)/$(.cpu-arch-$(c)) ;

            if $(.debug-configuration)
            {
                for local cpu-condition in $(cpu-conditions)
                {
                    ECHO "notice: [msvc-cfg] condition: '$(cpu-condition)', setup: '$(setup-$(c))'" ;
                }
            }

            local cpu-assembler = $(assembler) ;
            cpu-assembler ?= $(default-assembler-$(c)) ;

            toolset.flags msvc.compile .RC  <windows-api>$(api)/$(cpu-conditions) : $(setup-$(c))$(resource-compiler) ;
            toolset.flags msvc.compile .IDL <windows-api>$(api)/$(cpu-conditions) : $(setup-$(c))$(idl-compiler) ;
            toolset.flags msvc.compile .MC  <windows-api>$(api)/$(cpu-conditions) : $(setup-$(c))$(mc-compiler) ;
            toolset.flags msvc.link    .MT  <windows-api>$(api)/$(cpu-conditions) : $(setup-$(c))$(manifest-tool) -nologo ;

            for api in desktop store phone
            {
                local setup-script = $(setup-$(c)) ;
                if $(api) = phone
                {
                    setup-script = $(setup-phone-$(c)) ;
                }
                if $(api) = desktop
                {
                    toolset.flags msvc.compile .CC  <windows-api>$(api)/$(cpu-conditions) : $(setup-script)$(compiler) /Zm800 -nologo ;
                }
                else
                {
                    toolset.flags msvc.compile .CC  <windows-api>$(api)/$(cpu-conditions) : $(setup-script)$(compiler) /Zm800 /ZW /EHsc -nologo ;
                }
                toolset.flags msvc.compile .ASM <windows-api>$(api)/$(cpu-conditions) : $(setup-script)$(cpu-assembler) -nologo ;
                toolset.flags msvc.link    .LD  <windows-api>$(api)/$(cpu-conditions) : $(setup-script)$(linker) /NOLOGO /INCREMENTAL:NO ;
                toolset.flags msvc.archive .LD  <windows-api>$(api)/$(cpu-conditions) : $(setup-script)$(linker) /lib /NOLOGO  ;
            }

            if $(cc-filter)
            {
                toolset.flags msvc .CC.FILTER $(cpu-conditions) : "|" $(cc-filter) ;
            }
        }

        # Starting with Visual Studio 2013 the CRT is split into a desktop and app dll.
        # If targeting WinRT and 12.0 set lib path to link against app CRT.
        if [ MATCH "(12)" : $(version) ]
        {
            local storeLibPath = [ path.join $(parent) "lib/store" ] ;
            toolset.flags msvc.link LINKPATH $(conditions)/<windows-api>store/$(.cpu-arch-i386) : [ path.native $(storeLibPath) ] ;
            toolset.flags msvc.link LINKPATH $(conditions)/<windows-api>store/$(.cpu-arch-amd64) : [ path.native [ path.join $(storeLibPath) "amd64" ] ] ;
            toolset.flags msvc.link LINKPATH $(conditions)/<windows-api>store/$(.cpu-arch-arm) : [ path.native [ path.join $(storeLibPath) "arm" ] ] ;
        }

        # Set version-specific flags.
        configure-version-specific msvc : $(version) : $(conditions) ;
    }
}


# Returns the default installation path for the given version.
#
local rule default-path ( version )
{
    # Use auto-detected path if possible.
    local result = [ feature.get-values <command> : [ $(.versions).get $(version)
        : options ] ] ;

    if $(result)
    {
        result = $(result:D) ;
    }
    else
    {
        # try to use vswhere
        local pseudo_env_VS150 ;
        if $(version) = 14.1 || $(version) = "default"
        {
            local req = "-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64" ;
            local prop = "-property installationPath" ;
            local limit = "-version \"[15.0,16.0)\"" ;
            local root = [ os.environ "ProgramFiles(x86)" ] ;
            if ( ! $(root) )
            {
                root = [ os.environ "ProgramFiles" ] ;
            }
            local vswhere = "$(root)\\Microsoft Visual Studio\\Installer\\vswhere.exe" ;
            if ( [ path.exists $(vswhere) ] )
            {
                local vmwhere_cmd = "\"$(vswhere)\" -latest -products * $(req) $(prop) $(limit)" ;
                local shell_ret = [ SPLIT_BY_CHARACTERS [ SHELL $(vmwhere_cmd) ] : "\n" ] ;
                pseudo_env_VS150 = [ path.native [ path.join $(shell_ret) "\\Common7\\Tools" ] ] ;
            }
        }

        # Check environment or previous path_VS150
        for local env in $(.version-$(version)-env)
        {
            local env-path ;
            if ( $(pseudo_env_VS150) && [ path.exists $(pseudo_env_VS150) ] )
            {
                env-path = $(pseudo_env_VS150) ;
            }
            else
            {
                env-path = [ os.environ $(env) ] ;
            }
            if $(env-path) && $(.version-$(version)-path)
            {
                for local bin-path in $(.version-$(version)-path)
                {
                    result = [ path.glob [ path.make $(env-path) ] : $(bin-path) ] ;
                    if $(result)
                    {
                        result = [ path.native $(result[1]) ] ;
                        break ;
                    }
                }
            }
            if $(result)
            {
                break ;
            }
        }
    }

    return $(result) ;
}


# Returns either the default installation path (if 'version' is not empty) or
# list of all known default paths (if no version is given)
#
local rule default-paths ( version ? )
{
    local possible-paths ;

    if $(version)
    {
        possible-paths += [ default-path $(version) ] ;
    }
    else
    {
        for local i in $(.known-versions)
        {
            possible-paths += [ default-path $(i) ] ;
        }
    }

    return $(possible-paths) ;
}


rule get-rspline ( target : lang-opt )
{
    CC_RSPLINE on $(target) = [ on $(target) return $(lang-opt) -U$(UNDEFS)
        $(CFLAGS) $(C++FLAGS) $(OPTIONS) -c $(.nl)-D$(DEFINES)
        $(.nl)\"-I$(INCLUDES:W)\" ] ;
}

class msvc-linking-generator : linking-generator
{
    # Calls the base version.  If necessary, also create a target for the
    # manifest file.specifying source's name as the name of the created
    # target. As result, the PCH will be named whatever.hpp.gch, and not
    # whatever.gch.
    rule generated-targets ( sources + : property-set : project name ? )
    {
        local result = [ linking-generator.generated-targets $(sources)
          : $(property-set) : $(project) $(name) ] ;

        if $(result)
        {
            local name-main = [ $(result[0]).name ] ;
            local action = [ $(result[0]).action ] ;

            if [ $(property-set).get <debug-symbols> ] = "on"
            {
                # We force the exact name on PDB. The reason is tagging -- the
                # tag rule may reasonably special case some target types, like
                # SHARED_LIB. The tag rule will not catch PDBs, and it cannot
                # even easily figure out if a PDB is paired with a SHARED_LIB,
                # EXE or something else. Because PDBs always get the same name
                # as the main target, with .pdb as extension, just force it.
                local target = [ class.new file-target $(name-main:S=.pdb) exact
                    : PDB : $(project) : $(action) ] ;
                local registered-target = [ virtual-target.register $(target) ]
                    ;
                if $(target) != $(registered-target)
                {
                    $(action).replace-targets $(target) : $(registered-target) ;
                }
                result += $(registered-target) ;
            }

            if [ $(property-set).get <embed-manifest> ] = "off"
            {
                # Manifest is an evil target. It has .manifest appened to the
                # name of the main target, including extension, e.g.
                # a.exe.manifest. We use the 'exact' name to achieve this
                # effect.
                local target = [ class.new file-target $(name-main).manifest
                    exact : MANIFEST : $(project) : $(action) ] ;
                local registered-target = [ virtual-target.register $(target) ]
                    ;
                if $(target) != $(registered-target)
                {
                    $(action).replace-targets $(target) : $(registered-target) ;
                }
                result += $(registered-target) ;
            }
        }
        return $(result) ;
    }
}


# Unsafe worker rule for the register-toolset() rule. Must not be called
# multiple times.
#
local rule register-toolset-really ( )
{
    feature.extend toolset : msvc ;

    # Intel and msvc supposedly have link-compatible objects.
    feature.subfeature toolset msvc : vendor : intel : propagated optional ;

    # Inherit MIDL flags.
    toolset.inherit-flags msvc : midl ;

    # Inherit MC flags.
    toolset.inherit-flags msvc : mc ;

    # Dynamic runtime comes only in MT flavour.
    toolset.add-requirements
        <toolset>msvc,<runtime-link>shared:<threading>multi ;

    # Declare msvc toolset specific features.
    {
        feature.feature debug-store : object database : propagated ;
        feature.feature pch-source  :                 : dependency free ;
    }

    # Declare generators.
    {
        # TODO: Is it possible to combine these? Make the generators
        # non-composing so that they do not convert each source into a separate
        # .rsp file.
        generators.register [ new msvc-linking-generator msvc.link :
            OBJ SEARCHED_LIB STATIC_LIB IMPORT_LIB : EXE : <toolset>msvc ] ;
        generators.register [ new msvc-linking-generator msvc.link.dll :
            OBJ SEARCHED_LIB STATIC_LIB IMPORT_LIB : SHARED_LIB IMPORT_LIB :
            <toolset>msvc <suppress-import-lib>false ] ;
        generators.register [ new msvc-linking-generator msvc.link.dll :
            OBJ SEARCHED_LIB STATIC_LIB IMPORT_LIB : SHARED_LIB :
            <toolset>msvc <suppress-import-lib>true ] ;

        generators.register-archiver msvc.archive : OBJ : STATIC_LIB : <toolset>msvc ;
        generators.register-c-compiler msvc.compile.c++ : CPP : OBJ : <toolset>msvc ;
        generators.register-c-compiler msvc.compile.c : C : OBJ : <toolset>msvc ;
        generators.register-c-compiler msvc.compile.c++.preprocess : CPP : PREPROCESSED_CPP : <toolset>msvc ;
        generators.register-c-compiler msvc.compile.c.preprocess : C : PREPROCESSED_C : <toolset>msvc ;

        # Using 'register-c-compiler' adds the build directory to INCLUDES.
        generators.register-c-compiler msvc.compile.rc : RC : OBJ(%_res) : <toolset>msvc ;
        generators.override msvc.compile.rc : rc.compile.resource ;
        generators.register-standard msvc.compile.asm : ASM : OBJ : <toolset>msvc ;

        generators.register-c-compiler msvc.compile.idl : IDL : MSTYPELIB H C(%_i) C(%_proxy) C(%_dlldata) : <toolset>msvc ;
        generators.override msvc.compile.idl : midl.compile.idl ;

        generators.register-standard msvc.compile.mc : MC : H RC : <toolset>msvc ;
        generators.override msvc.compile.mc : mc.compile ;

        # Note: the 'H' source type will catch both '.h' and '.hpp' headers as
        # the latter have their HPP type derived from H. The type of compilation
        # is determined entirely by the destination type.
        generators.register [ new msvc-pch-generator msvc.compile.c.pch   : H :   C_PCH OBJ : <pch>on <toolset>msvc ] ;
        generators.register [ new msvc-pch-generator msvc.compile.c++.pch : H : CPP_PCH OBJ : <pch>on <toolset>msvc ] ;

        generators.override msvc.compile.c.pch   : pch.default-c-pch-generator ;
        generators.override msvc.compile.c++.pch : pch.default-cpp-pch-generator ;
    }

    toolset.flags msvc.compile PCH_FILE   <pch>on : <pch-file>   ;
    toolset.flags msvc.compile PCH_SOURCE <pch>on : <pch-source> ;
    toolset.flags msvc.compile PCH_HEADER <pch>on : <pch-header> ;

    #
    # Declare flags for compilation.
    #

    toolset.flags msvc.compile CFLAGS <optimization>speed : /O2 ;
    toolset.flags msvc.compile CFLAGS <optimization>space : /O1 ;

    toolset.flags msvc.compile CFLAGS $(.cpu-arch-ia64)/<instruction-set>$(.cpu-type-itanium) : /G1 ;
    toolset.flags msvc.compile CFLAGS $(.cpu-arch-ia64)/<instruction-set>$(.cpu-type-itanium2) : /G2 ;

    toolset.flags msvc.compile CFLAGS <debug-symbols>on/<debug-store>object : /Z7 ;
    toolset.flags msvc.compile CFLAGS <debug-symbols>on/<debug-store>database : /Zi ;
    toolset.flags msvc.compile CFLAGS <optimization>off : /Od ;
    toolset.flags msvc.compile CFLAGS <inlining>off : /Ob0 ;
    toolset.flags msvc.compile CFLAGS <inlining>on : /Ob1 ;
    toolset.flags msvc.compile CFLAGS <inlining>full : /Ob2 ;

    toolset.flags msvc.compile CFLAGS <warnings>on : /W3 ;
    toolset.flags msvc.compile CFLAGS <warnings>off : /W0 ;
    toolset.flags msvc.compile CFLAGS <warnings>all : /W4 ;
    toolset.flags msvc.compile CFLAGS <warnings-as-errors>on : /WX ;

    toolset.flags msvc.compile C++FLAGS  <exception-handling>on/<asynch-exceptions>off/<extern-c-nothrow>off : /EHs ;
    toolset.flags msvc.compile C++FLAGS  <exception-handling>on/<asynch-exceptions>off/<extern-c-nothrow>on : /EHsc ;
    toolset.flags msvc.compile C++FLAGS  <exception-handling>on/<asynch-exceptions>on/<extern-c-nothrow>off : /EHa ;
    toolset.flags msvc.compile C++FLAGS  <exception-handling>on/<asynch-exceptions>on/<extern-c-nothrow>on : /EHac ;

    toolset.flags msvc.compile C++FLAGS  <cxxstd>14 : /std:c++14 ;
    toolset.flags msvc.compile C++FLAGS  <cxxstd>17 : /std:c++17 ;
    toolset.flags msvc.compile C++FLAGS  <cxxstd>latest : /std:c++latest ;

    # By default 8.0 enables rtti support while prior versions disabled it. We
    # simply enable or disable it explicitly so we do not have to depend on this
    # default behaviour.
    toolset.flags msvc.compile CFLAGS <rtti>on : /GR ;
    toolset.flags msvc.compile CFLAGS <rtti>off : /GR- ;
    toolset.flags msvc.compile CFLAGS <runtime-debugging>off/<runtime-link>shared : /MD ;
    toolset.flags msvc.compile CFLAGS <runtime-debugging>on/<runtime-link>shared : /MDd ;

    toolset.flags msvc.compile CFLAGS <runtime-debugging>off/<runtime-link>static/<threading>multi : /MT ;
    toolset.flags msvc.compile CFLAGS <runtime-debugging>on/<runtime-link>static/<threading>multi : /MTd ;

    toolset.flags msvc.compile OPTIONS <cflags> : ;
    toolset.flags msvc.compile.c++ OPTIONS <cxxflags> : ;

    toolset.flags msvc.compile PDB_CFLAG <debug-symbols>on/<debug-store>database : /Fd ;

    toolset.flags msvc.compile DEFINES <define> ;
    toolset.flags msvc.compile UNDEFS <undef> ;
    toolset.flags msvc.compile INCLUDES <include> ;

    # Declare flags for the assembler.
    toolset.flags msvc.compile.asm USER_ASMFLAGS <asmflags> ;

    toolset.flags msvc.compile.asm ASMFLAGS <debug-symbols>on : "/Zi /Zd" ;

    toolset.flags msvc.compile.asm ASMFLAGS <warnings>on : /W3 ;
    toolset.flags msvc.compile.asm ASMFLAGS <warnings>off : /W0 ;
    toolset.flags msvc.compile.asm ASMFLAGS <warnings>all : /W4 ;
    toolset.flags msvc.compile.asm ASMFLAGS <warnings-as-errors>on : /WX ;

    toolset.flags msvc.compile.asm DEFINES <define> ;

    # Declare flags for linking.
    {
        toolset.flags msvc.link PDB_LINKFLAG <debug-symbols>on/<debug-store>database : /PDB: ;  # not used yet
        toolset.flags msvc.link LINKFLAGS <debug-symbols>on : /DEBUG ;
        toolset.flags msvc.link DEF_FILE <def-file> ;

        # The linker disables the default optimizations when using /DEBUG so we
        # have to enable them manually for release builds with debug symbols.
        toolset.flags msvc LINKFLAGS <debug-symbols>on/<runtime-debugging>off : /OPT:REF,ICF ;

        toolset.flags msvc LINKFLAGS <user-interface>console : /subsystem:console ;
        toolset.flags msvc LINKFLAGS <user-interface>gui : /subsystem:windows ;
        toolset.flags msvc LINKFLAGS <user-interface>wince : /subsystem:windowsce ;
        toolset.flags msvc LINKFLAGS <user-interface>native : /subsystem:native ;
        toolset.flags msvc LINKFLAGS <user-interface>auto : /subsystem:posix ;

        toolset.flags msvc.link OPTIONS <linkflags> ;
        toolset.flags msvc.link LINKPATH <library-path> ;

        toolset.flags msvc.link FINDLIBS_ST <find-static-library> ;
        toolset.flags msvc.link FINDLIBS_SA <find-shared-library> ;
        toolset.flags msvc.link LIBRARY_OPTION <toolset>msvc : "" : unchecked ;
        toolset.flags msvc.link LIBRARIES_MENTIONED_BY_FILE : <library-file> ;
        
        toolset.flags msvc.link.dll LINKFLAGS <suppress-import-lib>true : /NOENTRY ;
    }

    toolset.flags msvc.archive AROPTIONS <archiveflags> ;
}


# Locates the requested setup script under the given folder and returns its full
# path or nothing in case the script can not be found. In case multiple scripts
# are found only the first one is returned.
#
# TODO: There used to exist a code comment for the msvc.init rule stating that
# we do not correctly detect the location of the vcvars32.bat setup script for
# the free VC7.1 tools in case user explicitly provides a path. This should be
# tested or simply remove this whole comment in case this toolset version is no
# longer important.
#
local rule locate-default-setup ( command : parent : setup-name )
{
    local result = [ GLOB $(command) $(parent) : $(setup-name) ] ;
    if $(result[1])
    {
        return $(result[1]) ;
    }
}


# Validates given path, registers found configuration and prints debug
# information about it.
#
local rule register-configuration ( version : path ? )
{
    if $(path)
    {
        local command = [ GLOB $(path) : cl.exe ] ;

        if $(command)
        {
            if $(.debug-configuration)
            {
                ECHO notice: [msvc-cfg] msvc-$(version) detected, command:
                    '$(command)' ;
            }

            $(.versions).register $(version) ;
            $(.versions).set $(version) : options : <command>$(command) ;
        }
    }
}


################################################################################
#
#   Startup code executed when loading this module.
#
################################################################################

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

# Miscellaneous constants.
.RM = [ common.rm-command ] ;
.nl = "
" ;
.ProgramFiles = [ path.make [ common.get-program-files-dir ] ] ;
.escaped-double-quote = "\"" ;
.TOUCH_FILE = [ common.file-touch-command ] ;

# List of all registered configurations.
.versions = [ new configurations ] ;

# Supported CPU architectures.
.cpu-arch-i386 =
    <architecture>/<address-model>
    <architecture>/<address-model>32
    <architecture>x86/<address-model>
    <architecture>x86/<address-model>32 ;

.cpu-arch-amd64 =
    <architecture>/<address-model>64
    <architecture>x86/<address-model>64 ;

.cpu-arch-ia64 =
    <architecture>ia64/<address-model>
    <architecture>ia64/<address-model>64 ;

.cpu-arch-arm =
    <architecture>arm/<address-model>
    <architecture>arm/<address-model>32 ;


# Supported CPU types (only Itanium optimization options are supported from
# VC++ 2005 on). See
# http://msdn2.microsoft.com/en-us/library/h66s5s0e(vs.90).aspx for more
# detailed information.
.cpu-type-g5       = i586 pentium pentium-mmx ;
.cpu-type-g6       = i686 pentiumpro pentium2 pentium3 pentium3m pentium-m k6
                     k6-2 k6-3 winchip-c6 winchip2 c3 c3-2 ;
.cpu-type-em64t    = prescott nocona core2 corei7 corei7-avx core-avx-i
                     conroe conroe-xe conroe-l allendale merom
                     merom-xe kentsfield kentsfield-xe penryn wolfdale
                     yorksfield nehalem sandy-bridge ivy-bridge haswell
                     broadwell skylake skylake-avx512 cannonlake ;
.cpu-type-amd64    = k8 opteron athlon64 athlon-fx k8-sse3 opteron-sse3
                     athlon64-sse3 amdfam10 barcelona bdver1 bdver2 bdver3
                     bdver4 btver1 btver2 znver1 ;
.cpu-type-g7       = pentium4 pentium4m athlon athlon-tbird athlon-4 athlon-xp
                     athlon-mp $(.cpu-type-em64t) $(.cpu-type-amd64) ;
.cpu-type-itanium  = itanium itanium1 merced ;
.cpu-type-itanium2 = itanium2 mckinley ;
.cpu-type-arm      = armv2 armv2a armv3 armv3m armv4 armv4t armv5 armv5t armv5te armv6 armv6j iwmmxt ep9312
                     armv7 armv7s ;

# Known toolset versions, in order of preference.
.known-versions = 14.1 14.0 12.0 11.0 10.0 10.0express 9.0 9.0express 8.0 8.0express 7.1
    7.1toolkit 7.0 6.0 ;

# Version aliases.
.version-alias-6 = 6.0 ;
.version-alias-6.5 = 6.0 ;
.version-alias-7 = 7.0 ;
.version-alias-8 = 8.0 ;
.version-alias-9 = 9.0 ;
.version-alias-10 = 10.0 ;
.version-alias-11 = 11.0 ;
.version-alias-12 = 12.0 ;
.version-alias-14 = 14.0 ;
.version-alias-14.1 = 14.1 ;

# Names of registry keys containing the Visual C++ installation path (relative
# to "HKEY_LOCAL_MACHINE\SOFTWARE\\Microsoft").
.version-6.0-reg = "VisualStudio\\6.0\\Setup\\Microsoft Visual C++" ;
.version-7.0-reg = "VisualStudio\\7.0\\Setup\\VC" ;
.version-7.1-reg = "VisualStudio\\7.1\\Setup\\VC" ;
.version-8.0-reg = "VisualStudio\\8.0\\Setup\\VC" ;
.version-8.0express-reg = "VCExpress\\8.0\\Setup\\VC" ;
.version-9.0-reg = "VisualStudio\\9.0\\Setup\\VC" ;
.version-9.0express-reg = "VCExpress\\9.0\\Setup\\VC" ;
.version-10.0-reg = "VisualStudio\\10.0\\Setup\\VC" ;
.version-10.0express-reg = "VCExpress\\10.0\\Setup\\VC" ;
.version-11.0-reg = "VisualStudio\\11.0\\Setup\\VC" ;
.version-12.0-reg = "VisualStudio\\12.0\\Setup\\VC" ;
.version-14.0-reg = "VisualStudio\\14.0\\Setup\\VC" ;

# Visual C++ Toolkit 2003 does not store its installation path in the registry.
# The environment variable 'VCToolkitInstallDir' and the default installation
# path will be checked instead.
.version-7.1toolkit-path    = "Microsoft Visual C++ Toolkit 2003/bin" ;
.version-7.1toolkit-env     = VCToolkitInstallDir ;
# Visual Studio 2017 doesn't use a registry at all. And the suggested methods
# of discovery involve having a compiled program. So as a fallback we search
# paths for VS2017 (aka msvc >= 14.1).
.version-14.1-path =
    "../../VC/Tools/MSVC/*/bin/Host*/*"
    "Microsoft Visual Studio/2017/*/VC/Tools/MSVC/*/bin/Host*/*"
    ;
.version-14.1-env = VS150COMNTOOLS ProgramFiles ProgramFiles(x86) ;

# Auto-detect all the available msvc installations on the system.
auto-detect-toolset-versions ;


# And finally trigger the actual Boost Build toolset registration.
register-toolset ;