Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set some CMakeDeps properties from consumer #12609

Merged
merged 13 commits into from
Nov 29, 2022
Merged
Show file tree
Hide file tree
Changes from 12 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
41 changes: 40 additions & 1 deletion conan/tools/cmake/cmakedeps/cmakedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, conanfile):

# Enable/Disable checking if a component target exists or not
self.check_components_exist = False
self._properties = {}

def generate(self):
# FIXME: Remove this in 2.0
Expand Down Expand Up @@ -76,7 +77,7 @@ def content(self):
if dep.is_build_context and dep.ref.name not in self.build_context_activated:
continue

cmake_find_mode = dep.cpp_info.get_property("cmake_find_mode")
cmake_find_mode = self.get_property("cmake_find_mode", dep)
cmake_find_mode = cmake_find_mode or FIND_MODE_CONFIG
cmake_find_mode = cmake_find_mode.lower()
# Skip from the requirement
Expand Down Expand Up @@ -112,3 +113,41 @@ def _generate_files(self, require, dep, ret, find_module_mode):
# file is common for the different configurations.
if not os.path.exists(config.filename):
ret[config.filename] = config.render()

def set_property(self, dep, prop, value, build_context=False):
build_suffix = "_build" if build_context else ""
czoido marked this conversation as resolved.
Show resolved Hide resolved
self._properties.setdefault(f"{dep}{build_suffix}", {}).update({prop: value})

def get_property(self, prop, dep, comp_name=None):
dep_name = dep.ref.name
build_suffix = "_build" if str(
czoido marked this conversation as resolved.
Show resolved Hide resolved
dep_name) in self.build_context_activated and dep.context == "build" else ""
dep_comp = f"{str(dep_name)}::{comp_name}" if comp_name else f"{str(dep_name)}"
try:
return self._properties[f"{dep_comp}{build_suffix}"][prop]
except KeyError:
return dep.cpp_info.get_property(prop) if not comp_name else dep.cpp_info.components[
comp_name].get_property(prop)

def get_cmake_package_name(self, dep, module_mode=None):
czoido marked this conversation as resolved.
Show resolved Hide resolved
"""Get the name of the file for the find_package(XXX)"""
# This is used by CMakeDeps to determine:
# - The filename to generate (XXX-config.cmake or FindXXX.cmake)
# - The name of the defined XXX_DIR variables
# - The name of transitive dependencies for calls to find_dependency
if module_mode and self.get_find_mode(dep) in [FIND_MODE_MODULE, FIND_MODE_BOTH]:
ret = self.get_property("cmake_module_file_name", dep)
if ret:
return ret
ret = self.get_property("cmake_file_name", dep)
return ret or dep.ref.name

def get_find_mode(self, dep):
"""
:param dep: requirement
:return: "none" or "config" or "module" or "both" or "config" when not set
"""
tmp = self.get_property("cmake_find_mode", dep)
if tmp is None:
return "config"
return tmp.lower()
12 changes: 6 additions & 6 deletions conan/tools/cmake/cmakedeps/templates/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import jinja2
from jinja2 import Template

from conan.tools.cmake.utils import get_cmake_package_name
from conans.errors import ConanException


Expand All @@ -23,7 +22,7 @@ def root_target_name(self):

@property
def file_name(self):
return get_cmake_package_name(self.conanfile, module_mode=self.generating_module) + self.suffix
return self.cmakedeps.get_cmake_package_name(self.conanfile, module_mode=self.generating_module) + self.suffix

@property
def suffix(self):
Expand Down Expand Up @@ -85,10 +84,10 @@ def _get_target_default_name(req, component_name="", suffix=""):

def get_root_target_name(self, req, suffix=""):
if self.generating_module:
ret = req.cpp_info.get_property("cmake_module_target_name")
ret = self.cmakedeps.get_property("cmake_module_target_name", req)
if ret:
return ret
ret = req.cpp_info.get_property("cmake_target_name")
ret = self.cmakedeps.get_property("cmake_target_name", req)
return ret or self._get_target_default_name(req, suffix=suffix)

def get_component_alias(self, req, comp_name):
Expand All @@ -99,10 +98,11 @@ def get_component_alias(self, req, comp_name):
raise ConanException("Component '{name}::{cname}' not found in '{name}' "
"package requirement".format(name=req.ref.name, cname=comp_name))
if self.generating_module:
ret = req.cpp_info.components[comp_name].get_property("cmake_module_target_name")
ret = self.cmakedeps.get_property("cmake_module_target_name", req, comp_name=comp_name)
if ret:
return ret
ret = req.cpp_info.components[comp_name].get_property("cmake_target_name")
ret = self.cmakedeps.get_property("cmake_target_name", req, comp_name=comp_name)

