core: Add support for lazy installed layer.

New function configuration-layer/lazy-install to add support for
lazy installation of layers based on auto-mode-alist emacs mechanism.

This is essentially the Prelude feature but translated at the layer
level for Spacemacs.

New dotspacemacs variable to enable this feature:
dotspacemacs-enable-lazy-installation
For now this variable is set to nil by default, it will be put to t when
the feature is stable.

POC with elixir layer.
This commit is contained in:
syl20bnr 2016-01-31 00:05:27 -05:00
parent 5a061b3813
commit 45def1ec60
4 changed files with 114 additions and 37 deletions

View file

@ -62,10 +62,19 @@
(dir :initarg :dir
:type string
:documentation "Absolute path to the layer directory.")
(packages :initarg :packages
:initform nil
:type list
:documentation "List of package names declared in this layer.")
(variables :initarg :variables
:initform nil
:type list
:documentation "A list of variable-value pairs.")
(lazy-install :initarg :lazy-install
:initform nil
:type boolean
:documentation
"If non-nil then the layer needs to be installed")
(disabled :initarg :disabled-for
:initform nil
:type list
@ -104,6 +113,11 @@
:type boolean
:documentation
"If non-nil then this package is not installed by Spacemacs.")
(lazy-install :initarg :lazy-install
:initform nil
:type boolean
:documentation
"If non-nil then the package needs to be installed")
(protected :initarg :protected
:initform nil
:type boolean
@ -249,7 +263,10 @@ refreshed during the current session."
(setq configuration-layer--used-distant-packages
(configuration-layer//get-distant-used-packages
configuration-layer--packages))
(configuration-layer//load-packages configuration-layer--packages)
(configuration-layer//install-packages
(configuration-layer/filter-objects configuration-layer--used-distant-packages
(lambda (x) (not (oref x :lazy-install)))))
(configuration-layer//configure-packages configuration-layer--packages)
(when dotspacemacs-delete-orphan-packages
(configuration-layer/delete-orphan-packages configuration-layer--packages)))
@ -354,6 +371,7 @@ Properties that can be copied are `:location', `:step' and `:excluded'."
(dolist (layer layers)
(let* ((name (oref layer :name))
(dir (oref layer :dir))
(lazy-install (oref layer :lazy-install))
(packages-file (concat dir "packages.el")))
;; packages
(when (file-exists-p packages-file)
@ -371,10 +389,12 @@ Properties that can be copied are `:location', `:step' and `:excluded'."
(post-init-func (intern (format "%S/post-init-%S"
name pkg-name)))
(obj (object-assoc pkg-name :name result)))
(cl-pushnew pkg-name (oref layer :packages))
(if obj
(setq obj (configuration-layer/make-package pkg obj))
(setq obj (configuration-layer/make-package pkg))
(push obj result))
(oset obj :lazy-install lazy-install)
(when (fboundp init-func)
;; last owner wins over the previous one,
;; still warn about mutliple owners
@ -412,6 +432,27 @@ Properties that can be copied are `:location', `:step' and `:excluded'."
(sort packages (lambda (x y) (string< (symbol-name (oref x :name))
(symbol-name (oref y :name))))))
(defun configuration-layer/lazy-install (layer-name &rest props)
"Configure auto-installation of layer with name LAYER-NAME."
(declare (indent 1))
(when dotspacemacs-enable-lazy-installation
(let ((layer (object-assoc layer-name :name configuration-layer--layers))
(extensions (spacemacs/mplist-get props :extensions)))
(oset layer :lazy-install t)
(dolist (x extensions)
(let ((ext (car x))
(mode (cadr x)))
(add-to-list
'auto-mode-alist
`(,ext . (lambda ()
(configuration-layer//auto-mode
',layer-name ',mode)))))))))
(defun configuration-layer//auto-mode (layer-name mode)
"Auto mode support of lazily installed layers."
(when (configuration-layer//lazy-install-packages layer-name)
(funcall mode)))
(defun configuration-layer/filter-objects (objects ffunc)
"Return a filtered OBJECTS list where each element satisfies FFUNC."
(reverse (cl-reduce (lambda (acc x)
@ -613,11 +654,21 @@ path."
"Return non-nil if NAME is the name of a used layer."
(not (null (object-assoc name :name configuration-layer--layers))))
(defun configuration-layer/layer-lazy-installp (name)
"Return non-nil if NAME is the name of a layer to be lazily installed."
(let ((obj (object-assoc name :name configuration-layer--layers)))
(when obj (oref obj :lazy-install))))
(defun configuration-layer/package-usedp (name)
"Return non-nil if NAME is the name of a used package."
(let ((obj (object-assoc name :name configuration-layer--packages)))
(when (and obj (not (oref obj :excluded))) (oref obj :owner))))
(defun configuration-layer/package-lazy-installp (name)
"Return non-nil if NAME is the name of a package to be lazily installed."
(let ((obj (object-assoc name :name configuration-layer--packages)))
(when obj (oref obj :lazy-install))))
(defun configuration-layer//configure-layers (layers)
"Configure LAYERS."
;; FIFO loading of layers, this allow the user to put her layers at the
@ -644,15 +695,6 @@ path."
(configuration-layer//sort-packages (configuration-layer/get-packages
layers2 t))))
(defun configuration-layer//load-packages (packages)
"Load PACKAGES."
;; number of chuncks for the loading screen
(setq spacemacs-loading-dots-chunk-threshold
(/ (configuration-layer/configured-packages-count)
spacemacs-loading-dots-chunk-count))
(configuration-layer//install-packages packages)
(configuration-layer//configure-packages packages))
(defun configuration-layer//load-layers-files (layers files)
"Load the files of list FILES for all passed LAYERS."
(dolist (layer layers)
@ -668,14 +710,57 @@ path."
"Return the number of configured packages."
(length configuration-layer--packages))
(defun configuration-layer//install-package (pkg)
"Unconditionally install the package PKG."
(let* ((layer (when pkg (oref pkg :owner)))
(location (when pkg (oref pkg :location))))
(spacemacs-buffer/replace-last-line
(format "--> installing %s: %s%s... [%s/%s]"
(if layer "package" "dependency")
pkg-name (if layer (format "@%S" layer) "")
installed-count noinst-count) t)
(unless (package-installed-p pkg-name)
(condition-case err
(cond
((or (null pkg) (eq 'elpa location))
(configuration-layer//install-from-elpa pkg-name)
(oset pkg :lazy-install nil))
((and (listp location) (eq 'recipe (car location)))
(configuration-layer//install-from-recipe pkg)
(oset pkg :lazy-install nil))
(t (spacemacs-buffer/warning "Cannot install package %S."
pkg-name)))
('error
(configuration-layer//increment-error-count)
(spacemacs-buffer/append
(format (concat "\nAn error occurred while installing %s "
"(error: %s)\n") pkg-name err)))))))
(defun configuration-layer//lazy-install-packages (layer-name)
"Install packages of a lazily installed layer.
Returns non-nil if the packages have been installed."
(let* ((layer (object-assoc layer-name :name configuration-layer--layers))
(packages (delq nil (mapcar (lambda (x)
(object-assoc
x :name configuration-layer--packages))
(oref layer :packages))))
(pkg-count (length packages)))
(when (and (oref layer :lazy-install)
(yes-or-no-p (format
(concat "Support for %s requires installation of "
"%s package(s), do you want to install?")
layer-name pkg-count)))
(configuration-layer//install-packages packages)
(configuration-layer//configure-packages packages)
(oset layer :lazy-install nil))
(not (oref layer :lazy-install))))
(defun configuration-layer//install-packages (packages)
"Install PACKAGES."
"Install PACKAGES which are not lazy installed."
(interactive)
(let* ((noinst-pkg-names
(configuration-layer//get-uninstalled-packages
(mapcar 'car
(object-assoc-list
:name configuration-layer--used-distant-packages))))
(mapcar 'car (object-assoc-list :name packages))))
(noinst-count (length noinst-pkg-names))
installed-count)
;; installation
@ -687,28 +772,8 @@ path."
(setq installed-count 0)
(dolist (pkg-name noinst-pkg-names)
(setq installed-count (1+ installed-count))
(let* ((pkg (object-assoc pkg-name :name configuration-layer--packages))
(layer (when pkg (oref pkg :owner)))
(location (when pkg (oref pkg :location))))
(spacemacs-buffer/replace-last-line
(format "--> installing %s: %s%s... [%s/%s]"
(if layer "package" "dependency")
pkg-name (if layer (format "@%S" layer) "")
installed-count noinst-count) t)
(unless (package-installed-p pkg-name)
(condition-case err
(cond
((or (null pkg) (eq 'elpa location))
(configuration-layer//install-from-elpa pkg-name))
((and (listp location) (eq 'recipe (car location)))
(configuration-layer//install-from-recipe pkg))
(t (spacemacs-buffer/warning "Cannot install package %S."
pkg-name)))
('error
(configuration-layer//increment-error-count)
(spacemacs-buffer/append
(format (concat "\nAn error occurred while installing %s "
"(error: %s)\n") pkg-name err))))))
(configuration-layer//install-package
(object-assoc pkg-name :name configuration-layer--packages))
(spacemacs//redisplay))
(spacemacs-buffer/append "\n"))))
@ -739,7 +804,7 @@ path."
(defun configuration-layer//filter-packages-with-deps
(pkg-names filter &optional use-archive)
"Return a filtered PACKAGES list where each elements satisfies FILTER."
"Return a filtered PKG-NAMES list where each elements satisfies FILTER."
(when pkg-names
(let (result)
(dolist (pkg-name pkg-names)
@ -803,6 +868,9 @@ path."
(defun configuration-layer//configure-packages (packages)
"Configure all passed PACKAGES honoring the steps order."
(setq spacemacs-loading-dots-chunk-threshold
(/ (configuration-layer/configured-packages-count)
spacemacs-loading-dots-chunk-count))
(configuration-layer//configure-packages-2
(configuration-layer/filter-objects
packages (lambda (x) (eq 'pre (oref x :step)))))

View file

@ -65,6 +65,9 @@ environment, otherwise it is strongly recommended to let it set to t.")
"List of additional paths where to look for configuration layers.
Paths must have a trailing slash (ie. `~/.mycontribs/')")
(defvar dotspacemacs-enable-lazy-installation nil
"If non-nil layers with lazy install support are lazy installed.")
(defvar dotspacemacs-additional-packages '()
"List of additional packages that will be installed wihout being
wrapped in a layer. If you need some configuration for these

View file

@ -11,6 +11,9 @@ values."
;; `+distribution'. For now available distributions are `spacemacs-base'
;; or `spacemacs'. (default 'spacemacs)
dotspacemacs-distribution 'spacemacs
;; If non-nil layers with lazy install support are lazy installed.
;; (default nil)
dotspacemacs-enable-lazy-installation nil
;; List of additional paths where to look for configuration layers.
;; Paths must have a trailing slash (i.e. `~/.mycontribs/')
dotspacemacs-configuration-layer-path '()

View file

@ -11,5 +11,8 @@
;; Variables
(configuration-layer/lazy-install 'elixir
:extensions '("\\.\\(ex\\|exs|elixir\\)\\'" elixir-mode))
(spacemacs|defvar-company-backends elixir-mode)
(spacemacs|defvar-company-backends alchemist-iex-mode)