Repository: linkedin/shiv

Issue:
FileNotFoundError during bootstrapping to acquire_nix lock
Attempting to follow the example given in the readme, using OSX 10.11.6:
```
$ mktmpenv --python=`which python3.6`
$ which python3
/Users/<username>/Virtualenvs/tmp-cf98a678da0c70e/bin/python3

$ python -V
Python 3.6.2

$ python3 -m pip download boto
[...] Saved ./boto-2.49.0-py2.py3-none-any.whl

$ python3 -m pip install shiv
[...]

$ shiv -o boto.pyz --find-links . --no-index boto
[...] Successfully installed boto-2.49.0

$ ./boto.pyz
Traceback (most recent call last):
  File "/Users/<username>/.pyenv/versions/3.6.2/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/<username>/.pyenv/versions/3.6.2/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "boto.pyz/__main__.py", line 3, in <module>
  File "boto.pyz/_bootstrap/__init__.py", line 127, in bootstrap
  File "boto.pyz/_bootstrap/__init__.py", line 79, in extract_site_packages
  File "boto.pyz/_bootstrap/filelock.py", line 71, in __enter__
  File "boto.pyz/_bootstrap/filelock.py", line 39, in acquire_nix
FileNotFoundError: [Errno 2] No such file or directory: '/Users/<username>/.shiv/boto_cab7037d-ea57-4001-9b01-da4b73096d73.lock'

$ ls -al $HOME/.shiv
ls: /Users/<username>/.shiv: No such file or directory

$ mkdir $HOME/.shiv

$ ./boto.pyz
Python 3.6.2 [...]
>>> 
```

It seems like the lock acquisition assumes the directory exists, but I can't see where that's explained in the documentation.

Comments:

Patch:
diff --git a/src/shiv/bootstrap/__init__.py b/src/shiv/bootstrap/__init__.py
index 997f288..fc62175 100644
--- a/src/shiv/bootstrap/__init__.py
+++ b/src/shiv/bootstrap/__init__.py
@@ -73,8 +73,13 @@ def extract_site_packages(archive, target_path, compile_pyc, compile_workers=0,
     :param ZipFile archive: The zipfile object we are bootstrapping from.
     :param Path target_path: The path to extract our zip to.
     """
-    target_path_tmp = Path(target_path.parent, target_path.stem + ".tmp")
-    lock = Path(target_path.parent, target_path.stem + ".lock")
+    parent = target_path.parent
+    target_path_tmp = Path(parent, target_path.stem + ".tmp")
+    lock = Path(parent, target_path.stem + ".lock")
+
+    # If this is the first time that a pyz is being extracted, we'll need to create the ~/.shiv dir
+    if not parent.exists():
+        parent.mkdir(parents=True, exist_ok=True)
 
     with FileLock(lock):
 


Test patch:
diff --git a/test/test_bootstrap.py b/test/test_bootstrap.py
index 95b7c47..10330bd 100644
--- a/test/test_bootstrap.py
+++ b/test/test_bootstrap.py
@@ -76,13 +76,18 @@ class TestBootstrap:
         with mock.patch.object(sys, 'path', []):
             assert _first_sitedir_index() is None
 
+    @pytest.mark.parametrize("nested", (False, True))
     @pytest.mark.parametrize("compile_pyc", (False, True))
     @pytest.mark.parametrize("force", (False, True))
-    def test_extract_site_packages(self, tmpdir, zip_location, compile_pyc, force):
+    def test_extract_site_packages(self, tmpdir, zip_location, nested, compile_pyc, force):
 
         zipfile = ZipFile(str(zip_location))
         target = Path(tmpdir, "test")
 
+        if nested:
+            # we want to test for not-yet-created shiv root dirs
+            target = target / "nested" / "root"
+
         if force:
             # we want to make sure we overwrite if the target exists when using force
             target.mkdir(parents=True, exist_ok=True)