Skip to content

Commit 26caf37

Browse files
AbrilRBSmemsharded
andauthored
Fix [replace_requires] for replacements of same reference name (#17409)
* Fix [replace_requires] for replacements of same reference name * review * ci --------- Co-authored-by: memsharded <james@conan.io>
1 parent fb965e9 commit 26caf37

6 files changed

Lines changed: 81 additions & 16 deletions

File tree

.github/workflows/linux-tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
pull_request:
99
branches:
1010
- '*'
11+
- 'release/*'
1112
workflow_dispatch:
1213

1314
concurrency:
@@ -69,7 +70,7 @@ jobs:
6970
matrix:
7071
python-version: ['3.12.3', '3.9.2', '3.8.6', '3.6.15']
7172
test-type: ['unittests', 'integration', 'functional']
72-
name: Conan ${{ matrix.test-type }} (${{ matrix.python-version }})
73+
name: Conan ${{ matrix.test-type }} (${{ matrix.python-version }})
7374
steps:
7475
- name: Checkout code
7576
uses: actions/checkout@v4

.github/workflows/osx-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
pull_request:
99
branches:
1010
- '*'
11+
- 'release/*'
1112

1213
concurrency:
1314
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}

.github/workflows/win-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
pull_request:
99
branches:
1010
- '*'
11+
- 'release/*'
1112
workflow_dispatch:
1213

1314
concurrency:

conan/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from conans.model.conan_file import ConanFile
22
from conan.tools.scm import Version as _Version
33

4-
__version__ = '2.10.0'
4+
__version__ = '2.10.1'
55
conan_version = _Version(__version__)

conans/model/dependencies.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,19 @@ def from_node(node):
9292
d = OrderedDict((require, ConanFileInterface(transitive.node.conanfile))
9393
for require, transitive in node.transitive_deps.items())
9494
if node.replaced_requires:
95+
cant_be_removed = set()
9596
for old_req, new_req in node.replaced_requires.items():
9697
# Two different replaced_requires can point to the same real requirement
9798
existing = d[new_req]
9899
added_req = new_req.copy_requirement()
99100
added_req.ref = RecipeReference.loads(old_req)
100101
d[added_req] = existing
102+
if new_req.ref.name == added_req.ref.name:
103+
cant_be_removed.add(new_req)
101104
# Now remove the replaced from dependencies dict
102105
for new_req in node.replaced_requires.values():
103-
d.pop(new_req, None)
106+
if new_req not in cant_be_removed:
107+
d.pop(new_req, None)
104108
return ConanFileDependencies(d)
105109

106110
def filter(self, require_filter, remove_system=True):

test/integration/graph/test_replace_requires.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
("dep/[>=1.0 <2]", "dep/2.1", "dep/2.1@system", "dep/1.1"),
3737
# PATTERN - PATTERN REPLACE
3838
("dep/[>=1.3 <2]", "dep/*", "dep/[>=1.0 <1.9]", "dep/1.1"),
39+
# DIRECT REPLACE OF PINNED VERSIONS
40+
("dep/1.3", "dep/1.3", "dep/1.5", "dep/1.5"),
3941
])
4042
@pytest.mark.parametrize("tool_require", [False, True])
4143
class TestReplaceRequires:
@@ -160,14 +162,16 @@ def test_replace_requires_test_requires():
160162
assert "gtest/0.1: gtest/0.2" in c.out # The replacement happens
161163

162164

