Methods

source()

Method used to retrieve the source code from any other external origin like github using $ git clone or just a regular download.

For example, “exporting” the source code files, together with the conanfile.py file, can be handy if the source code is not under version control. But if the source code is available in a repository, you can directly get it from there:

from conans import ConanFile

class HelloConan(ConanFile):
    name = "Hello"
    version = "0.1"
    settings = "os", "compiler", "build_type", "arch"

    def source(self):
        self.run("git clone https://github.com/memsharded/hello.git")
        # You can also change branch, commit or whatever
        # self.run("cd hello && git checkout 2fe5...")
        #
        # Or using the Git class:
        # git = tools.Git(folder="hello")
        # git.clone("https://github.com/memsharded/hello.git", "static_shared")

This will work, as long as git is in your current path (so in Win you probably want to run things in msysgit, cmder, etc). You can also use another VCS or direct download/unzip. For that purpose, we have provided some helpers, but you can use your own code or origin as well. This is a snippet of the conanfile of the Poco library:

from conans import ConanFile
from conans.tools import download, unzip, check_md5, check_sha1, check_sha256
import os
import shutil

class PocoConan(ConanFile):
    name = "Poco"
    version = "1.6.0"

    def source(self):
        zip_name = "poco-1.6.0-release.zip"
        download("https://github.com/pocoproject/poco/archive/poco-1.6.0-release.zip", zip_name)
        # check_md5(zip_name, "51e11f2c02a36689d6ed655b6fff9ec9")
        # check_sha1(zip_name, "8d87812ce591ced8ce3a022beec1df1c8b2fac87")
        # check_sha256(zip_name, "653f983c30974d292de58444626884bee84a2731989ff5a336b93a0fef168d79")
        unzip(zip_name)
        shutil.move("poco-poco-1.6.0-release", "poco")
        os.unlink(zip_name)

The download, unzip utilities can be imported from conan, but you can also use your own code here to retrieve source code from any origin. You can even create packages for pre-compiled libraries you already have, even if you don’t have the source code. You can download the binaries, skip the build() method and define your package() and package_info() accordingly.

You can also use check_md5(), check_sha1() and check_sha256() from the tools module to verify that a package is downloaded correctly.

Note

It is very important to recall that the source() method will be executed just once, and the source code will be shared for all the package builds. So it is not a good idea to conditionally use settings or options to make changes or patches on the source code. Maybe the only setting that makes sense is the OS self.settings.os, if not doing cross-building, for example to retrieve different sources:

def source(self):
    if platform.system() == "Windows":
        # download some Win source zip
    else:
        # download sources from Nix systems in a tgz

If you need to patch the source code or build scripts differently for different variants of your packages, you can do it in the build() method, which uses a different folder and source code copy for each variant.

build()

This method is used to build the source code of the recipe using the desired commands. You can use your command line tools to invoke your build system or any of the build helpers provided with Conan.

def build(self):
    cmake = CMake(self)
    self.run("cmake . %s" % (cmake.command_line))
    self.run("cmake --build . %s" % cmake.build_config)

Build helpers

You can use these classes to prepare your build system’s command invocation:

  • CMake: Prepares the invocation of cmake command with your settings.

  • AutoToolsBuildEnvironment: If you are using configure/Makefile to build your project you can use this helper. Read more: Building with Autotools.

  • MSBuild: If you are using Visual Studio compiler directly to build your project you can use this helper MSBuild(). For lower level control, the VisualStudioBuildEnvironment can also be used: VisualStudioBuildEnvironment.

(Unit) Testing your library

We have seen how to run package tests with conan, but what if we want to run full unit tests on our library before packaging, so that they are run for every build configuration? Nothing special is required here. We can just launch the tests from the last command in our build() method:

def build(self):
    cmake = CMake(self)
    cmake.configure()
    cmake.build()
    # here you can run CTest, launch your binaries, etc
    cmake.test()

package()

The actual creation of the package, once that it is built, is done in the package() method. Using the self.copy() method, artifacts are copied from the build folder to the package folder.

The syntax of self.copy inside package() is as follows:

