conan-io/conan

View on GitHub
conan/tools/microsoft/nmaketoolchain.py

Summary

Maintainability
A
0 mins
Test Coverage
from conan.tools._compilers import cppstd_flag, build_type_flags, build_type_link_flags
from conan.tools.env import Environment
from conan.tools.microsoft.visual import msvc_runtime_flag, VCVars


class NMakeToolchain(object):
    """
    https://learn.microsoft.com/en-us/cpp/build/reference/running-nmake?view=msvc-170#toolsini-and-nmake
    We have also explored the usage of Tools.ini:
    https://learn.microsoft.com/en-us/cpp/build/reference/running-nmake?view=msvc-170
    but not possible, because it cannot include other files, it will also potentially collide with
    a user Tool.ini, without easy resolution. At least the environment is additive.
    """
    def __init__(self, conanfile):
        """
        :param conanfile: ``< ConanFile object >`` The current recipe object. Always use ``self``.
        """
        self._conanfile = conanfile

        # Flags
        self.extra_cflags = []
        self.extra_cxxflags = []
        self.extra_ldflags = []
        self.extra_defines = []

    def _format_options(self, options):
        return [f"{opt[0].replace('-', '/')}{opt[1:]}" for opt in options if len(opt) > 1]

    def _format_defines(self, defines):
        formated_defines = []
        for define in defines:
            if "=" in define:
                # CL env-var can't accept '=' sign in /D option, it can be replaced by '#' sign:
                # https://learn.microsoft.com/en-us/cpp/build/reference/cl-environment-variables
                macro, value = define.split("=", 1)
                if value and not value.isnumeric():
                    value = f'\\"{value}\\"'
                define = f"{macro}#{value}"
            formated_defines.append(f"/D\"{define}\"")
        return formated_defines

    @property
    def _cl(self):
        bt_flags = build_type_flags(self._conanfile.settings)
        bt_flags = bt_flags if bt_flags else []

        rt_flags = msvc_runtime_flag(self._conanfile)
        rt_flags = [f"/{rt_flags}"] if rt_flags else []

        cflags = []
        cflags.extend(self._conanfile.conf.get("tools.build:cflags", default=[], check_type=list))
        cflags.extend(self.extra_cflags)

        cxxflags = []
        cppstd = cppstd_flag(self._conanfile.settings)
        if cppstd:
            cxxflags.append(cppstd)
        cxxflags.extend(self._conanfile.conf.get("tools.build:cxxflags", default=[], check_type=list))
        cxxflags.extend(self.extra_cxxflags)

        defines = []
        build_type = self._conanfile.settings.get_safe("build_type")
        if build_type in ["Release", "RelWithDebInfo", "MinSizeRel"]:
            defines.append("NDEBUG")
        defines.extend(self._conanfile.conf.get("tools.build:defines", default=[], check_type=list))
        defines.extend(self.extra_defines)

        return ["/nologo"] + \
               self._format_options(bt_flags + rt_flags + cflags + cxxflags) + \
               self._format_defines(defines)

    @property
    def _link(self):
        bt_ldflags = build_type_link_flags(self._conanfile.settings)
        bt_ldflags = bt_ldflags if bt_ldflags else []

        ldflags = []
        ldflags.extend(bt_ldflags)
        ldflags.extend(self._conanfile.conf.get("tools.build:sharedlinkflags", default=[], check_type=list))
        ldflags.extend(self._conanfile.conf.get("tools.build:exelinkflags", default=[], check_type=list))
        ldflags.extend(self.extra_ldflags)

        return ["/nologo"] + self._format_options(ldflags)

    def environment(self):
        env = Environment()
        # Injection of compile flags in CL env-var:
        # https://learn.microsoft.com/en-us/cpp/build/reference/cl-environment-variables
        env.append("CL", self._cl)
        # Injection of link flags in _LINK_ env-var:
        # https://learn.microsoft.com/en-us/cpp/build/reference/linking
        env.append("_LINK_", self._link)
        # Also define some special env-vars which can override special NMake macros:
        # https://learn.microsoft.com/en-us/cpp/build/reference/special-nmake-macros
        conf_compilers = self._conanfile.conf.get("tools.build:compiler_executables", default={}, check_type=dict)
        if conf_compilers:
            compilers_mapping = {
                "AS": "asm",
                "CC": "c",
                "CPP": "cpp",
                "CXX": "cpp",
                "RC": "rc",
            }
            for env_var, comp in compilers_mapping.items():
                if comp in conf_compilers:
                    env.define(env_var, conf_compilers[comp])
        return env

    def vars(self):
        return self.environment().vars(self._conanfile, scope="build")

    def generate(self, env=None, scope="build"):
        env = env or self.environment()
        env.vars(self._conanfile, scope=scope).save_script("conannmaketoolchain")
        VCVars(self._conanfile).generate(scope=scope)