163-
def test_replace_requires_consumer_references():
165+
# We test even replacing by itself, not great, but shouldn't crash
166+
@pytest.mark.parametrize("name, version", [("zlib", "0.1"), ("zlib", "0.2"), ("zlib-ng", "0.1")])
167+
def test_replace_requires_consumer_references(name, version):
164168
c = TestClient()
165169
# IMPORTANT: The replacement package must be target-compatible
166-
zlib_ng = textwrap.dedent("""
170+
dep = textwrap.dedent(f"""
167171
from conan import ConanFile
168172
class ZlibNG(ConanFile):
169-
name = "zlib-ng"
170-
version = "0.1"
173+
name = "{name}"
174+
version = "{version}"
171175
def package_info(self):
172176
self.cpp_info.set_property("cmake_file_name", "ZLIB")
173177
self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
@@ -189,24 +193,78 @@ def package_info(self):
189193
self.output.info(f"DEP ZLIB package_info: {self.dependencies['zlib'].ref.name}!")
190194
self.cpp_info.requires = ["zlib::zlib"]
191195
""")
192-
c.save({"zlibng/conanfile.py": zlib_ng,
196+
c.save({"dep/conanfile.py": dep,
193197
"app/conanfile.py": conanfile,
194-
"profile": "[replace_requires]\nzlib/0.1: zlib-ng/0.1"})
195-
c.run("create zlibng")
198+
"profile": f"[replace_requires]\nzlib/0.1: {name}/{version}"})
199+
c.run("create dep")
196200
c.run("build app -pr=profile")
197-
assert "zlib/0.1: zlib-ng/0.1" in c.out
198-
assert "conanfile.py (app/0.1): DEP ZLIB generate: zlib-ng!" in c.out
199-
assert "conanfile.py (app/0.1): DEP ZLIB build: zlib-ng!" in c.out
201+
assert f"zlib/0.1: {name}/{version}" in c.out
202+
assert f"conanfile.py (app/0.1): DEP ZLIB generate: {name}!" in c.out
203+
assert f"conanfile.py (app/0.1): DEP ZLIB build: {name}!" in c.out
200204
# Check generated CMake code. If the targets are NOT compatible, then the replacement
201205
# Cannot happen
202206
assert "find_package(ZLIB)" in c.out
203207
assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
204208
cmake = c.load("app/ZLIBTargets.cmake")
205209
assert "add_library(ZLIB::ZLIB INTERFACE IMPORTED)" in cmake
206210
c.run("create app -pr=profile")
207-
assert "zlib/0.1: zlib-ng/0.1" in c.out
208-
assert "app/0.1: DEP ZLIB generate: zlib-ng!" in c.out
209-
assert "app/0.1: DEP ZLIB build: zlib-ng!" in c.out
211+
assert f"zlib/0.1: {name}/{version}" in c.out
212+
assert f"app/0.1: DEP ZLIB generate: {name}!" in c.out
213+
assert f"app/0.1: DEP ZLIB build: {name}!" in c.out
214+
215+
216+
def test_replace_requires_consumer_references_error_multiple():
217+
# https://github.com/conan-io/conan/issues/17407
218+
c = TestClient()
219+
# IMPORTANT: The replacement package must be target-compatible
220+
zlib = textwrap.dedent("""
221+
from conan import ConanFile
222+
class ZlibNG(ConanFile):
223+
name = "zlib"
224+
version = "0.2"
225+
def package_info(self):
226+
self.cpp_info.set_property("cmake_file_name", "ZLIB")
227+
self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
228+
""")
229+
conanfile = textwrap.dedent("""
230+
from conan import ConanFile
231+
class App(ConanFile):
232+
name = "app"
233+
version = "0.1"
234+
settings = "build_type"
235+
requires = "zlib/0.1", "bzip2/0.1"
236+
generators = "CMakeDeps"
237+
238+
def generate(self):
239+
self.output.info(f"DEP ZLIB generate: {self.dependencies['zlib'].ref.name}!")
240+
self.output.info(f"DEP BZIP2 generate: {self.dependencies['bzip2'].ref.name}!")
241+
def build(self):
242+
self.output.info(f"DEP ZLIB build: {self.dependencies['zlib'].ref.name}!")
243+
self.output.info(f"DEP BZIP2 build: {self.dependencies['bzip2'].ref.name}!")
244+
def package_info(self):
245+
self.output.info(f"DEP ZLIB package_info: {self.dependencies['zlib'].ref.name}!")
246+
self.cpp_info.requires = ["zlib::zlib", "bzip2::bzip2"]
247+
""")
248+
c.save({"zlib/conanfile.py": zlib,
249+
"app/conanfile.py": conanfile,
250+
"profile": "[replace_requires]\nzlib/0.1: zlib/0.2\nbzip2/0.1: zlib/0.2"})
251+
c.run("create zlib")
252+
c.run("build app -pr=profile")
253+
assert "zlib/0.1: zlib/0.2" in c.out
254+
assert "conanfile.py (app/0.1): DEP ZLIB generate: zlib!" in c.out
255+
assert "conanfile.py (app/0.1): DEP ZLIB build: zlib!" in c.out
256+
assert "conanfile.py (app/0.1): DEP BZIP2 generate: zlib!" in c.out
257+
assert "conanfile.py (app/0.1): DEP BZIP2 build: zlib!" in c.out
258+
# Check generated CMake code. If the targets are NOT compatible, then the replacement
259+
# Cannot happen
260+
assert "find_package(ZLIB)" in c.out
261+
assert "target_link_libraries(... ZLIB::ZLIB ZLIB::ZLIB)" in c.out
262+
cmake = c.load("app/ZLIBTargets.cmake")
263+
assert "add_library(ZLIB::ZLIB INTERFACE IMPORTED)" in cmake
264+
c.run("create app -pr=profile")
265+
assert "zlib/0.1: zlib/0.2" in c.out
266+
assert "app/0.1: DEP ZLIB generate: zlib!" in c.out
267+
assert "app/0.1: DEP ZLIB build: zlib!" in c.out
210268

211269

212270
def test_replace_requires_consumer_components_options():

0 commit comments

Comments
 (0)