If the expressiveness of the conanfile.txt
is not enough for your use case, or if you want
to further automate your project building with automatic management of settings and options,
you can use a conanfile.py
, which is an equivalent python version.
If you have a conanfile.txt
file, the conversion to a conanfile.py
is quite easy.
Let's take a look at the complete conanfile.txt
from the previous timer example with POCO library,
in which we have added a couple of extra generators
[requires]
Poco/1.7.3@lasote/stable
[generators]
gcc
cmake
txt
[options]
Poco:shared=True
OpenSSL:shared=True
[imports]
bin, *.dll -> ./bin # Copies all dll files from the package "bin" folder to my project "bin" folder
lib, *.dylib* -> ./bin # Copies all dylib files from the package "lib" folder to my project "bin" folder
The equivalent conanfile.py
file is:
from conans import ConanFile, CMake
class PocoTimerConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable" # comma separated list of requirements
generators = "cmake", "gcc", "txt"
default_options = "Poco:shared=True", "OpenSSL:shared=True"
def imports(self):
self.copy("*.dll", dst="bin", src="bin") # From bin to bin
self.copy("*.dylib*", dst="bin", src="lib") # From lib to bin
With this conanfile.py
you can just work as usual, nothing changes from the user perspective.
You can install the requirements with (from mytimer/build folder):
$ conan install ..
One advantage of using conanfile.py
is that the project build can be further simplified,
using the conanfile.py build()
method.
If you are building your project with CMake, edit your conanfile.py
and add the following build()
method:
from conans import ConanFile, CMake
class PocoTimerConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable"
generators = "cmake", "gcc", "txt"
default_options = "Poco:shared=True", "OpenSSL:shared=True"
def imports(self):
self.copy("*.dll", dst="bin", src="bin") # From bin to bin
self.copy("*.dylib*", dst="bin", src="lib") # From lib to bin
def build(self):
cmake = CMake(self.settings)
self.run('cmake "%s" %s' % (self.conanfile_directory, cmake.command_line))
self.run('cmake --build . %s' % cmake.build_config)
In the code above, we are using a CMake helper class. This class reads the current settings and sets cmake flags to handle arch, build_type, compiler and compiler.version.
Note that the first cmake
invocation is using the conanfile_directory
. This is necessary if
you want to do out-of-source builds or just building in a child folder, as cmake
should be
given the location of the root CMakeLists.txt
(in this case located in the same folder as the
conanfile.py
).
Then execute, from your project root:
$ mkdir build && cd build
$ conan install ..
$ conan build ..
The conan install command downloads and prepares the requirements of your project
(for the specified settings) and the conan build command uses all that information
to invoke your build()
method to build your project, which in turn calls cmake.
This conan build
will use the same settings used in the conan install
, which simplifies
the process and reduces the errors of mismatches between the installed packages and the current
project configuration.
If you want to build your project for x86_64 or another setting just change the parameters passed to conan install
:
$ rm -rf * //to clean the current build folder
$ conan install .. -s arch=x86_64
$ conan build ..
From now you can just type conan install and conan will remember the settings.
Implementing and using the conanfile.py build()
method ensures that we always use the same
settings both in the installation of requirements and the build of the project, and simplifies
calling the build system.
If you are using configure/make you can use AutoToolsBuildEnvironment helper.
This helper sets LIBS
, LDFLAGS
, CFLAGS
, CXXFLAGS
and CPPFLAGS
environment variables based on your requirements.
It works using the environment_append context manager applied to your configure and make commands:
from conans import ConanFile, AutoToolsBuildEnvironment
class ExampleConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable"
default_options = "Poco:shared=True", "OpenSSL:shared=True"
def imports(self):
self.copy("*.dll", dst="bin", src="bin")
self.copy("*.dylib*", dst="bin", src="lib")
def build(self):
env_build = AutoToolsBuildEnvironment(self)
with tools.environment_append(env_build.vars):
self.run("./configure")
self.run("make")
For Windows users:
- It also works with nmake.
- If you have
MSYS2
/MinGW
installed and in the PATH you take advantage of thetool.run_in_windows_bash
command:
from conans import ConanFile, AutoToolsBuildEnvironment
class ExampleConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable"
default_options = "Poco:shared=True", "OpenSSL:shared=True"
def _run_cmd(self, command):
if self.settings.os == "Windows":
tools.run_in_windows_bash(self, command)
else:
self.run(command)
def imports(self):
self.copy("*.dll", dst="bin", src="bin")
self.copy("*.dylib*", dst="bin", src="lib")
def build(self):
env_build = AutoToolsBuildEnvironment(self)
with tools.environment_append(env_build.vars):
self._run_cmd("./configure")
self._run_cmd("make")
The AutoToolsBuildEnvironment
lets to adjust some variables before calling the vars method, so you can
add or change some default value automatically filled:
PROPERTY | DESCRIPTION |
---|---|
.fpic | Boolean, Set it to True if you want to append the -fPIC flag |
.libs | List with library names of the requirements (-l in LIBS) |
.include_paths | List with the include paths of the requires (-I in CPPFLAGS) |
.library_paths | List with library paths of the requirements (-L in LDFLAGS) |
.defines | List with variables that will be defined with -D in CPPFLAGS |
.flags | List with compilation flags (CFLAGS and CXXFLAGS) |
.cxx_flags | List with only c++ compilation flags (CXXFLAGS) |
.link_flags | List with linker flags |
Example:
from conans import ConanFile, AutoToolsBuildEnvironment
class ExampleConan(ConanFile):
...
def build(self):
env_build = AutoToolsBuildEnvironment(self)
env_build.fpic = True
env_build.libs.append("pthread")
env_build.defines.append("NEW_DEFINE=23")
with tools.environment_append(env_build.vars):
self.run("./configure")
self.run("make")
Set environment variables:
NAME | DESCRIPTION |
---|---|
LIBS | Library names to link |
LDFLAGS | Link flags, (-L, -m64, -m32) |
CFLAGS | Options for the C compiler (-g, -s, -m64, -m32, -fPIC) |
CXXFLAGS | Options for the C++ compiler (-g, -s, -stdlib, -m64, -m32, -fPIC) |
CPPFLAGS | Preprocessor definitions (-D, -I) |
Note
The ConfigureEnvironment helper has been deprecated. if you are still using it we recommend to read the :ref:`Migrate to new env variables management guide <migrate_to_new_environment_management>`.
You could use the gcc generator directly to build your source code. It's valid to invoke both gcc and clang compilers.
from conans import ConanFile
class PocoTimerConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable"
generators = "gcc"
default_options = "Poco:shared=True", "OpenSSL:shared=True"
def imports(self):
self.copy("*.dll", dst="bin", src="bin") # From bin to bin
self.copy("*.dylib*", dst="bin", src="lib") # From lib to bin
def build(self):
self.run("mkdir -p bin")
command = 'g++ timer.cpp @conanbuildinfo.gcc -o bin/timer'
self.run(command)
Note
The old GCC build helper has been deprecated is no longer necessary, use only the gcc
generator.
You can invoke your Visual Studio compiler from command line using the VisualStudioBuildEnvironment
and the
:ref:`vcvars_command tool <tools>`, that will point to your Visual Studio installation.
Example:
from conans import ConanFile, VisualStudioBuildEnvironment, tools
class ExampleConan(ConanFile):
...
def build(self):
if self.settings.compiler == "Visual Studio":
env_build = VisualStudioBuildEnvironment(self)
with tools.environment_append(env_build.vars):
vcvars = tools.vcvars_command(self.settings)
self.run('%s && cl /c /EHsc hello.cpp' % vcvars)
self.run('%s && lib hello.obj -OUT:hello.lib' % vcvars
Set environment variables:
NAME | DESCRIPTION |
---|---|
LIB | Library paths separated with ";" |
CL | "/I" flags with include directories |
We are going to use the Poco timer example, but instead of building just an executable, we
are building also a library with the ExampleTimer
class, that is used by the executable.
Note
If you are using the repository in https://github.com/memsharded/example-poco-timer.git, the code is already available in a branch:
$ git checkout conanfile_py
The code will be split in 3 files: timer.cpp and timer.h containing the class, and an main.cpp containing the example app executable:
timer.h (note the required dllexport if we want to build a shared lib)
#pragma once
#include "Poco/Timer.h"
#include "Poco/Stopwatch.h"
#ifdef WIN32
#define POCO_TIMER_EXPORT __declspec(dllexport)
#else
#define POCO_TIMER_EXPORT
#endif
using Poco::Timer;
using Poco::Stopwatch;
class POCO_TIMER_EXPORT TimerExample{
public:
TimerExample(){ _sw.start();}
void onTimer(Timer& timer);
private:
Stopwatch _sw;
};
timer.cpp
#include "timer.h"
#include <iostream>
void TimerExample::onTimer(Timer& timer){
std::cout << "Callback called after " << _sw.elapsed()/1000 << " milliseconds." << std::endl;
}
main.cpp
#include "timer.h"
using Poco::TimerCallback;
using Poco::Thread;
int main(int argc, char** argv){
TimerExample example;
Timer timer(250, 500);
timer.start(TimerCallback<TimerExample>(example, &TimerExample::onTimer));
Thread::sleep(5000);
timer.stop();
return 0;
}
This library will depend in turn on POCO library too, so we could write a conanfile.py
for our
package and define options and default_options this way:
from conans import ConanFile, CMake
class PocoTimerConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable"
generators = "cmake", "gcc", "txt"
options = {"shared": [True, False]} # Values can be True or False (number or string value is also possible)
default_options = "shared=False", "Poco:shared=True", "OpenSSL:shared=True"
def imports(self):
self.copy("*.dll", dst="bin", src="bin") # From bin to bin
self.copy("*.dylib*", dst="bin", src="lib") # From lib to bin
def build(self):
cmake = CMake(self.settings)
shared_definition = "-DSHARED=1" if self.options.shared else ""
self.run('cmake "%s" %s %s' % (self.conanfile_directory, cmake.command_line, shared_definition))
self.run('cmake --build . %s' % cmake.build_config)
Observe the build method. We are reading self.options.shared and appending a definition to our cmake command.
So let's use this option in our CMakeLists.txt:
project(FoundationTimer)
cmake_minimum_required(VERSION 2.8.12)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
if(SHARED)
add_library(timer SHARED timer.cpp)
else()
add_library(timer STATIC timer.cpp)
endif()
target_link_libraries(timer PUBLIC ${CONAN_LIBS})
add_executable(example main.cpp)
target_link_libraries(example timer)
Now, we can pass the option shared
to the install command. It will be stored in the conaninfo.txt
file for later calls. So you can execute:
$ mkdir build && cd build
$ conan install .. -o shared=True
$ conan build ..
...
$ rm -rf * (in the build folder, better to remove cmake temporaries)
$ conan install .. -o shared=False
$ conan build ..
This feature is very convenient for example if you want to keep several different builds in parallel, without having to delete and re-create build projects. As explained in :ref:`common workflows<workflows>`, you could maintain both shared and static builds very easily:
$ mkdir build_shared && cd build_shared
$ conan install .. -o shared=True
$ conan build ..
$ cd ..
$ mkdir build_static && cd build_static
$ conan install .. -o shared=False
$ conan build ..
// now, move from build_static <-> build_shared as you want and
$ conan build ..
conanfile.py
becomes a self documented file for checking what options we can adjust to compile a library.
Note
You can use -DBUILD_SHARED_LIBS=ON instead of -DSHARED=1 and CMake will automatically build SHARED libraries, without the need of modifying your CMakeLists. We used a custom definition as an example to show you how to control your build through conan options and cmake definitions.
Remember, in your conanfile.py
you have also access to the options of your dependencies,
and you can use them to:
- Add requirements dynamically
- Change options values
The config method is the right place to change values of options and settings.
Here is an example of what we could do in our config method:
...
requires = "Poco/1.7.3@lasote/stable" # We will add OpenSSL dynamically "OpenSSL/1.0.2d@lasote/stable"
...
def config(self):
# We can control the options of our dependencies based on current options
self.options["OpenSSL"].shared = self.options.shared
# Maybe in windows we know that OpenSSL works better as shared (false)
if self.settings.os == "Windows":
self.options["OpenSSL"].shared = True
# Or adjust any other available option
self.options["Poco"].other_option = "foo"
# Or add a new requirement!
if self.options.testing:
self.requires("OpenSSL/2.1@memsharded/testing")
else:
self.requires("OpenSSL/1.0.2d@lasote/stable")
There is another advantage of using conanfile.py
instead of conanfile.txt
.
Note
Check the :ref:`reference <buildoptions>` and the :ref:`profiles <profiles>` sections to know more about options, settings and environment variables
In the previous example we added an option shared
to our conanfile.py to control if the library has to be static or shared.
For the Poco package, if we specify shared=True
or shared=False
in the conan install
command we get different binary packages.
When we declare new options we open the possibility of having multiple packages for the same recipe, as it happens with the settings.
First, we are going to see how to control the tests build with an option (generally not a good idea). Adding a new option build_tests
we can control when to run the tests:
conanfile.py
class PocoTimerConan(ConanFile):
...
options = {"build_tests": [True, False]} # NOT A GOOD APROACH
default_options = "build_tests=False"
...
def build(self):
cmake = CMake(self.settings)
flag_build_tests = "-DBUILD_TEST=1" if self.options.build_tests else ""
self.run('cmake "%s" %s %s' % (self.conanfile_directory, cmake.command_line, flag_build_tests))
self.run('cmake --build . %s' % cmake.build_config)
CMakeLists.txt
option(BUILD_TEST OFF)
if(BUILD_TEST)
include(CTest)
enable_testing()
...
endif()
Then we could use conan install -o build_test=False/True
to activate or deactivate the tests launch.
But, what happens if we are creating a conan package?
If we install our package specifying different values for the option "build_test", we will generate/require different conan packages, but the library (binary artifact) will be the same, so, why different conan packages?
Conan has scope variables to control the conanfile.py without generating different packages no matter what is the value of the scope variable.
Now using scope variables:
conanfile.py
class PocoTimerConan(ConanFile):
...
def build(self):
cmake = CMake(self.settings)
flag_build_tests = "-DBUILD_TEST=1" if self.scope.build_tests else ""
self.run('cmake "%s" %s %s' % (self.conanfile_directory, cmake.command_line, flag_build_tests))
self.run('cmake --build . %s' % cmake.build_config)
Then we could use conan install --scope build_test=False/True
to activate or deactivate the tests launch.
There is a special scope variable called dev
that is automatically set to True if you are using conanfile.py in your project.
If we export the recipe and install it from a local or remote repository, the variable dev
will be False.
It's specially useful to require some testing packages (just for run the tests) or anything that not affect to the built artifact.
In the following example we will require the catch
package for unit test our project:
class PocoTimerConan(ConanFile):
...
def config(self):
if self.scope.dev:
self.requires("catch/1.3.0@TyRoXx/stable")
def build(self):
cmake = CMake(self.settings)
flag_build_tests = "-DBUILD_TEST=1" if self.scope.dev and self.scope.build_tests else ""
self.run('cmake "%s" %s %s' % (self.conanfile_directory, cmake.command_line, flag_build_tests))
self.run('cmake --build . %s' % cmake.build_config)
It guarantees that when you build a conan package with your project, no one that requires it (from its conanfile.txt or its conanfile.py) will require the catch
library, because it's not needed.
There is also a simplified way to require development packages:
class PocoTimerConan(ConanFile):
...
def config(self):
self.requires("catch/1.3.0@TyRoXx/stable", dev=True)
An extra shortcut for this syntax would be to use the new dev_requires
attribute:
class PocoTimerConan(ConanFile):
dev_requires = "catch/1.3.0@TyRoXx/stable"
Setting a scope variable in a requirement is very similar to options:
$ conan install --scope Poco:somescope=somevalue
If we want to set it in our project conanfile we don't specify the package namespace:
$ conan install --scope somescope=somevalue
There is an special namespace called ALL
that will apply to all our requirements and our conanfile:
$ conan install --scope ALL:somescope=somevalue
Note that if defining specific values for a certain package, the specific value will have precedence:
$ conan install --scope ALL:somescope=somevalue Poco:somescope=othervalue
In this case, the scope somescope
of Poco will have the value othervalue
At this point you almost have your library prepared for being a conan package. In next section
we will create our own packages using conanfile.py
.
By default, conan install
command will search for a binary package (corresponding to our settings and defined options) in a remote, if it's not present the install command will fail.
As we previously see, we can use the --build option to change the default conan install
behaviour:
- - -build some_package will build only "some_package"
- - -build missing will build only the missing requires.
- - -build will build all requires from sources.
- - -build outdated will try to build from code if the binary is not built with the current recipe or when missing binary package
With the build_policy
attribute the package creator can change the default conan's build behavior.
The allowed build_policy values are:
missing
: If no binary package is found, conan will build it without the need of invoke conan install with --build missing option.always
: The package will be built always, retrieving each time the source code executing the "source" method.
class PocoTimerConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "Poco/1.7.3@lasote/stable" # comma separated list of requirements
generators = "cmake", "gcc", "txt"
default_options = "Poco:shared=True", "OpenSSL:shared=True"
build_policy = "always" # "missing"
These build policies are specially useful if the package creator don't want to provide binary packages, for example with header only libraries.
The "always" policy, will retrieve the sources each time the package is installed so it can be useful for provide a "latest" mechanism or ignore the uploaded binary packages.