Repository: pypa/pipenv

Issue:
pipenv run <executable> doesn't find executable that are in my PATH 
I upgraded pipenv to 2018.05.18 today from an older build (might have been 10.1.0) on my windows 10 machine, and now I'm having trouble using pipenv run to invoke executables I have in my PATH. It seems pipenv run doesn't resolve anything in my PATH.

For example, I have my IDE atom in my path and invoke it as `pipenv run atom ` which worked fine before I updated pipenv on my system. 

After the update, I now get this error: 
```
$ pipenv run atom
The system cannot find the path specified.
```

(I also see the same behavior trying to pipenv run <alias> on any alias I have defined in my .bashrc.)

<details><summary>$ python -m pipenv.help output</summary>

Pipenv version: `'2018.05.18'`

Pipenv location: `'C:\\Users\\user01\\AppData\\Local\\Programs\\Python\\Python36\\lib\\site-packages\\pipenv'`

Python location: `'C:\\Users\\user01\\AppData\\Local\\Programs\\Python\\Python36\\python.exe'`

Other Python installations in `PATH`:

  - `3.6`: `C:\Users\user01\AppData\Local\Programs\Python\Python36\python.exe`

  - `3.6.5`: `C:\Users\user01\AppData\Local\Programs\Python\Python36\python.exe`
  - `3.6.5`: `C:\Windows\py.exe`

PEP 508 Information:

```
{'implementation_name': 'cpython',
 'implementation_version': '3.6.5',
 'os_name': 'nt',
 'platform_machine': 'AMD64',
 'platform_python_implementation': 'CPython',
 'platform_release': '10',
 'platform_system': 'Windows',
 'platform_version': '10.0.10586',
 'python_full_version': '3.6.5',
 'python_version': '3.6',
 'sys_platform': 'win32'}
```

System environment variables:

  - `ACLOCAL_PATH`
  - `ALLUSERSPROFILE`
  - `ANSICON`
  - `ANSICON_DEF`
  - `APPDATA`
  - `COMMONPROGRAMFILES`
  - `COMPUTERNAME`
  - `COMSPEC`
  - `CONFIG_SITE`
  - `C_EM64T_REDIST11`
  - `COMMONPROGRAMFILES(X86)`
  - `COMMONPROGRAMW6432`
  - `CONEMUANSI`
  - `CONEMUANSILOG`
  - `CONEMUARGS2`
  - `CONEMUARGS`
  - `CONEMUBACKHWND`
  - `CONEMUBASEDIR`
  - `CONEMUBUILD`
  - `CONEMUCONFIG`
  - `CONEMUDIR`
  - `CONEMUDRAWHWND`
  - `CONEMUDRIVE`
  - `CONEMUHWND`
  - `CONEMUHOOKS`
  - `CONEMUPID`
  - `CONEMUPALETTE`
  - `CONEMUSERVERPID`
  - `CONEMUTASK`
  - `CONEMUWORKDIR`
  - `CONEMUWORKDRIVE`
  - `DISPLAY`
  - `EXEPATH`
  - `FSHARPINSTALLDIR`
  - `HOME`
  - `HOMEDRIVE`
  - `HOMEPATH`
  - `HOSTNAME`
  - `INFOPATH`
  - `LANG`
  - `LOCALAPPDATA`
  - `LOGONSERVER`
  - `MANPATH`
  - `MINGW_CHOST`
  - `MINGW_PACKAGE_PREFIX`
  - `MINGW_PREFIX`
  - `MSYSTEM`
  - `MSYSTEM_CARCH`
  - `MSYSTEM_CHOST`
  - `MSYSTEM_PREFIX`
  - `NUMBER_OF_PROCESSORS`
  - `OLDPWD`
  - `ORIGINAL_PATH`
  - `ORIGINAL_TEMP`
  - `ORIGINAL_TMP`
  - `OS`
  - `ONEDRIVE`
  - `PATH`
  - `PATHEXT`
  - `PIPENV_VENV_IN_PROJECT`
  - `PKG_CONFIG_PATH`
  - `PLINK_PROTOCOL`
  - `PROCESSOR_ARCHITECTURE`
  - `PROCESSOR_IDENTIFIER`
  - `PROCESSOR_LEVEL`
  - `PROCESSOR_REVISION`
  - `PROGRAMFILES`
  - `PS1`
  - `PSMODULEPATH`
  - `PUBLIC`
  - `PWD`
  - `PROGRAMDATA`
  - `PROGRAMFILES(X86)`
  - `PROGRAMW6432`
  - `RUBYOPT`
  - `SESSIONNAME`
  - `SHELL`
  - `SHLVL`
  - `SNOW_INVENTORY_HOME`
  - `SSH_AGENT_PID`
  - `SSH_ASKPASS`
  - `SSH_AUTH_SOCK`
  - `SSL_CERT_FILE`
  - `SYSTEMDRIVE`
  - `SYSTEMROOT`
  - `TEMP`
  - `TERM`
  - `TMP`
  - `TMPDIR`
  - `UATDATA`
  - `USERDNSDOMAIN`
  - `USERDOMAIN`
  - `USERDOMAIN_ROAMINGPROFILE`
  - `USERNAME`
  - `USERPROFILE`
  - `VBOX_MSI_INSTALL_PATH`
  - `VS140COMNTOOLS`
  - `WINDIR`
  - `_`
  - `USERDEV`
  - `USERZEN`
  - `SNOW_AGENT`
  - `PYTHONDONTWRITEBYTECODE`
  - `PIP_PYTHON_PATH`

