Skip to content
Open
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
6 changes: 4 additions & 2 deletions conan/internal/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ def propagate_downstream(self, require, node, visibility_conflicts, src_node=Non
d = self.dependants[0]

down_require = d.require.transform_downstream(self.conanfile.package_type, require,
node.conanfile.package_type)
node.conanfile.package_type,
d.src.conanfile)
if down_require is None:
return

Expand Down Expand Up @@ -204,7 +205,8 @@ def check_downstream_exists(self, require):
# TODO: Implement an optimization where the requires is checked against a graph global
# print(" Lets check_downstream one more")
down_require = dependant.require.transform_downstream(self.conanfile.package_type,
require, None)
require, None,
dependant.src.conanfile)

if down_require is None:
# print(" No need to check downstream more")
Expand Down
3 changes: 2 additions & 1 deletion conan/internal/model/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
If the policy 'required_conan_version>=version' is defined, different behaviors can be enabled:
- If required_conan_version>=2.28, bugfix https://github.com/conan-io/conan/pull/19705 for transitive static libraries package_id
- If required_conan_version>=2.28, bugfix https://github.com/conan-io/conan/pull/19849 for VirtualBuildEnv bindir path propagation based on requirement run trait
- If required_conan_version>=2.28, https://github.com/conan-io/conan/pull/19286 defaults the new 'consistent' trait to True for the host context, even when 'visible=False'"""
- If required_conan_version>=2.28, https://github.com/conan-io/conan/pull/19286 defaults the new 'consistent' trait to True for the host context, even when 'visible=False'
- If required_conan_version>=2.30, bugfix https://github.com/conan-io/conan/pull/20073 for propagation of the 'transitive_header' trait"""

