Repository: jazzband/pip-tools (python)

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.


Pull request:
Fix pip-sync to use pip script depending on a python version
Fixes #734.

**Changelog-friendly one-liner**: Fix pip-sync to use pip script depending on a python version

##### Contributor checklist

- [X] Provided the tests for the changes.
- [X] Requested a review from another contributor.
- [X] Gave a clear one-line description in the PR (that the maintainers can add to CHANGELOG.md on release).
- [X] Assign the PR to an existing or new milestone for the target version (following [Semantic Versioning](https://blog.versioneye.com/2014/01/16/semantic-versioning/)).


Patch:
diff --git a/.travis.yml b/.travis.yml
index 8917177..1b5d1e4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -29,11 +29,6 @@ after_success:
   - codecov -e PIP
   - "COVERALLS_PARALLEL=true coveralls"
 
-stages:
-- test
-- name: deploy
-  if: repo = jazzband/pip-tools AND tag IS present
-
 jobs:
   exclude:
     - python: "pypy3.5-6.0"
@@ -41,17 +36,14 @@ jobs:
   include:
     - python: 3.8-dev
       env: PIP=latest
-
     - stage: flake8
       python: 2.7
       env: TOXENV=flake8
       after_success: skip  # No need coverage
-
     - stage: readme
       python: 2.7
       env: TOXENV=readme
       after_success: skip  # No need coverage
-
     - stage: deploy
       install: skip  # No need to install tox-travis on deploy.
       script: skip  # No test on the deploy stage.
diff --git a/piptools/sync.py b/piptools/sync.py
index 614fa01..50cb4c3 100644
--- 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
index 32e820b..6885f4a 100644
--- 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)
+    )