-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbyte-code-cache.el
357 lines (270 loc) · 11.7 KB
/
byte-code-cache.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
;;; byte-code-cache.el --- Compile files as they're used
;; $Id: byte-code-cache.el 17 2007-11-16 12:13:42Z quotemstr $
;; This file is in the public domain.
;;; Commentary:
;; Description:
;; Automatically cache byte-compiled versions of .el files as they're
;; loaded, automatically recompiling them as necessary.
;; Install:
;; Load this file as early as possible in ~/.emacs (after setting
;; variables, and after custom, if you used custom to set them.)
;; Anything loaded after it will be automatically byte-compiled.
;; This file cannot byte-compile itself, so you should do that
;; separately.
;; Author: Daniel Colascione <[email protected]>
;;; Change Log:
;; Fri Nov 16 00:33:28 EST 2007 - Made more robust against
;; pathological recursive invocations.
;; Thu Jul 08 15:52:00 PST 2010 - Made work even with complex
;; advise definitions.
;;; Code:
(require 'advice) ; for documentation magic
(defgroup byte-code-cache nil
"Instead of separately byte-compiling everything, this package
intercepts LOADs and byte-compiles files on the fly."
:group 'internal)
(defcustom bcc-cache-directory "~/.emacs.d/byte-cache"
"The directory in which we store cached byte-compiled files"
:type 'directory
:group 'byte-code-cache)
(defcustom bcc-enabled t
"Whether to use the byte-code cache when loading"
:type 'boolean
:group 'byte-code-cache)
(defcustom bcc-blacklist '("/\\.recentf$" "/history$")
"List of regular expressions matching files that should
not be cached. Files that are modified every time Emacs
is run are good candidates for this list."
:type '(repeat regexp)
:group 'byte-code-cache)
(unless (boundp 'load-source-file-function)
(error "byte-code-cache requires LOAD-SOURCE-FILE-FUNCTION"))
(defvar bcc-old-load-source-file-function
load-source-file-function
"Saved LOAD-SOURCE-FILE-FUNCTION")
(defvar bcc-regenerate-toplevel t
"t unless we're inside BCC-REGENERATE-CACHE")
(defvar bcc-loaded-fake-cache-entry nil
"Internal. Fake cache entries set this to t to indicate that
BCC-LOAD-SOURCE-FILE should load the original file.")
(defvar bcc-loaded nil
"List of files loaded with bcc-load-source-file. List of conses.
car is origname, cdr is cachename.")
(defconst bcc-compiled-doc-string 4
"From lisp.h")
(defmacro bcc-assert (expr)
"Like ASSERT, but doesn't depend on CL"
`(or ,expr (signal 'bcc-assert-failed (list ',expr))))
(defun bcc-delete-file-noerror (filename)
"Delete file FILENAME. No error if it doesn't exist."
(condition-case nil
(delete-file filename)
(error nil)))
(defun bcc-unconditionally-kill-buffer (buffer)
"Kill buffer without asking the user or running any hooks"
(when (buffer-modified-p buffer)
(with-current-buffer buffer
(restore-buffer-modified-p nil)))
(let (kill-buffer-hook kill-buffer-query-functions)
(kill-buffer buffer)))
;; Life is ugly without CL. We need to avoid it in order to not
;; trigger recursive-load loops. In fact, we need to avoid anything
;; autoloaded, and anything not in the Emacs core.
(defun bcc-alist-member-delete-all (alist &rest keys)
"Delete from ALIST all elements whose car is `equal' to any element in KEYS.
Return the modified alist. Elements of ALIST that are not conses
are ignored."
(while (and (consp (car alist))
(member (car (car alist)) keys))
(setq alist (cdr alist)))
(let ((tail alist) tail-cdr)
(while (setq tail-cdr (cdr tail))
(if (and (consp (car tail-cdr))
(member (car (car tail-cdr)) keys))
(setcdr tail (cdr tail-cdr))
(setq tail tail-cdr))))
alist)
(defun bcc-cache-file-name (file-name)
"Transform an absolute file-name into its cache directory entry.
The resulting name is always an absolute path to a file ending in
.elc"
;; Assumes unix here
(concat
(file-name-as-directory (expand-file-name bcc-cache-directory))
(subst-char-in-string
?/ ?!
(file-name-sans-extension
(file-relative-name file-name "/")))
".elc"))
(defun bcc-in-blacklist (string blist)
"Return non-NIL iff STRING matches a regexp in BLIST.
Does not save match data."
(cond
((null blist) nil)
((string-match (car blist) string))
(t (bcc-in-blacklist string (cdr blist)))))
(defun bcc-byte-compile-to (input output)
"Like byte-compile-file, but puts the result in OUTPUT. Returns
the result of BYTE-COMPILE-FILE."
;; Require byte-compile here instead of letting the call to
;; byte-compile-file autoload it. We need byte-compile-dest-file to
;; be defined; if we define it ourselves and then let byte-compile
;; load, byte-compile will notice that byte-compile-dest-file is
;; alreay defined and not define its version.
(require 'bytecomp)
(let ((saved-dest-func (symbol-function #'byte-compile-dest-file)))
(unwind-protect
(progn
(fset #'byte-compile-dest-file
#'(lambda (src)
(if (equal src input)
output
(funcall saved-dest-func src))))
(byte-compile-file input))
(fset #'byte-compile-dest-file saved-dest-func))))
(defun bcc-make-fake-cache-entry (cachename origname)
"Creates a compiled lisp file called CACHENAME that simply
loads ORIGNAME."
;; This function is not on the fast path.
(let ((byte-compile-verbose nil)
(font-lock-verbose nil)
(byte-compile-warnings '())
(temp-file
(make-temp-file (expand-file-name
"fake-cache-"
(or small-temporary-file-directory
temporary-file-directory))
nil
".el")))
(unwind-protect
(progn
(with-temp-file temp-file
(prin1 `(setq bcc-loaded-fake-cache-entry t)
(current-buffer)))
(bcc-byte-compile-to temp-file cachename)
(bcc-assert (file-readable-p cachename)))
(delete-file temp-file))))
(defun bcc-regenerate-cache (input cachename nomessage)
"Regenerate the byte-code cache for INPUT,
putting the result in OUTPUT. If INPUT cannot be compiled,
generate a fake cache entry instead. NOMESSAGE means the same
thing it does for LOAD."
;; This function is not on the fast path.
(unless nomessage
(message "Regenerating cache for %s" fullname))
(let ((byte-compile-verbose nil)
(font-lock-verbose nil)
(byte-compile-warnings '())
(kill-buffer-query-functions '())
;; byte-comp (for some reason) sets the mode in its input
;; buffer to emacs-lisp-mode. That mode's hook might load code
;; that needs to be compiled using this very function.
(emacs-lisp-mode-hook '())
;; Make sure we don't enter byte-compile-file recursively
(bcc-regenerate-toplevel nil))
(let ((buf (find-buffer-visiting cachename)))
(when buf
(bcc-unconditionally-kill-buffer buf)))
(bcc-delete-file-noerror cachename)
(bcc-byte-compile-to input cachename)
(unless (file-readable-p cachename)
(unless nomessage
(message "Making fake cache entry for %S" cachename))
(bcc-make-fake-cache-entry cachename input)))
(unless nomessage
(message "Regenerating cache for %s...done" fullname)))
(defun bcc-load-cached-file (cachename origname noerror nomessage)
"Load compiled file CACHENAME, but pretend we're loading
ORIGNAME. NOERROR and NOMESSAGE mean what they do for LOAD."
;; We need this function (and can't use LOAD) because some elisp
;; files depend on LOAD-FILE-NAME being in the same directory as the
;; uncompiled file. LOAD unconditionally sets it to cachename. Also,
;; we can do away with the load-history manipulation if we just load
;; files here directly.
;; Patterned on LOAD-WITH-CODE-CONVERSION. Compiled elisp files
;; always use the interal Emacs encoding, according to lread.c.
;; This function is on the fast path, and it needs to be re-entrant.
(if (null (file-readable-p cachename))
(and (null noerror)
(signal 'file-error (list "Cannot open load file" cachename)))
(let* ((default-major-mode 'fundamental-mode)
(default-enable-multibyte-characters nil)
(buffer (get-buffer-create (generate-new-buffer-name " *load*")))
(load-in-progress t)
(byte-compile-warnings
(if (boundp 'byte-compile-warnings) byte-compile-warnings nil))
(byte-compile-verbose
(if (boundp 'byte-compile-verbose) byte-compile-verbose nil)))
(unless nomessage
(message "Loading %S as %S..." cachename origname))
(unwind-protect
(let ((load-file-name origname)
(inhibit-file-name-operation nil))
(with-current-buffer buffer
(let ((coding-system-for-read 'no-conversion)
deactivate-mark
buffer-undo-list)
(insert-file-contents cachename)
(set-buffer-multibyte nil)))
(setq bcc-loaded (cons (cons origname cachename) bcc-loaded))
(eval-buffer buffer nil origname nil t))
(bcc-unconditionally-kill-buffer buffer))
(do-after-load-evaluation origname)
(unless nomessage
(message "Loading %S as %S...done" cachename origname))
t)))
(defun bcc-load-source-file (fullname file noerror nomessage)
"Load the given file. If it's a plain elisp file, compile it
and stuff the compiled file in the cache directory. Then load
it."
;; This function is on the fast path. It also needs to be
;; re-entrant.
(let (cachename
hist-ent loaded-from-bcc-cache
bcc-loaded-fake-cache-entry)
(when (and bcc-enabled
(not (save-match-data
(bcc-in-blacklist fullname bcc-blacklist))))
(setq cachename (file-truename (bcc-cache-file-name fullname)))
(make-directory (file-name-directory cachename) t)
(when (and bcc-regenerate-toplevel
(file-newer-than-file-p fullname cachename))
(bcc-regenerate-cache fullname cachename nomessage))
(when (file-readable-p cachename)
(bcc-load-cached-file cachename fullname noerror nomessage)
(unless bcc-loaded-fake-cache-entry
(setq loaded-from-bcc-cache t))))
(unless loaded-from-bcc-cache
(funcall bcc-old-load-source-file-function
fullname file noerror nomessage))))
(defadvice documentation-property (before bcc-documentation-property-fix activate)
"Work around Emacs bug"
(let ((docobj (get (ad-get-arg 0) (ad-get-arg 1)))
loadinfo)
(when (and (numberp (cdr-safe docobj))
(setq loadinfo (assoc (car docobj) bcc-loaded)))
(setcar docobj (cdr loadinfo)))))
(defadvice documentation (before bcc-documentation-fix activate preactivate)
"Work around Emacs bug"
(let* ((fun (ad-get-arg 0))
docobj loadinfo funcar prop)
(if (and (symbolp fun)
(setq prop (get fun 'function-documentation)))
;; Called for side-effect
(documentation-property fun 'function-documentation (ad-get-arg 1))
(setq fun (indirect-function fun t))
(setq funcar (car-safe fun))
(when (eq funcar 'macro)
(setq fun (indirect-function (cdr fun)))
(setq funcar (car-safe fun)))
(cond ((memq funcar '(lambda autoload))
(setq docobj (car (cdr (cdr fun)))))
((and (byte-code-function-p fun)
(> (length fun) bcc-compiled-doc-string))
(setq docobj (aref fun bcc-compiled-doc-string))))
(when (and (consp docobj)
(setq loadinfo (assoc (car docobj) bcc-loaded)))
(setcar docobj (cdr loadinfo))))))
(setq load-source-file-function #'bcc-load-source-file)
(provide 'byte-code-cache)
;;; byte-code-cache.el ends here.