Repository: conan-io/conan

Issue:
Patching sources tool
This has been reported a few times, at least 2 publicly:
- @mcraveiro: https://github.com/conan-io/conan/issues/56
- @Klaim: http://boost.2283326.n4.nabble.com/Feedback-boost-conan-binary-packages-td4682377.html

I think this is a common use-case, maybe some helpers can be done to ease this task.

Patching sources tool
This has been reported a few times, at least 2 publicly:
- @mcraveiro: https://github.com/conan-io/conan/issues/56
- @Klaim: http://boost.2283326.n4.nabble.com/Feedback-boost-conan-binary-packages-td4682377.html

I think this is a common use-case, maybe some helpers can be done to ease this task.



Discussion:
Indeed.

By the way, at some point I tried to make a dependency repository for my project which would have used cmake's ExternalProject command. I failed to finish it because the imported target dance is so complex I can't even.
But you can look at the kind of complexity I have to manage day to day with this project: https://bitbucket.org/klaim/netrush-dependencies-cmake

There you can see a few use of patching of the dependencies after their download.

Note that unfortunately I don't have much time right now to attempt trying to manage my dependencies with conan, but I obviously have interest

Indeed.

By the way, at some point I tried to make a dependency repository for my project which would have used cmake's ExternalProject command. I failed to finish it because the imported target dance is so complex I can't even.
But you can look at the kind of complexity I have to manage day to day with this project: https://bitbucket.org/klaim/netrush-dependencies-cmake

There you can see a few use of patching of the dependencies after their download.

Note that unfortunately I don't have much time right now to attempt trying to manage my dependencies with conan, but I obviously have interest


Resolving patch:
diff --git a/conans/client/package_copier.py b/conans/client/package_copier.py
--- a/conans/client/package_copier.py
+++ b/conans/client/package_copier.py
@@ -23,7 +23,7 @@ def copy(self, reference, package_ids, username, channel, force=False):
         export_dest = self._paths.export(dest_ref)
         if os.path.exists(export_dest):
             if not force and not self._user_io.request_boolean("'%s' already exist. Override?"
-                                                               % str(reference)):
+                                                               % str(dest_ref)):
                 return
             rmdir(export_dest)
         shutil.copytree(export_origin, export_dest)
diff --git a/conans/tools.py b/conans/tools.py
--- a/conans/tools.py
+++ b/conans/tools.py
@@ -6,6 +6,7 @@
 import urllib2
 from conans.errors import ConanException
 from conans.util.files import _generic_algorithm_sum
+from patch import fromfile, fromstring
 
 
 def human_size(size_bytes):
@@ -151,3 +152,17 @@ def check_md5(file_path, signature):
 
 def check_sha256(file_path, signature):
     check_with_algorithm_sum("sha256", file_path, signature)
+
+
+def patch(base_path=None, patch_file=None, patch_string=None):
+    """Applies a diff from file (patch_file)  or string (patch_string)
+    in base_path directory or current dir if None"""
+
+    if not patch_file and not patch_string:
+        return
+    if patch_file:
+        patchset = fromfile(patch_file)
+    else:
+        patchset = fromstring(patch_string)
+
+    patchset.apply(root=base_path)


Test patch:
diff --git a/conans/test/conanfile_tools_test.py b/conans/test/conanfile_tools_test.py
--- a/conans/test/conanfile_tools_test.py
+++ b/conans/test/conanfile_tools_test.py
@@ -43,3 +43,86 @@ def build(self):
 
         content = load(other_file)
         self.assertEquals(content, "FOUR FIVE SIX")
+
+    def test_patch_from_file(self):
+        file_content = '''
+from conans import ConanFile
+from conans.tools import patch
+import os
+
+class ConanFileToolsTest(ConanFile):
+    name = "test"
+    version = "1.9.10"
+    settings = []
+
+    def source(self):
+        pass
+
+    def build(self):
+        patch(patch_file="file.patch")
+
+'''
+        patch_content = '''--- text.txt\t2016-01-25 17:57:11.452848309 +0100
++++ text_new.txt\t2016-01-25 17:57:28.839869950 +0100
+@@ -1 +1 @@
+-ONE TWO THREE
++ONE TWO FOUR'''
+
+        tmp_dir = temp_folder()
+        file_path = os.path.join(tmp_dir, "conanfile.py")
+        text_file = os.path.join(tmp_dir, "text.txt")
+        patch_file = os.path.join(tmp_dir, "file.patch")
+        save(file_path, file_content)
+        save(text_file, "ONE TWO THREE")
+        save(patch_file, patch_content)
+        loader = ConanFileLoader(None, Settings(), OptionsValues.loads(""))
+        ret = loader.load_conan(file_path, None)
+        curdir = os.path.abspath(os.curdir)
+        os.chdir(tmp_dir)
+        try:
+            ret.build()
+        finally:
+            os.chdir(curdir)
+
+        content = load(text_file)
+        self.assertEquals(content, "ONE TWO FOUR")
+
+    def test_patch_from_str(self):
+        file_content = '''
+from conans import ConanFile
+from conans.tools import patch
+import os
+
+class ConanFileToolsTest(ConanFile):
+    name = "test"
+    version = "1.9.10"
+    settings = []
+
+    def source(self):
+        pass
+
+    def build(self):
+        patch_content = \'''--- text.txt\t2016-01-25 17:57:11.452848309 +0100
++++ text_new.txt\t2016-01-25 17:57:28.839869950 +0100
+@@ -1 +1 @@
+-ONE TWO THREE
++ONE TWO DOH!\'''
+        patch(patch_string=patch_content)
+
+'''
+        tmp_dir = temp_folder()
+        file_path = os.path.join(tmp_dir, "conanfile.py")
+        text_file = os.path.join(tmp_dir, "text.txt")
+        save(file_path, file_content)
+        save(text_file, "ONE TWO THREE")
+        loader = ConanFileLoader(None, Settings(), OptionsValues.loads(""))
+        ret = loader.load_conan(file_path, None)
+        curdir = os.path.abspath(os.curdir)
+        os.chdir(tmp_dir)
+        try:
+            ret.build()
+        finally:
+            os.chdir(curdir)
+
+        content = load(text_file)
+        self.assertEquals(content, "ONE TWO DOH!")
diff --git a/conans/test/package_copier_test.py b/conans/test/package_copier_test.py
--- a/conans/test/package_copier_test.py
+++ b/conans/test/package_copier_test.py
@@ -51,7 +51,7 @@ def testCopier(self):
         self.assertIn("Copied Hello/0.1@lasote/testing to Hello/0.1@lasote/stable", output)
         self.assertIn("Copied 0101001 to Hello/0.1@lasote/stable", output)
         self.assertIn("Copied 2222222 to Hello/0.1@lasote/stable", output)
-        self.assertIn("'Hello/0.1@lasote/testing' already exist. Override?", output)
+        self.assertIn("'Hello/0.1@lasote/stable' already exist. Override?", output)
         self.assertIn("Package '2222222' already exist. Override?", output)
         self.assertIn("Package '0101001' already exist. Override?", output)