Using components and editable packages

It is possible to define components in the layout() method, to support the case of editable packages. That is, if we want to put a package in editable mode, and that package defines components, it is necessary to define the components layout correctly in the layout() method. Let’s see it in a real example.

Please, first clone the sources to recreate this project. You can find them in the examples2 repository in GitHub:

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/examples/conanfile/layout/editable_components

There we find a greetings subfolder and package, that contains 2 libraries, the hello library and the bye library. Each one is modeled as a component inside the package recipe:

greetings/conanfile.py
class GreetingsConan(ConanFile):
    name = "greetings"
    version = "0.1"
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"
    exports_sources = "src/*"

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

    def layout(self):
        cmake_layout(self, src_folder="src")
        # This "includedirs" starts in the source folder, which is "src"
        # So the components include dirs is the "src" folder (includes are
        # intended to be included as ``#include "hello/hello.h"``)
        self.cpp.source.components["hello"].includedirs = ["."]
        self.cpp.source.components["bye"].includedirs = ["."]
        # compiled libraries "libdirs" will be inside the "build" folder, depending
        # on the platform they will be in "build/Release" or directly in "build" folder
        bt = "." if self.settings.os != "Windows" else str(self.settings.build_type)
        self.cpp.build.components["hello"].libdirs = [bt]
        self.cpp.build.components["bye"].libdirs = [bt]

    def package(self):
        copy(self, "*.h", src=self.source_folder,
             dst=join(self.package_folder, "include"))
        copy(self, "*.lib", src=self.build_folder,
             dst=join(self.package_folder, "lib"), keep_path=False)
        copy(self, "*.a", src=self.build_folder,
             dst=join(self.package_folder, "lib"), keep_path=False)

    def package_info(self):
        self.cpp_info.components["hello"].libs = ["hello"]
        self.cpp_info.components["bye"].libs = ["bye"]

        self.cpp_info.set_property("cmake_file_name", "MYG")
        self.cpp_info.set_property("cmake_target_name", "MyGreetings::MyGreetings")
        self.cpp_info.components["hello"].set_property("cmake_target_name", "MyGreetings::MyHello")
        self.cpp_info.components["bye"].set_property("cmake_target_name", "MyGreetings::MyBye")

While the location of the hello and bye libraries in the final package is in the final lib folder, then nothing special is needed in the package_info() method, beyond the definition of the components. In this case, the customization of the CMake generated filenames and targets is also included, but it is not necessary for this example.

The important part is the layout() definition. Besides the common cmake_layout, it is necessary to define the location of the components headers (self.cpp.source as they are source code) and the location of the locally built libraries. As the location of the libraries depends on the platform, the final self.cpp.build.components["component"].libdirs depends on the platform.

With this recipe we can put the package in editable mode and locally build it with:

$ conan editable add greetings
$ conan build greetings
# we might want to also build the debug config

In the app folder we have a package recipe to build 2 executables, that link with the greeting package components. The app/conanfile.py recipe there is simple, the build() method builds and runs both example and example2 executables that are built with CMakeLists.txt:

# Note the MYG file name, not matching the package name,
# because the recipe defined "cmake_file_name"
find_package(MYG)

add_executable(example example.cpp)
# Note the MyGreetings::MyGreetings target name, not matching the package name,
# because the recipe defined "cmake_target_name"
# "example" is linking with the whole package, both "hello" and "bye" components
target_link_libraries(example MyGreetings::MyGreetings)

add_executable(example2 example2.cpp)
# "example2" is only using and linking "hello" component, but not "bye"
target_link_libraries(example2 MyGreetings::MyHello)
$ conan build app
hello: Release!
bye: Release!

If you now go to the bye.cpp source file and modify the output message, then build greetings and app locally, the final output message for the “bye” component library should change:

$ conan build greetings
$ conan build app
hello: Release!
adios: Release!