BUILT_IN_CONFS = {
"core:required_conan_version": "Raise if current version does not match the defined range.",
Expand Down
9 changes: 6 additions & 3 deletions conan/internal/model/requires.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from conan.errors import ConanException
from conan.internal.model.pkg_type import PackageType
from conan.api.model import RecipeReference
from conan.internal.model.version_range import VersionRange
from conan.internal.model.version_range import VersionRange, required_conan_version_policy


class Requirement:
Expand Down Expand Up @@ -286,7 +286,7 @@ def aggregate(self, other):
self.package_id_mode = other.package_id_mode
self.required_nodes.update(other.required_nodes)

def transform_downstream(self, pkg_type, require, dep_pkg_type):
def transform_downstream(self, pkg_type, require, dep_pkg_type, source):
"""
consumer ---self---> foo<pkg_type> ---require---> bar<dep_pkg_type>
\\ -------------------????-------------------- /
Expand Down Expand Up @@ -353,7 +353,10 @@ def transform_downstream(self, pkg_type, require, dep_pkg_type):
if require.transitive_headers is not None:
downstream_require.headers = require.headers and require.transitive_headers
if self.transitive_headers is not None:
downstream_require.transitive_headers = self.transitive_headers
transitive_propagation = required_conan_version_policy(source, "2.29.9")
downstream_require.transitive_headers = (self.transitive_headers
if not transitive_propagation else
self.transitive_headers and require.transitive_headers)

if require.transitive_libs is not None:
downstream_require.libs = require.libs and require.transitive_libs
Expand Down
4 changes: 3 additions & 1 deletion test/integration/graph/core/graph_manager_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class Alias(ConanFile):
self._cache_recipe(ref, conanfile)

@staticmethod
def recipe_consumer(reference=None, requires=None, build_requires=None):
def recipe_consumer(reference=None, requires=None, build_requires=None, shared=None):
path = temp_folder()
path = os.path.join(path, "conanfile.py")
conanfile = GenConanfile()
Expand All @@ -81,6 +81,8 @@ def recipe_consumer(reference=None, requires=None, build_requires=None):
if build_requires:
for r in build_requires:
conanfile.with_build_requires(r)
if shared is not None:
conanfile.with_shared_option(shared)
save(path, str(conanfile))
return path

Expand Down
184 changes: 172 additions & 12 deletions test/integration/graph/core/graph_manager_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import os

from conan.internal.graph.graph_error import GraphMissingError, GraphLoopError, GraphConflictError
from conan.errors import ConanException
Expand All @@ -19,7 +20,10 @@ def _check_transitive(node, transitive_deps):
assert v1.require.libs is v2[2], f"{v1.node}!=expected {v2[0]} ({v2[2]}) libs"
assert v1.require.build is v2[3], f"{v1.node}!=expected {v2[0]} ({v2[3]}) build"
assert v1.require.run is v2[4], f"{v1.node}!=expected {v2[0]} ({v2[4]}) run"
assert len(v2) <= 5
if len(v2) > 5:
assert v1.require.transitive_headers is v2[5], f"{v1.node}!=expected {v2[0]} ({v2[5]}) transitive_headers"
if len(v2) > 6:
assert v1.require.transitive_libs is v2[6], f"{v1.node}!=expected {v2[0]} ({v2[6]}) transitive_libs"


class TestLinear(GraphManagerTest):
Expand Down Expand Up @@ -199,6 +203,150 @@ def test_transitive_all_shared_transitive_headers_libs(self):
(liba, True, True, False, True)])
_check_transitive(libb, [(liba, True, True, False, True)])

@pytest.mark.parametrize("version", [None, ">=2.30-dev"])
@pytest.mark.parametrize("shared", [True, False])
def test_simple_transitive_headers_chain(self, shared, version):
# consumer -> libd -> libc - transitive_headers=True -> libb - > liba -> lib0
if version:
with open(os.path.join(self.cache_folder, "global.conf"), "w") as f:
f.write(f'core:policies=["required_conan_version{version}"]')
self.recipe_cache("lib0/0.1", option_shared=shared)
liba = GenConanfile().with_requirement("lib0/0.1").with_shared_option(shared)
self.recipe_conanfile("liba/0.1", liba)

libb = GenConanfile().with_requirement("liba/0.1").with_shared_option(shared)
self.recipe_conanfile("libb/0.1", libb)

libc = GenConanfile()
libc.with_requirement("libb/0.1", transitive_headers=True).with_shared_option(shared)
self.recipe_conanfile("libc/0.1", libc)

libd = GenConanfile().with_requirement("libc/0.1").with_shared_option(shared)
self.recipe_conanfile("libd/0.1", libd)

consumer = self.recipe_consumer("consumer/0.1", ["libd/0.1"])
deps_graph = self.build_consumer(consumer)

consumer = deps_graph.root
libd = consumer.edges[0].dst
libc = libd.edges[0].dst
libb = libc.edges[0].dst
liba = libb.edges[0].dst
lib0 = liba.edges[0].dst

# node, headers, lib, build, run (transitive_headers, transitive_libs)
_check_transitive(consumer, [
(libd, True, True, False, shared, None, None),
(libc, False, not shared, False, shared, None, None),
(libb, False, not shared, False, shared, None, None),
(liba, False, not shared, False, shared, None, None),
(lib0, False, not shared, False, shared, None, None),
])

_check_transitive(libd, [
(libc, True, True, False, shared, None, None),
(libb, True, not shared, False, shared, None, None),
(liba, False, not shared, False, shared, None, None),
(lib0, False, not shared, False, shared, None, None),
])

_check_transitive(libc, [
(libb, True, True, False, shared, True, None),
# Having the transitive_headers trait propagated without the fix
# does not create a new package id in this case, it only cases about the headers trait
(liba, False, not shared, False, shared, None if version else True, None),
(lib0, False, not shared, False, shared, None if version else True, None),
])

@pytest.mark.parametrize("version", [None, ">=2.30-dev"])
@pytest.mark.parametrize("shared", [True, False])
@pytest.mark.parametrize("liba_first", [True, False])
@pytest.mark.parametrize("transitive", [True, False, None])
def test_transitive_headers_duplicate_different_diamond(self, version, shared, liba_first, transitive):
# libd -> libc - > liba
# \ - transitive_headers=True -> libb -> liba
self.recipe_cache("liba/0.1", option_shared=shared)
libb = GenConanfile().with_requirement("liba/0.1")
libb.with_shared_option(shared)
self.recipe_conanfile("libb/0.1", libb)

libc = GenConanfile()
if liba_first:
libc.with_requirement("liba/0.1").with_requirement("libb/0.1", transitive_headers=transitive)
else:
libc.with_requirement("libb/0.1", transitive_headers=transitive).with_requirement("liba/0.1")

libc.with_shared_option(shared)
if version:
libc = str(libc) + f"\nrequired_conan_version = '{version}'"

self.recipe_conanfile("libc/0.1", libc)

consumer = self.recipe_consumer("libd/0.1", ["libc/0.1"])
deps_graph = self.build_consumer(consumer)

assert 4 == len(deps_graph.nodes)
libd = deps_graph.root
libc = libd.edges[0].dst
if liba_first:
liba = libc.edges[0].dst
libb = libc.edges[1].dst
else:
liba = libc.edges[1].dst
libb = libc.edges[0].dst

self._check_node(libd, "libd/0.1", deps=[libc])
self._check_node(libc, "libc/0.1#123", deps=[libb, liba], dependents=[libd])
self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[libc])
self._check_node(liba, "liba/0.1#123", dependents=[libb, libc])

# # node, headers, lib, build, run, (transitive_headers, transitive_libs)
_check_transitive(libd, [
(libc, True, True, False, shared, None, None),
(libb, bool(transitive), not shared, False, shared, None, None),
# Header does not depend on the transitive flag of libb
# when the fix is applied
(liba, False if version else bool(transitive), not shared, False, shared, None, None),
])
_check_transitive(libc, [
(libb, True, True, False, shared, transitive, None),
(liba, True, True, False, shared, None if version else (True if transitive else None), None),
])

@pytest.mark.parametrize("version", [None, ">=2.30-dev"])
def test_static_shared_transitive_chain(self, version):
# consumer -> shared1 - transitive_libs = True -> static2 -> static3
# Consumer needs static2 and static3 in all cases
if version:
with open(os.path.join(self.cache_folder, "global.conf"), "w") as f:
f.write(f'core:policies=["required_conan_version{version}"]')
self.recipe_cache("static3/0.1", option_shared=False)
self.recipe_cache("static2/0.1", option_shared=False, requires=["static3/0.1"])
shared1 = GenConanfile().with_requirement("static2/0.1", transitive_headers=True)
shared1.with_shared_option(True)
self.recipe_conanfile("shared1/0.1", shared1)

consumer = self.recipe_consumer("consumer/0.1", ["shared1/0.1"])
deps_graph = self.build_consumer(consumer)

assert 4 == len(deps_graph.nodes)
consumer = deps_graph.root
shared1 = consumer.edges[0].dst
static2 = shared1.edges[0].dst
static3 = static2.edges[0].dst

self._check_node(consumer, "consumer/0.1", deps=[shared1])
self._check_node(shared1, "shared1/0.1#123", deps=[static2], dependents=[consumer])
self._check_node(static2, "static2/0.1#123", deps=[static3], dependents=[shared1])
self._check_node(static3, "static3/0.1#123", dependents=[static2])

# # node, headers, lib, build, run, (transitive_headers, transitive_libs)
_check_transitive(consumer, [
(shared1, True, True, False, True, None, None),
(static2, True, False, False, False, None, None),
(static3, False, False, False, False, None, None),
])

def test_middle_shared_up_static(self):
# app -> libb0.1 (shared) -> liba0.1 (static)
self.recipe_cache("liba/0.1", option_shared=False)
Expand Down Expand Up @@ -376,12 +524,16 @@ def test_direct_header_only(self):
# node, headers, lib, build, run
_check_transitive(app, [(liba, True, False, False, False)])

def test_header_only(self):
@pytest.mark.parametrize("app_shared", [True, False, None])
@pytest.mark.parametrize("libb_shared", [True, False, None])
def test_header_only(self, app_shared, libb_shared):
# app -> libb0.1 -> liba0.1 (header_only)
self.recipe_conanfile("liba/0.1", GenConanfile().with_package_type("header-library"))
libb = GenConanfile().with_requirement("liba/0.1")
if libb_shared is not None:
libb.with_shared_option(libb_shared)
self.recipe_conanfile("libb/0.1", libb)
consumer = self.recipe_consumer("app/0.1", ["libb/0.1"])
consumer = self.recipe_consumer("app/0.1", ["libb/0.1"], shared=app_shared)

deps_graph = self.build_consumer(consumer)

Expand All @@ -395,9 +547,13 @@ def test_header_only(self):
self._check_node(liba, "liba/0.1#123", dependents=[libb])

# node, headers, lib, build, run
_check_transitive(app, [(libb, True, True, False, False),
(liba, False, False, False, False)])
_check_transitive(libb, [(liba, True, False, False, False)])
_check_transitive(app, [

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the check below are just to use the new transitive check functionality, not related to the fix itself

(libb, True, True, False, bool(libb_shared), None, None),
(liba, False, False, False, False, None, None)
])
_check_transitive(libb, [
(liba, True, False, False, False, None, None)
])

def test_header_only_with_transitives(self):
# app -> liba0.1(header) -> libb0.1 (static)
Expand All @@ -421,12 +577,16 @@ def test_header_only_with_transitives(self):
self._check_node(libb, "libb/0.1#123", dependents=[liba])
self._check_node(libc, "libc/0.1#123", dependents=[liba])

# node, headers, lib, build, run
_check_transitive(app, [(liba, True, False, False, False),
(libb, True, True, False, False),
(libc, True, True, False, True)])
_check_transitive(liba, [(libb, True, True, False, False),
(libc, True, True, False, True)])
# node, headers, lib, build, run, transitive_headers, transitive_libs
_check_transitive(app, [
(liba, True, False, False, False, None, None),
(libb, True, True, False, False, None, None),
(libc, True, True, False, True, None, None)
])
_check_transitive(liba, [
(libb, True, True, False, False, True, True),
(libc, True, True, False, True, True, True)
])

def test_multiple_header_only_with_transitives(self):
# app -> libd0.1(header) -> liba0.1(header) -> libb0.1 (static)
Expand Down
Loading