self.copy(pattern, dst="", src="", keep_path=True, links=False, symlinks=None, excludes=None, ignore_case=False)
Parameters:
  • pattern (Required): A pattern following fnmatch syntax of the files you want to copy, from the build to the package folders. Typically something like *.lib or *.h.

  • src (Optional, Defaulted to ""): The folder where you want to search the files in the build folder. If you know that your libraries when you build your package will be in build/lib, you will typically use build/lib in this parameter. Leaving it empty means the root build folder in local cache.

  • dst (Optional, Defaulted to ""): Destination folder in the package. They will typically be include for headers, lib for libraries and so on, though you can use any convention you like. Leaving it empty means the root package folder in local cache.

  • keep_path (Optional, Defaulted to True): Means if you want to keep the relative path when you copy the files from the src folder to the dst one. Typically headers are packaged with relative path.

  • symlinks (Optional, Defaulted to None): Set it to True to activate symlink copying, like typical lib.so->lib.so.9.

  • excludes (Optional, Defaulted to None): Single pattern or a tuple of patterns to be excluded from the copy. If a file matches both the include and the exclude pattern, it will be excluded.

  • ignore_case (Optional, Defaulted to False): If enabled, it will do a case-insensitive pattern matching.

For example:

self.copy("*.h", "include", "build/include") #keep_path default is True

The final path in the package will be: include/mylib/path/header.h, and as the include is usually added to the path, the includes will be in the form: #include "mylib/path/header.h" which is something desired.

keep_path=False is something typically desired for libraries, both static and dynamic. Some compilers as MSVC, put them in paths as Debug/x64/MyLib/Mylib.lib. Using this option, we could write:

self.copy("*.lib", "lib", "", keep_path=False)

And it will copy the lib to the package folder lib/Mylib.lib, which can be linked easily.

Note

If you are using CMake and you have an install target defined in your CMakeLists.txt, you might be able to reuse it for this package() method. Please check How to reuse cmake install for package() method.

This method copies files from build/source folder to the package folder depending on two situations:

  • Build folder and source folder are the same: Normally during conan create source folder content is copied to the build folder. In this situation src parameter of self.copy() will point to the build folder in the local cache.

  • Build folder is different from source folder: When developing a package recipe and source and build folder are different (conan package . --source-folder=source --build-folder=build) or when no_copy_source is defined, package() method is called twice: One will copy from the source folder (src parameter of self.copy() will point to the source folder), and the other will copy from the build folder (src parameter of self.copy() will point to the build folder).

package_info()

cpp_info

Each package has to specify certain build information for its consumers. This can be done in the cpp_info attribute within the package_info() method.

The cpp_info attribute has the following properties you can assign/append to:

self.cpp_info.includedirs = ['include']  # Ordered list of include paths
self.cpp_info.libs = []  # The libs to link against
self.cpp_info.libdirs = ['lib']  # Directories where libraries can be found
self.cpp_info.resdirs = ['res']  # Directories where resources, data, etc can be found
self.cpp_info.bindirs = ['bin']  # Directories where executables and shared libs can be found
self.cpp_info.defines = []  # preprocessor definitions
self.cpp_info.cflags = []  # pure C flags
self.cpp_info.cppflags = []  # C++ compilation flags
self.cpp_info.sharedlinkflags = []  # linker flags
self.cpp_info.exelinkflags = []  # linker flags
  • includedirs: List of relative paths (starting from the package root) of directories where headers can be found. By default it is initialized to ['include'], and it is rarely changed.

  • libs: Ordered list of libs the client should link against. Empty by default, it is common that different configurations produce different library names. For example:

def package_info(self):
    if not self.settings.os == "Windows":
        self.cpp_info.libs = ["libzmq-static.a"] if self.options.static else ["libzmq.so"]
    else:
        ...
  • libdirs: List of relative paths (starting from the package root) of directories in which to find library object binaries (*.lib, *.a, *.so, *.dylib). By default it is initialized to ['lib'], and it is rarely changed.

  • resdirs: List of relative paths (starting from the package root) of directories in which to find resource files (images, xml, etc). By default it is initialized to ['res'], and it is rarely changed.

  • bindirs: List of relative paths (starting from the package root) of directories in which to find library runtime binaries (like Windows .dlls). By default it is initialized to ['bin'], and it is rarely changed.

  • defines: Ordered list of preprocessor directives. It is common that the consumers have to specify some sort of defines in some cases, so that including the library headers matches the binaries:

  • cflags, cppflags, sharedlinkflags, exelinkflags: List of flags that the consumer should activate for proper behavior. Usage of C++11 could be configured here, for example, although it is true that the consumer may want to do some flag processing to check if different dependencies are setting incompatible flags (c++11 after c++14).

