Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Transitive headers settable #180

Open
Eric-Bwr opened this issue Feb 6, 2024 · 13 comments
Open

Feature Request: Transitive headers settable #180

Eric-Bwr opened this issue Feb 6, 2024 · 13 comments

Comments

@Eric-Bwr
Copy link

Eric-Bwr commented Feb 6, 2024

I need to be able to set transitive headers true / false for specific packages.
This is possible via the package xml.
But it is not really a beautiful interface to do so.
One would have to loop through the requirements and implement a switch case.

Feels a bit overlooked.

Maybe making it an argument in the conandata.yml would be cool.
At the moment this file will be overwritten when doing changes with the conan clion plugin.

@knimix
Copy link

knimix commented Feb 6, 2024

def requirements(self):
    requirements = self.conan_data.get('requirements', [])
    packages_with_transitive_headers = ["specific_package", "another_specific_package"]
    for requirement in requirements:
        package_name = requirement.split('/')[0]
        if package_name in packages_with_transitive_headers:
            self.requires(requirement, transitive_headers=True)
        else:
            self.requires(requirement)

@memsharded
Copy link
Member

Hi @Eric-Bwr

Thanks for your feedback.

I need to be able to set transitive headers true / false for specific packages.

I am not sure what you mean. Do you mean the requirement traits like when a conanfile does self.requires("somepkg/version", transitive_headers=True)? This is only a recipe thing, it doesn't make sense outside of a recipe. If you want to customize how a recipe works, you can inject any arbitrary user conf and condition your recipe logic to that conf

This is possible via the package xml.

You mean, the conanfile.py recipe?

@Eric-Bwr
Copy link
Author

Eric-Bwr commented Feb 6, 2024

Hi @memsharded ,

thats quick :D

I meant the package.py sry.

So we have a conandata.yml, conanfile.py and package.py (the recipe for packaging our library).
And the issue is that we dont want to have to add the dependencies to the conandata.yml and to the package.py to make them transitive. So at the moment we just have all requirements set to trans_headers=True.

If we want to be able to set individual ones to true/false, wed have to do it like @knimix did in the comment above.

Which is not beautiful.

What do you mean with the arbitrary user conf?

@memsharded
Copy link
Member

So we have a conandata.yml, conanfile.py and package.py (the recipe for packaging our library).

what is package.py? That doesn't exist in Conan, is it a custom file of your project?

And the issue is that we dont want to have to add the dependencies to the conandata.yml and to the package.py to make them transitive. So at the moment we just have all requirements set to trans_headers=True.

I am not sure what you mean. Generally, dependencies are directly defined in conanfile.py like:

def requirements(self):
      self.requires("boost/1.82")
      self.requires("zlib/1.3", transitive_headers=True)
      ...

the package conanfile.py recipe writer is the only one capable of knowing which headers are transitively public and which header are a private implementation detail of the current package. It is not a good practice in general to propagate all of them to the consumers.

If you want to have a declarative interface for your dependencies in conandata.yml, then something like what @knimix suggested is what make sense. You write in your conandata.yml something like:

requirements:
     boost/1.82:
          transitive_headers: True
     zlib/1.3:
     other/2.1:

And then something like:

def requirements(self):
    requirements = self.conan_data.get('requirements', [])
    for req, traits in requirements.items():
           self.requires(req, **traits)

