Repository: conan-io/conan

Issue:
[bug] deploy generator doesn't preserve symlinks
The [deploy generator](https://docs.conan.io/en/latest/reference/generators/deploy.html) uses [shutil.copy](https://docs.python.org/3/library/shutil.html#shutil.copy) to copy files. By default, shutil.copy follows symlinks rather than preserving them.
This means that e.g. libstdc++.so is deployed as a copy of libstdc++.so.6 rather than a symlink to it.
Since Python 3.3 shutil.copy can take a follow_symlinks argument. Therefore the fix could be:
```diff
diff --git a/conans/client/generators/deploy.py b/conans/client/generators/deploy.py
index 44bcf74..5aa2640 100644
--- a/conans/client/generators/deploy.py
+++ b/conans/client/generators/deploy.py
@@ -41,6 +41,6 @@ class DeployGenerator(Generator):
                                        os.path.relpath(root, rootpath), f)
                     dst = os.path.normpath(dst)
                     mkdir(os.path.dirname(dst))
-                    shutil.copy(src, dst)
+                    shutil.copy(src, dst, follow_symlinks=False)
                     copied_files.append(dst)
         return self.deploy_manifest_content(copied_files)
```
I'd submit a pull request but getting my head around the tests was taking more time than I can spare, sorry.

### Environment Details (include every applicable attribute)
  * Operating System+version: Ubuntu 16.04
  * Conan version: 1.21.2
  * Python version: 3.5.2


Discussion:
Hi, @michaelplatingsarm 

We would really appreciate it if you can submit the PR. A good place for the test would be the file `conans/test/functional/generators/deploy_test.py` (you can use `os.symlink` to create symlinks in Python).

Right now the test suite runs also for Python 2.7 so we need a fallback. The best thing would be to recreate the symlink for Python 2.7 (detect it is a symlink, know where the target file is being copied and create the symlink to it), but it'd be ok if, as a first step, pythong2.7 keeps the previous behavior and we get the bugfix for Python 3.

Thanks!
> Right now the test suite runs also for Python 2.7 so we need a fallback. The best thing would be to recreate the symlink for Python 2.7 (detect it is a symlink, know where the target file is being copied and create the symlink to it), but it'd be ok if, as a first step, pythong2.7 keeps the previous behavior and we get the bugfix for Python 3.

Yeah,  lets keep it simple, lets fix the bug for Python 3 only

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
@@ -8,6 +8,7 @@
 from conans.paths import BUILD_INFO_DEPLOY
 from conans.util.files import mkdir, md5sum
 
+
 FILTERED_FILES = ["conaninfo.txt", "conanmanifest.txt"]
 
 
@@ -41,6 +42,10 @@ def content(self):
                                        os.path.relpath(root, rootpath), f)
                     dst = os.path.normpath(dst)
                     mkdir(os.path.dirname(dst))
-                    shutil.copy(src, dst)
+                    if os.path.islink(src):
+                        linkto = os.path.relpath(os.readlink(src), os.path.dirname(src))
+                        os.symlink(linkto, dst)
+                    else:
+                        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
@@ -138,3 +138,31 @@ def same_permissions_test(self):
         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)
+
+
+@unittest.skipIf(platform.system() == "Windows", "Permissions in NIX systems only")
+class DeployGeneratorSymbolicLinkTest(unittest.TestCase):
+
+    def setUp(self):
+        conanfile = GenConanfile()
+        conanfile.with_package_file("include/header.h", "whatever")
+        self.ref = ConanFileReference("name", "version", "user", "channel")
+
+        self.client = TurboTestClient()
+        self.client.create(self.ref, conanfile)
+        layout = self.client.cache.package_layout(self.ref)
+        package_folder = layout.package(PackageReference(self.ref, NO_SETTINGS_PACKAGE_ID))
+        self.header_path = os.path.join(package_folder, "include", "header.h")
+
+    def test_symbolic_links(self):
+        link_path = self.header_path + ".lnk"
+        os.symlink(self.header_path, link_path)
+        self.client.current_folder = temp_folder()
+        self.client.run("install %s -g deploy" % self.ref.full_str())
+        base_path = os.path.join(self.client.current_folder, "name")
+        header_path = os.path.join(base_path, "include", "header.h")
+        link_path = os.path.join(base_path, "include", "header.h.lnk")
+        self.assertTrue(os.path.islink(link_path))
+        self.assertFalse(os.path.islink(header_path))
+        linkto = os.path.join(os.path.dirname(link_path), os.readlink(link_path))
+        self.assertEqual(linkto, header_path)