diff --git a/conan/internal/model/cpp_info.py b/conan/internal/model/cpp_info.py index 4a2fd191070..119eb29333e 100644 --- a/conan/internal/model/cpp_info.py +++ b/conan/internal/model/cpp_info.py @@ -89,6 +89,8 @@ def __init__(self, set_defaults=False): self._sysroot = None self._requires = None + self._consumer_conanfile = None + # LEGACY 1.X fields, can be removed in 2.X self.names = MockInfoProperty("cpp_info.names") self.filenames = MockInfoProperty("cpp_info.filenames") @@ -104,6 +106,9 @@ def __init__(self, set_defaults=False): self._location = None self._link_location = None + def set_consumer(self, conanfile): + self._consumer_conanfile = conanfile + def serialize(self): return { "includedirs": self._includedirs, @@ -337,6 +342,12 @@ def defines(self, value): def cflags(self): if self._cflags is None: self._cflags = [] + elif callable(self._cflags): + if self._consumer_conanfile is None: + ConanOutput().warning(f"Callable for cflags: {self._cflags}, but generator " + "didn't call 'set_consumer()' first") + return [] + return self._cflags(self._consumer_conanfile) return self._cflags @cflags.setter @@ -347,6 +358,12 @@ def cflags(self, value): def cxxflags(self): if self._cxxflags is None: self._cxxflags = [] + elif callable(self._cxxflags): + if self._consumer_conanfile is None: + ConanOutput().warning(f"Callable for cxxflags: {self._cxxflags}, but generator " + "didn't call 'set_consumer()' first") + return [] + return self._cxxflags(self._consumer_conanfile) return self._cxxflags @cxxflags.setter @@ -357,6 +374,12 @@ def cxxflags(self, value): def sharedlinkflags(self): if self._sharedlinkflags is None: self._sharedlinkflags = [] + elif callable(self._sharedlinkflags): + if self._consumer_conanfile is None: + ConanOutput().warning(f"Callable for sharedlinkflags: {self._sharedlinkflags}, but " + f"generator didn't call 'set_consumer()' first") + return [] + return self._sharedlinkflags(self._consumer_conanfile) return self._sharedlinkflags @sharedlinkflags.setter @@ -367,6 +390,12 @@ def sharedlinkflags(self, value): def exelinkflags(self): if self._exelinkflags is None: self._exelinkflags = [] + elif callable(self._exelinkflags): + if self._consumer_conanfile is None: + ConanOutput().warning(f"Callable for exelinkflags: {self._exelinkflags}, but " + f"generator didn't call 'set_consumer()' first") + return [] + return self._exelinkflags(self._consumer_conanfile) return self._exelinkflags @exelinkflags.setter diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index 13c33cc6524..6b57a59d6f5 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -194,6 +194,7 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var, comp_name extra_libs = self._cmakedeps.get_property("cmake_extra_interface_libs", self._conanfile, comp_name=comp_name, check_type=list) or [] sources = [self._path(source, pkg_folder, pkg_folder_var) for source in info.sources] + info.set_consumer(self._cmakedeps._conanfile) # noqa target = {"type": "INTERFACE", "includedirs": includedirs, "defines": defines, diff --git a/test/integration/package_id/compatible_test.py b/test/integration/package_id/compatible_test.py index f3586b5952b..9d35694ed05 100644 --- a/test/integration/package_id/compatible_test.py +++ b/test/integration/package_id/compatible_test.py @@ -726,6 +726,180 @@ def libc_compat(conanfile): f"libc_version=2" in tc.out +class TestCompatibleFlags: + + def test_compatible_flags(self): + """ The compiler flags depends on the consumer settings, not on the binary compatible + settings used to create that compatible binary. This test shows how the new info + can be used to parameterize on the consumer settings + """ + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + settings = "os" + + def compatibility(self): + if self.settings.os == "Windows" or self.settings.os == "Macos": + return [{"settings": [("os", "Linux")]}] + + def package_info(self): + def myflags(conanfile): + if conanfile.settings.get_safe("os") == "Windows": + return ["-mywinflag"] + elif conanfile.settings.get_safe("os") == "Linux": + return ["-mylinuxflag"] + else: + return ["-other-os-flag"] + self.cpp_info.cxxflags = myflags + self.cpp_info.cflags = myflags + self.cpp_info.sharedlinkflags = myflags + self.cpp_info.exelinkflags = myflags + """) + consumer = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + settings = "os", "build_type" + requires = "pkg/0.1" + generators = "CMakeDeps" + """) + c.save({"pkg/conanfile.py": conanfile, + "consumer/conanfile.py": consumer}) + + c.run("create pkg --name=pkg --version=0.1 -s os=Linux") + + def _check(flag, cmake_file): + assert f"$<$:$<$:{flag}>>)" in cmake_file + assert f"$<$:$<$:{flag}>>)" in cmake_file + assert (f"$<$,SHARED_LIBRARY>:" + f"$<$:{flag}>>") in cmake_file + assert (f"$<$,EXECUTABLE>:" + f"$<$:{flag}>>") in cmake_file + + c.run("install consumer -s os=Linux -c tools.cmake.cmakedeps:new=will_break_next") + cmake = c.load("consumer/pkg-Targets-release.cmake") + _check("-mylinuxflag", cmake) + + c.run("install consumer -s os=Windows -c tools.cmake.cmakedeps:new=will_break_next") + cmake = c.load("consumer/pkg-Targets-release.cmake") + _check("-mywinflag", cmake) + + c.run("install consumer -s os=Macos -c tools.cmake.cmakedeps:new=will_break_next") + cmake = c.load("consumer/pkg-Targets-release.cmake") + _check("-other-os-flag", cmake) + + def test_simple_lambda(self): + """ same as above, but more compact condition + """ + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.microsoft import is_msvc + + class Pkg(ConanFile): + def package_info(self): + self.cpp_info.cxxflags = lambda c: ["-mywinflag"] if is_msvc(c) else [] + """) + consumer = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + settings = "compiler" + requires = "pkg/0.1" + def generate(self): + cpp_info = self.dependencies["pkg"].cpp_info + cpp_info.set_consumer(self) + self.output.info(f"CXXFLAGS: {cpp_info.cxxflags}!!!") + """) + c.save({"pkg/conanfile.py": conanfile, + "consumer/conanfile.py": consumer}) + + settings = "-s compiler=msvc -s compiler.version=193 -s compiler.runtime=dynamic" + c.run(f"create pkg --name=pkg --version=0.1 {settings}") + + c.run(f"install consumer {settings}") + assert f"conanfile.py: CXXFLAGS: ['-mywinflag']!!!" in c.out + c.run(f"install consumer {settings} -s &:compiler=clang -s &:compiler.version=19") + assert f"conanfile.py: CXXFLAGS: []!!!" in c.out + + def test_compatible_flags_direct(self): + """ same as above but without compatibility + """ + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + def package_info(self): + def myflags(conanfile): + if conanfile.settings.get_safe("os") == "Windows": + return ["-mywinflag"] + elif conanfile.settings.get_safe("os") == "Linux": + return ["-mylinuxflag"] + else: + return ["-other-os-flag"] + self.cpp_info.cxxflags = myflags + """) + consumer = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + settings = "os" + requires = "pkg/0.1" + def generate(self): + cpp_info = self.dependencies["pkg"].cpp_info + cpp_info.set_consumer(self) + self.output.info(f"FLAGS: {cpp_info.cxxflags}!!!") + """) + c.save({"pkg/conanfile.py": conanfile, + "consumer/conanfile.py": consumer}) + + c.run("create pkg --name=pkg --version=0.1") + c.run("export consumer --name=dep1 --version=0.1") + c.run("export consumer --name=dep2 --version=0.1") + + c.run("install --requires=dep1/0.1 --requires=dep2/0.1 " + "-s os=Macos -s dep1/*:os=Linux -s dep2/*:os=Windows --build=missing") + assert "dep1/0.1: FLAGS: ['-mylinuxflag']!!!" in c.out + assert "dep2/0.1: FLAGS: ['-mywinflag']!!!" in c.out + + c.run("install --requires=dep1/0.1 --requires=dep2/0.1 " + "-s os=Macos -s dep1/*:os=Windows -s dep2/*:os=Linux --build=missing") + assert "dep1/0.1: FLAGS: ['-mywinflag']!!!" in c.out + assert "dep2/0.1: FLAGS: ['-mylinuxflag']!!!" in c.out + + c.run("install --requires=dep1/0.1 --requires=dep2/0.1 " + "-s os=Macos --build=missing") + assert "dep1/0.1: FLAGS: ['-other-os-flag']!!!" in c.out + assert "dep2/0.1: FLAGS: ['-other-os-flag']!!!" in c.out + + def test_warnings(self): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + def package_info(self): + def myflags(conanfile): + return ["-myflag"] + self.cpp_info.cxxflags = myflags + """) + consumer = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + requires = "pkg/0.1" + def generate(self): + cpp_info = self.dependencies["pkg"].cpp_info + self.output.info(f"FLAGS: {cpp_info.cxxflags}!!!") + """) + c.save({"pkg/conanfile.py": conanfile, + "consumer/conanfile.py": consumer}) + + c.run("create pkg --name=pkg --version=0.1") + c.run("install consumer") + print(c.out) + assert "WARN: Callable for cxxflags:" in c.out + assert "FLAGS: []!!!" in c.out + + def test_compatibility_remove_cppstd(): """ This test tries to reflect the following scenario: - User recently added compiler.cppstd to their settings