conan-io / conan

Conan - The open-source C and C++ package manager
https://conan.io
MIT License
8.25k stars 980 forks source link

[question] Express Transitive Components from package_info #15176

Open mikirothman opened 11 months ago

mikirothman commented 11 months ago

What is your question?

Hi,

I have something like the following requirements for package "MyPackage":

# from package MyPackage
def requirements(self):
   self.requires("a/1.0")
   self.requires("b/2.0")
   self.requires("c/3.0")

a, b and c are shared libs packages that creates several libs, and export components information regards them at package info methods, assume:

# from package a / b / c
def package_info(self):
     self.cpp_info.components["x"].libdirs = xlibdirs
     self.cpp_info.components["x"].includedirs = xincludedirs
     self.cpp_info.components["x"].bindirs = xbindirs
     self.cpp_info.components["x"].libs = ["x"]

     self.cpp_info.components["y"].libdirs = ylibdirs
     self.cpp_info.components["y"].includedirs = yincludedirs
     self.cpp_info.components["y"].bindirs = ybindirs
     self.cpp_info.components["y"].libs = ["y"]

now, MyPackage is a single lib shared library package, which includes c headers at it's own public headers, which means MyPackage consumers should get the transitive c headers propagated from MyPackage, so according to documentation I should do something like:

# from package MyPackage
def package_info(self):
    self.cpp_info.requires = ["c::y"]

but that fails on package method (MyPackage/x.0.0.0: Calling package()) with the following:

ERROR: MyPackage/x.0.0.0: Required package 'a' not in component 'requires'.

well, of course some of 'a' components are required, I just don't want them to get to the downstream. so can I state those are private implementation details ? how can I express the true transitivity of my package ?

Have you read the CONTRIBUTING guide?

memsharded commented 11 months ago

Hi @mikirothman

Thanks for your question

Required package 'a' not in component 'requires'.

I have a quick question. If a is a shared library, and it is a requires, there is no way to hide it from the consumers. The consumers of mypackage will necessarily need the a shared libraries to run if you are requiring a, it cannot be a "private implementation detail", am I missing something?

In any case, if something is really a private implementation detail, and you want to completely hide it, it is possible to define visible=False trait. In the majority of cases it will not be necessary, Conan can avoid the overlinking in the consumers of mypackage, it will only link the transitive dependencies when necessary.

mikirothman commented 11 months ago

Yeah, that is correct. the fact that the error occurred only when using require for some of the deps was pretty weird and still I can't understand why it didn't produce the error when not requiring any of the deps.

regarding the visibility trait, as you said, I can't think of a case it is necessary, but thanks anyway for the awesome work and assistance

memsharded commented 11 months ago

the fact that the error occurred only when using require for some of the deps was pretty weird and still I can't understand why it didn't produce the error when not requiring any of the deps.

When no components are require, it implicitly depend on the "root" components, that is the pkg::pkg of each dependency defined in requires. When components are detailed, then it does no longer add that default dependency to the package, as it expects a user-defined one of the components it requires, and raises an error if it detects something is not used at all, because it would be something missing in the definition.

mikirothman commented 11 months ago

Thanks ! that makes sense. now, assume MyPackage is a single lib package (no components exposed), which indicates it's requires (specific components from a, b and c), it still seems that when consuming MyPackage, the entire components of a, b and c are linked with the consumer, is that the correct behavior ? should I create a component just to avoid linking the entire dependency graph ?

memsharded commented 11 months ago

is that the correct behavior ? should I create a component just to avoid linking the entire dependency graph ?

Are you sure this is the case? I have checked one of the tests in the suite, and it seems to be doing the right thing: https://github.com/conan-io/conan/blob/2e568138707a4d35a53e063a86af2aa6ee2793fb/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py#L9

The top package contains top::cmp1 and top::cmp2. As long as the intermediate package does a self.cpp_info.requires = ["top::cmp1"], only top::cmp1 is linked in the consume application, but not top::cmp2.

mikirothman commented 5 months ago

OK, so assume the following example:

from package MyPackage

def requirements(self): self.requires("a/1.0", transitive_headers=True, transitive_libs=True) self.requires("b/2.0", transitive_headers=True, transitive_libs=True) self.requires("c/3.0")

def package_info(self): self.cpp_info.components["x"].requires = ["a::a", "b::b", "c::c"] self.cpp_info.components["y"].requires = ["a::a", "b::b", "c::c"]

here we have a package that creates 2 libraries with 3 requirements:

now lets assume that component x exposing a::a and b::b as public dependencies and component y exposing only a::a as public dependency. the consumer of component y will still get b::b propagated as a public dependency, there is no option to declare it private from the component side. the private / public characteristic of the dependency is resolved by the entire package. users of component y would be able to include headers from b::b even when they are not supposed to. what do you think? does it make sense or am I missing something ?