Pipenv–specific environment variables:

 - `PIPENV_VENV_IN_PROJECT`: `1`

Debug–specific environment variables:

  - `PATH`: `C:\Users\user01\bin;C:\Users\user01\AppData\Local\Programs\Git\mingw64\bin;C:\Users\user01\AppData\Local\Programs\Git\usr\local\bin;C:\Users\user01\AppData\Local\Programs\Git\usr\bin;C:\Users\user01\AppData\Local\Programs\Git\usr\bin;C:\Users\user01\AppData\Local\Programs\Git\cmd;C:\Program Files\ConEmu\ConEmu\Scripts;C:\Program Files\ConEmu;C:\Program Files\ConEmu\ConEmu;C:\Program Files (x86)\Common Files\Intel\Shared Files\cpp\bin\Intel64;C:\ProgramData\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0;C:\Program Files\SASHome\x86\Secure\ccme4;C:\Program Files\SASHome\Secure\ccme4;C:\Program Files (x86)\WinSCP;C:\Program Files (x86)\PuTTY;C:\Program Files\MySQL\MySQL Server 5.7\bin;C:\Apache24\bin;C:\php56;C:\HashiCorp\Vagrant\bin;C:\Program Files\Intel\WiFi\bin;C:\Program Files\Common Files\Intel\WirelessCommon;C:\Program Files\nodejs;C:\Users\user01\AppData\Roaming\Python\Python36\Scripts;C:\Users\user01\AppData\Local\Programs\Git\usr\bin;C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn;C:\Program Files (x86)\Microsoft SQL Server\140\DTS\Binn;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio;C:\Users\user01\AppData\Local\Programs\Python\Python36\Scripts;C:\Users\user01\AppData\Local\Programs\Python\Python36;C:\Ruby22\bin;C:\Users\user01\AppData\Local\atom\bin;C:\Users\user01\bin\zatit;C:\Program Files\PostgreSQL\9.6\bin;C:\Users\user01\bin;C:\Users\user01\AppData\Roaming\npm;C:\Users\user01\AppData\Local\Programs\Git\usr\bin\vendor_perl;C:\Users\user01\AppData\Local\Programs\Git\usr\bin\core_perl`
  - `SHELL`: `C:\Users\user01\AppData\Local\Programs\Git\usr\bin\bash.exe`
  - `LANG`: `en_US.UTF-8`
  - `PWD`: `C:/dev/path_resolve_pipenv2018.05.18`


---------------------------

Contents of `Pipfile` ('C:\\dev\\path_resolve_pipenv2018.05.18\\Pipfile'):

```toml
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

[dev-packages]

[requires]
python_version = "3.6"

```


Contents of `Pipfile.lock` ('C:\\dev\\path_resolve_pipenv2018.05.18\\Pipfile.lock'):

```json
{
    "_meta": {
        "hash": {
            "sha256": "415dfdcb118dd9bdfef17671cb7dcd78dbd69b6ae7d4f39e8b44e71d60ca72e7"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {},
    "develop": {}
}

```
</details>

------------

##### Expected result

When `<executable>` is in my PATH, running `pipenv run <executable>` would resolve and run `<executable>` within the virtual environment. 

##### Actual result

When `<executable>` is in my PATH, running `pipenv run <executable>` yields 
`The system cannot find the path specified.`

##### Steps to replicate

I'm on windows 10. I guess this could be OS-related.

