Repository: conan-io/conan

Issue:
Deploy generator clobbers file permissions, including executable bit
Deploying a binary file from a package using the deploy generator will discard executable bits, making any executables shipped with the package unusable out of the box.

Example using ninja:
```sh
$ conan install ninja_installer/1.9.0@bincrafters/stable -g deploy
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++
compiler.version=7
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

Installing package: ninja_installer/1.9.0@bincrafters/stable
Requirements
    ninja_installer/1.9.0@bincrafters/stable from 'conan-center' - Cache
Packages
    ninja_installer/1.9.0@bincrafters/stable:44fcf6b9a7fb86b2586303e3db40189d3b511830 - Cache

ninja_installer/1.9.0@bincrafters/stable: Already installed!
Generator deploy created deploy_manifest.txt
Generator txt created conanbuildinfo.txt
```

Output files are not executable:
```sh
$ ls -l ninja_installer/bin 
total 4372
-rw-rw-r-- 1 me me 4473408 May 13 17:58 ninja
```

This is probably due to `shutils`: https://github.com/conan-io/conan/blob/a21899a3cded38650cf5c83d3177d1cdd5665114/conans/client/generators/deploy.py#L45


- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.




Discussion:

Resolving patch:
diff --git a/conans/client/generators/deploy.py b/conans/client/generators/deploy.py
--- a/conans/client/generators/deploy.py
+++ b/conans/client/generators/deploy.py
@@ -42,6 +42,6 @@ def content(self):
                                        os.path.relpath(root, rootpath), f)
                     dst = os.path.normpath(dst)
                     mkdir(os.path.dirname(dst))
-                    shutil.copyfile(src, dst)
+                    shutil.copy(src, dst)
                     copied_files.append(dst)
         return self.deploy_manifest_content(copied_files)


Test patch:
diff --git a/conans/test/functional/generators/deploy_test.py b/conans/test/functional/generators/deploy_test.py
--- a/conans/test/functional/generators/deploy_test.py
+++ b/conans/test/functional/generators/deploy_test.py
@@ -1,10 +1,12 @@
 import os
+import platform
+import stat
 import unittest
 
 from conans import load
-from conans.model.ref import ConanFileReference
+from conans.model.ref import ConanFileReference, PackageReference
 from conans.test.utils.test_files import temp_folder
-from conans.test.utils.tools import GenConanfile, TurboTestClient
+from conans.test.utils.tools import GenConanfile, TurboTestClient, NO_SETTINGS_PACKAGE_ID
 
 
 class DeployGeneratorTest(unittest.TestCase):
@@ -106,3 +108,33 @@ def deploy_manifest_content_test(self):
     def file_paths_test(self):
         for path in self.get_expected_paths():
             self.assertTrue(os.path.exists(path))
+
+
+class DeployGeneratorPermissionsTest(unittest.TestCase):
+    """
+    Test files deployed by the deploy generator are copied with same permissions
+    """
+
+    def setUp(self):
+        conanfile1 = GenConanfile()
+        conanfile1.with_package_file("include/header1.h", "whatever")
+        self.ref1 = ConanFileReference("name1", "version", "user", "channel")
+
+        self.client = TurboTestClient()
+        self.client.create(self.ref1, conanfile1)
+        layout = self.client.cache.package_layout(self.ref1)
+        package_folder = layout.package(PackageReference(self.ref1, NO_SETTINGS_PACKAGE_ID))
+        self.header_path = os.path.join(package_folder, "include", "header1.h")
+        self.assertTrue(os.path.exists(self.header_path))
+
+    @unittest.skipIf(platform.system() == "Windows", "Permissions in NIX systems only")
+    def same_permissions_test(self):
+        stat_info = os.stat(self.header_path)
+        self.assertFalse(stat_info.st_mode & stat.S_IXUSR)
+        os.chmod(self.header_path, stat_info.st_mode | stat.S_IXUSR)
+        self.client.current_folder = temp_folder()
+        self.client.run("install %s -g deploy" % self.ref1.full_repr())
+        base1_path = os.path.join(self.client.current_folder, "name1")
+        header1_path = os.path.join(base1_path, "include", "header1.h")
+        stat_info = os.stat(header1_path)
+        self.assertTrue(stat_info.st_mode & stat.S_IXUSR)