config-system.el 19.1 KB
Newer Older
1
(require 'dotspacemacs)
2 3 4 5 6
(require 'ht)

(defconst config-system-contrib-directory
  (expand-file-name (concat user-emacs-directory "contrib/"))
  "Spacemacs contribution layers base directory.")
syl20bnr's avatar
syl20bnr committed
7

8 9 10 11
(defconst config-system-private-directory
  (expand-file-name (concat user-emacs-directory "private/"))
  "Spacemacs private layers base directory.")

12
(defvar config-system-config-layers '()
syl20bnr's avatar
syl20bnr committed
13 14 15 16 17 18 19
  "Alist of configuration layers with the form (symbol . plist) where
SYMBOL is the name of the layer and PLIST is a property list with the following
keys:
:contrib    if t then the layer is a contribution layer.
:dir        the absolute path to the base directory of the layer.
:ext-dir    the absolute path to the directory containing the extensions.
")
syl20bnr's avatar
syl20bnr committed
20

21
(defvar config-system-all-packages #s(hash-table size 256 data ())
syl20bnr's avatar
syl20bnr committed
22
  "Hash table of all declared packages in all layers where the key is a package
23 24
symbol and the value is a list of layer symbols responsible for initializing
and configuring the package.")
syl20bnr's avatar
syl20bnr committed
25

26
(defvar config-system-all-pre-extensions #s(hash-table size 128 data ())
syl20bnr's avatar
syl20bnr committed
27
  "Hash table of all declared pre-extensions in all layers where the key is a
28 29
extension symbol and the value is the layer symbols responsible for initializing
and configuring the package.")
syl20bnr's avatar
syl20bnr committed
30

31
(defvar config-system-all-post-extensions #s(hash-table size 128 data ())
syl20bnr's avatar
syl20bnr committed
32
  "Hash table of all declared post-extensions in all layers where the key is a
33 34
extension symbol and the value is the layer symbols responsible for initializing
and configuring the package.")
syl20bnr's avatar
syl20bnr committed
35

36
(defvar config-system-layer-paths #s(hash-table size 128 data ())
37 38
  "Hash table of layers locations where the key is a layer symbol and the value
is its path.")
syl20bnr's avatar
syl20bnr committed
39

40
(defvar config-system-contrib-categories '("usr" "lang")
41 42 43
  "List of strings corresponding to category names. A category is a
sub-directory of the contribution directory.")

44
(defvar config-system-excluded-packages-from-layers '()
45
  "List of all excluded packages declared at the layer level.")
46 47

(defun config-system/package.el-initialize ()
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
  "Initialize package.el"
  (require 'package)
  (unless package--initialized
    (setq package-archives '(("ELPA" . "http://tromey.com/elpa/")
                             ("gnu" . "http://elpa.gnu.org/packages/")
                             ("melpa" . "http://melpa.org/packages/")))
    (package-initialize)
    ;; Emacs 24.3 and above ships with python.el but in some Emacs 24.3.1 packages
    ;; for Ubuntu, python.el seems to be missing.
    ;; This hack adds marmalade repository for this case only.
    (unless (or (package-installed-p 'python) (version< emacs-version "24.3"))
      (add-to-list 'package-archives
                   '("marmalade" . "http://marmalade-repo.org/packages/")))
    (setq warning-minimum-level :error)))

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
(defun config-system/create-layer (name)
  "Ask the user for a configuration layer name and create a layer with this
name in the private layers directory."
  (interactive "sConfiguration layer name: ")
  (let ((layer-dir (config-system//get-private-layer-dir name)))
    (cond
     ((string-equal "" name)
      (message "Cannot create a configuration layer without a name."))
     ((file-exists-p layer-dir)
      (message "Cannot create configuration layer \"%s\", this layer already exists."
               name))
     (t
      (make-directory layer-dir)
      (config-system//copy-template "extensions")
      (config-system//copy-template "packages")
      (message "Configuration layer \"%s\" successfully created." name))
  )))

(defun config-system//get-private-layer-dir (name)
  "Return an absolute path the the private configuration layer with name
NAME."
  (concat config-system-private-directory name "/"))

(defun config-system//copy-template (template)
  "Copy and replace special values of TEMPLATE to LAYER_DIR."
  (let ((src (concat spacemacs-template-directory
                     (format "%s.template" template)))
        (dest (concat (config-system//get-private-layer-dir name)
                      (format "%s.el" template))))
    
    (copy-file src dest)
    (find-file dest)
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward "NAME" nil t)
        (replace-match name t)))
    (save-buffer)))

101
(defun config-system//get-contrib-category-dirs ()
102
  "Return a list of all absolute paths to the contribution categories stored
103
in `config-system-contrib-categories'"
104 105
  (mapcar
   (lambda (d) (expand-file-name
106 107
                (concat config-system-contrib-directory (format "%s/" d))))
   config-system-contrib-categories))
108

109 110
(defun config-system/discover-layers ()
  "Fill the hash table `config-system-layer-paths' where the key is the
111
layer symbol and the value is its path."
112
  (let ((cat-dirs (config-system//get-contrib-category-dirs)))
113
    (mapc 'config-system/discover-layers-in-dir
114
          (append (list config-system-contrib-directory)
115
                  cat-dirs
116 117 118 119 120
                  dotspacemacs-configuration-layer-path
                  ;; load private layers at the end on purpose
                  ;; we asume that the user layers must have the final word
                  ;; on configuration choices.
                  (list config-system-private-directory)))))
121

122 123
(defun config-system/discover-layers-in-dir (dir)
  "Fill the hash table `config-system-layer-paths' where the key is the
124
layer symbol and the value is its path for all layers found in directory DIR.
125
Also fill the list of excluded packages `config-system-excluded-packages-from-layers'
126
declared at the layer level."
127
  (spacemacs/message "Looking for configuration layers in %s" dir)
128
  (ignore-errors
129
    (let ((files (directory-files dir nil nil 'nosort))
130
          (filter-out (append config-system-contrib-categories '("." ".."))))
131
      (dolist (f files)
132 133
        (when (and (file-directory-p (concat dir f))
                   (not (member f filter-out)))
134
          (spacemacs/message "-> Discovered configuration layer: %s" f)
135
          (puthash (intern f) dir config-system-layer-paths))))))
136

137 138 139 140 141 142 143 144 145 146 147 148 149 150
(defun config-system/declare-layer (sym &optional contrib)
  "Declare a layer with SYM name (symbol). If CONTRIB is non nil then the layer
 is a contribution layer."
  (let* ((sym-name (symbol-name sym))
         (base-dir (if contrib
                       (ht-get config-system-layer-paths sym)
                     user-emacs-directory))
         (dir (format "%s%s/" base-dir sym-name))
         (ext-dir (format "%sextensions/" dir)))
    (if (file-exists-p dir)
        (push (cons sym (list :contrib contrib :dir dir :ext-dir ext-dir))
              config-system-config-layers)
      (spacemacs/message "Warning: layer %s does not exist!" sym-name))))

151
(defun config-system/load-layers ()
syl20bnr's avatar
syl20bnr committed
152
  "Load all declared layers."
153 154
  (config-system/load-layer-files '("funcs.el" "config.el"))
  (config-system/read-packages-and-extensions)
155
  (config-system/initialize-extensions config-system-all-pre-extensions t)
156
  (config-system/install-packages)
157
  (spacemacs/append-to-buffer spacemacs-loading-text)
158 159 160
  (config-system/initialize-packages)
  (config-system/initialize-extensions config-system-all-post-extensions)
  (config-system/load-layer-files '("keybindings.el")))
syl20bnr's avatar
syl20bnr committed
161

162
(defun config-system/load-layer-files (files)
syl20bnr's avatar
syl20bnr committed
163
  "Load the files of list FILES from all declared layers."
164
  (dolist (layer (reverse config-system-config-layers))
syl20bnr's avatar
syl20bnr committed
165 166 167
    (let* ((sym (car layer))
           (dir (plist-get (cdr layer) :dir)))
      (dolist (file files)
168 169 170
        (let ((file (concat dir file)))
          (if (file-exists-p file)
              (load file)))))))
syl20bnr's avatar
syl20bnr committed
171

172
(defsubst config-system//add-layer-to-hash (pkg layer hash)
173
  "Add LAYER to the list value stored in HASH with key PKG."
174 175 176
  (let ((list (ht-get hash pkg)))
    (puthash pkg (add-to-list 'list layer t) hash)))

177
(defun config-system//add-excluded-packages (layer)
178 179 180
  "Add excluded packages declared in LAYER."
  (let ((excl-var (intern (format "%s-excluded-packages" (symbol-name layer)))))
    (when (boundp excl-var)
181 182
      (setq config-system-excluded-packages-from-layers
            (append config-system-excluded-packages-from-layers
183 184
                    (eval excl-var))))))

185
(defsubst config-system//filter-out-excluded-packages ()
186 187 188 189
  "Remove excluded packages from the hash tables."
  (mapc (lambda (h)
          (dolist (x (ht-keys (eval h)))
            (when (or (member x dotspacemacs-excluded-packages)
190
                      (member x config-system-excluded-packages-from-layers))
191
              (ht-remove (eval h) x))))
192 193 194
        '(config-system-all-packages
          config-system-all-pre-extensions
          config-system-all-post-extensions)))
195

196
(defun config-system/read-packages-and-extensions ()
syl20bnr's avatar
syl20bnr committed
197 198
  "Load all packages and extensions declared in all layers and fill the
corresponding hash tables:
199 200 201 202
config-system-all-packages
config-system-all-pre-extensions
config-system-all-post-extensions "
  (dolist (layer (reverse config-system-config-layers))
syl20bnr's avatar
syl20bnr committed
203 204 205 206
    (let* ((sym (car layer))
           (dir (plist-get (cdr layer) :dir))
           (pkg-file (concat dir "packages.el"))
           (ext-file (concat dir "extensions.el")))
207
      (progn
syl20bnr's avatar
syl20bnr committed
208
        ;; packages
209
        (when (file-exists-p pkg-file)
210 211
          (load pkg-file)
          (dolist (pkg (eval (intern (format "%s-packages" (symbol-name sym)))))
212
            (config-system//add-excluded-packages sym)
213 214
            (config-system//add-layer-to-hash
             pkg sym config-system-all-packages)))
syl20bnr's avatar
syl20bnr committed
215
        ;; extensions
216
        (when (file-exists-p ext-file)
217
          (load ext-file)
218 219 220 221 222 223 224 225 226 227 228 229 230 231
          (let ((list-pre (intern (format "%s-pre-extensions"
                                          (symbol-name sym))))
                (list-post (intern (format "%s-post-extensions"
                                           (symbol-name sym)))))
            (when (boundp list-pre)
              (dolist (pkg (eval list-pre))
                (config-system//add-excluded-packages sym)
                (config-system//add-layer-to-hash
                 pkg sym config-system-all-pre-extensions)))
            (when (boundp list-post)
              (dolist (pkg (eval list-post))
                (config-system//add-excluded-packages sym)
                (config-system//add-layer-to-hash
                 pkg sym config-system-all-post-extensions))))))))
232
  (config-system//filter-out-excluded-packages)
233
  ;; number of chuncks for the loading screen
234 235 236
  (let ((total (+ (ht-size config-system-all-packages)
                  (ht-size config-system-all-pre-extensions)
                  (ht-size config-system-all-post-extensions))))
237 238
  (setq spacemacs-loading-dots-chunk-threshold
        (/ total spacemacs-loading-dots-chunk-count))))
syl20bnr's avatar
syl20bnr committed
239

240
(defun config-system/install-packages ()
syl20bnr's avatar
syl20bnr committed
241 242
  "Install the packages all the packages if there are not currently installed."
  (interactive)
243
  (let* ((pkg-list (ht-keys config-system-all-packages))
244 245 246 247
         (sorted-pkg-list (mapcar 'intern
                                  (sort (mapcar 'symbol-name pkg-list)
                                        'string<)))
         (not-installed (remove-if 'package-installed-p sorted-pkg-list))
248
         (not-installed-count (length not-installed)))
syl20bnr's avatar
syl20bnr committed
249 250
    ;; installation
    (if not-installed
251
        (progn
252
          (spacemacs/append-to-buffer
syl20bnr's avatar
syl20bnr committed
253 254
           (format "Found %s new package(s) to install...\n"
                   not-installed-count))
255
          (spacemacs/append-to-buffer
syl20bnr's avatar
syl20bnr committed
256
           "--> fetching new package repository indexes...\n")
syl20bnr's avatar
syl20bnr committed
257
          (redisplay)
258 259 260
          (package-refresh-contents)
          (setq installed-count 0)
          (dolist (pkg not-installed)
syl20bnr's avatar
syl20bnr committed
261
            (setq installed-count (1+ installed-count))
syl20bnr's avatar
syl20bnr committed
262 263
            (spacemacs/replace-last-line-of-buffer
             (format "--> installing %s:%s... [%s/%s]"
264
                     (ht-get config-system-all-packages pkg)
syl20bnr's avatar
syl20bnr committed
265 266 267
                     pkg
                     installed-count
                     not-installed-count) t)
268 269
            (when (not (package-installed-p pkg))
              (package-install pkg))
syl20bnr's avatar
syl20bnr committed
270
            (redisplay))
271
          (spacemacs/append-to-buffer "\n")))))
syl20bnr's avatar
syl20bnr committed
272

273
(defun config-system/initialize-packages ()
syl20bnr's avatar
syl20bnr committed
274
  "Initialize all the declared packages."
275
  (ht-each 'config-system/initialize-package config-system-all-packages))
syl20bnr's avatar
syl20bnr committed
276

277
(defun config-system/initialize-package (pkg layers)
278 279 280 281 282
  "Initialize the package PKG from the configuration layers LAYERS."
  (dolist (layer layers)
    (let* ((init-func (intern (format "%s/init-%s" (symbol-name layer) pkg))))
      (spacemacs/loading-animation)
      (if (and (package-installed-p pkg) (fboundp init-func))
283
          (progn  (spacemacs/message "Package: Initializing %s:%s..."
284 285
                           (symbol-name layer) pkg)
                  (funcall init-func))))))
syl20bnr's avatar
syl20bnr committed
286

287 288 289
(defun config-system/initialize-pre-extension (ext layers)
  "Initialize the pre-extensions EXT from configuration layers LAYERS."
  (config-system/initialize-extension ext layers t))
syl20bnr's avatar
syl20bnr committed
290

291 292 293 294 295 296 297 298 299 300
(defun config-system/initialize-extensions (ext-list &optional pre)
  "Initialize all the declared extensions in EXT-LIST hash table.
If PRE is non nil then the extensions are pre-extensions."
  (if pre 
      (ht-each 'config-system/initialize-pre-extension ext-list)
    (ht-each 'config-system/initialize-extension ext-list)))

(defun config-system/initialize-extension (ext layers &optional pre)
  "Initialize the extension EXT from the configuration layers LAYERS.
If PRE is non nil then the extension is a pre-extensions."
301
  (dolist (layer layers)
302
    (let* ((l (assq layer config-system-config-layers))
303 304 305 306
           (ext-dir (plist-get (cdr l) :ext-dir))
           (init-func (intern (format "%s/init-%s" (symbol-name layer) ext))))
      (add-to-list 'load-path (format "%s%s/" ext-dir ext))
      (spacemacs/loading-animation)
307 308
      (spacemacs/message "%s-extension: Initializing %s:%s..."
                         (if pre "Pre" "Post") (symbol-name layer) ext)
309
      (if (fboundp init-func) (funcall init-func)))))
310

311
(defun config-system/initialized-packages-count ()
312
  "Return the number of initialized packages and extensions."
313 314 315
  (+ (ht-size config-system-all-packages)
     (ht-size config-system-all-pre-extensions)
     (ht-size config-system-all-post-extensions)))
316

317
(defun config-system/declare-dotspacemacs-configuration-layers ()
318
  "Declare the configuration layer in order of appearance in list
319
`dotspacemacs-configuration-layers' defined in ~/.spacemacs."
320
  (config-system/discover-layers)
321 322
  (if (boundp 'dotspacemacs-configuration-layers)
      (dolist (layer dotspacemacs-configuration-layers)
323
        (config-system/declare-layer layer t))))
324

325
(defun config-system/get-layer-property (symlayer prop)
326
  "Return the value of the PROPerty for the given SYMLAYER symbol."
327
  (let* ((layer (assq symlayer config-system-config-layers)))
328
         (plist-get (cdr layer) prop)))
329

330
(defun config-system/get-packages-dependencies ()
331 332 333 334 335
  "Returns a hash map where key is a dependency package symbol and value is
a list of all packages which depend on it."
  (let ((result #s(hash-table size 200 data ())))
    (dolist (pkg package-alist)
      (let* ((pkg-sym (car pkg))
336
             (deps (config-system/get-package-dependencies pkg-sym)))
337 338 339 340 341 342 343 344
        (dolist (dep deps)
          (let* ((dep-sym (car dep))
                 (value (ht-get result dep-sym)))
            (puthash dep-sym
                     (if value (add-to-list 'value pkg-sym) (list pkg-sym))
                     result)))))
    result))

345
(defun config-system/get-implicit-packages ()
346
  "Returns a list of all packages in `packages-alist' which are not found
347
in `config-system-all-packages'"
348 349 350
  (let ((imp-pkgs))
    (dolist (pkg package-alist)
      (let ((pkg-sym (car pkg)))
351
        (if (not (ht-contains? config-system-all-packages pkg-sym))
352 353 354
            (add-to-list 'imp-pkgs pkg-sym))))
    imp-pkgs))

355
(defun config-system/get-orphan-packages (implicit-pkgs dependencies)
356
  "Return a list of all orphan packages which are basically meant to be
357
deleted safely."
358 359
  (let ((result '()))
    (dolist (imp-pkg implicit-pkgs)
360
      (if (config-system//is-package-orphan imp-pkg dependencies)
361 362 363
          (add-to-list 'result imp-pkg)))
    result))

364
(defun config-system//is-package-orphan (pkg dependencies)
365
  "Returns not nil if PKG is an orphan package."
366
  (if (ht-contains? config-system-all-packages pkg)
367 368 369 370
      nil
    (if (ht-contains? dependencies pkg)
        (let ((parents (ht-get dependencies pkg)))
          (reduce (lambda (x y) (and x y))
371
                  (mapcar (lambda (p) (config-system//is-package-orphan
372 373 374
                                       p dependencies))
                          parents)
                  :initial-value t))
375
      (not (ht-contains? config-system-all-packages pkg)))))
376

377
(defun config-system/get-package-dependencies (package)
378 379 380 381 382 383
  "Return the dependencies alist for PACKAGE."
  (let ((pkg (assq package package-alist)))
    (cond
     ((version< emacs-version "24.4") (aref (cdr pkg) 1))
     (t (package-desc-reqs (cadr pkg))))))

384
(defun config-system/get-package-version (package)
385
  "Return the version string for PACKAGE."
386 387 388 389 390 391 392
  (let ((pkg (assq package package-alist)))
    (cond
     ((version< emacs-version "24.4")
      (package-version-join (aref (cdr pkg) 0)))
     (t
      (package-version-join (package-desc-version (cadr pkg)))))))

393
(defun config-system/package-delete (package)
394 395 396 397
  "Delete the passed PACKAGE."
  (cond
   ((version< emacs-version "24.4")
    (package-delete (symbol-name package)
398
                    (config-system/get-package-version package)))
399 400
   (t
    (package-delete (cadr (assq package package-alist))))))
401

402
(defun config-system/delete-orphan-packages ()
403 404
  "Delete all the orphan packages."
  (interactive)
405 406 407
  (let* ((dependencies (config-system/get-packages-dependencies))
         (implicit-packages (config-system/get-implicit-packages))
         (orphans (config-system/get-orphan-packages implicit-packages
408 409
                                                  dependencies))
         (orphans-count (length orphans)))
410 411 412
    ;; (message "dependencies: %s" dependencies)
    ;; (message "implicit: %s" implicit-packages)
    ;; (message "orphans: %s" orphans)
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    (if orphans
        (progn
          ;; for the loading dot bar
          (spacemacs/append-to-buffer "OK!\n")
          (spacemacs/append-to-buffer
           (format "Found %s orphan package(s) to delete...\n"
                   orphans-count))
          (setq deleted-count 0)
          (dolist (orphan orphans)
            (setq deleted-count (1+ deleted-count))
            (spacemacs/replace-last-line-of-buffer
             (format "--> deleting %s... [%s/%s]"
                     orphan
                     deleted-count
                     orphans-count) t)
428
            (config-system/package-delete orphan)
429 430
            (redisplay))
          (spacemacs/append-to-buffer "\n"))
431
      (spacemacs/message "No orphan package to delete."))))
432

433
(defun config-system/setup-after-init-hook ()
434 435 436 437 438 439 440 441 442 443
  "Add post init processing."
  (add-hook 'after-init-hook
            (lambda ()
              (spacemacs/append-to-buffer (format "%s\n" spacemacs-loading-done-text))
              ;; from jwiegley
              ;; https://github.com/jwiegley/dot-emacs/blob/master/init.el
              (let ((elapsed (float-time
                              (time-subtract (current-time) emacs-start-time))))
                (spacemacs/append-to-buffer
                 (format "[%s packages loaded in %.3fs]\n"
444
                         (config-system/initialized-packages-count)
445 446
                         elapsed)))
              )))
447 448

(provide 'config-system)