Repository: conan-io/conan

Issue:
virtualbuildenv generator does not set LIB env var correctly in activate_build.bat
Running **conan install** with -g virtualbuildenv generates activate_build.bat.
The first four env vars set are:
```
SET PROMPT=...
SET CL=...
SET LIB=...
SET_LINK_=...
```
CL and \_LINK\_ are set correctly, but LIB does not contain my "lib_path".

According to the following code, what happens is LIB added to a dictionary with the correct lib_path, but then the call to "update" to add "vc_vars" overwrites the LIB value with the Visual Studio LIB value.

**virtualbuildenv.py**
```javascript
        if compiler == "Visual Studio":
            self.env = VisualStudioBuildEnvironment(conanfile).vars_dict
            self.env.update(vcvars_dict(conanfile.settings, output=conanfile.output))
        else:
            self.env = AutoToolsBuildEnvironment(conanfile).vars_dict
```
This is incorrect.  The lib_path needs to be merged with the Visual Studio LIB value instead of being replaced.

I'm using conan v1.12.3 on Windows 10 and Visual Studio 2015.





Discussion:

Resolving patch:
diff --git a/conans/client/generators/virtualbuildenv.py b/conans/client/generators/virtualbuildenv.py
--- a/conans/client/generators/virtualbuildenv.py
+++ b/conans/client/generators/virtualbuildenv.py
@@ -12,7 +12,10 @@ def __init__(self, conanfile):
         compiler = conanfile.settings.get_safe("compiler")
         if compiler == "Visual Studio":
             self.env = VisualStudioBuildEnvironment(conanfile).vars_dict
-            self.env.update(vcvars_dict(conanfile.settings, output=conanfile.output))
+            settings_vars = vcvars_dict(conanfile.settings, output=conanfile.output)
+            for env_var in self.env:
+                self.env[env_var].extend(settings_vars.pop(env_var, []))
+            self.env.update(settings_vars)
         else:
             self.env = AutoToolsBuildEnvironment(conanfile).vars_dict
 
diff --git a/conans/client/generators/virtualenv.py b/conans/client/generators/virtualenv.py
--- a/conans/client/generators/virtualenv.py
+++ b/conans/client/generators/virtualenv.py
@@ -6,7 +6,7 @@
 
 class VirtualEnvGenerator(Generator):
 
-    append_with_spaces = ["CPPFLAGS", "CFLAGS", "CXXFLAGS", "LIBS", "LDFLAGS", "CL"]
+    append_with_spaces = ["CPPFLAGS", "CFLAGS", "CXXFLAGS", "LIBS", "LDFLAGS", "CL", "_LINK_"]
 
     def __init__(self, conanfile):
         self.conanfile = conanfile


Test patch:
diff --git a/conans/test/functional/generators/cmake_generator_test.py b/conans/test/functional/generators/cmake_generator_test.py
--- a/conans/test/functional/generators/cmake_generator_test.py
+++ b/conans/test/functional/generators/cmake_generator_test.py
@@ -1,7 +1,5 @@
-import unittest
 import os
-
-from nose.plugins.attrib import attr
+import unittest
 
 from conans import tools
 from conans.test.utils.tools import TestClient