if self.options.static:
    if self.settings.compiler == "Visual Studio":
        self.cpp_info.libs.append("ws2_32")
    self.cpp_info.defines = ["ZMQ_STATIC"]

    if not self.settings.os == "Windows":
        self.cpp_info.cppflags = ["-pthread"]

If your recipe has requirements, you can access to your requirements cpp_info as well using the deps_cpp_info object.

class OtherConan(ConanFile):
    name = "OtherLib"
    version = "1.0"
    requires = "MyLib/1.6.0@conan/stable"

    def build(self):
        self.output.warn(self.deps_cpp_info["MyLib"].libdirs)

Note

Please take into account that defining self.cpp_info.bindirs directories, does not have any effect on system paths, PATH environment variable, nor will be directly accessible by consumers. self.cpp_info information is translated to build-systems information via generators, for example for CMake, it will be a variable in conanbuildinfo.cmake. If you want a package to make accessible its executables to its consumers, you have to specify it with self.env_info as described in env_info.

env_info

Each package can also define some environment variables that the package needs to be reused. It’s specially useful for installer packages, to set the path with the “bin” folder of the packaged application. This can be done in the env_info attribute within the package_info() method.

self.env_info.path.append("ANOTHER VALUE") # Append "ANOTHER VALUE" to the path variable
self.env_info.othervar = "OTHER VALUE" # Assign "OTHER VALUE" to the othervar variable
self.env_info.thirdvar.append("some value") # Every variable can be set or appended a new value

One of the most typical usages for the PATH environment variable, would be to add the current binary package directories to the path, so consumers can use those executables easily:

