defineTest(qtToolchainError) {
    msg = \
        $$1 \
        "===================" \
        $$2 \
        "===================" \
        $$3
    error($$join(msg, $$escape_expand(\\n)))
}

defineTest(qtCompilerError) {
    !cross_compile: \
        what =
    else: host_build: \
        what = " host"
    else: \
        what = " target"
    qtToolchainError("Cannot run$$what compiler '$$1'. Output:", $$2, \
                     "Maybe you forgot to setup the environment?")
}

cross_compile:host_build: \
    target_prefix = QMAKE_HOST_CXX
else: \
    target_prefix = QMAKE_CXX

#
# Determine and cache the compiler version
#

defineReplace(qtVariablesFromMSVC) {
    ret = $$system("$$1 -nologo -E $$2 $$system_quote($$PWD/data/macros.cpp) 2>NUL", lines, ec)
    !equals(ec, 0): qtCompilerError($$1, $$ret)
    return($$ret)
}

defineReplace(qtVariablesFromGCC) {
    ret = $$system("$$1 -E $$system_quote($$PWD/data/macros.cpp) \
        2>$$QMAKE_SYSTEM_NULL_DEVICE", lines, ec)
    !equals(ec, 0): qtCompilerError($$1, $$ret)
    return($$ret)
}

defineReplace(qtVariablesFromCLANG) {
    ret = $$system("$$1 $$2 -E $$system_quote($$PWD/data/macros.cpp) \
        <$$QMAKE_SYSTEM_NULL_DEVICE 2>$$QMAKE_SYSTEM_NULL_DEVICE", lines, ec)
    !equals(ec, 0): qtCompilerErrror($$1, $$ret)
    return($$ret)
}

isEmpty($${target_prefix}.COMPILER_MACROS) {
    msvc {
        clang_cl {
            # We need to obtain the cl.exe version first
            vars = $$qtVariablesFromMSVC(cl)
            for (v, vars) {
                isEmpty(v)|contains(v, $${LITERAL_HASH}.*): next()
                eval($$v)
            }
            isEmpty(QMAKE_MSC_FULL_VER): error("Could not determine the Visual Studio version")

            QMAKE_CFLAGS_MSVC_COMPAT = $$replace(QMAKE_MSC_FULL_VER, "(..)(..)(.*)", \
                                                 "-fms-compatibility-version=\\1.\\2.\\3")
            cache($${target_prefix}.QMAKE_CFLAGS_MSVC_COMPAT, set stash, QMAKE_CFLAGS_MSVC_COMPAT)
            $${target_prefix}.COMPILER_MACROS += QMAKE_CFLAGS_MSVC_COMPAT
            vars = $$qtVariablesFromMSVC($$QMAKE_CXX, $$QMAKE_CFLAGS_MSVC_COMPAT)
        } else {
            vars = $$qtVariablesFromMSVC($$QMAKE_CXX)
        }
    } else: clang {
        vars = $$qtVariablesFromCLANG($$QMAKE_CXX, $$QMAKE_CXXFLAGS)
    } else: gcc|ghs {
        vars = $$qtVariablesFromGCC($$QMAKE_CXX)
    }
    for (v, vars) {
        !contains(v, "[A-Z_]+ = .*"): next()
        # Set both <varname> for the outer scope ...
        eval($$v)
        v ~= s/ .*//
        isEmpty($$v): error("Compiler produced empty value for $${v}.")
        # ... and save QMAKE_(HOST_)?CXX.<varname> in the cache.
        cache($${target_prefix}.$$v, set stash, $$v)
        $${target_prefix}.COMPILER_MACROS += $$v
    }
    cache($${target_prefix}.COMPILER_MACROS, set stash)
} else {
    # load from the cache
    for (i, $${target_prefix}.COMPILER_MACROS): \
        $$i = $$eval($${target_prefix}.$$i)
}

# Populate QMAKE_COMPILER_DEFINES and some compatibility variables.
# The $$format_number() calls strip leading zeros to avoid misinterpretation as octal.
QMAKE_COMPILER_DEFINES += __cplusplus=$$QT_COMPILER_STDCXX
!isEmpty(QMAKE_MSC_VER): \
    QMAKE_COMPILER_DEFINES += _MSC_VER=$$QMAKE_MSC_VER _MSC_FULL_VER=$$QMAKE_MSC_FULL_VER
