Skip to content

Commit

Permalink
Set some CMakeDeps properties from consumer (#12609)
Browse files Browse the repository at this point in the history
* draft

* change arg order

* fix name

* minor changes

* minor changes

* change dict syntax

* fix get

* wip

* remove or

* restore or

* invalidate upstream

* change syntax

* change suffix
  • Loading branch information
czoido authored Nov 29, 2022
1 parent 2734e19 commit aa8b113
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 40 deletions.
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 ""
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(
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):
"""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 @@ -189,9 +189,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 @@ -201,8 +201,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)

0 comments on commit aa8b113

Please sign in to comment.