Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion conan/internal/model/conanfile_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class ConanFileInterface:
def __str__(self):
return str(self._conanfile)

def __init__(self, conanfile):
def __init__(self, conanfile, consumer):
self._conanfile = conanfile
self._consumer = consumer

def __eq__(self, other):
"""
Expand Down Expand Up @@ -71,6 +72,7 @@ def runenv_info(self):

@property
def cpp_info(self):
self._conanfile.cpp_info.set_consumer(self._consumer)
return self._conanfile.cpp_info

@property
Expand Down
34 changes: 30 additions & 4 deletions conan/internal/model/cpp_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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,
Expand Down Expand Up @@ -335,6 +340,8 @@ def defines(self, value):

@property
def cflags(self):
if callable(self._cflags):
return self._cflags(self._consumer_conanfile)
if self._cflags is None:
self._cflags = []
return self._cflags
Expand All @@ -345,6 +352,8 @@ def cflags(self, value):

@property
def cxxflags(self):
if callable(self._cxxflags):
return self._cxxflags(self._consumer_conanfile)
if self._cxxflags is None:
self._cxxflags = []
return self._cxxflags
Expand All @@ -355,6 +364,8 @@ def cxxflags(self, value):

@property
def sharedlinkflags(self):
if callable(self._sharedlinkflags):
return self._sharedlinkflags(self._consumer_conanfile)
if self._sharedlinkflags is None:
self._sharedlinkflags = []
return self._sharedlinkflags
Expand All @@ -365,6 +376,8 @@ def sharedlinkflags(self, value):

@property
def exelinkflags(self):
if callable(self._exelinkflags):
return self._exelinkflags(self._consumer_conanfile)
if self._exelinkflags is None:
self._exelinkflags = []
return self._exelinkflags
Expand Down Expand Up @@ -440,15 +453,17 @@ def get_property(self, property_name, check_type=None):
def get_init(self, attribute, default):
# Similar to dict.setdefault
item = getattr(self, attribute)
if callable(item):
return item(self._consumer_conanfile)
if item is not None:
return item
setattr(self, attribute, default)
return default

def merge(self, other, overwrite=False):
"""
@param overwrite:
@type other: _Component
:param overwrite:
:type other: _Component
"""
def merge_list(o, d):
d.extend(e for e in o if e not in d)
Expand All @@ -457,8 +472,14 @@ def merge_list(o, d):
other_values = getattr(other, varname)
if other_values is not None:
if not overwrite:
current_values = self.get_init(varname, [])
merge_list(other_values, current_values)
if (callable(other_values) and other._consumer_conanfile is None) or \
(callable(getattr(self, varname)) and self._consumer_conanfile is None):
setattr(self, varname, other_values)
else:
if callable(other_values):
other_values = other_values(other._consumer_conanfile)
current_values = self.get_init(varname, [])
merge_list(other_values, current_values)
else:
setattr(self, varname, other_values)

Expand Down Expand Up @@ -673,6 +694,11 @@ def __init__(self, set_defaults=False):
self.default_components = None
self._package = _Component(set_defaults)

def set_consumer(self, conanfile):
self._package.set_consumer(conanfile)
for comp in self.components.values():
comp.set_consumer(conanfile)

def __getattr__(self, attr):
# all cpp_info.xxx of not defined things will go to the global package
return getattr(self._package, attr)
Expand Down
2 changes: 1 addition & 1 deletion conan/internal/model/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class ConanFileDependencies(UserRequirementsDict):

@staticmethod
def from_node(node):
d = OrderedDict((require, ConanFileInterface(transitive.node.conanfile))
d = OrderedDict((require, ConanFileInterface(transitive.node.conanfile, node.conanfile))
for require, transitive in node.transitive_deps.items())
if node.replaced_requires:
cant_be_removed = set()
Expand Down
182 changes: 182 additions & 0 deletions test/integration/package_id/compatible_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,3 +960,185 @@ def no_cppstd_compat(conanfile):
# Now we try again, this time app will find the compatible dep without cppstd
tc.run("install --requires=dep/1.0 -pr=profile -s=compiler.cppstd=17")
assert f"dep/1.0: Found compatible package '{dep_package_id}'" in tc.out


class TestCompatibleFlags:

@pytest.mark.parametrize("components", [True, False])
def test_compatible_flags(self, components):
""" 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
""")
if components:
conanfile = conanfile.replace(".cpp_info.", ".cpp_info.components['mycomp'].")

c.save({"pkg/conanfile.py": conanfile,
"consumer/conanfile.txt": "[requires]\npkg/0.1\n[generators]\nCMakeDeps"})

c.run("create pkg --name=pkg --version=0.1 -s os=Linux")

def _check(flag, cmake_file):
assert f"$<$<COMPILE_LANGUAGE:CXX>:$<$<CONFIG:RELEASE>:{flag}>>" in cmake_file
assert f"$<$<COMPILE_LANGUAGE:C>:$<$<CONFIG:RELEASE>:{flag}>>" in cmake_file
assert (f"$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:"
f"$<$<CONFIG:RELEASE>:{flag}>>") in cmake_file
assert (f"$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:"
f"$<$<CONFIG:RELEASE>:{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)

# Check old CMakeDeps generator
c.run("install consumer -s os=Linux -s arch=x86_64")
cmake = c.load("consumer/pkg-release-x86_64-data.cmake")
assert "-mylinuxflag" in cmake

def test_editable(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 layout(self):
self.cpp.source.cxxflags = lambda c: ["-mywineditflag"] if is_msvc(c) else []

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
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"editable add pkg --name=pkg --version=0.1")

c.run(f"install consumer {settings}")
assert f"conanfile.py: CXXFLAGS: ['-mywineditflag']!!!" 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_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
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
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
1 change: 0 additions & 1 deletion test/unittests/model/build_info/new_build_info_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,3 @@ def test_cpp_info_merge_aggregating_components_first(aggregate_first):
"jar_{}_2".format(n)]
assert getattr(cppinfo.components["boo2"], n) == ["jar2_{}_1".format(n),
"jar2_{}_2".format(n)]
assert getattr(cppinfo, n) == None