1import copy
2import os
3
4from conans.client.build.compiler_flags import build_type_define, build_type_flags, format_defines, \
5    include_path_option, parallel_compiler_cl_flag, visual_runtime
6from conans.client.build.cppstd_flags import cppstd_from_settings, \
7    cppstd_flag_new as cppstd_flag
8from conans.client.tools.files import VALID_LIB_EXTENSIONS
9
10
11class VisualStudioBuildEnvironment(object):
12    """
13    - LIB: library paths with semicolon separator
14    - CL: /I (include paths)
15    - _LINK_: linker options and libraries
16    - UseEnv: True https://github.com/conan-io/conan/pull/4583
17
18    https://msdn.microsoft.com/en-us/library/19z1t1wy.aspx
19    https://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx
20    https://msdn.microsoft.com/en-us/library/9s7c9wdw.aspx
21    https://msdn.microsoft.com/en-us/library/6y6t9esh.aspx
22
23    """
24    def __init__(self, conanfile, with_build_type_flags=True):
25        """
26        :param conanfile: ConanFile instance
27        """
28        self._with_build_type_flags = with_build_type_flags
29
30        self._conanfile = conanfile
31        self._settings = conanfile.settings
32        self._deps_cpp_info = conanfile.deps_cpp_info
33        self._runtime = self._settings.get_safe("compiler.runtime")
34
35        self.include_paths = conanfile.deps_cpp_info.include_paths
36        self.lib_paths = conanfile.deps_cpp_info.lib_paths
37        self.defines = copy.copy(conanfile.deps_cpp_info.defines)
38        self.flags = self._configure_flags()
39        self.cxx_flags = copy.copy(self._deps_cpp_info.cxxflags)
40        self.link_flags = self._configure_link_flags()
41        self.libs = conanfile.deps_cpp_info.libs
42        self.std = self._std_cpp()
43        self.parallel = False
44
45    def _configure_link_flags(self):
46        ret = copy.copy(self._deps_cpp_info.exelinkflags)
47        ret.extend(self._deps_cpp_info.sharedlinkflags)
48        return ret
49
50    def _configure_flags(self):
51        ret = copy.copy(self._deps_cpp_info.cflags)
52        ret.extend(vs_build_type_flags(self._settings, with_flags=self._with_build_type_flags))
53        return ret
54
55    def _get_cl_list(self, quotes=True):
56        # FIXME: It should be managed with the compiler_flags module
57        # But need further investigation about the quotes and so on, so better to not break anything
58        if quotes:
59            ret = ['%s"%s"' % (include_path_option, lib) for lib in self.include_paths]
60        else:
61            ret = ['%s%s' % (include_path_option, lib) for lib in self.include_paths]
62
63        runtime = visual_runtime(self._runtime)
64        if runtime:
65            ret.append(runtime)
66
67        ret.extend(format_defines(self.defines))
68        ret.extend(self.flags)
69        ret.extend(self.cxx_flags)
70
71        if self.parallel:  # Build source in parallel
72            ret.append(parallel_compiler_cl_flag(output=self._conanfile.output))
73
74        if self.std:
75            ret.append(self.std)
76
77        return ret
78
79    def _get_link_list(self):
80        # FIXME: Conan 2.0. The libs are being added twice to visual_studio
81        # one in the conanbuildinfo.props, and the other in the env-vars
82        def format_lib(lib):
83            ext = os.path.splitext(lib)[1]
84            return lib if ext in VALID_LIB_EXTENSIONS else '%s.lib' % lib
85
86        ret = [flag for flag in self.link_flags]  # copy
87        ret.extend([format_lib(lib) for lib in self.libs])
88
89        return ret
90
91    @property
92    def vars(self):
93        """Used in conanfile with environment_append"""
94        flags = self._get_cl_list()
95        link_flags = self._get_link_list()
96
97        cl_args = " ".join(flags) + _environ_value_prefix("CL")
98        link_args = " ".join(link_flags)
99        lib_paths = (";".join(['%s' % lib for lib in self.lib_paths]) +
100                     _environ_value_prefix("LIB", ";"))
101        return {"CL": cl_args,
102                "LIB": lib_paths,
103                "_LINK_": link_args,
104                "UseEnv": "True"}
105
106    @property
107    def vars_dict(self):
108        """Used in virtualbuildenvironment"""
109        # Here we do not quote the include paths, it's going to be used by virtual environment
110        cl = self._get_cl_list(quotes=False)
111        link = self._get_link_list()
112
113        lib = [lib for lib in self.lib_paths]  # copy
114
115        if os.environ.get("CL", None):
116            cl.append(os.environ.get("CL"))
117
118        if os.environ.get("LIB", None):
119            lib.append(os.environ.get("LIB"))
120
121        if os.environ.get("_LINK_", None):
122            link.append(os.environ.get("_LINK_"))
123
124        ret = {"CL": cl,
125               "LIB": lib,
126               "_LINK_": link,
127               "UseEnv": "True"}
128        return ret
129
130    def _std_cpp(self):
131        return vs_std_cpp(self._settings)
132
133
134def vs_build_type_flags(settings, with_flags=True):
135    build_type = settings.get_safe("build_type")
136    ret = []
137    btd = build_type_define(build_type=build_type)
138    if btd:
139        ret.extend(format_defines([btd]))
140    if with_flags:
141        # When using to build a vs project we don't want to adjust these flags
142        btfs = build_type_flags(settings)
143        if btfs:
144            ret.extend(btfs)
145
146    return ret
147
148
149def vs_std_cpp(settings):
150    cppstd = cppstd_from_settings(settings)
151    if settings.get_safe("compiler") == "Visual Studio" and cppstd:
152        flag = cppstd_flag(settings)
153        return flag
154    return None
155
156
157def _environ_value_prefix(var_name, prefix=" "):
158    if os.environ.get(var_name, ""):
159        return "%s%s" % (prefix, os.environ.get(var_name, ""))
160    else:
161        return ""
162