Package layout
Warning
This is an experimental feature subject to breaking changes in future releases.
The layout()
feature will be fully functional only in the new build system integrations
(in the conan.tools space). If you are using other integrations, they
might not fully support this feature.
Available since: 1.37.0
Before starting
To understand correctly how the layout()
method can help us we need to recall first how Conan works.
Let’s say we are working in a project, using, for example, CMake:
<my_project_folder>
├── conanfile.py
└── src
├── CMakeLists.txt
├── hello.cpp
├── my_tool.cpp
└── include
└── hello.h
When we call conan create
, this is a simplified description of what happens:
Conan exports the recipe (conanfile.py) and the declared sources (exports_sources) to the cache. The folders in the cache would be something like:
├── export │ └── conanfile.py └── export_source └── src ├── CMakeLists.txt ├── hello.cpp ├── my_tool.cpp └── include └── hello.h
If the method
source()
exists, it might retrieve sources from the internet. Also, theexport_source
folder is copied to thesource
folder.├── export │ └── conanfile.py ├── export_source │ └── src │ ├── CMakeLists.txt │ ├── hello.cpp │ ├── my_tool.cpp │ └── include │ └── hello.h └── source └── src ├── CMakeLists.txt ├── hello.cpp ├── my_tool.cpp └── include └── hello.h
Before calling the
build()
method, a build folder is created and the sources are copied there. Later, we call thebuild()
method so the libraries and executables are built:├── export │ └── conanfile.py ├── export_source │ └── src │ ├── CMakeLists.txt │ ├── hello.cpp │ ├── my_tool.cpp │ └── include │ └── hello.h ├── source │ └── src │ ├── CMakeLists.txt │ ├── hello.cpp │ ├── my_tool.cpp │ └── include │ └── hello.h └── build └── <build_id> ├── say.a └── bin └── my_app
At last, Conan calls the
package()
method to copy the built artifacts from thesource
(typically includes) andbuild
folders (libraries and executables) to a package folder.├── export │ └── conanfile.py ├── export_source │ └── src │ ├── CMakeLists.txt │ ├── hello.cpp │ ├── my_tool.cpp │ └── include │ └── hello.h ├── source │ └── src │ ├── CMakeLists.txt │ ├── hello.cpp │ ├── my_tool.cpp │ └── include │ └── hello.h ├── build │ └── <build_id> │ ├── say.a │ └── bin │ └── my_app └── package └── <package_id> ├── lib │ └── say.a ├── bin │ └── my_app └── include └── hello.h
The
package_info(self)
method will describe with theself.cpp_info
object the contents of thepackage
folder, that is the one the consumers use to link against it. If we call conan create with different configurations the base folder in the cache is different and nothing gets messed.import os from conans import ConanFile from conan.tools.cmake import CMake class SayConan(ConanFile): name = "say" version = "0.1" exports_sources = "src/*" ... def package_info(self): # These are default values and doesn't need to be adjusted self.cpp_info.includedirs = ["include"] self.cpp_info.libdirs = ["lib"] self.cpp_info.bindirs = ["bin"] # The library name self.cpp_info.libs = ["say"]
So, this workflow in the cache works flawlessly but:
What if I’m developing the recipe in my local project and want to use the local methods (conan source, conan build) and later call export-pkg to create the package?
If you call conan build in your working directory, without specifying a
--build-folder
argument, you will end up with a bunch of files polluting your project. Moreover, if you want to build more configurations you will need to create several build folders by hand, this is inconvenient, error-prone, and wouldn’t be easy for Conan to locate the correct artifacts if you want to call export-pkg later.What if I don’t even want to call conan build but use my CLion IDE to build the project?
By default, the CLion IDE will create the folders cmake-build-release and cmake-build-debug to put the build files there, so maybe your
package()
method is not able to locate the files in there and the export-pkg might fail.What if I want to use my project as an editable package?
If you want to keep developing your package but let the consumers link with the artifacts in your project instead of the files in the Conan cache, this will not work, because it only declares the location of headers and libraries in the final packaged layout, but during development the files are typically in other locations.
So, just as we describe the package folder in the package_info()
method, we can use layout()
to describe the
source
and build
folders (both in a local project and in the cache):
We can run the conan local commands (conan source, conan build, conan export-pkg) without taking care of specifying directories, always with the same syntax.
If you are using an IDE, you can describe the build folder naming in the layout, so the libraries and executables are always in a known place.
In the cache, the layout (like a build subfolder) is kept, so we can always know where the artifacts are before packaging them.
It enables tools like the AutoPackager to automate the package() method.
It out-of-the-box enables to use editable packages, because the recipe describes where the contents will be, even for different configurations, so the consumers can link with the correct built artifacts.
Declaring the layout
In the layout()
method, you can set:
self.folders
self.folders.source: To specify a folder where your sources are.
self.folders.build: To specify a subfolder where the files from the build are (or will be).
self.folders.generators: To specify a subfolder where to write the files from the generators and the toolchains (e.g. the xx-config.cmake files from the
CMakeDeps
generator).self.folders.imports: To specify a subfolder where to write the files copied when using the
imports(self)
method in aconanfile.py
.self.folders.root: To specify the relative path from the
conanfile.py
to the root of the project, in case theconanfile.py
is in a subfolder and not in the project root. If defined, all the other paths will be relative to the project root, not to the location of theconanfile.py
.Check the complete reference of the self.folders attribute.
self.cpp.source and self.cpp.build: The same you set the
self.cpp.package
to describe the package folder after calling thepackage()
method, you can also describe the source and build folders.self.cpp.package: You can use it as you use the self.cpp_info at the
package_info(self)
method. The self.cpp_info object will be populated with the information declared in theself.cpp.package
object, so you can complete it or modify it later in thepackage_info(self)
method.self.layouts.source, self.layouts.build and self.layouts.package, each one containing one instance of
buildenv_info
,runenv_info
andconf_info
. If the environment or configuration needs to define values that depend on the current folders, it is necessary to define them in thelayout()
method.
Example: Everything together
Let’s say we are working in the project introduced in the section above:
<my_project_folder>
├── conanfile.py
└── src
├── CMakeLists.txt
├── hello.cpp
├── my_tool.cpp
└── include
└── hello.h
We are using the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
project(say CXX)
add_library(say hello.cpp)
target_include_directories(say PUBLIC "include")
add_executable(my_tool my_tool.cpp)
target_link_libraries(my_tool say)
# The executables are generated at the "bin" folder
set_target_properties(my_tool PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
Let’s see how we describe our project in the layout()
method:
import os
from conans import ConanFile
from conan.tools.cmake import CMake
class SayConan(ConanFile):
name = "say"
version = "0.1"
exports_sources = "src/*"
...
def layout(self):
self.folders.source = "src"
build_type = str(self.settings.build_type).lower()
self.folders.build = "cmake-build-{}".format(build_type)
self.folders.generators = os.path.join(self.folders.build, "conan")
self.cpp.package.libs = ["say"]
self.cpp.package.includedirs = ["include"] # includedirs is already set to this value by
# default, but declared for completion
# this information is relative to the source folder
self.cpp.source.includedirs = ["include"] # maps to ./src/include
# this information is relative to the build folder
self.cpp.build.libdirs = ["."] # maps to ./cmake-build-<build_type>
self.cpp.build.bindirs = ["bin"] # maps to ./cmake-build-<build_type>/bin
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
# we can also know where the executable we are building is
self.run(os.path.join(self.build_folder, self.cpp.build.bindirs[0], "my_tool"))
Let’s review the layout()
method changes:
self.folders
As we have our sources in the
src
folder,self.folders.source
is set to “src”.We set
self.folders.build
to be cmake-build-release or cmake-build-debug depending on the build_type.The
self.folders.generators
folder is where all files generated by Conan will be stored so they don’t pollute the other folders.
Please, note that the values above are for a single-configuration CMake generator. To support multi-configuration generators, such as Visual Studio, you should make some changes to this layout. For a complete layout that supports both single-config and multi-config, please check the cmake_layout() in the Conan documentation.
self.cpp
We can set the information about the package that the consumers need to use by setting the conanfile’s
cpp.package
attributes values:Declaring
self.cpp.package.libs
inside thelayout()
method is equivalent to the “classic”self.cpp_info.libs
declaration in thepackage_info()
method.Also, as you may know,
self.cpp.package.includedirs
is set to["include"]
by default, so there’s no need in declaring it but we are leaving it here for completeness.
We can also describe the
source
andbuild
folders with thecpp.source
andcpp.build
objects:We are setting
self.cpp.source.includedirs = ["include"]
. Theself.folders.source
information will be automatically prepended to that path for consumers so, for example, when working with an editable package, Conan will try to get the include files from the ./my_project_folder/src/include folder.We set the
self.cpp.build.libdirs
to [“.”], so we are declaring that, if we make the packageeditable
, the libraries will be at the ./cmake-build-<build_type> folder.We set the
self.cpp.build.bindirs
to [“bin”], because theCMakeLists.txt
file is changing theRUNTIME_OUTPUT_DIRECTORY
to that directory.
There is also an interesting line in the build(self)
method:
def build(self):
...
# we can also know where is the executable we are building
self.run(os.path.join(self.build_folder, self.cpp.build.bindirs[0], "my_tool"))
We are using the self.cpp.build.bindirs[0]
folder to locate the my_tool
. This is a very recommended
practice, especially when our layout depends on the build system. For example, when using CMake with Visual Studio,
the binaries are typically built at Release/ or Debug/ (multiconfiguration) but on Linux or macOS, the
output folder will typically be “.”, so it is better to declare the layout self.cpp.build.bindirs
following that logic and
then just access the correct path if we need to know where the resulting files of our build are. If you check the
cmake_layout(), you can see that the predefined cmake_layout
is doing
exactly that when using a multiconfiguration build system.
So, now we can run the conan local methods without taking much care of the directories where the files are or the build files should be, because everything is declared in the layout:
# This will write the toolchains and generator files from the dependencies to cmake-build-debug/generators
$ conan install . -if=my_install -s build_type=Debug
# In case we needed it (not the case as we don't have a source() method), this would fetch the sources to the ./src folder
$ conan source . -if=my_install
# This will build the project using the declared source folder and cmake-build-debug as the build folder
$ conan build . -if=my_install
Note
Maybe you are wondering why the install folder is not parametrized and has to be specified with the -if
argument.
Currently, Conan generates several files like the graph_info.json
and the conanbuildinfo.txt
that
are read to restore the configuration saved (settings, options, etc) to be applied in the local commands.
That configuration is needed before running the layout()
method because the folders might depend on the settings
like in the previous example. It is a kind of a chicken-egg issue. In Conan 2.0, likely, the
configuration won’t be stored, and the local methods like conan build . will compute the graph
from arguments (–profile, -s, -o…) and won’t need the --if
argument anymore, being always trivial to run.
Our current folder now looks like this:
<my_project_folder>
├── conanfile.py
├── src
│ ├── CMakeLists.txt
│ ├── hello.cpp
│ ├── my_tool.cpp
│ └── include
│ └── hello.h
└── cmake-build-debug
├── libsay.a
└── bin
└── my_tool
We could put the package in editable mode and other packages that require it would consume it in a completely transparent way, even locating the correct Release/Debug artifacts.
$ conan editable add . say/0.1
Note
When working with editable packages, the information set in self.cpp.source
and self.cpp.build
will be merged with the
information set in self.cpp.package
so that we don’t have to declare again something like self.cpp.build.libs = ["say"]
that is
the same for the consumers, independently of whether the package is in editable mode or not.
And of course, we can run also a conan create
command. When the build(self)
method is run in the conan cache, it is
also able to locate the my_tool
correctly, because it is using the same folders.build
:
├── source
│ └── src
│ ├── CMakeLists.txt
│ ├── hello.cpp
│ ├── my_tool.cpp
│ └── include
│ └── hello.h
├── build
│ └── cmake-build-debug
│ ├── say.a
│ └── bin
│ └── my_app
└── package
├── lib
│ └── say.a
├── bin
│ └── my_app
└── include
└── hello.h
Warning
The conan package
local command has been disabled (will raise an exception) when the layout()
method
is declared. If the package can be consumed “locally” in a handy way, the use case for the conan package
method
is only testing that the method is correctly coded, but that can also be done with the conan export-pkg
method.
Thus, as part of the migration to Conan 2.0, the conan package
method will disappear.
Example: export_sources_folder
If we have this project, intended to create a package for a third-party library which code is located externally:
├── conanfile.py
├── patches
│ └── mypatch
└── CMakeLists.txt
The conanfile.py
would look like this:
import os
from conan import ConanFile
class Pkg(ConanFile):
name = "pkg"
version = "0.1"
exports_sources = "CMakeLists.txt", "patches*"
def layout(self):
self.folders.source = "src"
def source(self):
# we are inside a "src" subfolder, as defined by layout
# download something, that will be inside the "src" subfolder
# access to patches and CMakeLists, to apply them, replace files is done with:
mypatch_path = os.path.join(self.export_sources_folder, "patches/mypatch")
cmake_path = os.path.join(self.export_sources_folder, "CMakeLists.txt")
# patching, replacing, happens here
def build(self):
# If necessary, the build() method also has access to the export_sources_folder
# for example if patching happens in build() instead of source()
cmake_path = os.path.join(self.export_sources_folder, "CMakeLists.txt")
We can see that the ConanFile.export_sources_folder
can provide access to the root folder of the sources:
Locally it will be the folder where the
conanfile.py
livesIn the cache it will be the “source” folder, that will contain a copy of
CMakeLists.txt
andpatches
, while the “source/src” folder will contain the actual downloaded sources.
Example: conanfile in subfolder
If we have this project, intended to package the code that is in the same repo as the conanfile.py
, but
the conanfile.py
is not in the root of the project:
├── CMakeLists.txt
└── conan
└── conanfile.py
The conanfile.py
would look like this:
import os
from conan import ConanFile
from conan.tools.files import load, copy
class Pkg(ConanFile):
name = "pkg"
version = "0.1"
def layout(self):
# The root of the project is one level above
self.folders.root = ".."
# The source of the project (the root CMakeLists.txt) is the source folder
self.folders.source = "."
self.folders.build = "build"
def export_sources(self):
# The path of the CMakeLists.txt we want to export is one level above
folder = os.path.join(self.recipe_folder, "..")
copy(self, "*.txt", folder, self.export_sources_folder)
def source(self):
# we can see that the CMakeLists.txt is inside the source folder
cmake = load(self, "CMakeLists.txt")
def build(self):
# The build() method can also access the CMakeLists.txt in the source folder
path = os.path.join(self.source_folder, "CMakeLists.txt")
cmake = load(self, path)
Example: Multiple subprojects
Lets say that we have a project that contains multiple subprojects, and some of these subprojects need to access some information that is at their same level (sibling folders). Each subproject would be a Conan package.
So we have the following folders and files:
├── pkg
│ ├── conanfile.py
│ ├── app.cpp # contains an #include "../common/myheader.h"
│ └── CMakeLists.txt # contains include(../common/myutils.cmake)
├── common
│ ├── myutils.cmake
│ └── myheader.h
└── othersubproject
The pkg
subproject needs to use some of the files located inside the common
folder (that might be
used and shared by other subprojects too), and it references them by their relative location.
Note that common
is not intended to be a Conan package. It is just some common code that will be copied
into the different subproject packages.
We can use the self.folders.root = ".."
layout specifier to locate the root of the project, then
use the self.folders.subproject = "subprojectfolder"
to relocate back most of the layout to the
current subproject folder, as it would be the one containing the build scripts, sources code, etc.,
so other helpers like cmake_layout()
keep working.
import os
from conan import ConanFile
from conan.tools.cmake import cmake_layout, CMake
from conan.tools.files import load, copy, save
class Pkg(ConanFile):
name = "pkg"
version = "0.1"
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain"
def layout(self):
self.folders.root = ".."
self.folders.subproject = "pkg"
cmake_layout(self)
def export_sources(self):
source_folder = os.path.join(self.recipe_folder, "..")
copy(self, "*", source_folder, self.export_sources_folder)
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
self.run(os.path.join(self.cpp.build.bindirs[0], "myapp"))
Note it is very important the export_sources()
method, that is able to maintain the same relative layout
of the pkg
and common
folders, both in the local developer flow in the current folder, but also
when those sources are copied to the Conan cache, to be built there with conan create
or conan install --build=pkg
.
This is one of the design principles of the layout()
, the relative location of things must be consistent in the user
folder and in the cache.
Environment variables and configuration
There are some packages that might define some environment variables in their
package_info()
method via self.buildenv_info
, self.runenv_info
. Other
packages can also use self.conf_info
to pass configuration to their consumers.
This is not an issue as long as the value of those environment variables or configuration
do not require using the self.package_folder
. If they do, then their values will
not be correct for the “source” and “build” layouts. Something like this will be broken
when used in editable
mode:
import os
from conan import ConanFile
class SayConan(ConanFile):
...
def package_info(self):
# This is BROKEN if we put this package in editable mode
self.runenv_info.define_path("MYDATA_PATH",
os.path.join(self.package_folder, "my/data/path"))
When the package is in editable mode, for example, self.package_folder
is None
, as
obviously there is no package yet.
The solution is to define it in the layout()
method, in the same way the cpp_info
can
be defined there:
from conan import ConanFile
class SayConan(ConanFile):
...
def layout(self):
# The final path will be relative to the self.source_folder
self.layouts.source.buildenv_info.define_path("MYDATA_PATH", "my/source/data/path")
# The final path will be relative to the self.build_folder
self.layouts.build.buildenv_info.define_path("MYDATA_PATH2", "my/build/data/path")
# The final path will be relative to the self.build_folder
self.layouts.build.conf_info.define_path("MYCONF", "my_conf_folder")
The layouts
object contains source
, build
and package
scopes, and each one contains
one instance of buildenv_info
, runenv_info
and conf_info
.