# If we don't specify the `cmake_target_name` property for the component it will
# fallback to the pkg_name::comp_name, it wont use the root cpp_info cmake_target_name
# property because that is also an absolute name (Greetings::Greetings), it is not a namespace
Expand Down
10 changes: 5 additions & 5 deletions conan/tools/cmake/cmakedeps/templates/target_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from conan.tools.cmake.cmakedeps import FIND_MODE_NONE, FIND_MODE_CONFIG, FIND_MODE_MODULE, \
FIND_MODE_BOTH
from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate
from conan.tools.cmake.utils import get_cmake_package_name, get_find_mode

"""

foo-release-x86_64-data.cmake
Expand Down Expand Up @@ -177,9 +177,9 @@ def _get_dependency_filenames(self):
for dep_name, _ in self.conanfile.cpp_info.required_components:
if dep_name and dep_name not in ret: # External dep
req = direct_host[dep_name]
ret.append(get_cmake_package_name(req))
ret.append(self.cmakedeps.get_cmake_package_name(req))
elif direct_host:
ret = [get_cmake_package_name(r, self.generating_module) for r in direct_host.values()]
ret = [self.cmakedeps.get_cmake_package_name(r, self.generating_module) for r in direct_host.values()]

return ret

Expand All @@ -189,8 +189,8 @@ def _get_dependencies_find_modes(self):
return ret
deps = self.conanfile.dependencies.filter({"build": False, "visible": True, "direct": True})
for dep in deps.values():
dep_file_name = get_cmake_package_name(dep, self.generating_module)
find_mode = get_find_mode(dep)
dep_file_name = self.cmakedeps.get_cmake_package_name(dep, self.generating_module)
find_mode = self.cmakedeps.get_find_mode(dep)
default_value = "NO_MODULE" if not self.generating_module else "MODULE"
values = {
FIND_MODE_NONE: "",
Expand Down
24 changes: 0 additions & 24 deletions conan/tools/cmake/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,3 @@ def is_multi_configuration(generator):
return False
return "Visual" in generator or "Xcode" in generator or "Multi-Config" in generator


def get_cmake_package_name(conanfile, module_mode=None):
"""Get the name of the file for the find_package(XXX)"""
# This is used by CMakeToolchain/CMakeDeps to determine:
# - The filename to generate (XXX-config.cmake or FindXXX.cmake)
# - The name of the defined XXX_DIR variables
# - The name of transitive dependencies for calls to find_dependency
if module_mode and get_find_mode(conanfile) in [FIND_MODE_MODULE, FIND_MODE_BOTH]:
ret = conanfile.cpp_info.get_property("cmake_module_file_name")
if ret:
return ret
ret = conanfile.cpp_info.get_property("cmake_file_name")
return ret or conanfile.ref.name


def get_find_mode(conanfile):
"""
:param conanfile: conanfile of the requirement
:return: "none" or "config" or "module" or "both" or "config" when not set
"""
tmp = conanfile.cpp_info.get_property("cmake_find_mode")
if tmp is None:
return "config"
return tmp.lower()
6 changes: 2 additions & 4 deletions conans/client/generators/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
from jinja2 import Environment

from conan.tools.cmake.cmakedeps.cmakedeps import CMakeDeps
from conan.tools.cmake.cmakedeps.templates import (
CMakeDepsFileTemplate,
get_cmake_package_name as cmake_get_file_name)
from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate
from conan.tools.gnu.pkgconfigdeps import (
_get_component_name as pkgconfig_get_component_name,
_get_name_with_namespace as pkgconfig_get_name_with_namespace,
Expand Down Expand Up @@ -444,7 +442,7 @@ def read_pkg_file(filename):
cmake_variables = {
'global_target_name': requirement.cpp_info.get_property('cmake_target_name') or "{0}::{0}".format(requirement.ref.name),
'component_alias': cmake_component_alias,
'file_name': cmake_get_file_name(requirement)
'file_name': cmake_deps.get_cmake_package_name(requirement)
}

pkgconfig_component_alias = {
Expand Down
199 changes: 199 additions & 0 deletions conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,202 @@ def package_info(self):
assert r"spaces=me you" in deps
assert r"foobar=bazbuz" in deps
assert r"answer=42" in deps


def test_dependency_props_from_consumer():
client = TestClient(path_with_spaces=False)
bar = textwrap.dedent(r'''
from conan import ConanFile
class FooConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
def package_info(self):
self.cpp_info.set_property("cmake_find_mode", "both")
self.cpp_info.components["component1"].requires = []
''')

foo = textwrap.dedent(r'''
from conan import ConanFile
from conan.tools.cmake import CMakeDeps, cmake_layout
class FooConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "bar/1.0"
def layout(self):
cmake_layout(self)
def generate(self):
deps = CMakeDeps(self)
{set_find_mode}
deps.set_property("bar", "cmake_file_name", "custom_bar_file_name")
deps.set_property("bar", "cmake_module_file_name", "custom_bar_module_file_name")
deps.set_property("bar", "cmake_target_name", "custom_bar_target_name")
deps.set_property("bar", "cmake_module_target_name", "custom_bar_module_target_name")
deps.set_property("bar::component1", "cmake_target_name", "custom_bar_component_target_name")
deps.generate()
''')

set_find_mode = """
deps.set_property("bar", "cmake_find_mode", {find_mode})
"""

client.save({"foo.py": foo.format(set_find_mode=""), "bar.py": bar}, clean_first=True)

module_file = os.path.join(client.current_folder, "build", "generators", "module-custom_bar_module_file_nameTargets.cmake")
components_module = os.path.join(client.current_folder, "build", "generators", "custom_bar_file_name-Target-release.cmake")
config_file = os.path.join(client.current_folder, "build", "generators", "custom_bar_file_nameTargets.cmake")

# uses cmake_find_mode set in bar: both
client.run("create bar.py bar/1.0@")
client.run("install foo.py foo/1.0@")
assert os.path.exists(module_file)
assert os.path.exists(config_file)
module_content = client.load(module_file)
assert "add_library(custom_bar_module_target_name INTERFACE IMPORTED)" in module_content
config_content = client.load(config_file)
assert "add_library(custom_bar_target_name INTERFACE IMPORTED)" in config_content
components_module_content = client.load(components_module)
assert "add_library(bar_custom_bar_component_target_name_DEPS_TARGET INTERFACE IMPORTED)" in components_module_content

client.save({"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="'none'")),
"bar.py": bar}, clean_first=True)
client.run("create bar.py bar/1.0@")
client.run("install foo.py foo/1.0@")
assert not os.path.exists(module_file)
assert not os.path.exists(config_file)

client.save({"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="'module'")),
"bar.py": bar}, clean_first=True)
client.run("create bar.py bar/1.0@")
client.run("install foo.py foo/1.0@")
assert os.path.exists(module_file)
assert not os.path.exists(config_file)

client.save({"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="'config'")),
"bar.py": bar}, clean_first=True)
client.run("create bar.py bar/1.0@")
client.run("install foo.py foo/1.0@")
assert not os.path.exists(module_file)
assert os.path.exists(config_file)


def test_props_from_consumer_build_context_activated():
client = TestClient(path_with_spaces=False)
bar = textwrap.dedent(r'''
from conan import ConanFile
class FooConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
def package_info(self):
self.cpp_info.set_property("cmake_find_mode", "both")
self.cpp_info.components["component1"].requires = []
''')

foo = textwrap.dedent(r'''
from conan import ConanFile
from conan.tools.cmake import CMakeDeps, cmake_layout
class FooConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "bar/1.0"
tool_requires = "bar/1.0"
def layout(self):
cmake_layout(self)
def generate(self):
deps = CMakeDeps(self)
deps.build_context_activated = ["bar"]
deps.build_context_suffix = {{"bar": "_BUILD"}}
{set_find_mode}

deps.set_property("bar", "cmake_file_name", "custom_bar_file_name")
deps.set_property("bar", "cmake_module_file_name", "custom_bar_module_file_name")
deps.set_property("bar", "cmake_target_name", "custom_bar_target_name")
deps.set_property("bar", "cmake_module_target_name", "custom_bar_module_target_name")
deps.set_property("bar::component1", "cmake_target_name", "custom_bar_component_target_name")

deps.set_property("bar", "cmake_file_name", "custom_bar_build_file_name", build_context=True)
deps.set_property("bar", "cmake_module_file_name", "custom_bar_build_module_file_name", build_context=True)
deps.set_property("bar", "cmake_target_name", "custom_bar_build_target_name", build_context=True)
deps.set_property("bar", "cmake_module_target_name", "custom_bar_build_module_target_name", build_context=True)
deps.set_property("bar::component1", "cmake_target_name", "custom_bar_build_component_target_name", build_context=True)

deps.generate()
''')

set_find_mode = """
deps.set_property("bar", "cmake_find_mode", {find_mode})
deps.set_property("bar", "cmake_find_mode", {find_mode}, build_context=True)
"""

client.save({"foo.py": foo.format(set_find_mode=""), "bar.py": bar}, clean_first=True)

module_file = os.path.join(client.current_folder, "build", "generators",
"module-custom_bar_module_file_nameTargets.cmake")
components_module = os.path.join(client.current_folder, "build", "generators",
"custom_bar_file_name-Target-release.cmake")
config_file = os.path.join(client.current_folder, "build", "generators",
"custom_bar_file_nameTargets.cmake")

module_file_build = os.path.join(client.current_folder, "build", "generators",
"module-custom_bar_build_module_file_name_BUILDTargets.cmake")
components_module_build = os.path.join(client.current_folder, "build", "generators",
"custom_bar_build_file_name_BUILD-Target-release.cmake")
config_file_build = os.path.join(client.current_folder, "build", "generators",
"custom_bar_build_file_name_BUILDTargets.cmake")

# uses cmake_find_mode set in bar: both
client.run("create bar.py bar/1.0@ -pr:h=default -pr:b=default")
client.run("install foo.py foo/1.0@ -pr:h=default -pr:b=default")
assert os.path.exists(module_file)
assert os.path.exists(config_file)
assert os.path.exists(module_file_build)
assert os.path.exists(config_file_build)

module_content = client.load(module_file)
assert "add_library(custom_bar_module_target_name INTERFACE IMPORTED)" in module_content
config_content = client.load(config_file)
assert "add_library(custom_bar_target_name INTERFACE IMPORTED)" in config_content

module_content_build = client.load(module_file_build)
assert "add_library(custom_bar_build_module_target_name INTERFACE IMPORTED)" in module_content_build
config_content_build = client.load(config_file_build)
assert "add_library(custom_bar_build_target_name INTERFACE IMPORTED)" in config_content_build

components_module_content = client.load(components_module)
assert "add_library(bar_custom_bar_component_target_name_DEPS_TARGET INTERFACE IMPORTED)" in components_module_content

components_module_content_build = client.load(components_module_build)
assert "add_library(bar_BUILD_custom_bar_build_component_target_name_DEPS_TARGET INTERFACE IMPORTED)" in components_module_content_build

client.save(
{"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="'none'")), "bar.py": bar},
clean_first=True)
client.run("create bar.py bar/1.0@ -pr:h=default -pr:b=default")
client.run("install foo.py foo/1.0@ -pr:h=default -pr:b=default")
assert not os.path.exists(module_file)
assert not os.path.exists(config_file)
assert not os.path.exists(module_file_build)
assert not os.path.exists(config_file_build)

client.save({"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="'module'")),
"bar.py": bar}, clean_first=True)
client.run("create bar.py bar/1.0@ -pr:h=default -pr:b=default")
client.run("install foo.py foo/1.0@ -pr:h=default -pr:b=default")
assert os.path.exists(module_file)
assert not os.path.exists(config_file)
assert os.path.exists(module_file_build)
assert not os.path.exists(config_file_build)

client.save({"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="'config'")),
"bar.py": bar}, clean_first=True)
client.run("create bar.py bar/1.0@ -pr:h=default -pr:b=default")
client.run("install foo.py foo/1.0@ -pr:h=default -pr:b=default")
assert not os.path.exists(module_file)
assert os.path.exists(config_file)
assert not os.path.exists(module_file_build)
assert os.path.exists(config_file_build)

# invalidate upstream property setting a None, will use config that's the default
client.save({"foo.py": foo.format(set_find_mode=set_find_mode.format(find_mode="None")),
"bar.py": bar}, clean_first=True)
client.run("create bar.py bar/1.0@ -pr:h=default -pr:b=default")
client.run("install foo.py foo/1.0@ -pr:h=default -pr:b=default")
assert not os.path.exists(module_file)
assert os.path.exists(config_file)
assert not os.path.exists(module_file_build)
assert os.path.exists(config_file_build)