diff --git a/conan/tools/env/virtualbuildenv.py b/conan/tools/env/virtualbuildenv.py index d4ffbf6958e..09d0de6cff5 100644 --- a/conan/tools/env/virtualbuildenv.py +++ b/conan/tools/env/virtualbuildenv.py @@ -22,8 +22,9 @@ def environment(self): profile_env = self._conanfile.buildenv build_env.compose_env(profile_env) - for require, build_require in self._conanfile.dependencies.build.items(): - if require.direct: + build_requires = self._conanfile.dependencies.build.topological_sort + for require, build_require in reversed(build_requires.items()): + if require.direct: # Only buildenv_info from direct deps is propagated # higher priority, explicit buildenv_info if build_require.buildenv_info: build_env.compose_env(build_require.buildenv_info) @@ -34,7 +35,8 @@ def environment(self): build_env.compose_env(runenv_from_cpp_info(self._conanfile, build_require.cpp_info)) # Requires in host context can also bring some direct buildenv_info - for require in self._conanfile.dependencies.host.values(): + host_requires = self._conanfile.dependencies.host.topological_sort + for require in reversed(host_requires.values()): if require.buildenv_info: build_env.compose_env(require.buildenv_info) diff --git a/conans/model/conanfile_interface.py b/conans/model/conanfile_interface.py index b389f3736f7..4f14d9c558b 100644 --- a/conans/model/conanfile_interface.py +++ b/conans/model/conanfile_interface.py @@ -19,6 +19,9 @@ def __eq__(self, other): """ return self._conanfile == other._conanfile + def __hash__(self): + return hash(self._conanfile) + def __ne__(self, other): return not self.__eq__(other) diff --git a/conans/model/dependencies.py b/conans/model/dependencies.py index 10256791ea7..70ce531c410 100644 --- a/conans/model/dependencies.py +++ b/conans/model/dependencies.py @@ -1,6 +1,5 @@ from collections import OrderedDict -from conans.client.graph.graph import CONTEXT_BUILD from conans.model.conanfile_interface import ConanFileInterface from conans.model.ref import ConanFileReference @@ -122,7 +121,34 @@ def expand(nodes, is_build, is_test): return ConanFileDependencies(d) def filter(self, require_filter): - return super(ConanFileDependencies, self).filter(require_filter) + # FIXME: Copy of hte above, to return ConanFileDependencies class object + def filter_fn(require): + for k, v in require_filter.items(): + if getattr(require, k) != v: + return False + return True + + data = OrderedDict((k, v) for k, v in self._data.items() if filter_fn(k)) + return ConanFileDependencies(data, require_filter) + + @property + def topological_sort(self): + # Return first independent nodes, final ones are the more direct deps + result = OrderedDict() + opened = self._data.copy() + + while opened: + opened_values = set(opened.values()) + new_opened = OrderedDict() + for req, conanfile in opened.items(): + deps_in_opened = any(d in opened_values for d in conanfile.dependencies.values()) + if deps_in_opened: + new_opened[req] = conanfile # keep it for next iteration + else: + result[req] = conanfile # No dependencies in open set! + + opened = new_opened + return ConanFileDependencies(result) @property def direct_host(self): diff --git a/conans/test/integration/build_requires/build_requires_test.py b/conans/test/integration/build_requires/build_requires_test.py index 8b16101386e..8e2f3e004ad 100644 --- a/conans/test/integration/build_requires/build_requires_test.py +++ b/conans/test/integration/build_requires/build_requires_test.py @@ -451,3 +451,57 @@ def build(self): .with_build_requires("harfbuzz/1.0@test/test")}) client.run("install . --build=missing") self.assertIn("ZLIBS LIBS: ['myzlib']", client.out) + + +def test_dependents_new_buildenv(): + client = TestClient() + boost = textwrap.dedent(""" + from conans import ConanFile + class Boost(ConanFile): + def package_info(self): + self.buildenv_info.define_path("PATH", "myboostpath") + """) + other = textwrap.dedent(""" + from conans import ConanFile + class Other(ConanFile): + requires = "boost/1.0" + def package_info(self): + self.buildenv_info.append_path("PATH", "myotherpath") + self.buildenv_info.prepend_path("PATH", "myotherprepend") + """) + consumer = textwrap.dedent(""" + from conans import ConanFile + from conan.tools.env import VirtualBuildEnv + import os + class Lib(ConanFile): + build_requires = {} + def generate(self): + build_env = VirtualBuildEnv(self).environment() + with build_env.apply(): + self.output.info("LIB PATH %s" % os.getenv("PATH")) + """) + client.save({"boost/conanfile.py": boost, + "other/conanfile.py": other, + "consumer/conanfile.py": consumer.format('"boost/1.0", "other/1.0"'), + "profile_define": "[buildenv]\nPATH=(path)profilepath", + "profile_append": "[buildenv]\nPATH+=(path)profilepath", + "profile_prepend": "[buildenv]\nPATH=+(path)profilepath"}) + client.run("create boost boost/1.0@") + client.run("create other other/1.0@") + client.run("install consumer") + result = os.pathsep.join(["myotherprepend", "myboostpath", "myotherpath"]) + assert "LIB PATH {}".format(result) in client.out + + # Now test if we declare in different order, still topological order should be respected + client.save({"consumer/conanfile.py": consumer.format('"other/1.0", "boost/1.0"')}) + client.run("install consumer") + assert "LIB PATH {}".format(result) in client.out + + client.run("install consumer -pr=profile_define") + assert "LIB PATH profilepath" in client.out + client.run("install consumer -pr=profile_append") + result = os.pathsep.join(["myotherprepend", "myboostpath", "myotherpath", "profilepath"]) + assert "LIB PATH {}".format(result) in client.out + client.run("install consumer -pr=profile_prepend") + result = os.pathsep.join(["profilepath", "myotherprepend", "myboostpath", "myotherpath"]) + assert "LIB PATH {}".format(result) in client.out diff --git a/conans/test/integration/environment/test_env.py b/conans/test/integration/environment/test_env.py index b3053a2a094..c36dc9ef041 100644 --- a/conans/test/integration/environment/test_env.py +++ b/conans/test/integration/environment/test_env.py @@ -267,13 +267,13 @@ def generate(self): """) client.save({"conanfile.py": consumer}, clean_first=True) client.run("install . -s:b os=Windows -s:h os=Linux --build") - assert "BUILDENV: MyOpenSSLWindowsValue MyGCCValue "\ + assert "BUILDENV: MyGCCValue MyOpenSSLWindowsValue "\ "MyCMakeRunValue MyCMakeBuildValue!!!" in client.out assert "RUNENV: MyOpenSSLLinuxValue!!!" in client.out # Even if the generator is duplicated in command line (it used to fail due to bugs) client.run("install . -s:b os=Windows -s:h os=Linux --build -g VirtualRunEnv -g VirtualBuildEnv") - assert "BUILDENV: MyOpenSSLWindowsValue MyGCCValue "\ + assert "BUILDENV: MyGCCValue MyOpenSSLWindowsValue "\ "MyCMakeRunValue MyCMakeBuildValue!!!" in client.out assert "RUNENV: MyOpenSSLLinuxValue!!!" in client.out