Skip to content
Draft
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
35 changes: 29 additions & 6 deletions conan/internal/model/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class Requirement:
"""
def __init__(self, ref, *, headers=None, libs=None, build=False, run=None, visible=None,
transitive_headers=None, transitive_libs=None, test=None, package_id_mode=None,
force=None, override=None, direct=None, options=None, no_skip=False):
force=None, override=None, direct=None, options=None, no_skip=False,
consistent=None):
# * prevents the usage of more positional parameters, always ref + **kwargs
# By default this is a generic library requirement
self.ref = ref
Expand All @@ -26,6 +27,7 @@ def __init__(self, ref, *, headers=None, libs=None, build=False, run=None, visib
self._force = force
self._override = override
self._direct = direct
self._consistent = consistent
self.options = options
# Meta and auxiliary information
# The "defining_require" is the require that defines the current value. If this require is
Expand All @@ -37,6 +39,10 @@ def __init__(self, ref, *, headers=None, libs=None, build=False, run=None, visib
self.skip = False
self.required_nodes = set() # store which intermediate nodes are required, to compute "Skip"
self.no_skip = no_skip
# computed ones, not default ones
if self.visible and not self.consistent:
raise ConanException(f"Requirement {ref} with visible=True and consistent=False is not"
f" supported. Please open a Github ticket to report it")

@property
def files(self): # require needs some files in dependency package
Expand Down Expand Up @@ -102,6 +108,15 @@ def direct(self):
def direct(self, value):
self._direct = value

@property
def consistent(self):
default_consistent = self.visible or self.test
return self._default_if_none(self._consistent, default_consistent)

@consistent.setter
def consistent(self, value):
self._consistent = value

@property
def build(self):
return self._build
Expand Down Expand Up @@ -156,7 +171,7 @@ def serialize(self):
"require": str(self._required_ref)}
serializable = ("run", "libs", "skip", "test", "force", "direct", "build",
"transitive_headers", "transitive_libs", "headers",
"package_id_mode", "visible")
"package_id_mode", "visible", "consistent")
for attribute in serializable:
result[attribute] = getattr(self, attribute)
return result
Expand All @@ -165,7 +180,8 @@ def copy_requirement(self):
return Requirement(self.ref, headers=self.headers, libs=self.libs, build=self.build,
run=self.run, visible=self.visible,
transitive_headers=self.transitive_headers,
transitive_libs=self.transitive_libs)
transitive_libs=self.transitive_libs,
consistent=self.consistent)

@property
def version_range(self):
Expand Down Expand Up @@ -229,7 +245,7 @@ def __eq__(self, other):
(self.headers and other.headers) or
(self.libs and other.libs) or
(self.run and other.run) or
((self.visible or self.test) and (other.visible or other.test)) or
(self.consistent and other.consistent) or
(self.ref == other.ref and self.options == other.options)))

def aggregate(self, other):
Expand All @@ -250,6 +266,7 @@ def aggregate(self, other):
self.libs |= other.libs
self.run = self.run or other.run
self.visible |= other.visible
self.consistent |= other.consistent
self.force |= other.force
self.direct |= other.direct
self.transitive_headers = self.transitive_headers or other.transitive_headers
Expand Down Expand Up @@ -281,15 +298,19 @@ def transform_downstream(self, pkg_type, require, dep_pkg_type):
# Build-requires will propagate its main trait for running exes/shared to downstream
# consumers so run=require.run, irrespective of the 'self.run' trait
downstream_require = Requirement(require.ref, headers=False, libs=False, build=True,
run=require.run, visible=self.visible, direct=False)
run=require.run, visible=self.visible, direct=False,
# require.consistent is always True
consistent=self.consistent)
return downstream_require

if self.build: # Build-requires
# If the above is shared or the requirement is explicit run=True
# visible=self.visible will further propagate it downstream
if dep_pkg_type is PackageType.SHARED or require.run:
downstream_require = Requirement(require.ref, headers=False, libs=False, build=True,
run=True, visible=self.visible, direct=False)
run=True, visible=self.visible, direct=False,
# require.consistent is always True
consistent=self.consistent)
return downstream_require
return

Expand Down Expand Up @@ -334,6 +355,8 @@ def transform_downstream(self, pkg_type, require, dep_pkg_type):
if self.transitive_libs is not None:
downstream_require.transitive_libs = self.transitive_libs

downstream_require.consistent = require.consistent and self.consistent

if self.visible is False:
downstream_require.visible = False

Expand Down
6 changes: 3 additions & 3 deletions test/integration/command/create_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,11 +494,11 @@ class MyTest(ConanFile):
'1': {'ref': 'hello/0.1', 'run': False, 'libs': True, 'skip': False,
'test': False, 'force': False, 'direct': True, 'build': False,
'transitive_headers': None, 'transitive_libs': None, 'headers': True,
'package_id_mode': None, 'visible': True, 'require': 'hello/0.1'},
'package_id_mode': None, 'visible': True, 'consistent': True, 'require': 'hello/0.1'},
'2': {'ref': 'pkg/0.2', 'run': False, 'libs': True, 'skip': False, 'test': False,
'force': False, 'direct': False, 'build': False, 'transitive_headers': None,
'transitive_libs': None, 'headers': True, 'package_id_mode': None,
'visible': True, 'require': 'pkg/0.2'}
'visible': True, 'consistent': True, 'require': 'pkg/0.2'}
}
assert consumer_info["dependencies"] == consumer_deps
# hello/0.1 pkg information
Expand All @@ -511,7 +511,7 @@ class MyTest(ConanFile):
"ref": "pkg/0.2", "run": False, "libs": True, "skip": False, "test": False,
"force": False, "direct": True, "build": False, "transitive_headers": None,
"transitive_libs": None, "headers": True, "package_id_mode": "semver_mode",
"visible": True, 'require': 'pkg/0.2'
"visible": True, 'consistent': True, 'require': 'pkg/0.2'
}
}
assert hello_pkg_info["dependencies"] == hello_pkg_info_deps
Expand Down
2 changes: 1 addition & 1 deletion test/integration/command/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class Pkg(ConanFile):
"requires: [{'ref': 'zlib/1.2.13', 'require': 'zlib/1.2.13', 'run': False, "
"'libs': True, 'skip': False, 'test': False, 'force': False, 'direct': True, 'build': "
"False, 'transitive_headers': None, 'transitive_libs': None, 'headers': "
"True, 'package_id_mode': None, 'visible': True}]",
"True, 'package_id_mode': None, 'visible': True, 'consistent': True}]",
'revision_mode: hash',
'vendor: False'] == tc.out.splitlines()

Expand Down
88 changes: 88 additions & 0 deletions test/integration/graph/conflict_diamond_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,91 @@ def test_transitive_orphans(self, order, test):
assert dep_pkg2["visible"] is True
assert dep_pkg3["ref"] == "pkg3/1.1"
assert dep_pkg2["visible"] is True


class TestConsistentTrait:
def test_visible_order_issue(self):
# libc -> libb/1.0 (static) -> liba/1.1 (header)
# \-------------------------------/
# Order mattered here
# libc2 ---------------------> liba/1.1 (header)
# \----> libb/1.0 (static) -> liba/1.2 (header)
c = TestClient(light=True)
c.save({"liba/conanfile.py": GenConanfile("liba").with_package_type("header-library"),
"libb/conanfile.py": GenConanfile("libb", "1.0").with_package_type("static-library")
.with_requires("liba/[>=1]"),
"libc/conanfile.py": GenConanfile("libc", "1.0").with_package_type("shared-library")
.with_requirement("libb/1.0",
visible=False,
consistent=True)
.with_requirement("liba/1.1"),
"libc2/conanfile.py": GenConanfile("libc", "1.0").with_package_type("shared-library")
.with_requirement("liba/1.1")
.with_requirement("libb/1.0",
visible=False,
consistent=True),
})
c.run("export liba --version=1.0")
c.run("export liba --version=1.1")
c.run("export liba --version=1.2")
c.run("export libb")
c.run("graph info libc --format=json")
graph = json.loads(c.stdout)
assert len(graph["graph"]["nodes"]) == 3

c.run("graph info libc2 --format=json")
graph = json.loads(c.stdout)
assert len(graph["graph"]["nodes"]) == 3

def test_visible_order_full_diamond_issue(self):
# This is a conflict, because the depth-first graph resolution approach can't see
# beyond the current branch to see there is an incompatible version in other branch not
# expanded yet
# libc --(v=F, c=T)-> libb/1.0 (static) -(range)--> liba/1.2(header)
# \----------------> libd/1.0 (static)-----------> liba/1.1 (header) CONFLICT

# Order matters here, with the other order not a conflict, because fixed dep is
# expanded first
# libc2 -----------> libd/1.0 (static) ---------> liba/1.1 (header)
# \--(v=F, c=T)---> libb/1.0 (static) --range-----/ (header)
c = TestClient(light=True)
c.save({"liba/conanfile.py": GenConanfile("liba").with_package_type("header-library"),
"libb/conanfile.py": GenConanfile("libb", "1.0").with_package_type("static-library")
.with_requires("liba/[>=1]"),
"libd/conanfile.py": GenConanfile("lib_d", "1.0").with_package_type("static-library")
.with_requires("liba/1.1"),
"libc/conanfile.py": GenConanfile("libc", "1.0").with_package_type("shared-library")
.with_requirement("libb/1.0",
visible=False,
consistent=True)
.with_requirement("lib_d/1.0"),
"libc2/conanfile.py": GenConanfile("libc", "1.0").with_package_type("shared-library")
.with_requirement("lib_d/1.0")
.with_requirement("libb/1.0",
visible=False,
consistent=True),
})
c.run("export liba --version=1.0")
c.run("export liba --version=1.1")
c.run("export liba --version=1.2")
c.run("export libb")
c.run("export libd")
# This is still a conflict, the consistent=True raises this conflict
c.run("graph info libc", assert_error=True)
assert "ERROR: Version conflict: Conflict between liba/1.1 and liba/1.2 in the graph" in c.out

c.run("graph info libc2 --format=json")
graph = json.loads(c.stdout)
assert len(graph["graph"]["nodes"]) == 4

def test_visible_consistent(self):
c = TestClient(light=True)
c.save({"liba/conanfile.py": GenConanfile("liba").with_package_type("header-library"),
"libb/conanfile.py": GenConanfile("libb", "1.0").with_package_type("static-library")
.with_requirement("liba/[>=1]",
consistent=False),
})
c.run("create liba --version=1.0")
c.run("install libb", assert_error=True)
assert ("Requirement liba/[>=1] with visible=True and "
"consistent=False is not supported") in c.out
Loading