Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
8.9 KiB

  1. ;;; async-bytecomp.el --- Compile elisp files asynchronously -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2014-2016 Free Software Foundation, Inc.
  3. ;; Authors: John Wiegley <jwiegley@gmail.com>
  4. ;; Thierry Volpiatto <thierry.volpiatto@gmail.com>
  5. ;; Keywords: dired async byte-compile
  6. ;; X-URL: https://github.com/jwiegley/dired-async
  7. ;; This program is free software; you can redistribute it and/or
  8. ;; modify it under the terms of the GNU General Public License as
  9. ;; published by the Free Software Foundation; either version 2, or (at
  10. ;; your option) any later version.
  11. ;; This program is distributed in the hope that it will be useful, but
  12. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;; General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  17. ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  18. ;; Boston, MA 02111-1307, USA.
  19. ;;; Commentary:
  20. ;;
  21. ;; This package provide the `async-byte-recompile-directory' function
  22. ;; which allows, as the name says to recompile a directory outside of
  23. ;; your running emacs.
  24. ;; The benefit is your files will be compiled in a clean environment without
  25. ;; the old *.el files loaded.
  26. ;; Among other things, this fix a bug in package.el which recompile
  27. ;; the new files in the current environment with the old files loaded, creating
  28. ;; errors in most packages after upgrades.
  29. ;;
  30. ;; NB: This package is advicing the function `package--compile'.
  31. ;;; Code:
  32. (require 'cl-lib)
  33. (require 'async)
  34. (defcustom async-bytecomp-allowed-packages
  35. ;; FIXME: Arguably the default should be `all', but currently
  36. ;; this minor mode is silently/forcefully enabled by Helm and Magit to ensure
  37. ;; they get compiled asynchronously, so this conservative default value is
  38. ;; here to make sure that the mode can be enabled without the user's
  39. ;; explicit consent.
  40. '(async helm helm-core helm-ls-git helm-ls-hg magit)
  41. "Packages in this list will be compiled asynchronously by `package--compile'.
  42. All the dependencies of these packages will be compiled async too,
  43. so no need to add dependencies to this list.
  44. The value of this variable can also be the symbol `all', in this case
  45. all packages are always compiled asynchronously."
  46. :group 'async
  47. :type '(choice
  48. (const :tag "All packages" all)
  49. (repeat symbol)))
  50. (defvar async-byte-compile-log-file
  51. (concat user-emacs-directory "async-bytecomp.log"))
  52. ;;;###autoload
  53. (defun async-byte-recompile-directory (directory &optional quiet)
  54. "Compile all *.el files in DIRECTORY asynchronously.
  55. All *.elc files are systematically deleted before proceeding."
  56. (cl-loop with dir = (directory-files directory t "\\.elc\\'")
  57. unless dir return nil
  58. for f in dir
  59. when (file-exists-p f) do (delete-file f))
  60. ;; Ensure async is reloaded when async.elc is deleted.
  61. ;; This happen when recompiling its own directory.
  62. (load "async")
  63. (let ((call-back
  64. (lambda (&optional _ignore)
  65. (if (file-exists-p async-byte-compile-log-file)
  66. (let ((buf (get-buffer-create byte-compile-log-buffer))
  67. (n 0))
  68. (with-current-buffer buf
  69. (goto-char (point-max))
  70. (let ((inhibit-read-only t))
  71. (insert-file-contents async-byte-compile-log-file)
  72. (compilation-mode))
  73. (display-buffer buf)
  74. (delete-file async-byte-compile-log-file)
  75. (unless quiet
  76. (save-excursion
  77. (goto-char (point-min))
  78. (while (re-search-forward "^.*:Error:" nil t)
  79. (cl-incf n)))
  80. (if (> n 0)
  81. (message "Failed to compile %d files in directory `%s'" n directory)
  82. (message "Directory `%s' compiled asynchronously with warnings" directory)))))
  83. (unless quiet
  84. (message "Directory `%s' compiled asynchronously with success" directory))))))
  85. (async-start
  86. `(lambda ()
  87. (require 'bytecomp)
  88. ,(async-inject-variables "\\`\\(load-path\\)\\|byte\\'")
  89. (let ((default-directory (file-name-as-directory ,directory))
  90. error-data)
  91. (add-to-list 'load-path default-directory)
  92. (byte-recompile-directory ,directory 0 t)
  93. (when (get-buffer byte-compile-log-buffer)
  94. (setq error-data (with-current-buffer byte-compile-log-buffer
  95. (buffer-substring-no-properties (point-min) (point-max))))
  96. (unless (string= error-data "")
  97. (with-temp-file ,async-byte-compile-log-file
  98. (erase-buffer)
  99. (insert error-data))))))
  100. call-back)
  101. (unless quiet (message "Started compiling asynchronously directory %s" directory))))
  102. (defvar package-archive-contents)
  103. (defvar package-alist)
  104. (declare-function package-desc-reqs "package.el" (cl-x))
  105. (defun async-bytecomp--get-package-deps (pkgs)
  106. ;; Same as `package--get-deps' but parse instead `package-archive-contents'
  107. ;; because PKG is not already installed and not present in `package-alist'.
  108. ;; However fallback to `package-alist' in case PKG no more present
  109. ;; in `package-archive-contents' due to modification to `package-archives'.
  110. ;; See issue #58.
  111. (let ((seen '()))
  112. (while pkgs
  113. (let ((pkg (pop pkgs)))
  114. (unless (memq pkg seen)
  115. (let ((pkg-desc (cadr (or (assq pkg package-archive-contents)
  116. (assq pkg package-alist)))))
  117. (when pkg-desc
  118. (push pkg seen)
  119. (setq pkgs (append (mapcar #'car (package-desc-reqs pkg-desc))
  120. pkgs)))))))
  121. seen))
  122. (defadvice package--compile (around byte-compile-async)
  123. (let ((cur-package (package-desc-name pkg-desc))
  124. (pkg-dir (package-desc-dir pkg-desc)))
  125. (if (or (member async-bytecomp-allowed-packages '(t all (all)))
  126. (memq cur-package (async-bytecomp--get-package-deps
  127. async-bytecomp-allowed-packages)))
  128. (progn
  129. (when (eq cur-package 'async)
  130. (fmakunbound 'async-byte-recompile-directory))
  131. ;; Add to `load-path' the latest version of async and
  132. ;; reload it when reinstalling async.
  133. (when (string= cur-package "async")
  134. (cl-pushnew pkg-dir load-path)
  135. (load "async-bytecomp"))
  136. ;; `async-byte-recompile-directory' will add directory
  137. ;; as needed to `load-path'.
  138. (async-byte-recompile-directory (package-desc-dir pkg-desc) t))
  139. ad-do-it)))
  140. ;;;###autoload
  141. (define-minor-mode async-bytecomp-package-mode
  142. "Byte compile asynchronously packages installed with package.el.
  143. Async compilation of packages can be controlled by
  144. `async-bytecomp-allowed-packages'."
  145. :group 'async
  146. :global t
  147. (if async-bytecomp-package-mode
  148. (ad-activate 'package--compile)
  149. (ad-deactivate 'package--compile)))
  150. ;;;###autoload
  151. (defun async-byte-compile-file (file)
  152. "Byte compile Lisp code FILE asynchronously.
  153. Same as `byte-compile-file' but asynchronous."
  154. (interactive "fFile: ")
  155. (let ((call-back
  156. (lambda (&optional _ignore)
  157. (let ((bn (file-name-nondirectory file)))
  158. (if (file-exists-p async-byte-compile-log-file)
  159. (let ((buf (get-buffer-create byte-compile-log-buffer))
  160. start)
  161. (with-current-buffer buf
  162. (goto-char (setq start (point-max)))
  163. (let ((inhibit-read-only t))
  164. (insert-file-contents async-byte-compile-log-file)
  165. (compilation-mode))
  166. (display-buffer buf)
  167. (delete-file async-byte-compile-log-file)
  168. (save-excursion
  169. (goto-char start)
  170. (if (re-search-forward "^.*:Error:" nil t)
  171. (message "Failed to compile `%s'" bn)
  172. (message "`%s' compiled asynchronously with warnings" bn)))))
  173. (message "`%s' compiled asynchronously with success" bn))))))
  174. (async-start
  175. `(lambda ()
  176. (require 'bytecomp)
  177. ,(async-inject-variables "\\`load-path\\'")
  178. (let ((default-directory ,(file-name-directory file)))
  179. (add-to-list 'load-path default-directory)
  180. (byte-compile-file ,file)
  181. (when (get-buffer byte-compile-log-buffer)
  182. (setq error-data (with-current-buffer byte-compile-log-buffer
  183. (buffer-substring-no-properties (point-min) (point-max))))
  184. (unless (string= error-data "")
  185. (with-temp-file ,async-byte-compile-log-file
  186. (erase-buffer)
  187. (insert error-data))))))
  188. call-back)))
  189. (provide 'async-bytecomp)
  190. ;;; async-bytecomp.el ends here