Repository: jazzband/pip-tools

Issue:
pip-sync uses first pip script on PATH instead of the one from the Python version it is run with
Describe the issue briefly here.

##### Environment Versions

1. OS Type: Android 8.1 aarch64/Latest Termux
1. Python version: 3.7.2
1. pip version:  `pip 18.1 from /data/data/com.termux/files/usr/lib/python2.7/site-packages/pip (python 2.7)` (pip3.7 --version is `pip 19.0.2 from /data/data/com.termux/files/usr/lib/python3.7/site-packages/pip (python 3.7)
1. pip-tools version: `pip-compile, version 3.3.2`

##### Steps to replicate

1. Get Termux
2. Run `pkg up && pkg install python python2`
3. Install pip on Python 3 (get-pip.py)
4. Install pip on Python 2 (get-pip.py)
5. Install piptools on Python 3
6. Use `pip-sync` with a requirements.txt file and watch it installing to Python 2


##### Expected result

Installs into Python 3 if ran on Python 3 and to Python 2 if ran on Python2

##### Actual result

Installs to whichever Python version `pip` script on `PATH` is from

##### Extra information

If using Python 2 and 3 together, an extra `pip-compile` and `pip-sync` script per Python major and per Python minor version to be able to select Python version to run piptools on would be very useful.



Discussion:
Hello @kaeptmblaubaer1000,

Thank you for the detailed report! The issue is probably here:
https://github.com/jazzband/pip-tools/blob/200bc6bcc4950eb5628c1472638d66bfa7c860a2/piptools/sync.py#L140-L141

Since `pip` uses python2.7 it would install packages to python2. I think pip-sync should decide to use `pip2` or `pip3`, depending on python's version.
Or use `console_scripts.pip` entry point and execute it inside the script.

Resolving patch:
diff --git a/piptools/sync.py b/piptools/sync.py
--- a/piptools/sync.py
+++ b/piptools/sync.py
@@ -133,20 +133,13 @@ def sync(to_install, to_uninstall, verbose=False, dry_run=False, pip_flags=None,
     if not verbose:
         pip_flags += ['-q']
 
-    if os.environ.get('VIRTUAL_ENV'):
-        # find pip via PATH
-        pip = 'pip'
-    else:
-        # find pip in same directory as pip-sync entry-point script
-        pip = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'pip')
-
     if to_uninstall:
         if dry_run:
             click.echo("Would uninstall:")
             for pkg in to_uninstall:
                 click.echo("  {}".format(pkg))
         else:
-            check_call([pip, 'uninstall', '-y'] + pip_flags + sorted(to_uninstall))
+            check_call([sys.executable, '-m', 'pip', 'uninstall', '-y'] + pip_flags + sorted(to_uninstall))
 
     if to_install:
         if install_flags is None:
@@ -168,7 +161,9 @@ def sync(to_install, to_uninstall, verbose=False, dry_run=False, pip_flags=None,
             tmp_req_file.close()
 
             try:
-                check_call([pip, 'install', '-r', tmp_req_file.name] + pip_flags + install_flags)
+                check_call(
+                    [sys.executable, '-m', 'pip', 'install', '-r', tmp_req_file.name] + pip_flags + install_flags
+                )
             finally:
                 os.unlink(tmp_req_file.name)
 


Test patch:
diff --git a/tests/test_sync.py b/tests/test_sync.py
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -230,7 +230,9 @@ def test_sync_install_temporary_requirement_file(from_line, from_editable, mocke
     with mock.patch('piptools.sync.check_call') as check_call:
         to_install = {from_line('django==1.8')}
         sync(to_install, set())
-        check_call.assert_called_once_with(['pip', 'install', '-r', mocked_tmp_req_file.name, '-q'])
+        check_call.assert_called_once_with(
+            [sys.executable, '-m', 'pip', 'install', '-r', mocked_tmp_req_file.name, '-q']
+        )
 
 
 def test_temporary_requirement_file_deleted(from_line, from_editable, mocked_tmp_file):
@@ -304,3 +306,13 @@ def test_sync_requirement_file_with_hashes(from_line, from_editable, mocked_tmp_
             '    --hash=sha256:f5c056e8f62d45ba8215e5cb8f50dfccb198b4b9fbea8500674f3443e4689589'
         )
         mocked_tmp_req_file.write.assert_called_once_with(expected)
+
+
+@mock.patch('piptools.sync.check_call')
+def test_sync_uninstall_pip_command(check_call):
+    to_uninstall = ['six', 'django', 'pytz', 'click']
+
+    sync(set(), to_uninstall)
+    check_call.assert_called_once_with(
+        [sys.executable, '-m', 'pip', 'uninstall', '-y', '-q'] + sorted(to_uninstall)
+    )