Patching sources

In this example we are going to see how to patch the source code. This is necessary sometimes, specially when you are creating a package for a third party library. A patch might be required in the build system scripts or even in the source code of the library if you want, for example, to apply a security patch.

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

$ git clone https://github.com/conan-io/examples2.git
$ cd examples/tools/files/patches

Patching using ‘replace_in_file’

The simplest way to patch a file is using the replace_in_file tool in your recipe. It searches in a file the specified string and replaces it with another string.

in source() method

The source() method is called only once for all the configurations (different calls to conan create for different settings/options) so you should patch only in the source() method if the changes are common for all the configurations.

Look at the source() method at the conanfile.py:

import os
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
from conan.tools.files import get, replace_in_file


class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

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

    def source(self):
        get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip", strip_root=True)
        replace_in_file(self, os.path.join(self.source_folder, "src", "hello.cpp"), "Hello World", "Hello Friends!")

    ...

We are replacing the "Hello World" string with “Hello Friends!”. We can run conan create . and verify that if the replace was done:

$ conan create .
...
-------- Testing the package: Running test() --------
hello/1.0: Hello Friends! Release!
...

in build() method

In this case, we need to apply a different patch depending on the configuration (self.settings, self.options…), so it has to be done in the build() method. Let’s modify the recipe to introduce a change that depends on the self.options.shared:

class helloRecipe(ConanFile):

    ...

    def source(self):
        get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip", strip_root=True)

    def build(self):
        replace_in_file(self, os.path.join(self.source_folder, "src", "hello.cpp"),
                        "Hello World",
                        "Hello {} Friends!".format("Shared" if self.options.shared else "Static"))
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    ...

If we call conan create with different option.shared we can check the output:

$ conan create .
...
hello/1.0: Hello Static Friends! Release!
...

$ conan create . -o shared=True
...
hello/1.0: Hello Shared Friends! Debug!
...

Patching using “patch” tool

If you have a patch file (diff between two versions of a file), you can use the conan.tools.files.patch tool to apply it. The rules about where to apply the patch (source() or build() methods) are the same.

We have this patch file, where we are changing again the message to say “Hello Patched World Release!”:

--- a/src/hello.cpp
+++ b/src/hello.cpp
@@ -3,9 +3,9 @@

 void hello(){
     #ifdef NDEBUG
-    std::cout << "hello/1.0: Hello World Release!\n";
+    std::cout << "hello/1.0: Hello Patched World Release!\n";
     #else
-    std::cout << "hello/1.0: Hello World Debug!\n";
+    std::cout << "hello/1.0: Hello Patched World Debug!\n";
     #endif

     // ARCHITECTURES

Edit the conanfile.py to:

  1. Import the patch tool.

  2. Add exports_sources to the patch file so we have it available in the cache.

  3. Call the patch tool.

import os
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
from conan.tools.files import get, replace_in_file, patch


class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

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

    def source(self):
        get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip", strip_root=True)
        patch_file = os.path.join(self.export_sources_folder, "hello_patched.patch")
        patch(self, patch_file=patch_file)

    ...

We can run “conan create” and see that the patch worked:

$ conan create .
...
-------- Testing the package: Running test() --------
hello/1.0: Hello Patched World Release!
...

We can also use the conandata.yml introduced in the tutorial so we can declare the patches to apply for each version:

patches:
  "1.0":
    - patch_file: "hello_patched.patch"

And there are the changes we introduce in the source() method:

.. code-block:: python

    def source(self):
        get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip", strip_root=True)
        patches = self.conan_data["patches"][self.version]
        for p in patches:
            patch_file = os.path.join(self.export_sources_folder, p["patch_file"])
            patch(self, patch_file=patch_file)

Check patch for more details.

If we run the conan create, the patch is also applied:

$ conan create .
...
-------- Testing the package: Running test() --------
hello/1.0: Hello Patched World Release!
...

Patching using “apply_conandata_patches” tool

The example above works but it is a bit complex. If you follow the same yml structure (check the apply_conandata_patches to see the full supported yml) you only need to call apply_conandata_patches:

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
from conan.tools.files import get, apply_conandata_patches


class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

    ...

    def source(self):
        get(self, "https://github.com/conan-io/libhello/archive/refs/heads/main.zip", strip_root=True)
        apply_conandata_patches(self)

Let’s check if the patch is also applied:

$ conan create .
...
-------- Testing the package: Running test() --------
hello/1.0: Hello Patched World Release!
...