diff --git a/conan/tools/microsoft/msbuild.py b/conan/tools/microsoft/msbuild.py index e7a8df5fb37..164f2ac9c60 100644 --- a/conan/tools/microsoft/msbuild.py +++ b/conan/tools/microsoft/msbuild.py @@ -1,3 +1,9 @@ +import os +import re +import xml.etree.ElementTree as ET + +from conan.tools.microsoft.msbuilddeps import MSBuildDeps +from conan.tools.microsoft.toolchain import MSBuildToolchain from conans.errors import ConanException @@ -16,10 +22,21 @@ def msbuild_arch(arch): 'armv8': 'ARM64'}.get(str(arch)) +def msbuild_arch_to_conf_arch(arch): + return { + "Win32": "Win32", + "x86": "Win32", + "x64": "x64", + "ARM": "ARM", + "ARM64": "ARM64", + }.get(str(arch)) + + class MSBuild(object): def __init__(self, conanfile): self._conanfile = conanfile self.build_type = conanfile.settings.get_safe("build_type") + self.configuration = conanfile.settings.get_safe("build_type") # if platforms: # msvc_arch.update(platforms) arch = conanfile.settings.get_safe("arch") @@ -28,10 +45,13 @@ def __init__(self, conanfile): msvc_arch = conanfile.settings.get_safe("os.platform") self.platform = msvc_arch - def command(self, sln, targets=None): + def command(self, sln, targets=None, force_import_generated_files=False): cmd = ('msbuild "%s" /p:Configuration=%s /p:Platform=%s' % (sln, self.build_type, self.platform)) + if force_import_generated_files: + cmd += f" {self._force_import_generated_files_cmd_line_arg()}" + verbosity = msbuild_verbosity_cmd_line_arg(self._conanfile) if verbosity: cmd += " {}".format(verbosity) @@ -48,11 +68,83 @@ def command(self, sln, targets=None): return cmd - def build(self, sln, targets=None): - cmd = self.command(sln, targets=targets) + def build(self, sln, targets=None, force_import_generated_files=False): + cmd = self.command(sln, targets=targets, force_import_generated_files=force_import_generated_files) self._conanfile.run(cmd) @staticmethod def get_version(_): return NotImplementedError("get_version() method is not supported in MSBuild " "toolchain helper") + + def _get_concrete_props_file(self, root_props_file): + concrete_props_file = "" + + root = ET.parse(root_props_file).getroot() + namespace = re.match('\{.*\}', root.tag) + namespace = namespace.group(0) if namespace else "" + importgroup_element = root.find(f"{namespace}ImportGroup") + if importgroup_element: + import_elements = importgroup_element.findall(f"{namespace}Import") + if len(import_elements) == 1: + concrete_props_file = import_elements[0].attrib.get("Project") + else: + expected_condition = (f"'$(Configuration)' == '{self.configuration}' And " + f"'$(Platform)' == '{msbuild_arch_to_conf_arch(self.platform)}'") + for import_element in import_elements: + if expected_condition == import_element.attrib.get("Condition"): + concrete_props_file = import_element.attrib.get("Project") + break + + if concrete_props_file: + concrete_props_file = os.path.join(self._conanfile.generators_folder, concrete_props_file) + + if not concrete_props_file or not os.path.exists(concrete_props_file): + raise ConanException( + f"MSBuildToolchain props file is missing for configuration={self.configuration} and " + f"platform={msbuild_arch_to_conf_arch(self.platform)}." + ) + + return concrete_props_file + + def _get_msbuildtoolchain_properties(self, root_props_file): + properties = {} + + # Get properties from props file of configuration and platform + concrete_props_file = self._get_concrete_props_file(root_props_file) + root = ET.parse(concrete_props_file).getroot() + namespace = re.match('\{.*\}', root.tag) + namespace = namespace.group(0) if namespace else "" + for propertygroup in root.iter(f"{namespace}PropertyGroup"): + if propertygroup.attrib.get("Label") == "Configuration": + for child in propertygroup: + propert_name = child.tag.replace(namespace, "") + properties[propert_name] = child.text + return properties + + def _force_import_generated_files_cmd_line_arg(self): + cmd_args = [] + props_paths = [] + + # MSBuildToolchan must be in generators for this MSBuild mode + msbuildtoolchain_file = os.path.join(self._conanfile.generators_folder, MSBuildToolchain.filename) + if not os.path.exists(msbuildtoolchain_file): + raise ConanException("Missing MSBuildToolchain, it should be added to generators") + props_paths.append(msbuildtoolchain_file) + + # Properties of MSBuildToolchain must be extracted and passed manually through command line + # because they don't have precedence when props file is injected with /p:ForceImportBeforeCppTargets + properties = self._get_msbuildtoolchain_properties(msbuildtoolchain_file) + for k, v in properties.items(): + cmd_args.append(f"/p:{k}=\"{v}\"") + + # MSBuildDeps generator is optional + msbuilddeps_file = os.path.join(self._conanfile.generators_folder, MSBuildDeps.filename) + if os.path.exists(msbuilddeps_file): + props_paths.append(msbuilddeps_file) + + # Inject root props generated by MSBuildToolchain & MSBuildDeps + if props_paths: + cmd_args.append(f"/p:ForceImportBeforeCppTargets=\"{';'.join(props_paths)}\"") + + return " ".join(cmd_args) diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py index 8e0ab92dcda..9f7c8028564 100644 --- a/conan/tools/microsoft/msbuilddeps.py +++ b/conan/tools/microsoft/msbuilddeps.py @@ -7,6 +7,7 @@ from jinja2 import Template from conan.tools._check_build_profile import check_using_build_profile +from conan.tools.microsoft.toolchain import arch_to_vcproj_platform from conans.errors import ConanException from conans.util.files import load, save @@ -19,6 +20,8 @@ class MSBuildDeps(object): """ + filename = "conandeps.props" + _vars_props = textwrap.dedent("""\ @@ -92,12 +95,7 @@ def __init__(self, conanfile): self._conanfile = conanfile self._conanfile.must_use_new_helpers = True # TODO: Remove 2.0 self.configuration = conanfile.settings.build_type - # TODO: This platform is not exactly the same as ``msbuild_arch``, because it differs - # in x86=>Win32 - self.platform = {'x86': 'Win32', - 'x86_64': 'x64', - 'armv7': 'ARM', - 'armv8': 'ARM64'}.get(str(conanfile.settings.arch)) + self.platform = arch_to_vcproj_platform(str(conanfile.settings.arch)) ca_exclude = "tools.microsoft.msbuilddeps:exclude_code_analysis" self.exclude_code_analysis = self._conanfile.conf.get(ca_exclude, check_type=list) check_using_build_profile(self._conanfile) @@ -251,7 +249,6 @@ def _conandeps(self): """ this is a .props file including direct declared dependencies """ # Current directory is the generators_folder - conandeps_filename = "conandeps.props" direct_deps = self._conanfile.dependencies.filter({"direct": True}) pkg_aggregated_content = textwrap.dedent("""\ @@ -262,12 +259,12 @@ def _conandeps(self): """) for req, dep in direct_deps.items(): dep_name = self._dep_name(dep, req.build) - filename = "conan_%s.props" % dep_name + dep_filename = "conan_%s.props" % dep_name comp_condition = "'$(conan_%s_props_imported)' != 'True'" % dep_name - pkg_aggregated_content = self._dep_props_file("", conandeps_filename, filename, + pkg_aggregated_content = self._dep_props_file("", self.filename, dep_filename, condition=comp_condition, content=pkg_aggregated_content) - return {conandeps_filename: pkg_aggregated_content} + return {self.filename: pkg_aggregated_content} def _package_props_files(self, dep, build=False): """ all the files for a given package: diff --git a/conan/tools/microsoft/toolchain.py b/conan/tools/microsoft/toolchain.py index b98f6be4560..3d48474a4c1 100644 --- a/conan/tools/microsoft/toolchain.py +++ b/conan/tools/microsoft/toolchain.py @@ -12,6 +12,15 @@ from conans.util.files import save, load +def arch_to_vcproj_platform(arch): + return { + "x86": "Win32", + "x86_64": "x64", + "armv7": "ARM", + "armv8": "ARM64", + }.get(arch) + + class MSBuildToolchain(object): filename = "conantoolchain.props" @@ -52,27 +61,23 @@ def __init__(self, conanfile): self.cflags = [] self.ldflags = [] self.configuration = conanfile.settings.build_type + self.platform = arch_to_vcproj_platform(str(conanfile.settings.arch)) self.runtime_library = self._runtime_library(conanfile.settings) self.cppstd = conanfile.settings.get_safe("compiler.cppstd") self.toolset = self._msvs_toolset(conanfile) self.properties = {} check_using_build_profile(self._conanfile) - def _name_condition(self, settings): + def _name_condition(self): props = [("Configuration", self.configuration), - # TODO: refactor, put in common with MSBuildDeps. Beware this is != msbuild_arch - # because of Win32 - ("Platform", {'x86': 'Win32', - 'x86_64': 'x64', - 'armv7': 'ARM', - 'armv8': 'ARM64'}.get(settings.get_safe("arch")))] + ("Platform", self.platform)] name = "".join("_%s" % v for _, v in props if v is not None) condition = " And ".join("'$(%s)' == '%s'" % (k, v) for k, v in props if v is not None) return name.lower(), condition def generate(self): - name, condition = self._name_condition(self._conanfile.settings) + name, condition = self._name_condition() config_filename = "conantoolchain{}.props".format(name) # Writing the props files self._write_config_toolchain(config_filename) diff --git a/conans/test/functional/toolchains/intel/test_using_msbuild.py b/conans/test/functional/toolchains/intel/test_using_msbuild.py index e602ea5cffa..68e4163187e 100644 --- a/conans/test/functional/toolchains/intel/test_using_msbuild.py +++ b/conans/test/functional/toolchains/intel/test_using_msbuild.py @@ -44,7 +44,7 @@ def test_use_msbuild_toolchain(self): # Prepare the actual consumer package self.t.save({"conanfile.py": conanfile_py, "MyProject.sln": sln_file, - "MyApp/MyApp.vcxproj": myapp_vcxproj, + "MyApp/MyApp.vcxproj": myapp_vcxproj(), "MyApp/MyApp.cpp": app, 'profile': self.profile}, clean_first=True) diff --git a/conans/test/functional/toolchains/microsoft/test_msbuild.py b/conans/test/functional/toolchains/microsoft/test_msbuild.py index aeb24a05c2b..2514e950d66 100644 --- a/conans/test/functional/toolchains/microsoft/test_msbuild.py +++ b/conans/test/functional/toolchains/microsoft/test_msbuild.py @@ -1,13 +1,10 @@ import os import platform -import shutil import textwrap -import unittest import pytest -from parameterized import parameterized, parameterized_class -from conan.tools.microsoft.visual import vcvars_command +from conan.tools.microsoft.visual import msvc_version_to_vs_ide_version, vcvars_command from conans.client.tools import vs_installation_path from conans.test.assets.sources import gen_function_cpp from conans.test.conftest import tools_locations @@ -18,8 +15,6 @@ sln_file = r""" Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.757 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MyApp", "MyApp\MyApp.vcxproj", "{B58316C0-C78A-4E9B-AE8F-5D6368CE3840}" EndProject @@ -55,8 +50,15 @@ EndGlobal """ +def myapp_vcxproj(force_import_generated_files=False): + intrusive_conan_integration = r""" + + + + +""" -myapp_vcxproj = r""" + return r""" @@ -87,7 +89,7 @@ 15.0 - {B58316C0-C78A-4E9B-AE8F-5D6368CE3840} + {{B58316C0-C78A-4E9B-AE8F-5D6368CE3840}} Win32Proj MyApp @@ -95,49 +97,46 @@ Application false - v141 + v120 true Unicode Application false - v141 + v120 true Unicode Application true - v141 + v120 Unicode Application false - v141 + v120 true Unicode Application true - v141 + v120 Unicode Application false - v141 + v120 true Unicode - - - - +{} @@ -302,7 +301,7 @@ -""" +""".format("" if force_import_generated_files else intrusive_conan_integration) @pytest.mark.tool_visual_studio(version='15') @@ -343,72 +342,100 @@ def generate(self): assert "MSVC FLAG=MD!!" in client.out -vs_versions = [{"vs_version": "15", "msvc_version": "191", "ide_year": "2017", "toolset": "v141"}] - -if "17" in tools_locations['visual_studio'] and not tools_locations['visual_studio']['17'].get('disabled', False): - vs_versions.append({"vs_version": "17", "msvc_version": "193", "ide_year": "2022", "toolset": "v143"}) +def is_visual_studio_installed(vs_version): + return vs_version in tools_locations["visual_studio"] and \ + not tools_locations["visual_studio"][vs_version].get("disabled", False) -@parameterized_class(vs_versions) @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows") @pytest.mark.tool_visual_studio -class WinTest(unittest.TestCase): +class TestMSBuild: - conanfile = textwrap.dedent(""" - from conans import ConanFile - from conan.tools.microsoft import MSBuildToolchain, MSBuild, MSBuildDeps - class App(ConanFile): - settings = "os", "arch", "compiler", "build_type" - requires = "hello/0.1" - options = {"shared": [True, False]} - default_options = {"shared": False} - - def layout(self): - self.folders.generators = "conan" - self.folders.build = "." - - def generate(self): - tc = MSBuildToolchain(self) - gen = MSBuildDeps(self) - if self.options["hello"].shared and self.settings.build_type == "Release": - tc.configuration = "ReleaseShared" - gen.configuration = "ReleaseShared" - - tc.preprocessor_definitions["DEFINITIONS_BOTH"] = '"True"' - tc.preprocessor_definitions["DEFINITIONS_BOTH2"] = 'DEFINITIONS_BOTH' - tc.preprocessor_definitions["DEFINITIONS_BOTH_INT"] = 123 - if self.settings.build_type == "Debug": - tc.preprocessor_definitions["DEFINITIONS_CONFIG"] = '"Debug"' - tc.preprocessor_definitions["DEFINITIONS_CONFIG_INT"] = 234 - else: - tc.preprocessor_definitions["DEFINITIONS_CONFIG"] = '"Release"' - tc.preprocessor_definitions["DEFINITIONS_CONFIG_INT"] = 456 - tc.preprocessor_definitions["DEFINITIONS_CONFIG2"] = 'DEFINITIONS_CONFIG' - - tc.generate() - gen.generate() - - def imports(self): - if self.options["hello"].shared and self.settings.build_type == "Release": - configuration = "ReleaseShared" - if self.settings.arch == "x86_64": - dst = "x64/%s" % configuration - else: - dst = configuration - else: - configuration = self.settings.build_type - dst = "%s/%s" % (self.settings.arch, configuration) - self.copy("*.dll", src="bin", dst=dst, keep_path=False) - - def build(self): - msbuild = MSBuild(self) - msbuild.build("MyProject.sln") - """) app = gen_function_cpp(name="main", includes=["hello"], calls=["hello"], preprocessor=["DEFINITIONS_BOTH", "DEFINITIONS_BOTH2", "DEFINITIONS_BOTH_INT", "DEFINITIONS_CONFIG", "DEFINITIONS_CONFIG2", "DEFINITIONS_CONFIG_INT"]) + _vs_versions = { + "11": { + "msvc_version": "170", + "ide_year": "2012", + }, + "12": { + "msvc_version": "180", + "ide_year": "2013", + }, + "14": { + "msvc_version": "190", + "ide_year": "2015", + }, + "15": { + "msvc_version": "191", + "ide_year": "2017", + }, + "16": { + "msvc_version": "192", + "ide_year": "2019", + }, + "17": { + "msvc_version": "193", + "ide_year": "2022", + }, + } + + @staticmethod + def _conanfile(force_import_generated_files=False): + return textwrap.dedent(f""" + from conans import ConanFile + from conan.tools.microsoft import MSBuildToolchain, MSBuild, MSBuildDeps + class App(ConanFile): + settings = "os", "arch", "compiler", "build_type" + requires = "hello/0.1" + options = {{"shared": [True, False]}} + default_options = {{"shared": False}} + + def layout(self): + self.folders.generators = "conan" + self.folders.build = "." + + def generate(self): + tc = MSBuildToolchain(self) + gen = MSBuildDeps(self) + if self.options["hello"].shared and self.settings.build_type == "Release": + tc.configuration = "ReleaseShared" + gen.configuration = "ReleaseShared" + + tc.preprocessor_definitions["DEFINITIONS_BOTH"] = '"True"' + tc.preprocessor_definitions["DEFINITIONS_BOTH2"] = 'DEFINITIONS_BOTH' + tc.preprocessor_definitions["DEFINITIONS_BOTH_INT"] = 123 + if self.settings.build_type == "Debug": + tc.preprocessor_definitions["DEFINITIONS_CONFIG"] = '"Debug"' + tc.preprocessor_definitions["DEFINITIONS_CONFIG_INT"] = 234 + else: + tc.preprocessor_definitions["DEFINITIONS_CONFIG"] = '"Release"' + tc.preprocessor_definitions["DEFINITIONS_CONFIG_INT"] = 456 + tc.preprocessor_definitions["DEFINITIONS_CONFIG2"] = 'DEFINITIONS_CONFIG' + + tc.generate() + gen.generate() + + def imports(self): + if self.options["hello"].shared and self.settings.build_type == "Release": + configuration = "ReleaseShared" + if self.settings.arch == "x86_64": + dst = "x64/%s" % configuration + else: + dst = configuration + else: + configuration = self.settings.build_type + dst = "%s/%s" % (self.settings.arch, configuration) + self.copy("*.dll", src="bin", dst=dst, keep_path=False) + + def build(self): + msbuild = MSBuild(self) + msbuild.build("MyProject.sln", force_import_generated_files={force_import_generated_files}) + """) + @staticmethod def _run_app(client, arch, build_type, shared=None): if build_type == "Release" and shared: @@ -422,42 +449,48 @@ def _run_app(client, arch, build_type, shared=None): command_str = "x64\\%s\\MyApp.exe" % configuration client.run_command(command_str) - @parameterized.expand([("Visual Studio", "15", "MT", "17"), - ("msvc", "191", "static", "17"), - ("msvc", "190", "static", "14")] - ) + @pytest.mark.parametrize("arch", ["x86", "x86_64"]) + @pytest.mark.parametrize( + "compiler,version,runtime,cppstd", + [ + ("Visual Studio", "14", "MT", "14"), + ("msvc", "190", "static", "14"), + ("Visual Studio", "15", "MT", "17"), + ("msvc", "191", "static", "17"), + ("Visual Studio", "16", "MT", "17"), + ("msvc", "192", "static", "17"), + ("Visual Studio", "17", "MT", "17"), + ("msvc", "193", "static", "17"), + ], + ) + @pytest.mark.parametrize("build_type", ["Debug", "Release"]) + @pytest.mark.parametrize("force_import_generated_files", [False, True]) @pytest.mark.tool_cmake - def test_toolchain_win_vs2017(self, compiler, version, runtime, cppstd): - if self.vs_version != "15": - pytest.skip("test for Visual Studio 2017") - else: - self.check_toolchain_win(compiler, version, runtime, cppstd) - - @parameterized.expand([("Visual Studio", "17", "MT", "17"), - ("msvc", "193", "static", "17")] - ) - def test_toolchain_win_vs2022(self, compiler, version, runtime, cppstd): - if self.vs_version != "17": - pytest.skip("test for Visual Studio 2022") - else: - self.check_toolchain_win(compiler, version, runtime, cppstd) + def test_toolchain_win(self, arch, compiler, version, runtime, cppstd, build_type, force_import_generated_files): + vs_version = msvc_version_to_vs_ide_version(version) if compiler == "msvc" else version + if not is_visual_studio_installed(vs_version): + pytest.skip(f"Visual Studio {vs_version} not installed") + + if compiler == "Visual Studio" and build_type == "Debug": + runtime += "d" - def check_toolchain_win(self, compiler, version, runtime, cppstd): client = TestClient(path_with_spaces=False) settings = [("compiler", compiler), ("compiler.version", version), ("compiler.cppstd", cppstd), ("compiler.runtime", runtime), - ("build_type", "Release"), - ("arch", "x86")] + ("build_type", build_type), + ("arch", arch)] + if compiler == "msvc": + settings.append(("compiler.runtime_type", "Debug" if build_type == "Debug" else "Release")) - profile = textwrap.dedent(""" + profile = textwrap.dedent(f""" [settings] os=Windows [conf] tools.microsoft.msbuild:vs_version={vs_version} - """.format(vs_version=self.vs_version)) + """) client.save({"myprofile": profile}) # Build the profile according to the settings provided settings = " ".join('-s %s="%s"' % (k, v) for k, v in settings if v) @@ -466,83 +499,71 @@ def check_toolchain_win(self, compiler, version, runtime, cppstd): client.run("create . hello/0.1@ %s" % (settings, )) # Prepare the actual consumer package - client.save({"conanfile.py": self.conanfile, + client.save({"conanfile.py": self._conanfile(force_import_generated_files), "MyProject.sln": sln_file, - "MyApp/MyApp.vcxproj": myapp_vcxproj, + "MyApp/MyApp.vcxproj": myapp_vcxproj(force_import_generated_files), "MyApp/MyApp.cpp": self.app, "myprofile": profile}, clean_first=True) # Run the configure corresponding to this test case client.run("install . %s -if=conan -pr=myprofile" % (settings, )) - self.assertIn("conanfile.py: MSBuildToolchain created conantoolchain_release_win32.props", - client.out) + msbuild_arch = { + "x86": "x86", + "x86_64": "x64", + "armv7": "ARM", + "armv8": "ARM64", + }[arch] + props_arch = { + "x86": "Win32", + "x86_64": "x64", + "armv7": "ARM", + "armv8": "ARM64", + }[arch] + props_file = f"conantoolchain_{build_type.lower()}_{props_arch.lower()}.props" + assert f"conanfile.py: MSBuildToolchain created {props_file}" in client.out client.run("build . -if=conan") - self.assertIn("Visual Studio {ide_year}".format(ide_year=self.ide_year), client.out) - self.assertIn("[vcvarsall.bat] Environment initialized for: 'x86'", client.out) - - self._run_app(client, "x86", "Release") - self.assertIn("Hello World Release", client.out) - compiler_version = version if compiler == "msvc" else self.msvc_version - check_exe_run(client.out, "main", "msvc", compiler_version, "Release", "x86", cppstd, - {"DEFINITIONS_BOTH": 'True', + assert "Visual Studio {ide_year}".format(ide_year=self._vs_versions[vs_version]["ide_year"]) in client.out + assert f"[vcvarsall.bat] Environment initialized for: '{msbuild_arch.lower()}'" in client.out + + self._run_app(client, arch, build_type) + assert f"Hello World {build_type}" in client.out + compiler_version = self._vs_versions[vs_version]["msvc_version"] + check_exe_run(client.out, "main", "msvc", compiler_version, build_type, arch, cppstd, + {"DEFINITIONS_BOTH": "True", "DEFINITIONS_BOTH2": "True", "DEFINITIONS_BOTH_INT": "123", - "DEFINITIONS_CONFIG": 'Release', - "DEFINITIONS_CONFIG2": 'Release', - "DEFINITIONS_CONFIG_INT": "456"}) - static_runtime = True if runtime == "static" or "MT" in runtime else False - check_vs_runtime("Release/MyApp.exe", client, self.vs_version, build_type="Release", - static_runtime=static_runtime) - + "DEFINITIONS_CONFIG": build_type, + "DEFINITIONS_CONFIG2": build_type, + "DEFINITIONS_CONFIG_INT": "234" if build_type == "Debug" else "456"}) + static_runtime = runtime == "static" or "MT" in runtime + check_vs_runtime("{}{}/MyApp.exe".format("" if arch == "x86" else f"{msbuild_arch}/", build_type), client, + vs_version, build_type=build_type, static_runtime=static_runtime) + + @pytest.mark.parametrize( + "compiler,version,runtime,cppstd", + [ + ("Visual Studio", "14", "MT", "14"), + ("msvc", "190", "static", "14"), + ("Visual Studio", "15", "MT", "17"), + ("msvc", "191", "static", "17"), + ("Visual Studio", "16", "MT", "17"), + ("msvc", "192", "static", "17"), + ("Visual Studio", "17", "MT", "17"), + ("msvc", "193", "static", "17"), + ], + ) @pytest.mark.tool_cmake - def test_toolchain_win_debug(self): - client = TestClient(path_with_spaces=False) - settings = [("compiler", "Visual Studio"), - ("compiler.version", self.vs_version), - ("compiler.toolset", "v140"), - ("compiler.runtime", "MDd"), - ("build_type", "Debug"), - ("arch", "x86_64")] - - # Build the profile according to the settings provided - settings = " ".join('-s %s="%s"' % (k, v) for k, v in settings if v) - - client.run("new hello/0.1 -s") - client.run("create . hello/0.1@ %s" % (settings,)) - - # Prepare the actual consumer package - client.save({"conanfile.py": self.conanfile, - "MyProject.sln": sln_file, - "MyApp/MyApp.vcxproj": myapp_vcxproj, - "MyApp/MyApp.cpp": self.app}, - clean_first=True) - - # Run the configure corresponding to this test case - client.run("install . %s -if=conan" % (settings, )) - self.assertIn("conanfile.py: MSBuildToolchain created conantoolchain_debug_x64.props", - client.out) - client.run("build . -if=conan") - self.assertIn("Visual Studio {ide_year}".format(ide_year=self.ide_year), client.out) - self.assertIn("[vcvarsall.bat] Environment initialized for: 'x64'", client.out) - self._run_app(client, "x64", "Debug") - self.assertIn("Hello World Debug", client.out) - check_exe_run(client.out, "main", "msvc", "190", "Debug", "x86_64", "14", - {"DEFINITIONS_BOTH": 'True', - "DEFINITIONS_BOTH2": "True", - "DEFINITIONS_BOTH_INT": "123", - "DEFINITIONS_CONFIG": 'Debug', - "DEFINITIONS_CONFIG2": 'Debug', - "DEFINITIONS_CONFIG_INT": "234"}) - check_vs_runtime("x64/Debug/MyApp.exe", client, self.vs_version, build_type="Debug") + def test_toolchain_win_multi(self, compiler, version, runtime, cppstd): + vs_version = msvc_version_to_vs_ide_version(version) if compiler == "msvc" else version + if not is_visual_studio_installed(vs_version): + pytest.skip(f"Visual Studio {vs_version} not installed") - @pytest.mark.tool_cmake - def test_toolchain_win_multi(self): client = TestClient(path_with_spaces=False) - settings = [("compiler", "Visual Studio"), - ("compiler.version", self.vs_version), - ("compiler.cppstd", "17")] + settings = [("compiler", compiler), + ("compiler.version", version), + ("compiler.cppstd", cppstd)] settings = " ".join('-s %s="%s"' % (k, v) for k, v in settings if v) client.run("new hello/0.1 -m=cmake_lib") @@ -553,24 +574,34 @@ def test_toolchain_win_multi(self): # TODO: It is a bit ugly to remove manually build_test_folder = os.path.join(client.current_folder, "test_package", "build") rmdir(build_test_folder) - runtime = "MT" if build_type == "Release" else "MTd" - client.run("create . hello/0.1@ %s -s build_type=%s -s arch=%s -s compiler.runtime=%s " - " -o hello:shared=%s" % (settings, build_type, arch, runtime, shared)) + cmd = f"create . hello/0.1@ {settings} -s build_type={build_type} -s arch={arch} -o hello:shared={shared}" + if compiler == "Visual Studio": + runtime_suffix = "d" if build_type == "Debug" else "" + cmd += f" -s compiler.runtime={runtime}{runtime_suffix}" + else: + runtime_type = "Debug" if build_type == "Debug" else "Release" + cmd += f" -s compiler.runtime={runtime} -s compiler.runtime_type={runtime_type}" + client.run(cmd) # Prepare the actual consumer package - client.save({"conanfile.py": self.conanfile, + client.save({"conanfile.py": self._conanfile(), "MyProject.sln": sln_file, - "MyApp/MyApp.vcxproj": myapp_vcxproj, + "MyApp/MyApp.vcxproj": myapp_vcxproj(), "MyApp/MyApp.cpp": self.app}, clean_first=True) # Run the configure corresponding to this test case for build_type, arch, shared in configs: - runtime = "MT" if build_type == "Release" else "MTd" - client.run("install . %s -s build_type=%s -s arch=%s -s compiler.runtime=%s -if=conan" - " -o hello:shared=%s" % (settings, build_type, arch, runtime, shared)) + cmd = f"install . {settings} -s build_type={build_type} -s arch={arch} -o hello:shared={shared}" + if compiler == "Visual Studio": + runtime_suffix = "d" if build_type == "Debug" else "" + cmd += f" -s compiler.runtime={runtime}{runtime_suffix}" + else: + runtime_type = "Debug" if build_type == "Debug" else "Release" + cmd += f" -s compiler.runtime={runtime} -s compiler.runtime_type={runtime_type}" + client.run(cmd) - vs_path = vs_installation_path(self.vs_version) + vs_path = vs_installation_path(vs_version) vcvars_path = os.path.join(vs_path, "VC/Auxiliary/Build/vcvarsall.bat") for build_type, arch, shared in configs: @@ -585,11 +616,11 @@ def test_toolchain_win_multi(self): '"%s" x64 && msbuild "MyProject.sln" /p:Configuration=%s ' '/p:Platform=%s ' % (vcvars_path, configuration, platform_arch)) client.run_command(cmd) - self.assertIn("Visual Studio {ide_year}".format(ide_year=self.ide_year), client.out) - self.assertIn("[vcvarsall.bat] Environment initialized for: 'x64'", client.out) + assert "Visual Studio {ide_year}".format(ide_year=self._vs_versions[vs_version]["ide_year"]) in client.out + assert "[vcvarsall.bat] Environment initialized for: 'x64'" in client.out self._run_app(client, arch, build_type, shared) - check_exe_run(client.out, "main", "msvc", self.msvc_version, build_type, arch, "17", + check_exe_run(client.out, "main", "msvc", self._vs_versions[vs_version]["msvc_version"], build_type, arch, cppstd, {"DEFINITIONS_BOTH": "True", "DEFINITIONS_CONFIG": build_type}) @@ -597,14 +628,14 @@ def test_toolchain_win_multi(self): command_str = "%s\\MyApp.exe" % configuration else: command_str = "x64\\%s\\MyApp.exe" % configuration - vcvars = vcvars_command(version=self.vs_version, architecture="amd64") + vcvars = vcvars_command(version=vs_version, architecture="amd64") cmd = ('%s && dumpbin /dependents "%s"' % (vcvars, command_str)) client.run_command(cmd) if shared: - self.assertIn("hello.dll", client.out) + assert "hello.dll" in client.out else: - self.assertNotIn("hello.dll", client.out) - self.assertIn("KERNEL32.dll", client.out) + assert "hello.dll" not in client.out + assert "KERNEL32.dll" in client.out def test_msvc_runtime_flag_common_usage():