!isEmpty(QMAKE_ICC_VER): \
    QMAKE_COMPILER_DEFINES += __INTEL_COMPILER=$$QMAKE_ICC_VER __INTEL_COMPILER_UPDATE=$$QMAKE_ICC_UPDATE_VER
!isEmpty(QMAKE_APPLE_CC): \
    QMAKE_COMPILER_DEFINES += __APPLE_CC__=$$QMAKE_APPLE_CC
!isEmpty(QMAKE_APPLE_CLANG_MAJOR_VERSION): \
    QMAKE_COMPILER_DEFINES += __clang__ \
        __clang_major__=$$QMAKE_APPLE_CLANG_MAJOR_VERSION \
        __clang_minor__=$$QMAKE_APPLE_CLANG_MINOR_VERSION \
        __clang_patchlevel__=$$QMAKE_APPLE_CLANG_PATCH_VERSION
!isEmpty(QMAKE_CLANG_MAJOR_VERSION): \
    QMAKE_COMPILER_DEFINES += __clang__ \
        __clang_major__=$$QMAKE_CLANG_MAJOR_VERSION \
        __clang_minor__=$$QMAKE_CLANG_MINOR_VERSION \
        __clang_patchlevel__=$$QMAKE_CLANG_PATCH_VERSION
!isEmpty(QMAKE_GCC_MAJOR_VERSION): \
    QMAKE_COMPILER_DEFINES += \
        __GNUC__=$$QMAKE_GCC_MAJOR_VERSION \
        __GNUC_MINOR__=$$QMAKE_GCC_MINOR_VERSION \
        __GNUC_PATCHLEVEL__=$$QMAKE_GCC_PATCH_VERSION
!isEmpty(QMAKE_GHS_VERSION): \
    QMAKE_COMPILER_DEFINES += __ghs__ __GHS_VERSION_NUMBER=$$QMAKE_GHS_VERSION

QMAKE_CFLAGS += $$QMAKE_CFLAGS_MSVC_COMPAT
QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_MSVC_COMPAT

clang_cl|intel_icl {
    include(../common/msvc-based-version.conf)
} else: msvc {
    include(../common/msvc-version.conf)
}

#
# Determine and cache the default search paths
#

defineReplace(qtMakeExpand) {
    out = "$$1"
    for(ever) {
        m = $$replace(out, ".*\\$\\(EXPORT_([^)]+)\\).*", \\1)
        equals(m, $$out): \
            return($$out)
        out = $$replace(out, "\\$\\(EXPORT_$$m\\)", $$eval($$m))
    }
}

defineReplace(qtSplitPathList) {
    paths = $$split(1, $$QMAKE_DIRLIST_SEP)
    ret =
    for (p, paths): \
        ret += $$clean_path($$p)
    return($$ret)
}

defineReplace(qtNmakePathList) {
    paths =
    for (p, 1): \
        paths += $$shell_path($$p)
    paths ~= s,$${LITERAL_HASH},^$${LITERAL_HASH},g
    paths ~= s,\\$,\$\$,g
    return($$join(paths, $$QMAKE_DIRLIST_SEP))
}

msvc {
    arch = $$lower($$VCPROJ_ARCH)
    equals(arch, x64): \  # may be "win32" or undefined
        arch = amd64
    else: !equals(arch, arm):!equals(arch, arm64): \  # may be "win32" or undefined
        arch = x86
    # Consider only WinRT and ARM64 desktop builds to be cross-builds -
    # the host is assumed to be Intel and capable of running the target
    # executables (so building for x64 on x86 will break).
    winrt|equals(arch, arm64): \
        CONFIG += msvc_cross
}