1. Open shell. I have replicated this on Git bash (for windows) and regular PowerShell.
1. Create an empty directory and cd into.
2. `$ pipenv install --three`
3. Have a PATH environment variable that inludes an exectuable, e.g. `C:\Users\user01\AppData\Local\atom\bin` (that contains an executable, here: `atom`)
4. Verify you can trigger executable from shell (`$ atom`)
5. Now try `$ pipenv run atom`
6. Get error message `The system cannot find the path specified.`






Discussion:
What happens if you run `where atom` and `pipenv run where atom`? I could have a fix locally and this might be a good opportunity to propose it.
The output of `where atom` and `pipenv run where atom` are the same:
```
C:\Users\user01\AppData\Local\atom\bin\atom
C:\Users\user01\AppData\Local\atom\bin\atom.cmd
```
Yup that’s the problem I was talking about. Pipenv currently doesn’t check for all possible executable extensions, only a few selected ones. We should instead check for entries in `PATHEXT`. I’ll put up a fix in a few days.
Meanwhile `atom.cmd` (instead of just `atom`) should work correctly.
We were doing this for windows, btw.

Resolving patch:
diff --git a/pipenv/core.py b/pipenv/core.py
--- a/pipenv/core.py
+++ b/pipenv/core.py
@@ -126,10 +126,10 @@ def which(command, location=None, allow_global=False):
     if not allow_global:
         if os.name == 'nt':
             p = find_windows_executable(
-                os.path.join(location, 'Scripts'), command
+                os.path.join(location, 'Scripts'), command,
             )
         else:
-            p = os.sep.join([location] + ['bin/{0}'.format(command)])
+            p = os.path.join(location, 'bin', command)
     else:
         if command == 'python':
             p = sys.executable
@@ -1508,7 +1508,7 @@ def pip_install(
 
 
 def pip_download(package_name):
-    cache_dir = Path(PIPENV_CACHE_DIR)    
+    cache_dir = Path(PIPENV_CACHE_DIR)
     pip_config = {
         'PIP_CACHE_DIR': fs_str(cache_dir.as_posix()),
         'PIP_WHEEL_DIR': fs_str(cache_dir.joinpath('wheels').as_posix()),
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -835,20 +835,18 @@ def get_windows_path(*args):
 def find_windows_executable(bin_path, exe_name):
     """Given an executable name, search the given location for an executable"""
     requested_path = get_windows_path(bin_path, exe_name)
-    if os.path.exists(requested_path):
+    if os.path.isfile(requested_path):
         return requested_path
 
-    # Ensure we aren't adding two layers of file extensions
-    exe_name = os.path.splitext(exe_name)[0]
-    files = [
-        '{0}.{1}'.format(exe_name, ext) for ext in ['', 'py', 'exe', 'bat']
-    ]
-    exec_paths = [get_windows_path(bin_path, f) for f in files]
-    exec_files = [
-        filename for filename in exec_paths if os.path.isfile(filename)
-    ]
-    if exec_files:
-        return exec_files[0]
+    try:
+        pathext = os.environ['PATHEXT']
+    except KeyError:
+        pass
+    else:
+        for ext in pathext.split(os.pathsep):
+            path = get_windows_path(bin_path, exe_name + ext.strip().lower())
+            if os.path.isfile(path):
+                return path
 
     return find_executable(exe_name)
 


Test patch:
diff --git a/tests/unit/test_utils_windows_executable.py b/tests/unit/test_utils_windows_executable.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/test_utils_windows_executable.py
@@ -0,0 +1,30 @@
+import os
+
+import mock
+import pytest
+
+from pipenv import utils
+
+
+# This module is run only on Windows.
+pytestmark = pytest.mark.skipif(
+    os.name != 'nt',
+    reason="only relevant on windows",
+)
+
+
+@mock.patch('os.path.isfile')
+@mock.patch('pipenv.utils.find_executable')
+def test_find_windows_executable(mocked_find_executable, mocked_isfile):
+    mocked_isfile.return_value = False
+    mocked_find_executable.return_value = None
+    found = utils.find_windows_executable('fake/path', 'python')
+    assert found is None
+
+    assert mocked_isfile.call_count > 1
+
+    calls = [mock.call('fake\\path\\python')] + [
+        mock.call('fake\\path\\python{0}'.format(ext.lower()))
+        for ext in os.environ['PATHEXT'].split(';')
+    ]
+    assert mocked_isfile.mock_calls == calls