Repository: conda/conda-build

Issue:
Permissions not set when extracting from zipfile.
When extracting source code from a `zipfile`, permissions are lost, and for example:

```
./configure
```

will fail, because the file is no more executable.

This was solved in [Distribute](https://pythonhosted.org/distribute/) (deprecated fork of the Setuptools project) with this [pull request](https://bitbucket.org/tarek/distribute/pull-requests/14/set-permissions-when-extracting-from/diff).

Currently, [Setuptools](http://pythonhosted.org/setuptools) has a [unpack_zip](https://bitbucket.org/pypa/setuptools/src/0d80c1398b1901def27014ec91230103d0830188/setuptools/archive_util.py?at=default&fileviewer=file-view-default#archive_util.py-88:122) function that manage permissions, while the `conda-build` [unzip](https://github.com/conda/conda-build/blob/7a6a0d3a791acd94c680769b6e470add7db5a462/conda_build/utils.py#L86-L97) function do not.

We may update the `conda-build` `unzip` function, or maybe remove it and use `archive_util.py` module of`Setuptools` instead?



Discussion:
Just ran into this issue as well when unpacking the bazel zip archive.
I'd be in favor of adding setuptools as a runtime dependency and using their function.  PRs welcome.  I can't get to this right away.
I'm not sure if the setuptools `unpack_zipfile` function is part of setuptools public API so I do not think using it in this manner would be wise.  I can put together a PR that adds the few lines from the mentioned distribute pull request.

Resolving patch:
diff --git a/conda_build/utils.py b/conda_build/utils.py
--- a/conda_build/utils.py
+++ b/conda_build/utils.py
@@ -345,7 +345,8 @@ def tar_xf(tarball, dir_path, mode='r:*'):
 
 def unzip(zip_path, dir_path):
     z = zipfile.ZipFile(zip_path)
-    for name in z.namelist():
+    for info in z.infolist():
+        name = info.filename
         if name.endswith('/'):
             continue
         path = join(dir_path, *name.split('/'))
@@ -354,6 +355,9 @@ def unzip(zip_path, dir_path):
             os.makedirs(dp)
         with open(path, 'wb') as fo:
             fo.write(z.read(name))
+        unix_attributes = info.external_attr >> 16
+        if unix_attributes:
+            os.chmod(path, unix_attributes)
     z.close()
 
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,8 @@
-import unittest
 import os
+import stat
 import sys
+import unittest
+import zipfile
 
 import pytest
 
@@ -66,6 +68,21 @@ def test_disallow_merge_conflicts(namespace_setup, testing_config):
                                                  'package'))
 
 
+def test_unzip(testing_workdir):
+    with open('file_with_execute_permission', 'w') as f:
+        f.write("test")
+    file_path = os.path.join(testing_workdir, 'file_with_execute_permission')
+    current_permissions = os.stat(file_path).st_mode
+    os.chmod(file_path, current_permissions | stat.S_IXUSR)
+    with zipfile.ZipFile('test.zip', 'w') as z:
+        z.write('file_with_execute_permission')
+    utils.unzip('test.zip', 'unpack')
+    unpacked_path = os.path.join('unpack', 'file_with_execute_permission')
+    assert os.path.isfile(unpacked_path)
+    st_mode = os.stat(unpacked_path).st_mode
+    assert st_mode & stat.S_IXUSR
+
+
 def test_disallow_in_tree_merge(testing_workdir):
     with open('testfile', 'w') as f:
         f.write("test")