isEmpty($${target_prefix}.INCDIRS) {
    #
    # Get default include and library paths from compiler
    #
    wasm {
        # wasm compiler does not work here, just use defaults
    } else: gcc {
        cmd_suffix = "<$$QMAKE_SYSTEM_NULL_DEVICE >$$QMAKE_SYSTEM_NULL_DEVICE"
        equals(QMAKE_HOST.os, Windows): \
            cmd_prefix = "set LC_ALL=C&"
        else: \
            cmd_prefix = "LC_ALL=C"

        cxx_flags = $$QMAKE_CXXFLAGS

        # Manually inject the sysroot for Apple Platforms because its resolution
        # normally does not happen until default_post.prf. This is especially
        # important for moc to gain the correct default include directory list.
        # While technically incorrect but without any likely practical effect,
        # UIKit simulator platforms will see the device SDK's sysroot in
        # QMAKE_DEFAULT_*DIRS, because they're handled in a single build pass.
        darwin {
            uikit:macx-xcode: \
                cxx_flags += -isysroot $$sdk_path_device.value
            else: \
                cxx_flags += -isysroot $$QMAKE_MAC_SDK_PATH
        }

        rim_qcc: \
            # Need the cc1plus and ld command lines to pick up the paths
            cxx_flags += $$QMAKE_LFLAGS_SHLIB -o $$QMAKE_SYSTEM_NULL_DEVICE -v
        else: darwin:clang: \
            # Need to link to pick up library paths
            cxx_flags += $$QMAKE_LFLAGS_SHLIB -o /dev/null -v -Wl,-v
        else: \
            # Just preprocess, might not pick up library paths
            cxx_flags += -E -v

        output = $$system("$$cmd_prefix $$QMAKE_CXX $$qtMakeExpand($$cxx_flags) -xc++ - 2>&1 $$cmd_suffix", lines, ec)
        !equals(ec, 0): qtCompilerError($$QMAKE_CXX, $$output)

        rim_qcc {
            for (line, output) {
                contains(line, "^[^ ]*cc1plus .*") {
                    take_next = false
                    for (parameter, $$list($$line)) {
                        $$take_next {
                            QMAKE_DEFAULT_INCDIRS += $$clean_path($$parameter)
                            take_next = false
                        } else: equals(parameter, "-isystem") {
                            take_next = true
                        }
                    }
                } else: contains(line, "^[^ ]*-ld .*") {
                    for (parameter, $$list($$line)) {
                        contains(parameter, "^-L.*") {
                            parameter ~= s/^-L//
                            QMAKE_DEFAULT_LIBDIRS += $$clean_path($$parameter)
                        }
                    }
                }
            }
        } else {
            add_includes = false
            add_libraries = false
            for (line, output) {
                line ~= s/^[ \\t]*//  # remove leading spaces
                contains(line, "LIBRARY_PATH=.*") {
                    line ~= s/^LIBRARY_PATH=//  # remove leading LIBRARY_PATH=
                    equals(QMAKE_HOST.os, Windows): \
                        paths = $$split(line, ;)
                    else: \
                        paths = $$split(line, $$QMAKE_DIRLIST_SEP)
                    for (path, paths): \
                        QMAKE_DEFAULT_LIBDIRS += $$clean_path($$path)
                } else: contains(line, "Library search paths:") {
                    add_libraries = true
                } else: contains(line, "$${LITERAL_HASH}include <.*") {  # #include <...> search starts here:
                    add_includes = true
                } else: contains(line, "End of search.*") {
                    add_includes = false
                } else: $$add_libraries {
                    # We assume all library search paths are absolute
                    !contains(line, "^/.*") {
                        add_libraries = false
                        next()
                    }
                    QMAKE_DEFAULT_LIBDIRS += $$clean_path($$line)
                } else: $$add_includes {
                    !contains(line, ".* \\(framework directory\\)"): \
                        QMAKE_DEFAULT_INCDIRS += $$clean_path($$line)
                }
            }
        }
        if(!darwin:clang)|intel_icc {
            # Clang on a non-Apple system (that is, a system without ld64 -- say, with GNU ld
            # or gold under Linux) will not print any library search path. Need to use another
            # invocation with different options (which in turn doesn't print include search
            # paths, so it can't just be used in place of the above code).
            # What's more, -print-search-dirs can't be used on clang on Apple because it
            # won't print all the library paths (only the clang-internal ones).
            output = $$system("$$cmd_prefix $$QMAKE_LINK $$QMAKE_LFLAGS -print-search-dirs", lines, ec)
            !equals(ec, 0): qtCompilerError($$QMAKE_LINK, $$output)

            for (line, output) {
                contains(line, "^libraries: .*") {
                    line ~= s,^libraries: ,,
                    equals(QMAKE_HOST.os, Windows) {
                        # clang (7.x) on Windows uses the wrong path list separator ...
                        line ~= s,:(?![/\\\\]),;,
                        paths = $$split(line, ;)
                    } else {
                        paths = $$split(line, $$QMAKE_DIRLIST_SEP)
                    }
                    for (path, paths): \
                        QMAKE_DEFAULT_LIBDIRS += $$clean_path($$replace(path, ^=, $$[SYSROOT]))
                }
            }
        }
        isEmpty(QMAKE_DEFAULT_LIBDIRS)|isEmpty(QMAKE_DEFAULT_INCDIRS): \
            !integrity: \
                error("failed to parse default search paths from compiler output")
        QMAKE_DEFAULT_LIBDIRS = $$unique(QMAKE_DEFAULT_LIBDIRS)
    } else: ghs {
        cmd = $$QMAKE_CXX $$QMAKE_CXXFLAGS -$${LITERAL_HASH} -o /tmp/fake_output /tmp/fake_input.cpp
        output = $$system("$$cmd", blob, ec)
        !equals(ec, 0): qtCompilerError($$QMAKE_CXX, $$output)
        output ~= s/\\\\\\n {8}//g
        output = $$split(output, $$escape_expand(\\n))
        for (line, output) {
            contains(line, "^[^ ]+/ecom[^ ]+ .* /tmp/fake_input\\.cpp") {
                for (parameter, $$list($$line)) {
                    contains(parameter, "^(-I|--include_no_mmd=|--sys_include=).*") {
                        parameter ~= s/^(-I|--include_no_mmd=|--sys_include=)//
                        QMAKE_DEFAULT_INCDIRS += $$clean_path($$parameter)
                    }
                }
            } else: contains(line, "^[^ ]+/elxr .*") {
                for (parameter, $$list($$line)) {
                    contains(parameter, "^-L.*") {
                        parameter ~= s/^-L//
                        QMAKE_DEFAULT_LIBDIRS += $$clean_path($$parameter)
                    }
                }
            }
        }
    } else: msvc_cross {
        # Use a batch file, because %VAR% in the system() call expands to
        # the pre-script-call value, and !VAR! cannot be enabled outside
        # a batch file without invoking another shell instance.
        cmd = $$system_quote($$system_path($$PWD/data/dumpvcvars.bat))

        hostArch = $$QMAKE_HOST.arch
        equals(hostArch, x86_64): \
            hostArch = amd64
        !equals(arch, $$hostArch): \
            arch = $${hostArch}_$$arch

        isEmpty(MSVC_VER): \
            error("Mkspec does not specify MSVC_VER. Cannot continue.")
        versionAtLeast(MSVC_VER, 15.0) {
            dir = $$(VSINSTALLDIR)
            isEmpty(dir): \
                dir = $$read_registry(HKLM, \
                    "Software\\Microsoft\\VisualStudio\\SxS\\VS7\\$$MSVC_VER", 32)
            isEmpty(dir): \
                error("Failed to find the Visual Studio installation directory.")
            cmd += $$system_quote($$dir\\VC\\Auxiliary\\Build\\vcvarsall.bat) $$arch
        } else {
            dir = $$(VCINSTALLDIR)
            isEmpty(dir): \
                dir = $$read_registry(HKLM, \
                    "Software\\Microsoft\\VisualStudio\\$$MSVC_VER\\Setup\\VC\\ProductDir", 32)
            isEmpty(dir): \
                error("Failed to find the Visual C installation directory.")
            cmd += $$system_quote($$dir\\vcvarsall.bat) $$arch
        }
        winrt: cmd += store

        isEmpty(WINSDK_VER): \
            error("Mkspec does not specify WINSDK_VER. Cannot continue.")
        # We prefer the environment variable, because that may work around
        # a broken registry entry after uninstalling a newer SDK.
        # However, we do that only if the major+minor SDK version matches
        # the one requested by the mkspec, as we might be building for a
        # newer target than the host.
        winsdk_ver = $$(WindowsSDKVersion)
        !isEmpty(winsdk_ver) {
            winsdk_ver ~= s,\\\\$,,  # Work around SDK breakage.
            !equals(WINSDK_VER, $$replace(winsdk_ver, ^(\\d+\\.\\d+).*$, \\1)): \
                winsdk_ver =
        }
        !isEmpty(winsdk_ver) {
            cmd += $$winsdk_ver
        } else {
            winsdk_ver = $$read_registry(HKLM, \
                "Software\\Microsoft\\Microsoft SDKs\\Windows\\v$$WINSDK_VER\\ProductVersion", 32)
            isEmpty(winsdk_ver): \
                error("Windows SDK $$WINSDK_VER requested by mkspec is not installed. Cannot continue.")
            cmd += $${winsdk_ver}.0
        }

        output = $$system("$$cmd 2>&1", lines, ec)
        !equals(ec, 0): \
            qtToolchainError("SDK setup script failed. Output:", $$output, \
                             "Command was: $$cmd")
        lines = $$output
        for(ever) {
            isEmpty(lines): \
                break()
            line = $$take_first(lines)
            equals(line, "=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="): \
                break()
        }
        !count(lines, 3): \
            qtToolchainError("SDK setup script returned unexpected output:", $$output, \
                             "Command was: $$cmd")

        # These contain only paths for the target.
        QMAKE_DEFAULT_INCDIRS = $$qtSplitPathList($$member(lines, 0))
        QMAKE_DEFAULT_LIBDIRS = $$qtSplitPathList($$member(lines, 1))
        # PATH is inherently for the host, and paths that are not shadowed
        # by vcvarsall.bat are assumed to contain only tools that work for
        # both host and target builds.
        QMAKE_DEFAULT_PATH = $$qtSplitPathList($$member(lines, 2))
        # We de-duplicate, because the script just prepends to the paths for
        # the host, some of which are identical to the ones for the target.
        QMAKE_DEFAULT_PATH = $$unique(QMAKE_DEFAULT_PATH)
    } else: msvc {
        LIB = $$getenv("LIB")
        QMAKE_DEFAULT_LIBDIRS = $$split(LIB, $$QMAKE_DIRLIST_SEP)
        INCLUDE = $$getenv("INCLUDE")
        QMAKE_DEFAULT_INCDIRS = $$split(INCLUDE, $$QMAKE_DIRLIST_SEP)
    }

    unix:if(!cross_compile|host_build) {
        isEmpty(QMAKE_DEFAULT_INCDIRS): QMAKE_DEFAULT_INCDIRS = /usr/include /usr/local/include
        isEmpty(QMAKE_DEFAULT_LIBDIRS): QMAKE_DEFAULT_LIBDIRS = /lib /usr/lib
    }

    # cache() complains about undefined variables and doesn't persist empty ones.
    !isEmpty(QMAKE_DEFAULT_INCDIRS): \
        cache($${target_prefix}.INCDIRS, set stash, QMAKE_DEFAULT_INCDIRS)
    !isEmpty(QMAKE_DEFAULT_LIBDIRS): \
        cache($${target_prefix}.LIBDIRS, set stash, QMAKE_DEFAULT_LIBDIRS)
    !isEmpty(QMAKE_DEFAULT_PATH): \
        cache($${target_prefix}.PATH, set stash, QMAKE_DEFAULT_PATH)
} else {
    QMAKE_DEFAULT_INCDIRS = $$eval($${target_prefix}.INCDIRS)
    QMAKE_DEFAULT_LIBDIRS = $$eval($${target_prefix}.LIBDIRS)
    QMAKE_DEFAULT_PATH = $$eval($${target_prefix}.PATH)
}

msvc_cross {
    qmake_inc_exp.name = INCLUDE
    qmake_inc_exp.value = $$qtNmakePathList($$QMAKE_DEFAULT_INCDIRS)
    qmake_lib_exp.name = LIB
    qmake_lib_exp.value = $$qtNmakePathList($$QMAKE_DEFAULT_LIBDIRS)
    qmake_path_exp.name = PATH
    qmake_path_exp.value = $$qtNmakePathList($$QMAKE_DEFAULT_PATH)
    QMAKE_EXPORTED_VARIABLES += qmake_inc_exp qmake_lib_exp qmake_path_exp
}

unset(target_prefix)
