Repository: conan-io/conan

Issue:
"visual_studio" generator support for multi-config variables (N configs -> 1 package)
I'm introducing ```conan``` in products of my company.
But, I found one limitation and I want improvement of it.

http://docs.conan.io/en/latest/creating_packages/package_approaches.html#n-configs-1-package

> The automatic conversion of multi-config variables to generators is currently only implemented in the cmake and txt generators. If you want to have support for them in another build system, please open a GitHub issue for it.

I found that sentence in above documentation. And I want to have support for them in **Visual Studio**, So I open this issue.

Thank you.


Discussion:
You mean to support it with the `visual_studio` generator?
@lasote Yes, ```visual_studio``` and ```visual_studio_multi```, both.

Resolving patch:
diff --git a/conans/client/generators/visualstudio.py b/conans/client/generators/visualstudio.py
--- a/conans/client/generators/visualstudio.py
+++ b/conans/client/generators/visualstudio.py
@@ -13,15 +13,19 @@ class VisualStudioGenerator(Generator):
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Label="Conan-RootDirs">{item_properties}
   </PropertyGroup>
-  <PropertyGroup Label="ConanVariables">
-    <ConanBinaryDirectories>{bin_dirs}</ConanBinaryDirectories>
-    <ConanResourceDirectories>{res_dirs}</ConanResourceDirectories>
+  {properties}
+  <ItemGroup />
+</Project>'''
+
+    properties_template = '''  <PropertyGroup Label="ConanVariables"{condition}>
+    <ConanBinaryDirectories>{bin_dirs};%(ConanBinaryDirectories)</ConanBinaryDirectories>
+    <ConanResourceDirectories>{res_dirs};%(ConanResourceDirectories)$(</ConanResourceDirectories>
   </PropertyGroup>
-  <PropertyGroup>
+  <PropertyGroup{condition}>
     <LocalDebuggerEnvironment>PATH=%PATH%;{bin_dirs}</LocalDebuggerEnvironment>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
   </PropertyGroup>
-  <ItemDefinitionGroup>
+  <ItemDefinitionGroup{condition}>
     <ClCompile>
       <AdditionalIncludeDirectories>{include_dirs}%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>{definitions}%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -40,16 +44,14 @@ class VisualStudioGenerator(Generator):
       <PreprocessorDefinitions>{definitions}%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalOptions>{compiler_flags} %(AdditionalOptions)</AdditionalOptions>
     </ResourceCompile>
-  </ItemDefinitionGroup>
-  <ItemGroup />
-</Project>'''
+  </ItemDefinitionGroup>'''
 
     item_template = '''
     <Conan-{name}-Root>{root_dir}</Conan-{name}-Root>'''
 
     def _format_items(self):
         sections = []
-        for dep_name, cpp_info in self.deps_build_info.dependencies:
+        for dep_name, cpp_info in self._deps_build_info.dependencies:
             fields = {
                 'root_dir': cpp_info.rootpath.replace("\\", "/"),
                 'name': dep_name.replace(".", "-")
@@ -62,25 +64,41 @@ def _format_items(self):
     def filename(self):
         return BUILD_INFO_VISUAL_STUDIO
 
+    def _format_properties(self, build_info, condition):
+        fields = {
+            'condition': condition,
+            'bin_dirs': "".join("%s;" % p for p in build_info.bin_paths).replace("\\", "/"),
+            'res_dirs': "".join("%s;" % p for p in build_info.res_paths).replace("\\", "/"),
+            'include_dirs': "".join("%s;" % p for p in build_info.include_paths).replace("\\", "/"),
+            'lib_dirs': "".join("%s;" % p for p in build_info.lib_paths).replace("\\", "/"),
+            'libs': "".join(['%s.lib;' % lib if not lib.endswith(".lib")
+                             else '%s;' % lib for lib in build_info.libs]),
+            'definitions': "".join("%s;" % d for d in build_info.defines),
+            'compiler_flags': " ".join(build_info.cxxflags + build_info.cflags),
+            'linker_flags': " ".join(build_info.sharedlinkflags),
+            'exe_flags': " ".join(build_info.exelinkflags)
+        }
+        formatted_template = self.properties_template.format(**fields)
+        return formatted_template
+
     @property
     def content(self):
         per_item_props = self._format_items()
+
+        properties = [self._format_properties(self._deps_build_info, condition='')]
+        for config, cpp_info in self._deps_build_info.configs.items():
+            condition = " Condition=\"'$(Configuration)' == '%s'\"" % config
+            properties.append(self._format_properties(cpp_info, condition=condition))
+
         fields = {
             'item_properties': per_item_props,
-            'bin_dirs': "".join("%s;" % p for p in self._deps_build_info.bin_paths).replace("\\", "/"),
-            'res_dirs': "".join("%s;" % p for p in self._deps_build_info.res_paths).replace("\\", "/"),
-            'include_dirs': "".join("%s;" % p for p in self._deps_build_info.include_paths).replace("\\", "/"),
-            'lib_dirs': "".join("%s;" % p for p in self._deps_build_info.lib_paths).replace("\\", "/"),
-            'libs': "".join(['%s.lib;' % lib if not lib.endswith(".lib")
-                             else '%s;' % lib for lib in self._deps_build_info.libs]),
-            'definitions': "".join("%s;" % d for d in self._deps_build_info.defines),
-            'compiler_flags': " ".join(self._deps_build_info.cxxflags + self._deps_build_info.cflags),
-            'linker_flags': " ".join(self._deps_build_info.sharedlinkflags),
-            'exe_flags': " ".join(self._deps_build_info.exelinkflags)
+            'properties': '\n'.join(properties)
         }
         formatted_template = self.template.format(**fields)
+
         userprofile = os.getenv("USERPROFILE")
         if userprofile:
             userprofile = userprofile.replace("\\", "/")
-            formatted_template = re.sub(userprofile, "$(USERPROFILE)", formatted_template, flags=re.I)
+            formatted_template = re.sub(userprofile, "$(USERPROFILE)", formatted_template,
+                                        flags=re.I)
         return formatted_template


Test patch:
diff --git a/conans/test/functional/generators/visual_studio_multi_test.py b/conans/test/functional/generators/visual_studio_multi_test.py
new file mode 100644
--- /dev/null
+++ b/conans/test/functional/generators/visual_studio_multi_test.py
@@ -0,0 +1,107 @@
+# coding=utf-8
+
+import os
+import platform
+import unittest
+
+from nose.plugins.attrib import attr
+from parameterized import parameterized
+
+from conans import MSBuild, tools
+from conans.client.runner import ConanRunner
+from conans.test.utils.conanfile import MockConanfile, MockSettings
+from conans.test.utils.tools import TestClient, TestBufferConanOutput
+from conans.test.utils.visual_project_files import get_vs_project_files
+
+main_cpp = r"""#include <hello.h>
+
+int main(){
+    hello();
+}
+"""
+
+conanfile_txt = r"""[requires]
+Hello1/0.1@lasote/testing
+[generators]
+{generator}
+"""
+
+hello_conanfile_py = r"""from conans import ConanFile
+
+class HelloConan(ConanFile):
+    name = "Hello1"
+    version = "0.1"
+    exports = '*'
+
+    def package(self):
+        self.copy("*.h", dst="include")
+
+    def package_info(self):
+        self.cpp_info.debug.defines = ["CONAN_DEBUG"]
+        self.cpp_info.release.defines = ["CONAN_RELEASE"]
+"""
+
+
+hello_h = r"""#include <iostream>
+
+#ifdef CONAN_DEBUG
+void hello(){
+    std::cout << "Hello Debug!!!" << std::endl;
+}
+#endif
+#ifdef CONAN_RELEASE
+void hello(){
+    std::cout << "Hello Release!!!" << std::endl;
+}
+#endif
+"""
+
+
+@attr('slow')
+@unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild")
+class VisualStudioMultiTest(unittest.TestCase):
+
+    @parameterized.expand([("visual_studio", "conanbuildinfo.props"),
+                           ("visual_studio_multi", "conanbuildinfo_multi.props")])
+    def build_vs_project_test(self, generator, props):
+        client = TestClient()
+        files = {}
+        files["conanfile.py"] = hello_conanfile_py
+        files["hello.h"] = hello_h
+        client.save(files)
+        client.run("create . lasote/testing")
+
+        files = get_vs_project_files()
+        files["MyProject/main.cpp"] = main_cpp
+        files["conanfile.txt"] = conanfile_txt.format(generator=generator)
+        props = os.path.join(client.current_folder, props)
+        old = '<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />'
+        new = old + '<Import Project="{props}" />'.format(props=props)
+        files["MyProject/MyProject.vcxproj"] = files["MyProject/MyProject.vcxproj"].replace(old, new)
+        client.save(files, clean_first=True)
+
+        for build_type in ["Debug", "Release"]:
+            arch = "x86"
+            runner = ConanRunner(print_commands_to_output=True,
+                                 generate_run_log_file=False,
+                                 log_run_to_output=True)
+            settings = MockSettings({"os": "Windows",
+                                     "build_type": build_type,
+                                     "arch": arch,
+                                     "compiler": "Visual Studio",
+                                     "compiler.version": "15",
+                                     "compiler.toolset": "v141"})
+            conanfile = MockConanfile(settings, runner=runner)
+            settings = " -s os=Windows " \
+                       " -s build_type={build_type} " \
+                       " -s arch={arch}" \
+                       " -s compiler=\"Visual Studio\"" \
+                       " -s compiler.toolset=v141" \
+                       " -s compiler.version=15".format(build_type=build_type, arch=arch)
+            client.run("install . %s" % settings)
+            with tools.chdir(client.current_folder):
+                msbuild = MSBuild(conanfile)
+                msbuild.build(project_file="MyProject.sln", build_type=build_type, arch=arch)
+                output = TestBufferConanOutput()
+                runner("%s\MyProject.exe" % build_type, output)
+                self.assertIn("Hello %s!!!" % build_type, output)
diff --git a/conans/test/unittests/client/generators/visual_studio_test.py b/conans/test/unittests/client/generators/visual_studio_test.py
--- a/conans/test/unittests/client/generators/visual_studio_test.py
+++ b/conans/test/unittests/client/generators/visual_studio_test.py
@@ -64,3 +64,42 @@ def user_profile_test(self):
             xml.etree.ElementTree.fromstring(content)
             self.assertIn("<AdditionalIncludeDirectories>$(USERPROFILE)/pkg1/include;"
                           "$(USERPROFILE)/pkg2/include;", content)
+
+    def multi_config_test(self):
+        tmp_folder = temp_folder()
+        ref = ConanFileReference.loads("MyPkg/0.1@lasote/stables")
+        conanfile = ConanFile(TestBufferConanOutput(), None)
+        conanfile.initialize(Settings({}), EnvValues())
+        cpp_info = CppInfo(tmp_folder)
+        cpp_info.defines = ["_WIN32_WINNT=x0501"]
+        cpp_info.debug.defines = ["_DEBUG", "DEBUG"]
+        cpp_info.release.defines = ["NDEBUG"]
+        cpp_info.custom.defines = ["CUSTOM_BUILD"]
+        conanfile.deps_cpp_info.update(cpp_info, ref.name)
+        generator = VisualStudioGenerator(conanfile)
+
+        content = generator.content
+
+        defines_common = "<PreprocessorDefinitions>" \
+                         "_WIN32_WINNT=x0501;%(PreprocessorDefinitions)" \
+                         "</PreprocessorDefinitions>"
+        defines_debug = "<PreprocessorDefinitions>" \
+                        "_DEBUG;DEBUG;%(PreprocessorDefinitions)" \
+                        "</PreprocessorDefinitions>"
+        defines_release = "<PreprocessorDefinitions>" \
+                        "NDEBUG;%(PreprocessorDefinitions)" \
+                        "</PreprocessorDefinitions>"
+        defines_custom = "<PreprocessorDefinitions>" \
+                          "CUSTOM_BUILD;%(PreprocessorDefinitions)" \
+                          "</PreprocessorDefinitions>"
+        self.assertIn(defines_common, content)
+        self.assertIn(defines_debug, content)
+        self.assertIn(defines_release, content)
+        self.assertIn(defines_custom, content)
+
+        condition_debug = "<ItemDefinitionGroup Condition=\"'$(Configuration)' == 'debug'\">"
+        condition_release = "<ItemDefinitionGroup Condition=\"'$(Configuration)' == 'release'\">"
+        condition_custom = "<ItemDefinitionGroup Condition=\"'$(Configuration)' == 'custom'\">"
+        self.assertIn(condition_debug, content)
+        self.assertIn(condition_release, content)
+        self.assertIn(condition_custom, content)