@@ -40,6 +38,7 @@ def build(self):
 os_build={os_build}
 """
 
+
 class CMakeGeneratorTest(unittest.TestCase):
 
     def _check_build_generator(self, os_build, generator):
diff --git a/conans/test/integration/build_environment_test.py b/conans/test/integration/build_environment_test.py
--- a/conans/test/integration/build_environment_test.py
+++ b/conans/test/integration/build_environment_test.py
@@ -1,8 +1,11 @@
 import platform
 import unittest
+from textwrap import dedent
 
 from conans.paths import CONANFILE
 from conans.test.utils.tools import TestClient
+from conans.test.utils.cpp_test_files import cpp_hello_conan_files
+
 
 mylibh = '''
 double mean(double a, double b);
@@ -59,9 +62,8 @@ def package_info(self):
 
 class BuildEnvironmenTest(unittest.TestCase):
 
+    @unittest.skipUnless(platform.system() == "Linux", "Requires Linux")
     def use_build_virtualenv_test(self):
-        if platform.system() != "Linux":
-            return
         client = TestClient(path_with_spaces=False)
         client.save({CONANFILE: conanfile, "mean.cpp": mylib, "mean.h": mylibh})
         client.run("export . lasote/stable")
@@ -108,3 +110,37 @@ def build(self):
         client.run("install . --build missing")
         client.run("build .")
         self.assertIn("15", client.user_io.out)
+
+    @unittest.skipUnless(platform.system() == "Windows", "Requires windows")
+    def use_build_virtualenv_windows_test(self):
+        files = cpp_hello_conan_files("hello", "0.1",  use_cmake=False, with_exe=False)
+        client = TestClient(path_with_spaces=False)
+        client.save(files)
+        client.run("create . user/testing")
+        files = cpp_hello_conan_files("hello2", "0.1", deps=["hello/0.1@user/testing"],
+                                      use_cmake=False, with_exe=False)
+        client.save(files, clean_first=True)
+        client.run("create . user/testing")
+
+        reuse_conanfile = dedent('''
+            from conans import ConanFile
+
+            class ConanReuseLib(ConanFile):
+                requires = "hello2/0.1@user/testing"
+                generators = "virtualbuildenv"
+                settings = "os", "compiler", "build_type", "arch"
+
+                def build(self):
+                    self.run('activate_build.bat && cl /c /EHsc hello.cpp')
+                    self.run('activate_build.bat && lib hello.obj -OUT:helloapp.lib')
+                    self.run('activate_build.bat && cl /EHsc main.cpp helloapp.lib')
+            ''')
+
+        reuse = cpp_hello_conan_files("app", "0.1", deps=["hello2/0.1@user/testing"],
+                                      use_cmake=False)
+        reuse["conanfile.py"] = reuse_conanfile
+        client.save(reuse, clean_first=True)
+        client.run("install .")
+        client.run("build .")
+        client.runner("main.exe", cwd=client.current_folder)
+        self.assertIn("Hello app;Hello hello2;Hello hello", ";".join(str(client.out).splitlines()))
diff --git a/conans/test/unittests/client/build/cmake_flags.py b/conans/test/unittests/client/build/cmake_flags.py
deleted file mode 100644
--- a/conans/test/unittests/client/build/cmake_flags.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import unittest
-
-from conans import CMake
-from conans.client.build.cmake_flags import get_generator
-from conans.test.utils.conanfile import MockSettings, ConanFileMock
-
-
-class CMakeGeneratorTest(unittest.TestCase):
-
-    def test_cmake_default_generator_linux(self):
-        settings = MockSettings({"os_build": "Linux"})
-        generator = get_generator(settings)
-        self.assertEquals("Unix Makefiles", generator)
-        self._test_default_generator_cmake(settings, "Unix Makefiles")
-
-    def test_cmake_default_generator_osx(self):
-        settings = MockSettings({"os_build": "Macos"})
-        generator = get_generator(settings)
-        self.assertEquals("Unix Makefiles", generator)
-        self._test_default_generator_cmake(settings, "Unix Makefiles")
-
-    def test_default_generator_windows(self):
-        settings = MockSettings({"os_build": "Windows"})
-        generator = get_generator(settings)
-        self.assertEquals("MinGW Makefiles", generator)
-        self._test_default_generator_cmake(settings, "MinGW Makefiles")
-
-    def _test_default_generator_cmake(self, settings, generator):
-        conanfile = ConanFileMock()
-        conanfile.settings = settings
-        cmake = CMake(conanfile)
-        self.assertEquals(generator, cmake.generator)
-        self.assertIn('-G "{}"'.format(generator), cmake.command_line)
-
diff --git a/conans/test/unittests/client/build/visual_environment.py b/conans/test/unittests/client/build/visual_environment.py
deleted file mode 100644
--- a/conans/test/unittests/client/build/visual_environment.py
+++ /dev/null
@@ -1,157 +0,0 @@
-import copy
-import os
-
-from conans.client.build.compiler_flags import build_type_define, build_type_flags, format_defines, \
-    include_path_option, parallel_compiler_cl_flag, visual_runtime
-from conans.client.build.cppstd_flags import cppstd_flag
-
-
-class VisualStudioBuildEnvironment(object):
-    """
-    - LIB: library paths with semicolon separator
-    - CL: /I (include paths)
-    - _LINK_: linker options and libraries
-
-    https://msdn.microsoft.com/en-us/library/19z1t1wy.aspx
-    https://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx
-    https://msdn.microsoft.com/en-us/library/9s7c9wdw.aspx
-    https://msdn.microsoft.com/en-us/library/6y6t9esh.aspx
-
-    """
-    def __init__(self, conanfile, with_build_type_flags=True):
-        """
-        :param conanfile: ConanFile instance
-        :param quote_paths: The path directories will be quoted. If you are using the vars together with
-                            environment_append keep it to True, for virtualbuildenv quote_paths=False is required.
-        """
-        self._with_build_type_flags = with_build_type_flags
-
-        self._conanfile = conanfile
-        self._settings = conanfile.settings
-        self._deps_cpp_info = conanfile.deps_cpp_info
-        self._runtime = self._settings.get_safe("compiler.runtime")
-
-        self.include_paths = conanfile.deps_cpp_info.include_paths
-        self.lib_paths = conanfile.deps_cpp_info.lib_paths
-        self.defines = copy.copy(conanfile.deps_cpp_info.defines)
-        self.flags = self._configure_flags()
-        self.cxx_flags = copy.copy(self._deps_cpp_info.cppflags)
-        self.link_flags = self._configure_link_flags()
-        self.libs = conanfile.deps_cpp_info.libs
-        self.std = self._std_cpp()
-        self.parallel = False
-
-    def _configure_link_flags(self):
-        ret = copy.copy(self._deps_cpp_info.exelinkflags)
-        ret.extend(self._deps_cpp_info.sharedlinkflags)
-        return ret
-
-    def _configure_flags(self):
-        ret = copy.copy(self._deps_cpp_info.cflags)
-        ret.extend(vs_build_type_flags(self._settings, with_flags=self._with_build_type_flags))
-        return ret
-
-    def _get_cl_list(self, quotes=True):
-        # FIXME: It should be managed with the compiler_flags module
-        # But need further investigation about the quotes and so on, so better to not break anything
-        if quotes:
-            ret = ['%s"%s"' % (include_path_option, lib) for lib in self.include_paths]
-        else:
-            ret = ['%s%s' % (include_path_option, lib) for lib in self.include_paths]
-
-        runtime = visual_runtime(self._runtime)
-        if runtime:
-            ret.append(runtime)
-
-        ret.extend(format_defines(self.defines))
-        ret.extend(self.flags)
-        ret.extend(self.cxx_flags)
-
-        if self.parallel:  # Build source in parallel
-            ret.append(parallel_compiler_cl_flag(output=self._conanfile.output))
-
-        if self.std:
-            ret.append(self.std)
-
-        return ret
-
-    def _get_link_list(self):
-        def format_lib(lib):
-            return lib if lib.endswith('.lib') else '%s.lib' % lib
-
-        ret = [flag for flag in self.link_flags]  # copy
-        ret.extend([format_lib(lib) for lib in self.libs])
-
-        return ret
-
-    @property
-    def vars(self):
-        """Used in conanfile with environment_append"""
-        flags = self._get_cl_list()
-        link_flags = self._get_link_list()
-
-        cl_args = " ".join(flags) + _environ_value_prefix("CL")
-        link_args = " ".join(link_flags)
-        lib_paths = ";".join(['%s' % lib for lib in self.lib_paths]) + _environ_value_prefix("LIB", ";")
-        return {"CL": cl_args,
-                "LIB": lib_paths,
-                "_LINK_": link_args}
-
-    @property
-    def vars_dict(self):
-        """Used in virtualbuildenvironment"""
-        # Here we do not quote the include paths, it's going to be used by virtual environment
-        cl = self._get_cl_list(quotes=False)
-        link = self._get_link_list()
-
-        lib = [lib for lib in self.lib_paths]  # copy
-
-        if os.environ.get("CL", None):
-            cl.append(os.environ.get("CL"))
-
-        if os.environ.get("LIB", None):
-            lib.append(os.environ.get("LIB"))
-
-        if os.environ.get("_LINK_", None):
-            link.append(os.environ.get("_LINK_"))
-
-        ret = {"CL": cl,
-               "LIB": lib,
-               "_LINK_": link}
-        return ret
-
-    def _std_cpp(self):
-        return vs_std_cpp(self._settings)
-
-
-def vs_build_type_flags(settings, with_flags=True):
-    build_type = settings.get_safe("build_type")
-    ret = []
-    btd = build_type_define(build_type=build_type)
-    if btd:
-        ret.extend(format_defines([btd]))
-    if with_flags:
-        # When using to build a vs project we don't want to adjust these flags
-        btfs = build_type_flags("Visual Studio", build_type=build_type,
-                                vs_toolset=settings.get_safe("compiler.toolset"))
-        if btfs:
-            ret.extend(btfs)
-
-    return ret
-
-
-def vs_std_cpp(settings):
-    if settings.get_safe("compiler") == "Visual Studio" and \
-       settings.get_safe("cppstd"):
-        flag = cppstd_flag(settings.get_safe("compiler"),
-                           settings.get_safe("compiler.version"),
-                           settings.get_safe("cppstd"))
-        return flag
-    return None
-
-
-def _environ_value_prefix(var_name, prefix=" "):
-    if os.environ.get(var_name, ""):
-        return "%s%s" % (prefix, os.environ.get(var_name, ""))
-    else:
-        return ""