branch: elpa/exec-path-from-shell
commit ad68d367475e698cee043df3af160d85d5fa29e4
Author: Steve Purcell <[email protected]>
Commit: Steve Purcell <[email protected]>
Instead of checking startup files, just warn if execution is slow
See #100
---
README.md | 40 +++++++++++++++++++++++++++++-----------
exec-path-from-shell.el | 41 ++++++++++++++++++-----------------------
2 files changed, 47 insertions(+), 34 deletions(-)
diff --git a/README.md b/README.md
index 9f7acccafc..a50fc66cc2 100644
--- a/README.md
+++ b/README.md
@@ -97,15 +97,21 @@ but instead do this:
export PATH=/usr/local/bin:/usr/bin:/bin
```
-You should also set your environment variables so that they are
-available to both interactive and non-interactive shells. In practical
-terms, for most people this means setting them in `~/.profile`,
-`~/.bash_profile`, `~/.zshenv` instead of `~/.bashrc` and
-`~/.zshrc`. By default, `exec-path-from-shell` checks for this
-mistake, at the cost of some execution time. If your config files are
-set up properly, you can set `exec-path-from-shell-arguments`
-appropriately (often to `nil`) before calling
-`exec-path-from-shell-initialize` to avoid this overhead.
+To be safe, `exec-path-from-shell` starts an interactive (and login)
+shell by default, but this can be much slower than necessary.
+Interactive shells often have fancy features enabled that are only
+helpful when one interacts directly with the shell, and this can
+frequently cause startup time to exceed 750ms. This can be avoided:
+
+* Follow best practice by setting your environment variables so that
+ they are available to both interactive and non-interactive shells.
+ In practical terms, for most people this means setting them in
+ `~/.profile`, `~/.bash_profile`, `~/.zshenv` instead of `~/.bashrc`
+ and `~/.zshrc`.
+* Once a non-interactive shell sets your environment variables
+ correctly, adjust `exec-path-from-shell-arguments` appropriately
+ (often to `nil`) before calling `exec-path-from-shell-initialize` so
+ that it will start a non-interactive shell.
To learn more about how popular shells load start-up files, read
[this helpful
article](https://blog.flowblok.id.au/2013-02/shell-startup-scripts.html).
@@ -113,8 +119,20 @@ To learn more about how popular shells load start-up
files, read
Making `exec-path-from-shell` faster
------------------------------------
-* Invoking the shell has a non-trivial overhead. Don't call
`exec-path-from-shell-copy-env` repeatedly, since each invocation starts a
shell. Instead, set `exec-path-from-shell-variables` to the full list of vars
you want, and call `exec-path-from-shell-initialize` once.
-* Non-interactive shells start up faster. Follow the steps in the section
above so that you can run your shell without `-i` and still get the right
environment variable settings. When `"-i"` is then removed from
`exec-path-from-shell-arguments`, this package becomes more efficient.
+If evaluation takes more than
+`exec-path-from-shell-warn-duration-millis` (500ms by default) then
+`exec-path-from-shell` will print a warning.
+
+* Non-interactive shells start up faster. Follow the steps in the
+ section above so that you can run your shell without `-i` and still
+ get the right environment variable settings. When `"-i"` is then
+ removed from `exec-path-from-shell-arguments`, this package becomes
+ more efficient.
+* Invoking the shell has a non-trivial overhead in any case. Don't
+ call `exec-path-from-shell-copy-env` repeatedly, since each
+ invocation starts a shell. Instead, set
+ `exec-path-from-shell-variables` to the full list of vars you want,
+ and call `exec-path-from-shell-initialize` once.
Further help
------------
diff --git a/exec-path-from-shell.el b/exec-path-from-shell.el
index 6caa746ae6..87a9e88f69 100644
--- a/exec-path-from-shell.el
+++ b/exec-path-from-shell.el
@@ -87,12 +87,9 @@
:type '(repeat (string :tag "Environment variable"))
:group 'exec-path-from-shell)
-(defcustom exec-path-from-shell-check-startup-files t
- "If non-nil, warn if variables are being set in the wrong shell startup
files.
-Environment variables should be set in .profile or .zshenv rather than
-.bashrc or .zshrc."
- :type 'boolean
- :group 'exec-path-from-shell)
+(defcustom exec-path-from-shell-warn-duration-millis 500
+ "Print a warning message if shell execution takes longer than this many
milliseconds."
+ :type 'integer)
(defcustom exec-path-from-shell-shell-name nil
"If non-nil, use this shell executable.
@@ -141,6 +138,19 @@ The default value denotes an interactive login shell."
"Return non-nil iff SHELL supports the standard ${VAR-default} syntax."
(not (string-match "\\(fish\\|t?csh\\)$" shell)))
+(defmacro exec-path-from-shell--warn-duration (&rest body)
+ "Evaluate BODY and warn if execution duration exceeds a time limit.
+The limit is given by `exec-path-from-shell-warn-duration-millis'."
+ (let ((start-time (gensym))
+ (duration-millis (gensym)))
+ `(let ((,start-time (current-time)))
+ (prog1
+ (progn ,@body)
+ (let ((,duration-millis (* 1000.0 (float-time (time-subtract
(current-time) ,start-time)))))
+ (if (> ,duration-millis exec-path-from-shell-warn-duration-millis)
+ (message "Warning: exec-path-from-shell execution took %dms.
See the README for tips on reducing this." ,duration-millis)
+ (exec-path-from-shell--debug "Shell execution took %dms"
,duration-millis)))))))
+
(defun exec-path-from-shell-printf (str &optional args)
"Return the result of printing STR in the user's shell.
@@ -166,7 +176,8 @@ shell-escaped, so they may contain $ etc."
(concat "sh -c " (shell-quote-argument
printf-command)))))))
(with-temp-buffer
(exec-path-from-shell--debug "Invoking shell %s with args %S" shell
shell-args)
- (let ((exit-code (apply #'call-process shell nil t nil shell-args)))
+ (let ((exit-code (exec-path-from-shell--warn-duration
+ (apply #'call-process shell nil t nil shell-args))))
(exec-path-from-shell--debug "Shell printed: %S" (buffer-string))
(unless (zerop exit-code)
(error "Non-zero exit code from shell %s invoked with args %S.
Output was:\n%S"
@@ -225,26 +236,10 @@ As a special case, if the variable is $PATH, then the
variables
The result is an alist, as described by
`exec-path-from-shell-getenvs'."
(let ((pairs (exec-path-from-shell-getenvs names)))
- (when exec-path-from-shell-check-startup-files
- (exec-path-from-shell--maybe-warn-about-startup-files pairs))
(mapc (lambda (pair)
(exec-path-from-shell-setenv (car pair) (cdr pair)))
pairs)))
-(defun exec-path-from-shell--maybe-warn-about-startup-files (pairs)
- "Warn the user if the value of PAIRS seems to depend on interactive shell
startup files."
- (let ((without-minus-i (remove "-i" exec-path-from-shell-arguments)))
- ;; If the user is using "-i", we warn them if it is necessary.
- (unless (eq exec-path-from-shell-arguments without-minus-i)
- (let* ((exec-path-from-shell-arguments without-minus-i)
- (alt-pairs (exec-path-from-shell-getenvs (mapcar 'car pairs)))
- different)
- (dolist (pair pairs)
- (unless (equal pair (assoc (car pair) alt-pairs))
- (push (car pair) different)))
- (when different
- (message "You appear to be setting environment variables %S in your
.bashrc or .zshrc: those files are only read by interactive shells, so you
should instead set environment variables in startup files like .profile,
.bash_profile or .zshenv. Refer to your shell's man page for more info.
Customize `exec-path-from-shell-arguments' to remove \"-i\" when done, or
disable `exec-path-from-shell-check-startup-files' to disable this message."
different))))))
-
;;;###autoload
(defun exec-path-from-shell-copy-env (name)
"Set the environment variable $NAME from the user's shell.