Repository: conan-io/conan
Pull request #16034 (2024-04-05 18:15:02)

allow conf in exports-sources and export
Changelog: Feature: Allow ``.conf`` access (exclusively to ``global.conf`` information, not to profile information) in the ``export()`` and ``export_sources()`` methods.
Docs: https://github.com/conan-io/docs/pull/XXXX

Close https://github.com/conan-io/conan/issues/16032


Patch:
diff --git a/conan/tools/files/copy_pattern.py b/conan/tools/files/copy_pattern.py
index e960ea13c..ef0796da0 100644
--- a/conan/tools/files/copy_pattern.py
+++ b/conan/tools/files/copy_pattern.py
@@ -1,3 +1,4 @@
+import filecmp
 import fnmatch
 import os
 import shutil
@@ -7,7 +8,7 @@ from conans.util.files import mkdir
 
 
 def copy(conanfile, pattern, src, dst, keep_path=True, excludes=None,
-         ignore_case=True):
+         ignore_case=True, overwrite_equal=False):
     """
     Copy the files matching the pattern (fnmatch) at the src folder to a dst folder.
 
@@ -25,6 +26,9 @@ def copy(conanfile, pattern, src, dst, keep_path=True, excludes=None,
     :param ignore_case: (Optional, defaulted to ``True``) If enabled, it will do a
            case-insensitive pattern matching. will do a case-insensitive pattern matching when
            ``True``
+    :param overwrite_equal: (Optional, default ``False``). If the file to be copied already exists
+           in the destination folder, only really copy it if it seems different (different size,
+           different modification time)
     :return: list of copied files
     """
     if src == dst:
@@ -40,7 +44,7 @@ def copy(conanfile, pattern, src, dst, keep_path=True, excludes=None,
     files_to_copy, files_symlinked_to_folders = _filter_files(src, pattern, excludes, ignore_case,
                                                               excluded_folder)
 
-    copied_files = _copy_files(files_to_copy, src, dst, keep_path)
+    copied_files = _copy_files(files_to_copy, src, dst, keep_path, overwrite_equal)
     copied_files.extend(_copy_files_symlinked_to_folders(files_symlinked_to_folders, src, dst))
     if conanfile:  # Some usages still pass None
         copied = '\n    '.join(files_to_copy)
@@ -108,7 +112,7 @@ def _filter_files(src, pattern, excludes, ignore_case, excluded_folder):
     return files_to_copy, files_symlinked_to_folders
 
 
-def _copy_files(files, src, dst, keep_path):
+def _copy_files(files, src, dst, keep_path, overwrite_equal):
     """ executes a multiple file copy from [(src_file, dst_file), (..)]
     managing symlinks if necessary
     """
@@ -122,14 +126,17 @@ def _copy_files(files, src, dst, keep_path):
             os.makedirs(parent_folder, exist_ok=True)
 
         if os.path.islink(abs_src_name):
-            linkto = os.readlink(abs_src_name)  # @UndefinedVariable
+            linkto = os.readlink(abs_src_name)
             try:
                 os.remove(abs_dst_name)
             except OSError:
                 pass
-            os.symlink(linkto, abs_dst_name)  # @UndefinedVariable
+            os.symlink(linkto, abs_dst_name)
         else:
-            shutil.copy2(abs_src_name, abs_dst_name)
+            # Avoid the copy if the file exists and has the exact same signature (size + mod time)
+            if overwrite_equal or not os.path.exists(abs_dst_name) \
+                    or not filecmp.cmp(abs_src_name, abs_dst_name):
+                shutil.copy2(abs_src_name, abs_dst_name)
         copied_files.append(abs_dst_name)
     return copied_files
 
diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py
index 4ff4f40d7..93ae8bc26 100644
--- a/conans/client/cmd/export.py
+++ b/conans/client/cmd/export.py
@@ -25,6 +25,7 @@ def cmd_export(app, global_conf, conanfile_path, name, version, user, channel, g
     ref.validate_ref(allow_uppercase=global_conf.get("core:allow_uppercase_pkg_names",
                                                      check_type=bool))
 
+    conanfile.conf = global_conf.get_conanfile_conf(ref, is_consumer=True)
     conanfile.display_name = str(ref)
     conanfile.output.scope = conanfile.display_name
     scoped_output = conanfile.output


Test patch:
diff --git a/conans/test/functional/tools/test_files.py b/conans/test/functional/tools/test_files.py
index 2aef28f18..c6f533166 100644
--- a/conans/test/functional/tools/test_files.py
+++ b/conans/test/functional/tools/test_files.py
@@ -86,6 +86,32 @@ class TestConanToolFiles:
         client.save({"profile": profile})
         client.run("create . -pr=profile")
 
+    def test_download_export_sources(self):
+        client = TestClient(light=True)
+        file_server = TestFileServer()
+        client.servers["file_server"] = file_server
+        save(os.path.join(file_server.store, "myfile.txt"), "some content")
+        save(os.path.join(file_server.store, "myfile2.txt"), "some content")
+
+        conanfile = textwrap.dedent(f"""
+            import os
+            from conan import ConanFile
+            from conan.tools.files import download
+
+            class Pkg(ConanFile):
+                name = "mypkg"
+                version = "1.0"
+                def export(self):
+                    download(self, "{file_server.fake_url}/myfile.txt", "myfile.txt")
+                    assert os.path.exists("myfile.txt")
+                def export_sources(self):
+                    download(self, "{file_server.fake_url}/myfile2.txt", "myfile2.txt")
+                    assert os.path.exists("myfile2.txt")
+            """)
+
+        client.save({"conanfile.py": conanfile})
+        client.run("create .")
+
 
 def test_patch(mock_patch_ng):
     conanfile = textwrap.dedent("""