💥 Replace exec-path-from-shell w/ 'bin/doom env'

IMPORTANT: This is a breaking update for Mac users, as your shell
environment will no longer be inherited correctly (with the removal of
exec-path-from-shell). The quick fix is: 'bin/doom env refresh'. Also,
the set-env! autodef now does nothing (and is deprecated), be sure to
remove calls to it in your config.

Smaller changes:
+ This update also adds --no-* switches to doom quickstart
+ Includes general improvements to the documentation of several bin/doom
  commands.
+ Moves doom/reload* commands to core/autoload/config.el
+ doom/reload-project has been removed (it didn't actually do anything)

The breaking change:
This update adds an "envvar file" to Doom Emacs. This file is generated
by `doom env refresh`, populated with variables scraped from your shell
environment (from both non-interactive and interactive sessions). This
file is then (inexpensively) loaded at startup, if it exists.

+ The file is manually generated with `doom env refresh`.
+ It can be regenerated automatically whenever `doom refresh` is run by
  running `doom env enable` (`doom env clear` will reverse this and
  delete the env file).
+ `doom quickstart` will ask if you want to auto-generate this envvar
  file. You won't need it if you're confident Emacs will always be
  started from the correct environment, however.
+ Your env file can be reloaded from a running Emacs session with `M-x
  doom/reload-env`. Note: this won't work if the Emacs session you're
  running it in doesn't have a correct SHELL set. i.e. don't use this to
  create your first env file!

The idea isn't mine -- it's borrowed from Spacemacs -- and was
introduced to me in #1053 by @yurimx. I was impressed with it. Prior to
this, I was unhappy with exec-path-from-shell (no hate to the dev, I
understand its necessity), and 'doom patch-macos' wasn't ideal for mac
users (needed to be reapplied every time you update Emacs). What's more,
many users (even Linux users) had to install exec-path-from-shell
anyway.

This solution suffers from none of their shortcomings. More reliable
than patch-macos, more performant and complete than
exec-path-from-shell, and easily handled by bin/doom.
This commit is contained in:
Henrik Lissner 2019-03-28 00:06:10 -04:00
parent ab616cfb95
commit 2dc52bc9be
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
19 changed files with 266 additions and 136 deletions

95
core/cli/env.el Normal file
View file

@ -0,0 +1,95 @@
;;; core/cli/env.el -*- lexical-binding: t; -*-
(dispatcher! env
(let ((env-file (abbreviate-file-name doom-env-file)))
(pcase (car args)
("refresh"
(doom-reload-env-file 'force))
("enable"
(setenv "DOOMENV" "1")
(print! (green "Enabling auto-reload of %S" env-file))
(doom-reload-env-file 'force)
(print! (green "Done! `doom reload' will now refresh your envvar file.")))
("clear"
(setenv "DOOMENV" nil)
(unless (file-exists-p env-file)
(user-error "%S does not exist to be cleared" env-file))
(delete-file env-file)
(print! (green "Disabled envvar file by deleting %S" env-file)))
(_
(message "No valid subcommand provided. See `doom help env`."))))
"Manages your envvars file.
env [SUBCOMMAND]
Available subcommands:
refresh Create or regenerate your envvar file
enable enable auto-reloading of your envvars file (on `doom refresh`)
clear deletes your envvar file (if it exists) and disables auto-reloading
An envvars file (its location is controlled by the `doom-env-file' variable)
will contain a list of environment variables scraped from your shell environment
and loaded when Doom starts (if it exists). This is necessary when Emacs can't
be launched from your shell environment (e.g. on MacOS or certain app launchers
on Linux).
To generate a file, run `doom env refresh`. If you'd like this file to be
auto-reloaded when running `doom refresh`, run `doom env enable` instead (only
needs to be run once).")
;;
;; Helpers
(defvar doom-ignored-env-vars
'("DBUS_SESSION_BUS_ADDRESS"
"GPG_AGENT_INFO"
"SSH_AGENT_PID"
"SSH_AUTH_SOCK")
"Environment variables to not save in `doom-env-file'.")
;; Borrows heavily from Spacemacs'`spacemacs//init-spacemacs-env'.
(defun doom-reload-env-file (&optional force-p)
"Generates `doom-env-file', if it doesn't exist (or FORCE-P is non-nil)."
(when (or force-p (not (file-exists-p doom-env-file)))
(with-temp-file doom-env-file
(message "%s envvars file at %S"
(if (file-exists-p doom-env-file)
"Regenerating"
"Generating")
(abbreviate-file-name doom-env-file))
(insert
(concat
"# -*- mode: dotenv -*-\n"
"# ---------------------------------------------------------------------------\n"
"# This file was auto-generated by Doom. It contains all environment variables\n"
"# scraped from your default shell (excluding variables blacklisted in\n"
"# doom-ignored-env-vars).\n"
"#\n"
"# It is NOT safe to edit this file. Changes will be overwritten next time\n"
"# that `doom env reload` is executed. Alternatively, create your own env file\n"
"# in your DOOMDIR and load that with `(load-env-vars FILE)`.\n"
"#\n"
"# To auto-regenerate this file when `doom reload` is run, use `doom env enable'\n"
"# or set DOOMENV=1 in your shell environment/config.\n"
"# ---------------------------------------------------------------------------\n\n"))
(let ((env-point (point))
(switches
(cond (IS-WINDOWS '("-c"))
;; Execute twice, once in a non-interactive login shell and
;; once in an interactive shell in order to capture all the
;; init files possible.
((or IS-MAC IS-LINUX) '("-lc" "-ic"))))
(executable (if IS-WINDOWS
"set"
(executable-find "env"))))
(dolist (shell-command-switch switches)
(insert (shell-command-to-string executable)))
;; sort the environment variables
(sort-lines nil env-point (point-max))
;; remove adjacent duplicated lines
(delete-duplicate-lines env-point (point-max) nil t)
;; remove ignored environment variables
(dolist (var doom-ignored-env-vars)
(flush-lines (concat "^" var "=") env-point (point-max)))))))

View file

@ -1,68 +1,87 @@
;;; core/cli/quickstart.el -*- lexical-binding: t; -*-
(dispatcher! (quickstart qs) (doom-quickstart)
(dispatcher! (quickstart qs) (apply #'doom-quickstart args)
"Quickly deploy a private module and Doom.
This deploys a barebones config to ~/.doom.d. The destination can be changed
with the -p option, e.g.
This deploys a barebones config to ~/.doom.d (if it doesn't already exist). The
destination can be changed with the -p option, e.g.
doom -p ~/.config/doom quickstart
This command will refuse to overwrite the private directory if it already
exists.")
Quickstart understands the following switches:
--no-config Don't deploy dummy config to ~/.doom.d
--no-install Don't auto-install packages
--no-env Don't generate an envvars file (see `doom help env`)
--no-fonts Don't install (or prompt to install) all-the-icons fonts
This command is idempotent and is safe to reuse.")
;;
;; Library
(defun doom-quickstart ()
(defun doom-quickstart (&rest args)
"Quickly deploy a private module and Doom.
This deploys a barebones config to `doom-private-dir', installs all missing
packages and regenerates the autoloads file."
;; Create `doom-private-dir'
(let ((short-private-dir (abbreviate-file-name doom-private-dir)))
(if (file-directory-p doom-private-dir)
(print! (yellow "%s directory already exists. Skipping.") short-private-dir)
(print! "Creating %s" short-private-dir)
(make-directory doom-private-dir t)
(print! (green "Done!"))
;; Create init.el, config.el & packages.el
(dolist (file (list (cons "init.el"
(lambda ()
(insert-file-contents (expand-file-name "init.example.el" doom-emacs-dir))))
(cons "config.el"
(lambda ()
(insert (format ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n"
short-private-dir)
";; Place your private configuration here\n")))
(cons "packages.el"
(lambda ()
(insert (format ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n"
short-private-dir)
";;; Examples:\n"
";; (package! some-package)\n"
";; (package! another-package :recipe (:fetcher github :repo \"username/repo\"))\n"
";; (package! builtin-package :disable t)\n")))))
(cl-destructuring-bind (path . fn) file
(print! "Creating %s%s" short-private-dir path)
(with-temp-file (expand-file-name path doom-private-dir)
(funcall fn))
(print! (green "Done!"))))))
(if (member "--no-config" args)
(print! (yellow "Not copying private config template, as requested"))
(if (file-directory-p doom-private-dir)
(print! (yellow "%s directory already exists. Skipping.") short-private-dir)
(print! "Creating %s" short-private-dir)
(make-directory doom-private-dir t)
(print! (green "Done!"))
;; Create init.el, config.el & packages.el
(dolist (file (list (cons "init.el"
(lambda ()
(insert-file-contents (expand-file-name "init.example.el" doom-emacs-dir))))
(cons "config.el"
(lambda ()
(insert (format ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n"
short-private-dir)
";; Place your private configuration here\n")))
(cons "packages.el"
(lambda ()
(insert (format ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n"
short-private-dir)
";;; Examples:\n"
";; (package! some-package)\n"
";; (package! another-package :recipe (:fetcher github :repo \"username/repo\"))\n"
";; (package! builtin-package :disable t)\n")))))
(cl-destructuring-bind (path . fn) file
(print! "Creating %s%s" short-private-dir path)
(with-temp-file (expand-file-name path doom-private-dir)
(funcall fn))
(print! (green "Done!")))))))
;; Ask if Emacs.app should be patched
(when IS-MAC
(message "MacOS detected")
(condition-case e
(doom-patch-macos nil (doom--find-emacsapp-path))
(user-error (message "%s" (error-message-string e)))))
(if (member "--no-env" args)
(print! (yellow "Not generating envvars file, as requested"))
(when (or (file-exists-p doom-env-file)
(y-or-n-p "Would you like to generate an envvars file (see `doom help env` for details)?"))
(setenv "DOOMENV" "1")
(doom-reload-env-file 'force-p)))
;; Install Doom packages
(print! "Installing plugins")
(doom-packages-install doom-auto-accept)
(if (member "--no-install" args)
(print! (yellow "Not installing plugins, as requested"))
(print! "Installing plugins")
(doom-packages-install doom-auto-accept))
(print! "Regenerating autoloads files")
(doom-reload-autoloads nil 'force-p)
(when (y-or-n-p "Download and install all-the-icon's fonts?")
(require 'all-the-icons)
(all-the-icons-install-fonts 'yes))
(if (member "--no-fonts" args)
(print! (yellow "Not installing fonts, as requested"))
(when (y-or-n-p "Download and install all-the-icon's fonts?")
(require 'all-the-icons)
(all-the-icons-install-fonts 'yes)))
(print! (bold (green "\nFinished! Doom is ready to go!\n")))
(with-temp-buffer
(doom-template-insert "QUICKSTART_INTRO")