Skip to content

[bug] order of calls to self.requires changes the resulting dependency graph? #19274

@puetzk

Description

@puetzk

Describe the bug

Conan version 2.22.1

How to reproduce it

lib_a/conanfile.py

from conan import ConanFile

class ConanLibA(ConanFile):
    name = "lib_a"
    package_type = "header-library"

conan export lib_a --version 1.0
conan export lib_a --version 1.1
conan export lib_a --version 1.2

lib_b/conanfile.py

from conan import ConanFile

class ConanLibB(ConanFile):
    name="lib_b"
    version="1.0"
    package_type = "static-library"

    def requirements(self):
        self.requires("lib_a/[>=1]")

conan export lib_b

lib_c/conanfile.py

from conan import ConanFile

class ConanLibC(ConanFile):
    name="lib_c"
    version="1.0"
    package_type = "shared-library"

    def requirements(self):
        self.requires("lib_b/1.0", visible=False)
        self.requires("lib_a/1.1", visible=True)

Happy path (this is the expected outcome)

conan graph info lib_c --format html > graph.html --no-remote

Requirements
lib_a/1.1#6a0c16aa1e2f729b87c13ce846c6fd24:da39a3ee5e6b4b0d3255bfef95601890afd80709 - Missing
lib_b/1.0#3ed7aadc114623df36e0697db429e9a8:fcdabf9ba48019c6920aec36e9d7e15d8446b678 - Missing

Image

Buggy path? (I found this outcome unexpected and unwanted...)

swap the order of those two lines in requirements, so lib_a comes first:

     def requirements(self):
+        self.requires("lib_a/1.1", visible=True)
         self.requires("lib_b/1.0", visible=False)
-        self.requires("lib_a/1.1", visible=True)

Now the override/forced resolution doesn't happen, and I get a dependency graph with both lib_a/1.1 and lib_a/1.2

Requirements
lib_a/1.1#6a0c16aa1e2f729b87c13ce846c6fd24 - Cache
lib_a/1.2#6a0c16aa1e2f729b87c13ce846c6fd24 - Cache
lib_b/1.0#3ed7aadc114623df36e0697db429e9a8 - Cache
Resolved version ranges
lib_a/[>=1]: lib_a/1.2

Image

Seemingly now the usage of lib_a/1.1 doesn't get propagated to its upstream lib_b, so the version range in lib_b just resolved to the latest (lib_a/1.2). Now both versions of lib_a are visible in the the self.dependencies of lib_c if I add

    def generate(self):
        for require, dependency in self.dependencies.items():
            self.output.info(f"{require} -> {dependency}")

conan install lib_c --no-remote --build missing

conanfile.py (lib_c/1.0): lib_a/1.1, Traits: build=False, headers=True, libs=False, run=False, visible=True -> lib_a/1.1
conanfile.py (lib_c/1.0): lib_b/1.0, Traits: build=False, headers=True, libs=True, run=False, visible=False -> lib_b/1.0
conanfile.py (lib_c/1.0): lib_a/1.2, Traits: build=False, headers=False, libs=False, run=False, visible=False -> lib_a/1.2

This seems wrong in several ways:

  1. The upstream/downstream relationships certainly matter, but I did not expect the order sibling edges (lib_c -> lib_a and lib_c -> lib_b) are added to change the resulting dependency graph. Nothing in https://docs.conan.io/2/reference/conanfile/methods/requirements.html suggests that order requirements are listed within a single recipe matters... just upstream/downstream relationships.
  2. As I understand it, visible=False is supposed to affect whether a require is propagated downstream. So I didn't expect that to change anything about what is (or isn't) present in the current recipe's self.dependencies, or what gets forced upstream.
  3. The latter outcome (having lib_c depend directly on lib_a/1.1, and transitively on lib_a/1.2 via lib_b) would lead to undefined behavior in linking lib_c - the object files in lib_b (a static-library) will have embedded symbols from lib_a/1.2 (a header-library), while sources directly compiled in lib_c will embed lib_a/1.1, so linking these both into lib_c's shared module is (probably) an ODR violation.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions