diff --git a/conan/tools/env/__init__.py b/conan/tools/env/__init__.py index 7656f772c0d..0eed8d1a0f5 100644 --- a/conan/tools/env/__init__.py +++ b/conan/tools/env/__init__.py @@ -1 +1,2 @@ from conan.tools.env.environment import Environment +from conan.tools.env.virtualenv import VirtualEnv diff --git a/conan/tools/env/virtualenv.py b/conan/tools/env/virtualenv.py new file mode 100644 index 00000000000..2c345de163c --- /dev/null +++ b/conan/tools/env/virtualenv.py @@ -0,0 +1,108 @@ +import platform + +from conan.tools.env import Environment +from conans.client.graph.graph import CONTEXT_HOST + + +class VirtualEnv: + """ captures the conanfile environment that is defined from its + dependencies, and also from profiles + """ + + def __init__(self, conanfile): + self._conanfile = conanfile + self._conanfile.virtualenv = False + + def build_environment(self): + """ collects the buildtime information from dependencies. This is the typical use case + of build_requires defining information for consumers + """ + build_env = Environment() + # First visit the direct build_requires + for build_require in self._conanfile.dependencies.build_requires: + # Lower priority, the runenv of all transitive "requires" of the build requires + for require in build_require.dependencies.requires: + build_env.compose(self._collect_transitive_runenv(require)) + # Second, the implicit self information in build_require.cpp_info + build_env.compose(self._runenv_from_cpp_info(build_require.cpp_info)) + # Finally, higher priority, explicit buildenv_info + if build_require.buildenv_info: + build_env.compose(build_require.buildenv_info) + + # Requires in host context can also bring some direct buildenv_info + def _collect_transitive_buildenv(d): + r = Environment() + for child in d.dependencies.requires: + r.compose(_collect_transitive_buildenv(child)) + # Then the explicit self + if d.buildenv_info: + r.compose(d.buildenv_info) + return r + for require in self._conanfile.dependencies.requires: + build_env.compose(_collect_transitive_buildenv(require)) + + # The profile environment has precedence, applied last + profile_env = self._conanfile.buildenv + build_env.compose(profile_env) + return build_env + + @staticmethod + def _runenv_from_cpp_info(cpp_info): + """ return an Environment deducing the runtime information from a cpp_info + """ + dyn_runenv = Environment() + if cpp_info is None: # This happens when the dependency is a private one = BINARY_SKIP + return dyn_runenv + if cpp_info.bin_paths: # cpp_info.exes is not defined yet + dyn_runenv.prepend_path("PATH", cpp_info.bin_paths) + # If it is a build_require this will be the build-os, otherwise it will be the host-os + if cpp_info.lib_paths: + dyn_runenv.prepend_path("LD_LIBRARY_PATH", cpp_info.lib_paths) + dyn_runenv.prepend_path("DYLD_LIBRARY_PATH", cpp_info.lib_paths) + if cpp_info.framework_paths: + dyn_runenv.prepend_path("DYLD_FRAMEWORK_PATH", cpp_info.framework_paths) + return dyn_runenv + + def _collect_transitive_runenv(self, d): + r = Environment() + for child in d.dependencies.requires: + r.compose(self._collect_transitive_runenv(child)) + # Apply "d" runenv, first the implicit + r.compose(self._runenv_from_cpp_info(d.cpp_info)) + # Then the explicit + if d.runenv_info: + r.compose(d.runenv_info) + return r + + def run_environment(self): + """ collects the runtime information from dependencies. For normal libraries should be + very occasional + """ + runenv = Environment() + # At the moment we are adding "test-requires" (build_requires in host context) + # to the "runenv", but this will be investigated + for build_require in self._conanfile.dependencies.build_requires: + if build_require.context == CONTEXT_HOST: + runenv.compose(self._collect_transitive_runenv(build_require)) + for require in self._conanfile.dependencies.requires: + runenv.compose(self._collect_transitive_runenv(require)) + + # FIXME: Missing profile info + result = runenv + return result + + def generate(self): + build_env = self.build_environment() + run_env = self.run_environment() + # FIXME: Use settings, not platform Not always defined :( + # os_ = self._conanfile.settings_build.get_safe("os") + if build_env: # Only if there is something defined + if platform.system() == "Windows": + build_env.save_bat("conanbuildenv.bat") + else: + build_env.save_sh("conanbuildenv.sh") + if run_env: + if platform.system() == "Windows": + run_env.save_bat("conanrunenv.bat") + else: + run_env.save_sh("conanrunenv.sh") diff --git a/conans/assets/templates/new_v2_cmake.py b/conans/assets/templates/new_v2_cmake.py index a6ef8f799ca..c30442d69ba 100644 --- a/conans/assets/templates/new_v2_cmake.py +++ b/conans/assets/templates/new_v2_cmake.py @@ -45,32 +45,22 @@ def package_info(self): test_conanfile_v2 = """import os from conans import ConanFile, tools -from conan.tools.cmake import CMakeToolchain, CMake, CMakeDeps +from conan.tools.cmake import CMake class {package_name}TestConan(ConanFile): settings = "os", "compiler", "build_type", "arch" - - def generate(self): - deps = CMakeDeps(self) - deps.generate() - tc = CMakeToolchain(self) - tc.generate() + generators = "CMakeDeps", "CMakeToolchain", "VirtualEnv" + apply_env = False def build(self): cmake = CMake(self) cmake.configure() cmake.build() - def imports(self): - self.copy("*.dll", dst="bin", src="bin") - self.copy("*.dylib*", dst="bin", src="lib") - self.copy('*.so*', dst='bin', src='lib') - def test(self): if not tools.cross_building(self): - os.chdir("bin") - self.run(".%sexample" % os.sep) + self.run(os.path.sep.join([".", "bin", "example"]), env="conanrunenv") """ @@ -84,7 +74,7 @@ def test(self): set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${{CMAKE_RUNTIME_OUTPUT_DIRECTORY}}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${{CMAKE_RUNTIME_OUTPUT_DIRECTORY}}) -find_package({name}) +find_package({name} CONFIG REQUIRED) add_executable(example example.cpp) target_link_libraries(example {name}::{name}) diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index 7ca1ca0d7a4..bb5eb3990e9 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -66,7 +66,8 @@ def __init__(self): "deploy": DeployGenerator, "markdown": MarkdownGenerator} self._new_generators = ["CMakeToolchain", "CMakeDeps", "MakeToolchain", "MSBuildToolchain", - "MesonToolchain", "MSBuildDeps", "QbsToolchain", "msbuild"] + "MesonToolchain", "MSBuildDeps", "QbsToolchain", "msbuild", + "VirtualEnv"] def add(self, name, generator_class, custom=False): if name not in self._generators or custom: @@ -110,6 +111,9 @@ def _new_generator(self, generator_name, output): elif generator_name == "QbsToolchain" or generator_name == "QbsProfile": from conan.tools.qbs.qbsprofile import QbsProfile return QbsProfile + elif generator_name == "VirtualEnv": + from conan.tools.env.virtualenv import VirtualEnv + return VirtualEnv else: raise ConanException("Internal Conan error: Generator '{}' " "not commplete".format(generator_name)) @@ -196,4 +200,9 @@ def write_toolchain(conanfile, path, output): with conanfile_exception_formatter(str(conanfile), "generate"): conanfile.generate() - # TODO: Lets discuss what to do with the environment + # tools.env.virtualenv:auto_use will be always True in Conan 2.0 + if conanfile.conf["tools.env.virtualenv"].auto_use and conanfile.virtualenv: + with chdir(path): + from conan.tools.env.virtualenv import VirtualEnv + env = VirtualEnv(conanfile) + env.generate() diff --git a/conans/client/graph/conanfile_dependencies.py b/conans/client/graph/conanfile_dependencies.py new file mode 100644 index 00000000000..1446979bddd --- /dev/null +++ b/conans/client/graph/conanfile_dependencies.py @@ -0,0 +1,18 @@ +from conans.model.conanfile_interface import ConanFileInterface + + +class ConanFileDependencies: + + def __init__(self, node): + self._node = node + + @property + def build_requires(self): + return [ConanFileInterface(edge.dst.conanfile) for edge in self._node.dependencies + if edge.build_require] + + @property + def requires(self): + # public direct requires + return [ConanFileInterface(edge.dst.conanfile) for edge in self._node.dependencies + if not edge.build_require and not edge.private] diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py index c49bb8e3851..f86498811dd 100644 --- a/conans/client/graph/graph.py +++ b/conans/client/graph/graph.py @@ -65,6 +65,7 @@ def __init__(self, ref, conanfile, context, recipe=None, path=None): self.path = path # path to the consumer conanfile.xx for consumer, None otherwise self._package_id = None self.prev = None + conanfile._conan_node = self # Reference to self, to access data self.conanfile = conanfile self.dependencies = [] # Ordered Edges self.dependants = set() # Edges diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py index 270957213a5..2e29829f95a 100644 --- a/conans/model/conan_file.py +++ b/conans/model/conan_file.py @@ -5,7 +5,9 @@ from six import string_types from conan.tools.env import Environment +from conan.tools.env.environment import environment_wrap_command from conans.client import tools +from conans.client.graph.conanfile_dependencies import ConanFileDependencies from conans.client.output import ScopedOutput from conans.client.tools.env import environment_append, no_op, pythonpath from conans.client.tools.oss import OSInfo @@ -151,6 +153,16 @@ def __init__(self, output, runner, display_name="", user=None, channel=None, req self.buildenv_info = Environment() self.runenv_info = Environment() self._conan_buildenv = None # The profile buildenv, will be assigned initialize() + self._conan_node = None # access to container Node object, to access info, context, deps... + self.virtualenv = True # Set to false to opt-out automatic usage of VirtualEnv + + @property + def context(self): + return self._conan_node.context + + @property + def dependencies(self): + return ConanFileDependencies(self._conan_node) @property def buildenv(self): @@ -313,7 +325,9 @@ def package_info(self): """ def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True, - ignore_errors=False, run_environment=False, with_login=True): + ignore_errors=False, run_environment=False, with_login=True, env="conanbuildenv"): + + command = environment_wrap_command(env, command) def _run(): if not win_bash: diff --git a/conans/model/conanfile_interface.py b/conans/model/conanfile_interface.py new file mode 100644 index 00000000000..45b9c465166 --- /dev/null +++ b/conans/model/conanfile_interface.py @@ -0,0 +1,34 @@ + + +class ConanFileInterface: + """ this is just a protective wrapper to give consumers + a limited view of conanfile dependencies, "read" only, + and only to some attributes, not methods + """ + + def __init__(self, conanfile): + self._conanfile = conanfile + + @property + def buildenv_info(self): + return self._conanfile.buildenv_info + + @property + def runenv_info(self): + return self._conanfile.runenv_info + + @property + def cpp_info(self): + return self._conanfile.cpp_info + + @property + def settings(self): + return self._conanfile.settings + + @property + def context(self): + return self._conanfile.context + + @property + def dependencies(self): + return self._conanfile.dependencies diff --git a/conans/test/functional/toolchains/env/__init__.py b/conans/test/functional/toolchains/env/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/conans/test/functional/toolchains/env/test_complete.py b/conans/test/functional/toolchains/env/test_complete.py new file mode 100644 index 00000000000..76ccbd8b7e6 --- /dev/null +++ b/conans/test/functional/toolchains/env/test_complete.py @@ -0,0 +1,162 @@ +import textwrap + +from conans.test.assets.sources import gen_function_cpp +from conans.test.utils.tools import TestClient + + +def test_cmake_virtualenv(): + client = TestClient() + client.run("new hello/0.1 -s") + client.run("create .") + + cmakewrapper = textwrap.dedent(r""" + from conans import ConanFile + import os + from conans.tools import save, chdir + class Pkg(ConanFile): + def package(self): + with chdir(self.package_folder): + save("cmake.bat", "@echo off\necho MYCMAKE WRAPPER!!\ncmake.exe %*") + save("cmake.sh", 'echo MYCMAKE WRAPPER!!\ncmake "$@"') + os.chmod("cmake.sh", 0o777) + + def package_info(self): + # Custom buildenv not defined by cpp_info + self.buildenv_info.prepend_path("PATH", self.package_folder) + """) + consumer = textwrap.dedent(""" + from conans import ConanFile + from conan.tools.cmake import CMake + class App(ConanFile): + settings = "os", "arch", "compiler", "build_type" + exports_sources = "CMakeLists.txt", "main.cpp" + requires = "hello/0.1" + build_requires = "cmakewrapper/0.1" + generators = "CMakeDeps", "CMakeToolchain", "VirtualEnv" + apply_env = False + + def build(self): + cmake = CMake(self) + if self.settings.os != "Windows": + cmake._cmake_program = "cmake.sh" # VERY DIRTY HACK + cmake.configure() + cmake.build() + """) + + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.15) + project(MyApp CXX) + + find_package(hello) + add_executable(app main.cpp) + target_link_libraries(app hello::hello) + """) + + client.save({"cmakewrapper/conanfile.py": cmakewrapper, + "consumer/conanfile.py": consumer, + "consumer/main.cpp": gen_function_cpp(name="main", includes=["hello"], + calls=["hello"]), + "consumer/CMakeLists.txt": cmakelists}, + clean_first=True) + + client.run("create cmakewrapper cmakewrapper/0.1@") + client.run("create consumer consumer/0.1@") + assert "MYCMAKE WRAPPER!!" in client.out + assert "consumer/0.1: Created package" in client.out + + +def test_complete(): + client = TestClient() + client.run("new myopenssl/1.0 -m=v2_cmake") + client.run("create . -o myopenssl:shared=True") + client.run("create . -o myopenssl:shared=True -s build_type=Debug") + + mycmake_main = gen_function_cpp(name="main", msg="mycmake", + includes=["myopenssl"], calls=["myopenssl"]) + mycmake_conanfile = textwrap.dedent(""" + from conans import ConanFile + from conan.tools.cmake import CMake + class App(ConanFile): + settings = "os", "arch", "compiler", "build_type" + requires = "myopenssl/1.0" + default_options = {"myopenssl:shared": True} + generators = "CMakeDeps", "CMakeToolchain", "VirtualEnv" + exports = "*" + apply_env = False + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + src = str(self.settings.build_type) if self.settings.os == "Windows" else "" + self.copy("mycmake*", src=src, dst="bin") + + def package_info(self): + self.cpp_info.bindirs = ["bin"] + """) + mycmake_cmakelists = textwrap.dedent(""" + set(CMAKE_CXX_COMPILER_WORKS 1) + set(CMAKE_CXX_ABI_COMPILED 1) + cmake_minimum_required(VERSION 3.15) + project(MyCmake CXX) + + find_package(myopenssl REQUIRED) + add_executable(mycmake main.cpp) + target_link_libraries(mycmake PRIVATE myopenssl::myopenssl) + """) + client.save({"conanfile.py": mycmake_conanfile, + "CMakeLists.txt": mycmake_cmakelists, + "main.cpp": mycmake_main}, clean_first=True) + client.run("create . mycmake/1.0@") + + mylib = textwrap.dedent(r""" + from conans import ConanFile + import os + from conan.tools.cmake import CMake + class Pkg(ConanFile): + settings = "os", "compiler", "build_type", "arch" + build_requires = "mycmake/1.0" + requires = "myopenssl/1.0" + default_options = {"myopenssl:shared": True} + exports_sources = "CMakeLists.txt", "main.cpp" + generators = "CMakeDeps", "CMakeToolchain", "VirtualEnv" + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + self.run("mycmake") + self.output.info("RUNNING MYAPP") + if self.settings.os == "Windows": + self.run(os.sep.join([".", str(self.settings.build_type), "myapp"]), + env="conanrunenv") + else: + self.run(os.sep.join([".", "myapp"]), env="conanrunenv") + """) + + cmakelists = textwrap.dedent(""" + set(CMAKE_CXX_COMPILER_WORKS 1) + set(CMAKE_CXX_ABI_COMPILED 1) + cmake_minimum_required(VERSION 3.15) + project(MyApp CXX) + + find_package(myopenssl) + add_executable(myapp main.cpp) + target_link_libraries(myapp myopenssl::myopenssl) + """) + + client.save({"conanfile.py": mylib, + "main.cpp": gen_function_cpp(name="main", msg="myapp", includes=["myopenssl"], + calls=["myopenssl"]), + "CMakeLists.txt": cmakelists}, + clean_first=True) + + client.run("create . myapp/0.1@ -s:b build_type=Release -s:h build_type=Debug") + first, last = str(client.out).split("RUNNING MYAPP") + assert "mycmake: Release!" in first + assert "myopenssl/1.0: Hello World Release!" in first + + assert "myapp: Debug!" in last + assert "myopenssl/1.0: Hello World Debug!" in last diff --git a/conans/test/integration/environment/test_env.py b/conans/test/integration/environment/test_env.py index 98a860381c5..5ae6c7bf1cb 100644 --- a/conans/test/integration/environment/test_env.py +++ b/conans/test/integration/environment/test_env.py @@ -2,13 +2,130 @@ import platform import textwrap +import pytest from conan.tools.env.environment import environment_wrap_command from conans.test.utils.tools import TestClient +from conans.util.files import save + + +@pytest.fixture() +def client(): + openssl = textwrap.dedent(r""" + import os + from conans import ConanFile + from conans.tools import save, chdir + class Pkg(ConanFile): + settings = "os" + def package(self): + with chdir(self.package_folder): + echo = "@echo off\necho MYOPENSSL={}!!".format(self.settings.os) + save("bin/myopenssl.bat", echo) + save("bin/myopenssl.sh", echo) + os.chmod("bin/myopenssl.sh", 0o777) + """) + + cmake = textwrap.dedent(r""" + import os + from conans import ConanFile + from conans.tools import save, chdir + class Pkg(ConanFile): + settings = "os" + requires = "openssl/1.0" + def package(self): + with chdir(self.package_folder): + echo = "@echo off\necho MYCMAKE={}!!".format(self.settings.os) + save("mycmake.bat", echo + "\ncall myopenssl.bat") + save("mycmake.sh", echo + "\n myopenssl.sh") + os.chmod("mycmake.sh", 0o777) + + def package_info(self): + # Custom buildenv not defined by cpp_info + self.buildenv_info.prepend_path("PATH", self.package_folder) + self.buildenv_info.define("MYCMAKEVAR", "MYCMAKEVALUE!!") + """) + + gtest = textwrap.dedent(r""" + import os + from conans import ConanFile + from conans.tools import save, chdir + class Pkg(ConanFile): + settings = "os" + def package(self): + with chdir(self.package_folder): + echo = "@echo off\necho MYGTEST={}!!".format(self.settings.os) + save("bin/mygtest.bat", echo) + save("bin/mygtest.sh", echo) + os.chmod("bin/mygtest.sh", 0o777) + + def package_info(self): + self.runenv_info.define("MYGTESTVAR", "MyGTestValue{}".format(self.settings.os)) + """) + client = TestClient() + save(client.cache.new_config_path, "tools.env.virtualenv:auto_use=True") + client.save({"cmake/conanfile.py": cmake, + "gtest/conanfile.py": gtest, + "openssl/conanfile.py": openssl}) + + client.run("export openssl openssl/1.0@") + client.run("export cmake mycmake/1.0@") + client.run("export gtest mygtest/1.0@") + + myrunner_bat = "@echo off\necho MYGTESTVAR=%MYGTESTVAR%!!\n" + myrunner_sh = "echo MYGTESTVAR=$MYGTESTVAR!!\n" + client.save({"myrunner.bat": myrunner_bat, + "myrunner.sh": myrunner_sh}, clean_first=True) + os.chmod(os.path.join(client.current_folder, "myrunner.sh"), 0o777) + return client + + +def test_complete(client): + conanfile = textwrap.dedent(""" + import platform + from conans import ConanFile + class Pkg(ConanFile): + requires = "openssl/1.0" + build_requires = "mycmake/1.0" + apply_env = False + + def build_requirements(self): + self.build_requires("mygtest/1.0", force_host_context=True) + + def build(self): + mybuild_cmd = "mycmake.bat" if platform.system() == "Windows" else "mycmake.sh" + self.run(mybuild_cmd) + mytest_cmd = "mygtest.bat" if platform.system() == "Windows" else "mygtest.sh" + self.run(mytest_cmd, env="conanrunenv") + """) + + client.save({"conanfile.py": conanfile}) + client.run("install . -s:b os=Windows -s:h os=Linux --build=missing") + # Run the BUILD environment + ext = "bat" if platform.system() == "Windows" else "sh" # TODO: Decide on logic .bat vs .sh + cmd = environment_wrap_command("conanbuildenv", "mycmake.{}".format(ext), + cwd=client.current_folder) + client.run_command(cmd) + assert "MYCMAKE=Windows!!" in client.out + assert "MYOPENSSL=Windows!!" in client.out + + # Run the RUN environment + cmd = environment_wrap_command("conanrunenv", + "mygtest.{ext} && .{sep}myrunner.{ext}".format(ext=ext, + sep=os.sep), + cwd=client.current_folder) + client.run_command(cmd) + assert "MYGTEST=Linux!!" in client.out + assert "MYGTESTVAR=MyGTestValueLinux!!" in client.out + + client.run("build .") + assert "MYCMAKE=Windows!!" in client.out + assert "MYOPENSSL=Windows!!" in client.out + assert "MYGTEST=Linux!!" in client.out def test_profile_buildenv(): client = TestClient() + save(client.cache.new_config_path, "tools.env.virtualenv:auto_use=True") conanfile = textwrap.dedent("""\ import os, platform from conans import ConanFile @@ -48,7 +165,8 @@ def generate(self): client.run("install . -pr=myprofile") # Run the BUILD environment ext = "bat" if platform.system() == "Windows" else "sh" # TODO: Decide on logic .bat vs .sh - cmd = environment_wrap_command("pkgenv", "mycompiler.{}".format(ext), cwd=client.current_folder) + cmd = environment_wrap_command("conanbuildenv", "mycompiler.{}".format(ext), + cwd=client.current_folder) client.run_command(cmd) assert "MYCOMPILER!!" in client.out assert "MYPATH=" in client.out @@ -58,3 +176,96 @@ def generate(self): client.run_command(cmd) assert "MYCOMPILER2!!" in client.out assert "MYPATH2=" in client.out + + +def test_transitive_order(): + gcc = textwrap.dedent(r""" + from conans import ConanFile + class Pkg(ConanFile): + def package_info(self): + self.runenv_info.append("MYVAR", "MyGCCValue") + """) + openssl = textwrap.dedent(r""" + from conans import ConanFile + class Pkg(ConanFile): + settings = "os" + build_requires = "gcc/1.0" + def package_info(self): + self.runenv_info.append("MYVAR", "MyOpenSSL{}Value".format(self.settings.os)) + """) + cmake = textwrap.dedent(r""" + from conans import ConanFile + class Pkg(ConanFile): + requires = "openssl/1.0" + build_requires = "gcc/1.0" + def package_info(self): + self.runenv_info.append("MYVAR", "MyCMakeRunValue") + self.buildenv_info.append("MYVAR", "MyCMakeBuildValue") + """) + client = TestClient() + client.save({"gcc/conanfile.py": gcc, + "cmake/conanfile.py": cmake, + "openssl/conanfile.py": openssl}) + + client.run("export gcc gcc/1.0@") + client.run("export openssl openssl/1.0@") + client.run("export cmake cmake/1.0@") + + consumer = textwrap.dedent(r""" + from conans import ConanFile + from conan.tools.env import VirtualEnv + class Pkg(ConanFile): + requires = "openssl/1.0" + build_requires = "cmake/1.0", "gcc/1.0" + def generate(self): + env = VirtualEnv(self) + buildenv = env.build_environment() + self.output.info("BUILDENV: {}!!!".format(buildenv.value("MYVAR"))) + runenv = env.run_environment() + self.output.info("RUNENV: {}!!!".format(runenv.value("MYVAR"))) + """) + client.save({"conanfile.py": consumer}, clean_first=True) + client.run("install . -s:b os=Windows -s:h os=Linux --build -g VirtualEnv") + assert "BUILDENV: MYVAR MyOpenSSLWindowsValue MyCMakeBuildValue!!!" in client.out + assert "RUNENV: MYVAR MyOpenSSLLinuxValue!!!" in client.out + + +def test_buildenv_from_requires(): + openssl = textwrap.dedent(r""" + from conans import ConanFile + class Pkg(ConanFile): + settings = "os" + def package_info(self): + self.buildenv_info.append("OpenSSL_ROOT", + "MyOpenSSL{}Value".format(self.settings.os)) + """) + poco = textwrap.dedent(r""" + from conans import ConanFile + class Pkg(ConanFile): + requires = "openssl/1.0" + settings = "os" + def package_info(self): + self.buildenv_info.append("Poco_ROOT", "MyPoco{}Value".format(self.settings.os)) + """) + client = TestClient() + client.save({"poco/conanfile.py": poco, + "openssl/conanfile.py": openssl}) + + client.run("export openssl openssl/1.0@") + client.run("export poco poco/1.0@") + + consumer = textwrap.dedent(r""" + from conans import ConanFile + from conan.tools.env import VirtualEnv + class Pkg(ConanFile): + requires = "poco/1.0" + def generate(self): + env = VirtualEnv(self) + buildenv = env.build_environment() + self.output.info("BUILDENV POCO: {}!!!".format(buildenv.value("Poco_ROOT"))) + self.output.info("BUILDENV OpenSSL: {}!!!".format(buildenv.value("OpenSSL_ROOT"))) + """) + client.save({"conanfile.py": consumer}, clean_first=True) + client.run("install . -s:b os=Windows -s:h os=Linux --build -g VirtualEnv") + assert "BUILDENV POCO: Poco_ROOT MyPocoLinuxValue!!!" in client.out + assert "BUILDENV OpenSSL: OpenSSL_ROOT MyOpenSSLLinuxValue!!!" in client.out diff --git a/conans/test/integration/generators/xcode_gcc_vs_test.py b/conans/test/integration/generators/xcode_gcc_vs_test.py index 29c6026a1f4..c988578d52d 100644 --- a/conans/test/integration/generators/xcode_gcc_vs_test.py +++ b/conans/test/integration/generators/xcode_gcc_vs_test.py @@ -1,5 +1,6 @@ import os import re +import textwrap import unittest from conans.model.graph_info import GRAPH_INFO_FILE @@ -15,35 +16,37 @@ class VSXCodeGeneratorsTest(unittest.TestCase): def test_generators(self): ref = ConanFileReference.loads("Hello/0.1@lasote/stable") client = TestClient() - client.save({"conanfile.py": """from conans import ConanFile -import os -class Pkg(ConanFile): - def package(self): - os.makedirs(os.path.join(self.package_folder, "lib")) - os.makedirs(os.path.join(self.package_folder, "include")) - def package_info(self): - self.cpp_info.libs = ["hello"] - self.cpp_info.cxxflags = ["-some_cxx_compiler_flag"] - self.cpp_info.cflags = ["-some_c_compiler_flag"] - self.cpp_info.system_libs = ["system_lib1"] -"""}) + client.save({"conanfile.py": textwrap.dedent(""" + from conans import ConanFile + import os + class Pkg(ConanFile): + def package(self): + os.makedirs(os.path.join(self.package_folder, "lib")) + os.makedirs(os.path.join(self.package_folder, "include")) + def package_info(self): + self.cpp_info.libs = ["hello"] + self.cpp_info.cxxflags = ["-some_cxx_compiler_flag"] + self.cpp_info.cflags = ["-some_c_compiler_flag"] + self.cpp_info.system_libs = ["system_lib1"] + """)}) client.run("export . Hello/0.1@lasote/stable") - conanfile_txt = '''[requires] -Hello/0.1@lasote/stable # My req comment -[generators] -gcc # I need this generator for.. -cmake -visual_studio -xcode -''' + conanfile_txt = textwrap.dedent(''' + [requires] + Hello/0.1@lasote/stable # My req comment + [generators] + gcc # I need this generator for.. + cmake + visual_studio + xcode + ''') client.save({"conanfile.txt": conanfile_txt}, clean_first=True) # Install requirements client.run('install . --build missing') - self.assertEqual(sorted([CONANFILE_TXT, BUILD_INFO_GCC, BUILD_INFO_CMAKE, - BUILD_INFO_VISUAL_STUDIO, BUILD_INFO, - BUILD_INFO_XCODE, CONANINFO, GRAPH_INFO_FILE, LOCKFILE]), - sorted(os.listdir(client.current_folder))) + current_files = os.listdir(client.current_folder) + for f in [CONANFILE_TXT, BUILD_INFO_GCC, BUILD_INFO_CMAKE, BUILD_INFO_VISUAL_STUDIO, + BUILD_INFO, BUILD_INFO_XCODE, CONANINFO, GRAPH_INFO_FILE, LOCKFILE]: + assert f in current_files cmake = client.load(BUILD_INFO_CMAKE) gcc = client.load(BUILD_INFO_GCC) @@ -67,7 +70,7 @@ def package_info(self): from xml.dom import minidom xmldoc = minidom.parse(os.path.join(client.current_folder, BUILD_INFO_VISUAL_STUDIO)) definition_group = xmldoc.getElementsByTagName('ItemDefinitionGroup')[0] - compiler = definition_group.getElementsByTagName("ClCompile")[0] + _ = definition_group.getElementsByTagName("ClCompile")[0] linker = definition_group.getElementsByTagName("Link")[0] def element_content(node):