# assuming the binaries are in the "bin" subfolder
self.env_info.PATH.append(os.path.join(self.package_folder, "bin")

The virtualenv generator will use the self.env_info variables to prepare a script to activate/deactive a virtual environment.

They will be automatically applied before calling the consumer conanfile.py methods source(), build(), package() and imports().

If your recipe has requirements, you can access to your requirements env_info as well using the deps_env_info object.

class OtherConan(ConanFile):
    name = "OtherLib"
    version = "1.0"
    requires = "MyLib/1.6.0@conan/stable"

    def build(self):
        self.output.warn(self.deps_env_info["MyLib"].othervar)

user_info

If you need to declare custom variables not related with C/C++ (cpp_info) and the variables are not environment variables (env_info), you can use the self.user_info object.

Currently only the cmake, cmake_multi and txt generators supports user_info variables.

class MyLibConan(ConanFile):
    name = "MyLib"
    version = "1.6.0"

    # ...

    def package_info(self):
        self.user_info.var1 = 2

For the example above, in the cmake and cmake_multi generators, a variable CONAN_USER_MYLIB_var1 will be declared. If your recipe has requirements, you can access to your requirements user_info using the deps_user_info object.

class OtherConan(ConanFile):
    name = "OtherLib"
    version = "1.0"
    requires = "MyLib/1.6.0@conan/stable"

    def build(self):
        self.out.warn(self.deps_user_info["MyLib"].var1)

configure(), config_options()

If the package options and settings are related, and you want to configure either, you can do so in the configure() and config_options() methods.

class MyLibConan(ConanFile):
    name = "MyLib"
    version = "2.5"
    settings = "os", "compiler", "build_type", "arch"
    options = {"static": [True, False],
                "header_only": [True False]}

    def configure(self):
        # If header only, the compiler, etc, does not affect the package!
        if self.options.header_only:
            self.settings.clear()
            self.options.remove("static")

The package has 2 options set, to be compiled as a static (as opposed to shared) library, and also not to involve any builds, because header-only libraries will be used. In this case, the settings that would affect a normal build, and even the other option (static vs shared) do not make sense, so we just clear them. That means, if someone consumes MyLib with the header_only=True option, the package downloaded and used will be the same, irrespective of the OS, compiler or architecture the consumer is building with.

You can also restrict the settings used deleting any specific one. For example, it is quite common for C libraries to delete the libcxx as your library does not depend on any C++ standard library:

def configure(self):
    del self.settings.compiler.libcxx

The most typical usage would be the one with configure() while config_options() should be used more sparingly. config_options() is used to configure or constraint the available options in a package, before they are given a value. So when a value is tried to be assigned it will raise an error. For example, let’s suppose that a certain package library cannot be built as shared library in Windows, it can be done:

def config_options(self):
    if self.settings.os == "Windows":
        del self.options.shared

This will be executed before the actual assignment of options (then, such options values cannot be used inside this function), so the command conan install -o Pkg:shared=True will raise an exception in Windows saying that shared is not an option for such package.

requirements()

Besides the requires field, more advanced requirement logic can be defined in the requirements() optional method, using for example values from the package settings or options:

def requirements(self):
    if self.options.myoption:
        self.requires("zlib/1.2@drl/testing")
    else:
        self.requires("opencv/2.2@drl/stable")

This is a powerful mechanism for handling conditional dependencies.

When you are inside the method, each call to self.requires() will add the corresponding requirement to the current list of requirements. It also has optional parameters that allow defining the special cases, as is shown below:

def requirements(self):
    self.requires("zlib/1.2@drl/testing", private=True, override=False)
self.requires() parameters:
  • override (Optional, Defaulted to False): True means that this is not an actual requirement, but something to be passed upstream and override possible existing values.

  • private (Optional, Defaulted to False): True means that this requirement will be somewhat embedded (like a static lib linked into a shared lib), so it is not required to link.

build_requirements()

Build requirements are requirements that are only installed and used when the package is built from sources. If there is an existing pre-compiled binary, then the build requirements for this package will not be retrieved.

This method is useful for defining conditional build requirements, for example:

class MyPkg(ConanFile):

    def build_requirements(self):
        if self.settings.os == "Windows":
            self.build_requires("ToolWin/0.1@user/stable")

system_requirements()

It is possible to install system-wide packages from conan. Just add a system_requirements() method to your conanfile and specify what you need there.

For a special use case you can use also conans.tools.os_info object to detect the operating system, version and distribution (linux):

  • os_info.is_linux: True if Linux.

  • os_info.is_windows: True if Windows.

  • os_info.is_macos: True if OSx.

  • os_info.is_freebsd: True if FreeBSD.

  • os_info.is_solaris: True if SunOS.

  • os_info.os_version: OS version.

  • os_info.os_version_name: Common name of the OS (Windows 7, Mountain Lion, Wheezy…).

  • os_info.linux_distro: Linux distribution name (None if not Linux).

  • os_info.bash_path: Returns the absolute path to a bash in the system.

  • os_info.uname(options=None): Runs the “uname” command and returns the ouput. You can pass arguments with the options parameter.

  • os_info.detect_windows_subsystem(): Returns “MSYS”, “MSYS2”, “CYGWIN” or “WSL” if any of these Windows subsystems are detected.

You can also use SystemPackageTool class, that will automatically invoke the right system package tool: apt, yum, pkg, pkgutil, brew and pacman depending on the system we are running.

from conans.tools import os_info, SystemPackageTool

def system_requirements(self):
    pack_name = None
    if os_info.linux_distro == "ubuntu":
        if os_info.os_version > "12":
            pack_name = "package_name_in_ubuntu_10"
        else:
            pack_name = "package_name_in_ubuntu_12"
    elif os_info.linux_distro == "fedora" or os_info.linux_distro == "centos":
        pack_name = "package_name_in_fedora_and_centos"
    elif os_info.is_macos:
        pack_name = "package_name_in_macos"
    elif os_info.is_freebsd:
        pack_name = "package_name_in_freebsd"
    elif os_info.is_solaris:
        pack_name = "package_name_in_solaris"

    if pack_name:
        installer = SystemPackageTool()
        installer.install(pack_name) # Install the package, will update the package database if pack_name isn't already installed

On Windows, there is no standard package manager, however choco can be invoked as an optional:

from conans.tools import os_info, SystemPackageTool, ChocolateyTool

def system_requirements(self):
    if os_info.is_windows:
        pack_name = "package_name_in_windows"
        installer = SystemPackageTool(tool=ChocolateyTool()) # Invoke choco package manager to install the package
        installer.install(pack_name)

SystemPackageTool

def SystemPackageTool(tool=None)

Available tool classes: AptTool, YumTool, BrewTool, PkgTool, PkgUtilTool, ChocolateyTool, PacManTool.

Methods:
  • update(): Updates the system package manager database. It’s called automatically from the install() method by default.

  • install(packages, update=True, force=False): Installs the packages (could be a list or a string). If update is True it will execute update() first if it’s needed. The packages won’t be installed if they are already installed at least of force parameter is set to True. If packages is a list the first available package will be picked (short-circuit like logical or).

The use of sudo in the internals of the install() and update() methods is controlled by the CONAN_SYSREQUIRES_SUDO environment variable, so if the users don’t need sudo permissions, it is easy to opt-in/out.

Conan will keep track of the execution of this method, so that it is not invoked again and again at every Conan command. The execution is done per package, since some packages of the same library might have different system dependencies. If you are sure that all your binary packages have the same system requirements, just add the following line to your method:

def system_requirements(self):
    self.global_system_requirements=True
    if ...

imports()

Importing files copies files from the local store to your project. This feature is handy for copying shared libraries (dylib in Mac, dll in Win) to the directory of your executable, so that you don’t have to mess with your PATH to run them. But there are other use cases:

  • Copy an executable to your project, so that it can be easily run. A good example is the Google’s protobuf code generator.

  • Copy package data to your project, like configuration, images, sounds… A good example is the OpenCV demo, in which face detection XML pattern files are required.

Importing files is also very convenient in order to redistribute your application, as many times you will just have to bundle your project’s bin folder.

A typical imports() method for shared libs could be:

def imports(self):
   self.copy("*.dll", "", "bin")
   self.copy("*.dylib", "", "lib")

The self.copy() method inside imports() supports the following arguments:

def copy(pattern, dst="", src="", root_package=None, folder=False, ignore_case=False, excludes=None, keep_path=True)
Parameters:
  • pattern (Required): An fnmatch file pattern of the files that should be copied.

  • dst (Optional, Defaulted to ""): Destination local folder, with reference to current directory, to which the files will be copied.

  • src (Optional, Defaulted to ""): Source folder in which those files will be searched. This folder will be stripped from the dst parameter. Eg.: lib/Debug/x86

  • root_package (Optional, Defaulted to all packages in deps): An fnmatch pattern of the package name (“OpenCV”, “Boost”) from which files will be copied.

  • folder (Optional, Defaulted to False): If enabled, it will copy the files from the local cache to a subfolder named as the package containing the files. Useful to avoid conflicting imports of files with the same name (e.g. License).

  • ignore_case (Optional, Defaulted to False): If enabled, it will do a case-insensitive pattern matching.

  • excludes (Optional, Defaulted to None): Allows defining a list of patterns (even a single pattern) to be excluded from the copy, even if they match the main pattern.

  • keep_path (Optional, Defaulted to True): Means if you want to keep the relative path when you copy the files from the src folder to the dst one. Useful to ignore (keep_path=False) path of library.dll files in the package it is imported from.

Example to collect license files from dependencies:

def imports(self):
    self.copy("license*", dst="licenses", folder=True, ignore_case=True)

If you want to be able to customize the output user directory to work with both the cmake and cmake_multi generators, then you can do:

def imports(self):
    dest = os.getenv("CONAN_IMPORT_PATH", "bin")
    self.copy("*.dll", dst=dest, src="bin")
    self.copy("*.dylib*", dst=dest, src="lib")

And then use, for example: conan install . -e CONAN_IMPORT_PATH=Release -g cmake_multi

When a conanfile recipe has an imports() method and it builds from sources, it will do the following:

  • Before running build() it will execute imports() in the build folder, copying dependencies artifacts

  • Run the build() method, which could use such imported binaries.

  • Remove the copied (imported) artifacts after build() is finished.

You can use the keep_imports attribute to keep the imported artifacts, and maybe repackage them.

package_id()

Creates a unique ID for the package. Default package ID is calculated using settings, options and requires properties. When a package creator specifies the values for any of thoses properties, it is telling that any value change will require a different binary package.

However, sometimes a package creator would need to alter the default behavior, for example, to have only one binary package for several different compiler versions. In that case you can set a custom self.info object implementing this method and the package ID will be computed with the given information:

def package_id(self):
    v = Version(str(self.settings.compiler.version))
    if self.settings.compiler == "gcc" and (v >= "4.5" and v < "5.0"):
        self.info.settings.compiler.version = "GCC 4 between 4.5 and 5.0"

Please, check the section Define package ABI compatibility to get more details.

self.info

This self.info object stores the information that will be used to compute the package ID.

This object can be manipulated to reflect the information you want in the computation of the package ID. For example, you can delete any setting or option:

def package_id(self):
    del self.info.settings.compiler
    del self.info.options.shared

self.info.header_only()

The package will always be the same, irrespective of the OS, compiler or architecture the consumer is building with.

def package_id(self):
    self.info.header_only()

self.info.vs_toolset_compatible() / self.info.vs_toolset_incompatible()

By default (vs_toolset_compatible() mode) Conan will generate the same binary package when the compiler is Visual Studio and the compiler.toolset matches the specified compiler.version. For example, if we install some packages specifying the following settings:

def package_id(self):
    self.info.vs_toolset_compatible()
    # self.info.vs_toolset_incompatible()
compiler="Visual Studio"
compiler.version=14

And then we install again specifying these settings:

compiler="Visual Studio"
compiler.version=15
compiler.toolset=v140

The compiler version is different, but Conan will not install a different package, because the used toolchain in both cases are considered the same. You can deactivate this default behavior using calling self.info.vs_toolset_incompatible().

This is the relation of Visual Studio versions and the compatible toolchain:

Visual Studio Version

Compatible toolset

15

v141

14

v140

13

v120

12

v120

11

v110

10

v100

9

v90

8

v80

self.info.discard_build_settings() / self.info.include_build_settings()

By default (discard_build_settings()) Conan will generate the same binary when you change the os_build or arch_build when the os and arch are declared respectively. This is because os_build represent the machine running Conan, so, for the consumer, the only setting that matters is where the built software will run, not where is running the compilation. The same applies to arch_build.

With self.info.include_build_settings(), Conan will generate different packages when you change the os_build or arch_build.

def package_id(self):
    self.info.discard_build_settings()
    # self.info.include_build_settings()

self.info.default_std_matching() / self.info.default_std_non_matching()

By default (default_std_matching()) Conan will detect the default C++ standard of your compiler to not generate different binary packages.

For example, you already built some gcc > 6.1 packages, where the default std is gnu14. If you introduce the cppstd setting in your recipes and specify the gnu14 value, Conan won’t generate new packages, because it was already the default of your compiler.

With self.info.default_std_non_matching(), Conan will generate different packages when you specify the cppstd even if it matches with the default of the compiler being used:

def package_id(self):
    self.info.default_std_non_matching()
    # self.info.default_std_matching()

build_id()

In the general case, there is one build folder for each binary package, with the exact same hash/ID of the package. However this behavior can be changed, there are a couple of scenarios that this might be interesting:

  • You have a build script that generates several different configurations at once, like both debug and release artifacts, but you actually want to package and consume them separately. Same for different architectures or any other setting.

  • You build just one configuration (like release), but you want to create different binary packages for different consuming cases. For example, if you have created tests for the library in the build step, you might want to create two packages: one just containing the library for general usage, and another one also containing the tests. First package could be used as a reference and the other one as a tool to debug errors.

In both cases, if using different settings, the system will build twice (or more times) the same binaries, just to produce a different final binary package. With the build_id() method this logic can be changed. build_id() will create a new package ID/hash for the build folder, and you can define the logic you want in it. For example:

settings = "os", "compiler", "arch", "build_type"

def build_id(self):
    self.info_build.settings.build_type = "Any"

So this recipe will generate a final different package for each debug/release configuration. But as the build_id() will generate the same ID for any build_type, then just one folder and one build will be done. Such build should build both debug and release artifacts, and then the package() method should package them accordingly to the self.settings.build_type value. Different builds will still be executed if using different compilers or architectures. This method is basically an optimization of build time, avoiding multiple re-builds.

Other information like custom package options can also be changed:

def build_id(self):
    self.info_build.options.myoption = 'MyValue' # any value possible
    self.info_build.options.fullsource = 'Always'

If the build_id() method does not modify the build_id, and produce a different one than the package_id, then the standard behavior will be applied. Consider the following:

settings = "os", "compiler", "arch", "build_type"

def build_id(self):
    if self.settings.os == "Windows":
        self.info_build.settings.build_type = "Any"

This will only produce a build ID different if the package is for Windows. So the behavior in any other OS will be the standard one, as if the build_id() method was not defined: the build folder will be wiped at each conan create command and a clean build will be done.

deploy()

This method can be used in a conanfile.py to install in the system or user folder artifacts from packages.

def deploy(self):
    self.copy("*.exe")  # copy from current package
    self.copy_deps("*.dll") # copy from dependencies

Where:

Both methods allow the definition of absolute paths (to install in the system), in the dst argument. By default, the dst destionation folder will be the current one.

The deploy() method is designed to work on a package that is installed directly from its reference, as:

$ conan install Pkg/0.1@user/channel
> ...
> Pkg/0.1@user/testing deploy(): Copied 1 '.dll' files: mylib.dll
> Pkg/0.1@user/testing deploy(): Copied 1 '.exe' files: myexe.exe

All other packages and dependencies, even transitive dependencies of “Pkg/0.1@user/testing” will not be deployed, it is the responsibility of the installed package to deploy what it needs from its dependencies.