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:
Import the
patch
tool.Add
exports_sources
to the patch file so we have it available in the cache.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!
...