(Just a very rough idea, not tested, most likely doesn't work as-is)

Is this what you want?

@Eric-Bwr
Copy link
Author

Eric-Bwr commented Feb 6, 2024

@memsharded
Yes that would be a nice idea.
But as the first two lines read:

  • This file is managed by Conan, contents will be overwritten.
  • To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements

So when I add that line (transitive_headers: True) which would be a nice interface, and then use the clion plugin, the entire config gets overwritten.

@memsharded
Copy link
Member

This file is managed by Conan, contents will be overwritten.

Sure, the moment you start writing your own logic for your conanfile.py, then the plugin cannot automatically add new requires to the conanfile.py, because how would it now that you are modeling them in your conandata.yml. So basically you remove the first line comment, and make the conanfile.py yours. From there, you add dependencies by writing them in your conandata.yml, not via the plugin. Also typically commit it to version control too, of course, to not lose the changes.

It is not possible to create a GUI interface for every possible variation or detail that can go in requires or in recipes, it would be inventing a full graphical interface to edit a Python file, which only makes sense for very simple and straightforward cases, including the "consuming" story to which the plugin is intended. If you start to create your own packages and you need to define things like headers visibility transitivity there will be other things that will be needed to customize and write in the conanfile.py recipe, for example the package_info() method. So the main interface is the conanfile.py edition, in the same way that the main interface with the build system is editing the CMakeLists.txt.

@knimix
Copy link

knimix commented Feb 6, 2024

@memsharded
Currently, if you want to create a library in clion, you have to create your own recipe in addition to the conanfile.py. It would be nice if the gui would take over and allow the creation / export of a library in clion. Then you could include this feature of transitive_headers

@memsharded
Copy link
Member

Currently, if you want to create a library in clion, you have to create your own recipe in addition to the conanfile.py. It would be nice if the gui would take over and allow the creation / export of a library in clion. Then you could include this feature of transitive_headers

I am afraid this is not possible, but I'll wait for @czoido feedback. It would be the equivalent to have CLion fully write your CMakeLists.txt. It is not possible, maybe beyond a simple common template (Conan has templates, writing conan new cmake_lib -d name=pkg -d version=0.1 can create a sample template project with conanfile.py + CMakeLists.txt + pkg.h + pkg.cpp). It would be possible to call that from the CLion plugin, but that would be just a skeleton template. Users need to write and edit the conanfile.py in the same way that is necessary to write and edit the CMakeLists.txt. The task is simply to wide to expect it to be doable from a GUI interface.

@knimix
Copy link

knimix commented Feb 6, 2024

@memsharded
hm okay too bad, what would you use for a good workflow so that I can build my clion project as a lib or use it as a conan package so that it can be used in other projects? Currently you would have a conanfile.py and a conandata.yml (created by clion) and you could also have a e.g. package.py, which is your own recipe for creating the package. Is it possible to integrate them together? Without writing everything twice?

@memsharded
Copy link
Member

No, no, you only need one file a conanfile.py. The package.py is not a thing, it doesn't exist for Conan, not a Conan thing. All you need to create a conan package is conanfile.py, even the conandata.yml is not mandatory.

I'd suggest following the tutorial in https://docs.conan.io/2/tutorial.html to understand the basic concepts about Conan.

@knimix
Copy link

knimix commented Feb 7, 2024

Hi @memsharded
I've really got to grips with the basics of conan. As far as I know, you have to write like conan create conanfile.py to create the package and store it in the conan cache. But the conanfile.py created by default did not fulfill all requirements such as the name, version, export_sources etc. So the default conanfile (generated by the Plugin) looks like this


    def layout(self):
        cmake_layout(self)

    def generate(self):
        tc = CMakeToolchain(self)
        tc.user_presets_path = False
        tc.generate()

    def requirements(self):
        requirements = self.conan_data.get('requirements', [])
        for requirement in requirements:
            self.requires(requirement)

But the conanfile for a package looks more Like this (conan new my_lib....):

    def requirements(self):
        requirements = self.conan_data.get('requirements', [])
        for requirement in requirements:
            self.requires(requirement, transitive_headers=True)
    def config_options(self):
        if self.settings.os == "Windows":
            self.options.rm_safe("fPIC")
    def configure(self):
        if self.options.shared:
            self.options.rm_safe("fPIC")
    def layout(self):
        cmake_layout(self)
    def generate(self):
        tc = CMakeToolchain(self)
        tc.variables["CONAN_BUILD"] = 1
        tc.preprocessor_definitions["BUILD_LIBRARY"] = 1
        tc.generate()
    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()
    def package(self):
        cmake = CMake(self)
        cmake.install()
    def package_info(self):
        self.cpp_info.set_property("cmake_target_name", "Nuc")
        self.cpp_info.libs = ["Nuc"]
      

I thought it's not so good to put all these additional functions into the conanfile.py created by the plugin, so I created a package.py which just creates the missing stuff like the conan new command does.
Anyway, I can then simply call conan create package.py to save the package in the cache. And this runs independently of conanfile.py. If I have understood this correctly, I can still simply write this function into the standard conanfile.py so that clion works and I can also execute conan create conanfile.py, right?

The question is would it then be possible for the plugin to create data such as name, version etc. without me having to do it myself? And also the missing functions like e.g. the package_info()

Or maby integrate the conan create command

I hope you could understand my steps

@czoido
Copy link
Contributor

czoido commented Feb 7, 2024

Hi @knimix,

Thanks for your suggestions. Unfortunately, covering the package creator case is not something the plugin can do, as it's outside its scope. However, you can still take advantage of the plugin and perform the creation steps yourself. Some tips for you:

As @memsharded mentioned, you don't need both a conanfile.py and a package.py. Instead, just overwrite the conanfile.py that the plugin creates—which is intended for pure consumption—with another to package the library. A good starting point for this is to use the conan new command with the cmake_lib template by executing conan new cmake_lib -d name=mylibrary -d version=1.0. This will output a minimal CMake project with Conan, from which you can take some pieces of code and integrate them into your CLion project.

Doing so, you could end up with a conanfile.py that looks like this:

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps


class mylibraryRecipe(ConanFile):
    name = "mylibrary"
    version = "1.0"
    package_type = "library"

    # Optional metadata
    license = "<Put the package license here>"
    author = "<Put your name here> <And your email here>"
    url = "<Package recipe repository url here, for issues about the package>"
    description = "<Description of mylibrary package here>"
    topics = ("<Put some tag here>", "<here>", "<and here>")

    # Binary configuration
    settings = "os", "compiler", "build_type", "arch"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {"shared": False, "fPIC": True}

    # Sources are located in the same place as this recipe, copy them to the recipe
    exports_sources = "CMakeLists.txt", "src/*", "include/*"

    def requirements(self):
        requirements = self.conan_data.get('requirements', [])
        for requirement in requirements:
            self.requires(requirement)

    def config_options(self):
        if self.settings.os == "Windows":
            self.options.rm_safe("fPIC")

    def configure(self):
        if self.options.shared:
            self.options.rm_safe("fPIC")

    def layout(self):
        cmake_layout(self)

    def generate(self):
        deps = CMakeDeps(self)
        deps.generate()
        tc = CMakeToolchain(self)
        tc.user_presets_path = False
        tc.generate()

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def package(self):
        cmake = CMake(self)
        cmake.install()

    def package_info(self):
        self.cpp_info.libs = ["mylibrary"]

a CMakeListst.txt like this one:

cmake_minimum_required(VERSION 3.26)
project(mylibrary)

set(CMAKE_CXX_STANDARD 17)
find_package(fmt)

add_library(mylibrary src/mylibrary.cpp)
target_include_directories(mylibrary PUBLIC "include")
set_target_properties(mylibrary PROPERTIES PUBLIC_HEADER "include/mylibrary.h")

target_link_libraries(mylibrary fmt::fmt)
install(TARGETS mylibrary)

The structure of the project could look like this:

├── CMakeLists.txt
├── conan_provider.cmake
├── conandata.yml
├── conanfile.py
├── include
│   └── mylibrary.h
└── src
    └── mylibrary.cpp

As long as you don't modify the conandata.yml the plugin will keep on adding there the libraries that you select to use from the plugin UI.

Hope this helps. Please feel free to ask if you have any other questions.

@knimix
Copy link

knimix commented Feb 7, 2024

@czoido
I have now tried it out a bit and it seems to work thanks.
I actually have one more question. It is a bit related to this post of mine that has not been answered until today (#139)
Will there be a nice way to change conan's profile in the future?
Currently you can already make settings with parameters such as CONAN_HOST_PROFILE and CONAN_BUILD_PROFILE.
But it would also be nice to use the conan_toolchain.cmake generated by conan. Currently I can only change the default Cmake-profile parameter like the conan profile, which creates a CMakeUserPresets.json with which another Cmake-profile is created. The CMakeUserPresets.json Cmake profile also contains the toolchain, can't this also be the case in the default profile? After all, it is always generated but not used

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants