Skip to content

Commit

Permalink
Ensure the rpc virtualenv is created from the system environment (#1699)
Browse files Browse the repository at this point in the history
This is necessary for venv to create a proper virtualenv
  • Loading branch information
galaunay committed Oct 17, 2019
1 parent fea0825 commit 4b79ed2
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 26 deletions.
4 changes: 2 additions & 2 deletions Cask
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
(package "elpy" "1.31.0" "Emacs Python Development Environment")

(depends-on "company" "0.9.10")
(depends-on "find-file-in-project" "5.7.4")
(depends-on "find-file-in-project" "5.7.7")
(depends-on "highlight-indentation" "0.7.0")
(depends-on "pyvenv" "1.2")
(depends-on "pyvenv" "1.20")
(depends-on "yasnippet" "0.13.0")
(depends-on "s" "1.12.0")

Expand Down
60 changes: 40 additions & 20 deletions elpy-rpc.el
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,10 @@ During the execution of BODY the following variables are available:
(file-name-directory
current-environment-binaries)))))
;; No need to change of venv if they are the same
(same-venv (file-equal-p current-environment
(elpy-rpc-get-or-create-virtualenv)))
(same-venv (or (string= current-environment
(elpy-rpc-get-virtualenv-path))
(file-equal-p current-environment
(elpy-rpc-get-virtualenv-path))))
current-environment-is-deactivated)
(unless same-venv
(pyvenv-activate (elpy-rpc-get-or-create-virtualenv))
Expand Down Expand Up @@ -312,7 +314,7 @@ binaries used to create the virtualenv."
(and (or (not is-venv-exist) venv-need-update)
(or is-default-rpc-venv
(y-or-n-p
(format "`elpy-rpc-virtualenv-path' was set to '%s', but this virtualenv does not exist, create it ?" rpc-venv-path))))))
(format "`elpy-rpc-virtualenv-path' was set to '%s', but this virtualenv does not exist, create it ? " rpc-venv-path))))))
;; Delete the rpc virtualenv if obsolete
(when venv-need-update
(delete-directory rpc-venv-path t)
Expand All @@ -325,36 +327,38 @@ binaries used to create the virtualenv."
(message "Elpy is %s the RPC virtualenv ('%s')"
(if venv-need-update "updating" "creating")
rpc-venv-path)
(elpy-rpc--create-virtualenv rpc-venv-path venv-need-update)
(elpy-rpc--create-virtualenv rpc-venv-path)
(pyvenv-activate rpc-venv-path)
;; Add a file to keep track of the `elpy-rpc-python-command` used
(with-temp-file venv-python-path-command-file
(insert elpy-rpc-python-command))
;; safeguard to be sure we don't install stuff in the wrong venv
(when (file-equal-p pyvenv-virtual-env rpc-venv-path)
(elpy-rpc--install-dependencies))
(elpy-rpc-restart)
;; Deactivate the rpc venv
(if deact-venv
(pyvenv-activate (directory-file-name deact-venv))
(pyvenv-deactivate)))))
rpc-venv-path))

(defun elpy-rpc--create-virtualenv (rpc-venv-path venv-need-update)
"Create a virtualenv for the RPC in RPC-VENV-PATH.
if VENV-NEED-UPDATE is not nil, update the virtualenv."
(cond
((= 0 (call-process elpy-rpc-python-command nil nil nil
"-m" "venv" "-h"))
(with-current-buffer (generate-new-buffer "*venv*")
(call-process elpy-rpc-python-command nil t t
"-m" "venv" rpc-venv-path)))
((executable-find "virtualenv")
(with-current-buffer (generate-new-buffer "*virtualenv*")
(call-process "virtualenv" nil t t
"-p" elpy-rpc-python-command rpc-venv-path)))
(t
(error "Elpy necessitates the 'virtualenv' python package, please install it with `pip install virtualenv`"))))
(defun elpy-rpc--create-virtualenv (rpc-venv-path)
"Create a virtualenv for the RPC in RPC-VENV-PATH."
;; venv cannot create a proper virtualenv from inside another virtualenv
(let ((elpy-rpc-virtualenv-path 'global))
(with-elpy-rpc-virtualenv-activated
(cond
((= 0 (call-process elpy-rpc-python-command nil nil nil
"-m" "venv" "-h"))
(with-current-buffer (generate-new-buffer "*venv*")
(call-process elpy-rpc-python-command nil t t
"-m" "venv" rpc-venv-path)))
((executable-find "virtualenv")
(with-current-buffer (generate-new-buffer "*virtualenv*")
(call-process "virtualenv" nil t t
"-p" elpy-rpc-python-command rpc-venv-path)))
(t
(error "Elpy necessitates the 'virtualenv' python package, please install it with `pip install virtualenv`"))))))

(defun elpy-rpc--install-dependencies ()
"Install the RPC dependencies in the current virtualenv."
Expand All @@ -369,6 +373,22 @@ if VENV-NEED-UPDATE is not nil, update the virtualenv."
(message "Elpy failed to install some of the RPC dependencies, please use `elpy-config' to install them.")))
(message "Some of Elpy's functionnalities will not work, please use `elpy-config' to install the needed python dependencies.")))

