# Copyright Vladimir Prus 2004.
# Copyright Peter Dimov 2018
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt
# or copy at https://www.bfgroup.xyz/b2/LICENSE.txt)

import common ;
import errors ;
import feature ;
import clang ;
import msvc ;
import os ;
import toolset ;
import generators ;
import path ;
import regex ;
import version ;

feature.extend-subfeature toolset clang : platform : win ;

toolset.inherit-generators clang-win <toolset>clang <toolset-clang:platform>win : msvc ;
toolset.inherit-flags clang-win : msvc : : YLOPTION ;
toolset.inherit-rules clang-win : msvc ;


if [ MATCH (--debug-(clang-(win-)?)?configuration) : [ modules.peek : ARGV ] ]
{
    local rule .notice ( messages * )
    {
        ECHO "notice: [clang-win]" $(messages) ;
    }
}
else
{
    local rule .notice ( messages * )
    {
    }
}

# [ get-option archiver : 32 : $(options) ]
#
# returns <archiver-32>, or <archiver>

local rule get-option ( option : addr : options * )
{
    local r = [ feature.get-values "<$(option)-$(addr)>" : $(options) ] ;
    r ?= [ feature.get-values "<$(option)>" : $(options) ] ;
    return $(r) ;
}

# init
#
# options:
#
# <assembler>ml.exe (or <assembler-32>, or <assembler-64>)
# <archiver>lib.exe
# <manifest-tool>mt.exe
# <resource-compiler>rc.exe
# <mc-compiler>mc.exe
# <idl-compiler>midl.exe

