diff --git a/conan/tools/cmake/generic.py b/conan/tools/cmake/generic.py index da630c6235b..a05040fd42b 100644 --- a/conan/tools/cmake/generic.py +++ b/conan/tools/cmake/generic.py @@ -42,7 +42,7 @@ def get_generator_platform(settings, generator): if settings.get_safe("os") == "WindowsCE": return settings.get_safe("os.platform") - if (compiler == "Visual Studio" or compiler_base == "Visual Studio") and \ + if (compiler in ("Visual Studio", "msvc") or compiler_base == "Visual Studio") and \ generator and "Visual" in generator: return {"x86": "Win32", "x86_64": "x64", @@ -213,6 +213,12 @@ def _runtimes(self): "MTd": "MultiThreadedDebug", "MD": "MultiThreadedDLL", "MDd": "MultiThreadedDebugDLL"}[runtime] + if compiler == "msvc": + runtime_type = settings.get_safe("compiler.runtime_type") + rt = "MultiThreadedDebug" if runtime_type == "Debug" else "MultiThreaded" + if runtime != "static": + rt += "DLL" + config_dict[build_type] = rt return config_dict def _get_libcxx(self): diff --git a/conan/tools/cmake/utils.py b/conan/tools/cmake/utils.py index 705af2e02b6..7973585e02d 100644 --- a/conan/tools/cmake/utils.py +++ b/conan/tools/cmake/utils.py @@ -1,5 +1,6 @@ import os +from conans.errors import ConanException from conans.util.log import logger @@ -48,6 +49,21 @@ def get_generator(conanfile): compiler = conanfile.settings.get_safe("compiler") compiler_version = conanfile.settings.get_safe("compiler.version") + + if compiler == "msvc": + if compiler_version is None: + raise ConanException("compiler.version must be defined") + version = compiler_version[:4] # Remove the latest version number 19.1X if existing + try: + _visuals = {'19.0': '14 2015', + '19.1': '15 2017', + '19.2': '16 2019'}[version] + except KeyError: + raise ConanException("compiler.version '{}' doesn't map " + "to a known VS version".format(version)) + base = "Visual Studio %s" % _visuals + return base + compiler_base = conanfile.settings.get_safe("compiler.base") arch = conanfile.settings.get_safe("arch") diff --git a/conan/tools/microsoft/__init__.py b/conan/tools/microsoft/__init__.py index 82a697a3fd6..ae839d2bbd5 100644 --- a/conan/tools/microsoft/__init__.py +++ b/conan/tools/microsoft/__init__.py @@ -1,3 +1,17 @@ from .toolchain import MSBuildToolchain from .msbuild import MSBuild from .msbuilddeps import MSBuildDeps + + +def msvc_runtime_flag(conanfile): + settings = conanfile.settings + compiler = settings.get_safe("compiler") + runtime = settings.get_safe("compiler.runtime") + if compiler == "Visual Studio": + return runtime + if compiler == "msvc": + runtime_type = settings.get_safe("compiler.runtime_type") + runtime = "MT" if runtime == "static" else "MD" + if runtime_type == "Debug": + runtime = "{}d".format(runtime) + return runtime diff --git a/conans/client/build/cppstd_flags.py b/conans/client/build/cppstd_flags.py index a1e58de92a6..b4c3e7f3b9d 100644 --- a/conans/client/build/cppstd_flags.py +++ b/conans/client/build/cppstd_flags.py @@ -32,6 +32,7 @@ def cppstd_flag(compiler, compiler_version, cppstd, compiler_base=None): "clang": _cppstd_clang, "apple-clang": _cppstd_apple_clang, "Visual Studio": _cppstd_visualstudio, + "msvc": _cppstd_msvc, "intel": cppstd_intel, "mcst-lcc": _cppstd_mcst_lcc}.get(str(compiler), None) flag = None @@ -112,6 +113,23 @@ def _cppstd_visualstudio(visual_version, cppstd): return "/std:%s" % flag if flag else None +def _cppstd_msvc(visual_version, cppstd): + # https://docs.microsoft.com/en-us/cpp/build/reference/std-specify-language-standard-version + v14 = None + v17 = None + v20 = None + + if Version(visual_version) >= "19.0": + v14 = "c++14" + v17 = "c++latest" + if Version(visual_version) >= "19.1": + v17 = "c++17" + v20 = "c++latest" + + flag = {"14": v14, "17": v17, "20": v20}.get(str(cppstd), None) + return "/std:%s" % flag if flag else None + + def _cppstd_apple_clang(clang_version, cppstd): """ Inspired in: diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py index e0e010d20ee..eccdbbcb789 100644 --- a/conans/client/conf/__init__.py +++ b/conans/client/conf/__init__.py @@ -85,6 +85,13 @@ LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142, llvm, ClangCL] cppstd: [None, 14, 17, 20] + msvc: + version: ["19.0", + "19.1", "19.10", "19.11", "19.12", "19.13", "19.14", "19.15", "19.16", + "19.2", "19.20", "19.21", "19.22", "19.23", "19.24", "19.25", "19.26", "19.27", "19.28"] + runtime: [static, dynamic] + runtime_type: [Debug, Release] + cppstd: [14, 17, 20] clang: version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0", "5.0", "6.0", "7.0", "7.1", @@ -497,6 +504,14 @@ def full_transitive_package_id(self): except ConanException: return None + @property + def msvc_visual_incompatible(self): + try: + visual_comp = self.get_item("general.msvc_visual_incompatible") + return visual_comp.lower() in ("1", "true") + except ConanException: + return False + @property def short_paths_home(self): short_paths_home = get_env("CONAN_USER_HOME_SHORT") diff --git a/conans/client/graph/graph_binaries.py b/conans/client/graph/graph_binaries.py index aa74c5acfb6..4a0980fcd4e 100644 --- a/conans/client/graph/graph_binaries.py +++ b/conans/client/graph/graph_binaries.py @@ -339,6 +339,10 @@ def _compute_package_id(self, node, default_package_id_mode, default_python_requ python_requires=python_requires, default_python_requires_id_mode= default_python_requires_id_mode) + if not self._cache.config.msvc_visual_incompatible: + msvc_compatible = conanfile.info.msvc_compatible() + if msvc_compatible: + conanfile.compatible_packages.append(msvc_compatible) # Once we are done, call package_id() to narrow and change possible values with conanfile_exception_formatter(str(conanfile), "package_id"): diff --git a/conans/client/migrations_settings.py b/conans/client/migrations_settings.py index 40cf101af2e..1333ed58bfc 100644 --- a/conans/client/migrations_settings.py +++ b/conans/client/migrations_settings.py @@ -1890,6 +1890,13 @@ LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142, llvm, ClangCL] cppstd: [None, 14, 17, 20] + msvc: + version: ["19.0", + "19.1", "19.10", "19.11", "19.12", "19.13", "19.14", "19.15", "19.16", + "19.2", "19.20", "19.21", "19.22", "19.23", "19.24", "19.25", "19.26", "19.27", "19.28"] + runtime: [static, dynamic] + runtime_type: [Debug, Release] + cppstd: [14, 17, 20] clang: version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0", "5.0", "6.0", "7.0", "7.1", diff --git a/conans/client/settings_preprocessor.py b/conans/client/settings_preprocessor.py index 93a3998a0ef..8730b765959 100644 --- a/conans/client/settings_preprocessor.py +++ b/conans/client/settings_preprocessor.py @@ -61,6 +61,10 @@ def _fill_runtime(settings): settings.compiler.base.runtime = runtime msg = "Setting 'compiler.base.runtime' not declared, automatically adjusted to '%s'" logger.info(msg % runtime) + elif settings.compiler == "msvc": + if settings.get_safe("compiler.runtime_type") is None: + runtime = "Debug" if settings.get_safe("build_type") == "Debug" else "Release" + settings.compiler.runtime_type = runtime except Exception: # If the settings structure doesn't match these general # asumptions, like unexistant runtime pass diff --git a/conans/model/info.py b/conans/model/info.py index 3b6cbfb3e29..8eabacdc727 100644 --- a/conans/model/info.py +++ b/conans/model/info.py @@ -577,6 +577,27 @@ def header_only(self): self.options.clear() self.requires.clear() + def msvc_compatible(self): + if self.settings.compiler != "msvc": + return + + compatible = self.clone() + version = compatible.settings.compiler.version + runtime = compatible.settings.compiler.runtime + runtime_type = compatible.settings.compiler.runtime_type + + compatible.settings.compiler = "Visual Studio" + version = str(version)[:4] + _visuals = {'19.0': '14', + '19.1': '15', + '19.2': '16'} + compatible.settings.compiler.version = _visuals[version] + runtime = "MT" if runtime == "static" else "MD" + if runtime_type == "Debug": + runtime = "{}d".format(runtime) + compatible.settings.compiler.runtime = runtime + return compatible + def vs_toolset_compatible(self): """Default behaviour, same package for toolset v140 with compiler=Visual Studio 15 than using Visual Studio 14""" @@ -613,7 +634,11 @@ def default_std_matching(self): If we are building with gcc 7, and we specify -s cppstd=gnu14, it's the default, so the same as specifying None, packages are the same """ - + if self.full_settings.compiler == "msvc": + # This post-processing of package_id was a hack to introduce this in a non-breaking way + # This whole function will be removed in Conan 2.0, and the responsibility will be + # of the input profile + return if (self.full_settings.compiler and self.full_settings.compiler.version): default = cppstd_default(self.full_settings) diff --git a/conans/test/functional/toolchains/test_cmake.py b/conans/test/functional/toolchains/test_cmake.py index 9e27fe5de2c..a8be13d638a 100644 --- a/conans/test/functional/toolchains/test_cmake.py +++ b/conans/test/functional/toolchains/test_cmake.py @@ -163,7 +163,9 @@ def _run_app(self, build_type, bin_folder=False, msg="App", dyld_path=None): @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows") class WinTest(Base): @parameterized.expand([("Visual Studio", "Debug", "MTd", "15", "14", "x86", "v140", True), - ("Visual Studio", "Release", "MD", "15", "17", "x86_64", "", False)] + ("Visual Studio", "Release", "MD", "15", "17", "x86_64", "", False), + ("msvc", "Debug", "static", "19.1", "14", "x86", None, True), + ("msvc", "Release", "dynamic", "19.11", "17", "x86_64", None, False)] ) def test_toolchain_win(self, compiler, build_type, runtime, version, cppstd, arch, toolset, shared): @@ -230,7 +232,7 @@ def _verify_out(marker=">>"): self._run_app("Debug", bin_folder=True) check_msc_ver(toolset or "v141", self.client.out) self.assertIn("main _MSVC_LANG20{}".format(cppstd), self.client.out) - static = "MT" in runtime + static = (runtime == "static" or "MT" in runtime) check_vs_runtime("build/Release/app.exe", self.client, "15", build_type="Release", static=static) check_vs_runtime("build/Debug/app.exe", self.client, "15", build_type="Debug", diff --git a/conans/test/functional/toolchains/test_msbuild.py b/conans/test/functional/toolchains/test_msbuild.py index 037d4977fdb..17bfb3c73b8 100644 --- a/conans/test/functional/toolchains/test_msbuild.py +++ b/conans/test/functional/toolchains/test_msbuild.py @@ -305,6 +305,29 @@ """ +@pytest.mark.tool_visual_studio +def test_msvc_runtime_flag(): + client = TestClient() + conanfile = textwrap.dedent(""" + from conans import ConanFile + from conan.tools.microsoft import msvc_runtime_flag + class App(ConanFile): + settings = "os", "arch", "compiler", "build_type" + + def generate(self): + self.output.info("MSVC FLAG={}!!".format(msvc_runtime_flag(self))) + """) + client.save({"conanfile.py": conanfile}) + client.run('install . -s compiler="Visual Studio" -s compiler.version=15 -s compiler.runtime=MD') + assert "MSVC FLAG=MD!!" in client.out + client.run('install . -s compiler=msvc -s compiler.version=19.1 -s compiler.runtime=static ' + '-s compiler.runtime_type=Debug -s compiler.cppstd=14') + assert "MSVC FLAG=MTd!!" in client.out + client.run('install . -s compiler=msvc -s compiler.version=19.1 -s compiler.runtime=dynamic ' + '-s compiler.cppstd=14') + assert "MSVC FLAG=MD!!" in client.out + + @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows") @pytest.mark.tool_visual_studio class WinTest(unittest.TestCase): diff --git a/conans/test/integration/package_id/compatible_test.py b/conans/test/integration/package_id/compatible_test.py index f35d6b9d9c6..a54b9a47592 100644 --- a/conans/test/integration/package_id/compatible_test.py +++ b/conans/test/integration/package_id/compatible_test.py @@ -492,3 +492,20 @@ def package_id(self): self.assertIn("pkg/0.1@user/testing:1ebf4db7209535776307f9cd06e00d5a8034bc84 - Cache", client.out) self.assertIn("pkg/0.1@user/testing: Already installed!", client.out) + + +def test_msvc_visual_incompatible(): + conanfile = GenConanfile().with_settings("os", "compiler", "build_type", "arch") + client = TestClient() + client.save({"conanfile.py": conanfile}) + client.run('create . pkg/0.1@ -s os=Windows -s compiler="Visual Studio" -s compiler.version=15 ' + '-s compiler.runtime=MD -s build_type=Release -s arch=x86_64') + client.run("install pkg/0.1@ -s os=Windows -s compiler=msvc -s compiler.version=19.1 " + "-s compiler.runtime=dynamic -s build_type=Release -s arch=x86_64 " + "-s compiler.cppstd=14") + assert "Using compatible package" in client.out + client.run("config set general.msvc_visual_incompatible=1") + client.run("install pkg/0.1@ -s os=Windows -s compiler=msvc -s compiler.version=19.1 " + "-s compiler.runtime=dynamic -s build_type=Release -s arch=x86_64 " + "-s compiler.cppstd=14", assert_error=True) + assert "ERROR: Missing prebuilt package for 'pkg/0.1'" in client.out