(defun elpy-rpc-reinstall-virtualenv ()
"Re-install the RPC virtualenv."
(interactive)
(let ((rpc-venv-path (elpy-rpc-get-virtualenv-path)))
(when
(cond
((or (eq elpy-rpc-virtualenv-path 'system)
(eq elpy-rpc-virtualenv-path 'global)) ;; backward compatibility
(error "Cannot reinstall the system environment, please reinstall the necessary packages manually"))
((string= (elpy-rpc-default-virtualenv-path) rpc-venv-path)
t)
(t
(y-or-n-p (format "Are you sure you want to reinstall the virtualenv in '%s' (every manual modifications will be lost) ? " rpc-venv-path))))
(delete-directory rpc-venv-path t)
(elpy-rpc-get-or-create-virtualenv))))

;;;;;;;;;;;;;;;;;;;
;;; Promise objects

Expand Down
12 changes: 11 additions & 1 deletion elpy/blackutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
"""

import sys
from pkg_resources import parse_version

# in case pkg_resources is not properly installed
# (see https://github.com/jorgenschaefer/elpy/issues/1674).
try:
from pkg_resources import parse_version
except ImportError:
parse_version = None

import os

Expand Down Expand Up @@ -31,6 +37,10 @@ def fix_code(code, directory):
"""
if not black:
raise Fault("black not installed", code=400)
if not parse_version:
raise Fault("`pkg_resources` could not be imported, "
"please reinstall Elpy RPC virtualenv with"
" `M-x elpy-rpc-reinstall-virtualenv`", code=400)
# Get black config from pyproject.toml
line_length = black.DEFAULT_LINE_LENGTH
string_normalization = True
Expand Down
4 changes: 1 addition & 3 deletions test/elpy-rpc-get-virtualenv-path-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@
(elpy-testcase ()
(let ((elpy-rpc-virtualenv-path 'system)
(old-venv pyvenv-virtual-env))
(should-not (string-match "\\(travis/virtualenv\\|.virtualenvs\\)"
(elpy-rpc-get-virtualenv-path)))
(pyvenv-workon "elpy-test-venv")
(should-not (string-match "\\(travis/virtualenv\\|.virtualenvs\\)"
(should-not (string-match "elpy-test-venv"
(elpy-rpc-get-virtualenv-path)))
(if old-venv
(pyvenv-activate old-venv)
Expand Down
47 changes: 47 additions & 0 deletions test/elpy-rpc-reinstall-venv-test.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
;;; -*-coding: utf-8-*-

(unless elpy-test-dont-use-virtualenv
(ert-deftest elpy-rpc-reinstall-virtualenv-should-reinstall ()
(elpy-testcase ()
(let* ((elpy-rpc-virtualenv-path 'default)
(rpc-venv-path (elpy-rpc-get-or-create-virtualenv)))
(mletf* ((messages "")
(y-or-n-p (question)
(setq messages (concat messages question))
t))
(with-temp-file (concat (file-name-as-directory rpc-venv-path)
"additional-file")
(insert "nothing here"))
(elpy-rpc-reinstall-virtualenv)
(should-not (file-exists-p (concat
(file-name-as-directory rpc-venv-path)
"additional-file")))
(should (string= messages "Automatically install the RPC dependencies from PyPI (needed for completion, autoformatting and documentation) ? ")))))))

(unless elpy-test-dont-use-virtualenv
(ert-deftest elpy-rpc-reinstall-virtualenv-should-reinstall-with-global-venv ()
(elpy-testcase ()
(let* ((elpy-rpc-virtualenv-path 'system)
(rpc-venv-path (elpy-rpc-get-or-create-virtualenv)))
(should-error (elpy-rpc-reinstall-virtualenv))))))

(unless elpy-test-dont-use-virtualenv
(ert-deftest elpy-rpc-reinstall-virtualenv-should-reinstall-with-custom-venv ()
(elpy-testcase ()
(let* ((elpy-rpc-virtualenv-path (concat
(file-name-as-directory
(pyvenv-workon-home))
"elpy-test-venv"))
(rpc-venv-path (elpy-rpc-get-or-create-virtualenv)))
(mletf* ((messages "")
(y-or-n-p (question)
(setq messages (concat messages question))
t))
(with-temp-file (concat (file-name-as-directory rpc-venv-path)
"additional-file")
(insert "nothing here"))
(elpy-rpc-reinstall-virtualenv)
(should-not (file-exists-p (concat
(file-name-as-directory rpc-venv-path)
"additional-file")))
(should (= 0 (string-match "Are you sure you want to reinstall the virtualenv in '[^']*' (every manual modifications will be lost) \\? `elpy-rpc-virtualenv-path' was set to '[^']*', but this virtualenv does not exist, create it \\? Automatically install the RPC dependencies from PyPI (needed for completion, autoformatting and documentation) \\? " messages))))))))
20 changes: 20 additions & 0 deletions test/test-helper.el
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@
elpy-dir
(getenv "PYTHONPATH")))
(add-to-list 'process-environment "ELPY_TEST=1"))
;; Travis is using virtualenvs to test specific version of python
;; we need to use it as the system environment
(advice-add 'elpy-rpc-get-virtualenv-path
:around (lambda (fun &rest args)
(if (and (getenv "TRAVIS")
(or (eq elpy-rpc-virtualenv-path 'global)
(eq elpy-rpc-virtualenv-path 'system)))
(expand-file-name
(concat
"~/virtualenv/"
"python"
(getenv "TRAVIS_PYTHON_VERSION")))
(apply fun args))))

(require 'elpy)
;; Travis regularly has some lag for some reason.
(setq elpy-rpc-timeout 10)
Expand Down Expand Up @@ -36,6 +50,12 @@
bindings)
,@body))

;; Print elpy configuration
(mletf* ((y-or-n-p (&rest rest) t))
(elpy-config)
(with-current-buffer "*Elpy Config*"
(message (buffer-substring-no-properties (point-min) (point-max)))))

(defmacro with-temp-dir (name &rest body)
"Create a temporary directory and bind the symbol NAME to the path.
Expand Down

0 comments on commit 4b79ed2

Please sign in to comment.