rule init ( version ? : command * : options * )
{
    local ProgramFiles = [ os.environ "ProgramFiles" ] [ os.environ "ProgramFiles(x86)" ] ;
    command = [ common.get-invocation-command-nodefault clang-win : clang-cl.exe : $(command) : "$(ProgramFiles)\\LLVM\\bin" ] ;

    if ! $(command)
    {
        errors.error "Cannot configure toolset clang-win: no 'clang-cl.exe' command found or given" ;
    }

    local compiler = "\"$(command)\"" ;
    compiler = "$(compiler:J= )" ;

    local version-output = [ SHELL "$(compiler) -v 2>&1" ] ;

    version ?= [ MATCH "version ([0-9.]+)" : $(version-output) ] ;
    local target = [ MATCH "Target: ([A-Za-z0-9_]+)" : $(version-output) ] ;

    local default-addr ;
    local default-arch ;
    switch $(target) {
    case x86_64  : default-arch = x86 ; default-addr = 64 ;
    case i386    : default-arch = x86 ; default-addr = 32 ;
    case aarch64 : default-arch = arm ; default-addr = 64 ;
    case arm     : default-arch = arm ; default-addr = 32 ;
    }

    .notice "using compiler '$(compiler)', version '$(version)', target '$(target)', default architecture=$(default-arch) address-model=$(default-addr)" ;

    local condition = [ common.check-init-parameters clang-win : version $(version) ] ;

    common.handle-options clang-win : $(condition) : $(command) : $(options) ;

    for local addr in 32 64
    {
        for local arch in x86 arm
        {
            local clang-arch ;
            local target-assembler ;

            switch $(arch)-$(addr) {
            case x86-64 : clang-arch = x86_64  ; target-assembler = ml64 ;
            case x86-32 : clang-arch = i386    ; target-assembler = ml ;
            case arm-64 : clang-arch = aarch64 ; target-assembler = armasm64 ;
            case arm-32 : clang-arch = arm     ; target-assembler = armasm ;
            }

            local config = [ SPLIT_BY_CHARACTERS [ SHELL "$(compiler) --target=$(clang-arch)-pc-windows-msvc -### foo.obj /link 2>&1" ] : "\n" ] ;

            local match = 1 ;
            local items ;

            while $(match)
            {
                match = [ MATCH "^ *(\"[^\"]*\")(.*)" : $(config) ] ;

                if $(match)
                {
                    items += [ regex.replace $(match[1]) "\\\\+" "\\" ] ;
                    config = $(match[2]) ;
                }
            }

            local assembler = [ get-option "assembler" : $(addr) : $(options) ] ;
            local linker ;

            if $(items)
            {
                linker = [ regex.replace $(items[1]) "\\\\HostX64\\\\x86\\\\" "\\HostX86\\x86\\" ] ;
            }
            if $(linker) {
                assembler ?= $(linker:B=$(target-assembler)) ;
            }
            assembler ?= $(target-assembler) ;

            .notice "$(arch)-$(addr):" "using linker '$(linker)'" ;

            local archiver = [ get-option "archiver" : $(addr) : $(options) ] ;

            if $(linker)
            {
                archiver ?= "$(linker) /lib" ;
            }
            archiver ?= lib.exe ;

            .notice "$(arch)-$(addr):" "using assembler '$(assembler)'" ;
            .notice "$(arch)-$(addr):" "using archiver '$(archiver)'" ;

            local manifest-tool = [ get-option "manifest-tool" : $(addr) : $(options) ] ;
            local resource-compiler = [ get-option "resource-compiler" : $(addr) : $(options) ] ;
            local mc-compiler = [ get-option "mc-compiler" : $(addr) : $(options) ] ;
            local idl-compiler = [ get-option "idl-compiler" : $(addr) : $(options) ] ;

            for local item in $(items)
            {
                match = [ MATCH "\"-libpath:(.*)\\\\Lib\\\\(.*)\\\\um\\\\x(.*)\"" : $(item) ] ;

                if $(match)
                {
                    local sdk-path = "$(match[1])\\bin\\$(match[2])\\x$(match[3])" ;
                    if ! [ path.exists $(sdk-path) ]
                    {
                        sdk-path = "$(match[1])\\bin\\x$(match[3])" ;
                    }

                    .notice "$(arch)-$(addr):" "using SDK path '$(sdk-path)'" ;

                    manifest-tool ?= "\"$(sdk-path)\\mt.exe\"" ;
                    resource-compiler ?= "\"$(sdk-path)\\rc.exe\"" ;
                    mc-compiler ?= "\"$(sdk-path)\\mc.exe\"" ;
                    idl-compiler ?= "\"$(sdk-path)\\midl.exe\"" ;
                }
            }

            manifest-tool ?= mt.exe ;
            resource-compiler ?= rc.exe ;
            mc-compiler ?= mc.exe ;
            idl-compiler ?= midl.exe ;

            .notice "$(arch)-$(addr):" "using manifest-tool '$(manifest-tool)'" ;
            .notice "$(arch)-$(addr):" "using resource-compiler '$(resource-compiler)'" ;
            .notice "$(arch)-$(addr):" "using mc-compiler '$(mc-compiler)'" ;
            .notice "$(arch)-$(addr):" "using idl-compiler '$(idl-compiler)'" ;

            local linker-arch ;
            if $(arch) = x86
            {
                if $(addr) = 32 { linker-arch = x86 ; } else { linker-arch = x64 ; }
            }
            else if $(arch) = arm
            {
                if $(addr) = 32 { linker-arch = arm ; } else { linker-arch = arm64 ; }
            }

            local cond = "$(condition)/<architecture>$(arch)/<address-model>$(addr)" ;
            if $(addr) = $(default-addr)
            {
                cond += "$(condition)/<architecture>$(arch)/<address-model>" ;
            }
            if $(arch) = $(default-arch)
            {
                cond += "$(condition)/<architecture>/<address-model>$(addr)" ;
            }
            if $(addr) = $(default-addr) && $(arch) = $(default-arch)
            {
                cond += "$(condition)/<architecture>/<address-model>" ;
            }

            toolset.flags clang-win.compile .CC $(cond) : $(compiler) --target=$(clang-arch)-pc-windows-msvc ;
            toolset.flags clang-win.link .LD $(cond) : $(compiler) --target=$(clang-arch)-pc-windows-msvc -fuse-ld=lld ;
            toolset.flags clang-win.link LINKOPT $(cond) : /link ;
            toolset.flags clang-win.link LINKFLAGS $(cond) : "/incremental:no" "/machine:$(linker-arch)" ;
            if $(arch) = x86
            {
                toolset.flags clang-win.compile .ASM $(cond) : $(assembler) -nologo -c -Zp4 -Cp -Cx ;
                toolset.flags clang-win.compile .ASM_OUTPUT $(cond) : -Fo ;
            }
            else if $(arch) = arm
            {
                toolset.flags clang-win.compile .ASM $(cond) : $(assembler) -nologo ;
                toolset.flags clang-win.compile .ASM_OUTPUT $(cond) : -o ;
            }
            toolset.flags clang-win.archive .LD $(cond) : $(archiver) /nologo ;
            toolset.flags clang-win.link .MT $(cond) : $(manifest-tool) -nologo ;
            toolset.flags clang-win.compile .MC $(cond) : $(mc-compiler) ;
            toolset.flags clang-win.compile .RC $(cond) : $(resource-compiler) ;
            toolset.flags clang-win.compile .IDL $(cond) : $(idl-compiler) ;
        }
    }

    local conditions = [ feature.split $(condition) ] ;
    if ! [ version.version-less [ SPLIT_BY_CHARACTERS $(version) : . ] : 15 ] {
        toolset.add-defaults $(conditions:J=,)\:<embed-manifest-via>linker ;
    }
    else {
        # Clang seems to not care about adding WinSDK to PATH.
        # Both MSVC link and LLVM lld-link (until v15) will fail to embed manifest.
        # LINK : fatal error LNK1158: cannot run 'rc.exe'
        # lld-link: error: unable to find mt.exe in PATH: no such file or directory
        toolset.add-requirements $(conditions:J=,)\:<embed-manifest-via>mt ;
    }

    toolset.flags clang-win.link LIBRARY_OPTION <toolset>clang-win : "" : unchecked ;

    # Enable response file control
    toolset.flags clang-win RESPONSE_FILE_SUB <response-file>auto : a ;
    toolset.flags clang-win RESPONSE_FILE_SUB <response-file>file : f ;
    toolset.flags clang-win RESPONSE_FILE_SUB <response-file>contents : c ;
}
