Skip to content

Commit

Permalink
explore replace_requires references adjustements (#16443)
Browse files Browse the repository at this point in the history
* explore replace_requires references adjustements

* fix tests

* new test with requires components and options
  • Loading branch information
memsharded authored Sep 30, 2024
1 parent 97ab818 commit f33e5c0
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 2 deletions.
1 change: 1 addition & 0 deletions conans/client/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def __init__(self, ref, conanfile, context, recipe=None, path=None, test=False):
self.should_build = False # If the --build or policy wants to build this binary
self.build_allowed = False
self.is_conf = False
self.replaced_requires = {} # To track the replaced requires for self.dependencies[old-ref]

def __lt__(self, other):
"""
Expand Down
1 change: 1 addition & 0 deletions conans/client/graph/graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ def _resolve_replace_requires(self, node, require, profile_build, profile_host,
node.conanfile.requires.reindex(require, alternative_ref.name)
require.ref.name = alternative_ref.name
graph.replaced_requires[original_require] = repr(require.ref)
node.replaced_requires[original_require] = require
break # First match executes the alternative and finishes checking others

def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock):
Expand Down
6 changes: 4 additions & 2 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,10 @@ def check_component_requires(self, conanfile):
if not external:
return
# Only direct host (not test) dependencies can define required components
direct_dependencies = [d.ref.name for d in conanfile.requires.values()
if not d.build and not d.is_test and d.visible and not d.override]
# We use conanfile.dependencies to use the already replaced ones by "replace_requires"
# So consumers can keep their ``self.cpp_info.requires = ["pkg_name::comp"]``
direct_dependencies = [r.ref.name for r, d in conanfile.dependencies.items() if r.direct
and not r.build and not r.is_test and r.visible and not r.override]

for e in external:
if e not in direct_dependencies:
Expand Down
6 changes: 6 additions & 0 deletions conans/model/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ class ConanFileDependencies(UserRequirementsDict):
def from_node(node):
d = OrderedDict((require, ConanFileInterface(transitive.node.conanfile))
for require, transitive in node.transitive_deps.items())
for old_req, new_req in node.replaced_requires.items():
existing = d.pop(new_req, None)
if existing is not None:
added_req = new_req.copy_requirement()
added_req.ref = RecipeReference.loads(old_req)
d[added_req] = existing
return ConanFileDependencies(d)

def filter(self, require_filter, remove_system=True):
Expand Down
118 changes: 118 additions & 0 deletions test/integration/graph/test_replace_requires.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import textwrap

import pytest

Expand Down Expand Up @@ -157,3 +158,120 @@ def test_replace_requires_test_requires():
c.run("create gtest")
c.run("install app -pr=profile")
assert "gtest/0.1: gtest/0.2" in c.out # The replacement happens


def test_replace_requires_consumer_references():
c = TestClient()
# IMPORTANT: The replacement package must be target-compatible
zlib_ng = textwrap.dedent("""
from conan import ConanFile
class ZlibNG(ConanFile):
name = "zlib-ng"
version = "0.1"
def package_info(self):
self.cpp_info.set_property("cmake_file_name", "ZLIB")
self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
""")
conanfile = textwrap.dedent("""
from conan import ConanFile
class App(ConanFile):
name = "app"
version = "0.1"
settings = "build_type"
requires = "zlib/0.1"
generators = "CMakeDeps"
def generate(self):
self.output.info(f"DEP ZLIB generate: {self.dependencies['zlib'].ref.name}!")
def build(self):
self.output.info(f"DEP ZLIB build: {self.dependencies['zlib'].ref.name}!")
def package_info(self):
self.output.info(f"DEP ZLIB package_info: {self.dependencies['zlib'].ref.name}!")
self.cpp_info.requires = ["zlib::zlib"]
""")
c.save({"zlibng/conanfile.py": zlib_ng,
"app/conanfile.py": conanfile,
"profile": "[replace_requires]\nzlib/0.1: zlib-ng/0.1"})
c.run("create zlibng")
c.run("build app -pr=profile")
assert "zlib/0.1: zlib-ng/0.1" in c.out
assert "conanfile.py (app/0.1): DEP ZLIB generate: zlib-ng!" in c.out
assert "conanfile.py (app/0.1): DEP ZLIB build: zlib-ng!" in c.out
# Check generated CMake code. If the targets are NOT compatible, then the replacement
# Cannot happen
assert "find_package(ZLIB)" in c.out
assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
cmake = c.load("app/ZLIBTargets.cmake")
assert "add_library(ZLIB::ZLIB INTERFACE IMPORTED)" in cmake
c.run("create app -pr=profile")
assert "zlib/0.1: zlib-ng/0.1" in c.out
assert "app/0.1: DEP ZLIB generate: zlib-ng!" in c.out
assert "app/0.1: DEP ZLIB build: zlib-ng!" in c.out


def test_replace_requires_consumer_components_options():
c = TestClient()
# IMPORTANT: The replacement package must be target-compatible
zlib_ng = textwrap.dedent("""
from conan import ConanFile
class ZlibNG(ConanFile):
name = "zlib-ng"
version = "0.1"
options = {"compat": [False, True]}
default_options = {"compat": False}
def package_info(self):
self.cpp_info.set_property("cmake_file_name", "ZLIB")
self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
if self.options.compat:
self.cpp_info.components["myzlib"].set_property("cmake_target_name",
"ZLIB::zmylib")
""")
conanfile = textwrap.dedent("""
from conan import ConanFile
class App(ConanFile):
name = "app"
version = "0.1"
settings = "build_type"
requires = "zlib/0.1"
generators = "CMakeDeps"
def generate(self):
self.output.info(f"DEP ZLIB generate: {self.dependencies['zlib'].ref.name}!")
def build(self):
self.output.info(f"DEP ZLIB build: {self.dependencies['zlib'].ref.name}!")
def package_info(self):
self.output.info(f"DEP ZLIB package_info: {self.dependencies['zlib'].ref.name}!")
self.cpp_info.requires = ["zlib::myzlib"]
""")
profile = textwrap.dedent("""
[options]
zlib-ng/*:compat=True
[replace_requires]
zlib/0.1: zlib-ng/0.1
""")
c.save({"zlibng/conanfile.py": zlib_ng,
"app/conanfile.py": conanfile,
"profile": profile})

c.run("create zlibng -o *:compat=True")
c.run("build app -pr=profile")
assert "zlib/0.1: zlib-ng/0.1" in c.out
assert "conanfile.py (app/0.1): DEP ZLIB generate: zlib-ng!" in c.out
assert "conanfile.py (app/0.1): DEP ZLIB build: zlib-ng!" in c.out
# Check generated CMake code. If the targets are NOT compatible, then the replacement
# Cannot happen
assert "find_package(ZLIB)" in c.out
assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
cmake = c.load("app/ZLIBTargets.cmake")
assert "add_library(ZLIB::ZLIB INTERFACE IMPORTED)" in cmake
cmake = c.load("app/ZLIB-Target-none.cmake")
assert "set_property(TARGET ZLIB::ZLIB APPEND PROPERTY INTERFACE_LINK_LIBRARIES ZLIB::zmylib)" \
in cmake

c.run("create app -pr=profile")
assert "zlib/0.1: zlib-ng/0.1" in c.out
assert "app/0.1: DEP ZLIB generate: zlib-ng!" in c.out
assert "app/0.1: DEP ZLIB build: zlib-ng!" in c.out
assert "find_package(ZLIB)" in c.out
assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
14 changes: 14 additions & 0 deletions test/unittests/tools/cmake/test_cmaketoolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def conanfile():
c.folders.set_base_generators("/some/abs/path") # non-existing to not relativize
c._conan_node = Mock()
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -229,6 +230,8 @@ def conanfile_apple():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
# FIXME: Repeated fix pattern
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -258,6 +261,7 @@ def conanfile_msvc():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -332,6 +336,7 @@ def test_older_msvc_toolset():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
toolchain = CMakeToolchain(c)
content = toolchain.content
assert 'CMAKE_GENERATOR_TOOLSET "v110"' in content
Expand Down Expand Up @@ -360,6 +365,7 @@ def test_older_msvc_toolset_update():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
toolchain = CMakeToolchain(c)
content = toolchain.content
assert 'CMAKE_GENERATOR_TOOLSET "v142,version=14.29"' in content
Expand All @@ -385,6 +391,7 @@ def test_msvc_xp_toolsets():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
toolchain = CMakeToolchain(c)
content = toolchain.content
assert 'CMAKE_GENERATOR_TOOLSET "v110_xp"' in content
Expand All @@ -411,6 +418,7 @@ def conanfile_linux():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -442,6 +450,7 @@ def conanfile_linux_shared():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -482,6 +491,7 @@ def conanfile_windows_fpic():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -513,6 +523,7 @@ def conanfile_linux_fpic():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c


Expand Down Expand Up @@ -547,6 +558,7 @@ def test_libcxx_abi_flag():
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}

toolchain = CMakeToolchain(c)
content = toolchain.content
Expand Down Expand Up @@ -597,6 +609,7 @@ def test_apple_cmake_osx_sysroot(os, os_sdk, arch, expected_sdk):
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}

toolchain = CMakeToolchain(c)
content = toolchain.content
Expand Down Expand Up @@ -680,6 +693,7 @@ def conanfile_cross(self):
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
c._conan_node.replaced_requires = {}
return c

def test_cmake_system_name(self, conanfile_cross):
Expand Down

0 comments on commit f33e5c0

Please sign in to comment.