Commit | Line | Data |
---|---|---|
e8af40ee | 1 | ;;; shadow.el --- locate Emacs Lisp file shadowings |
50584ac0 | 2 | |
ba318903 | 3 | ;; Copyright (C) 1995, 2001-2014 Free Software Foundation, Inc. |
50584ac0 KH |
4 | |
5 | ;; Author: Terry Jones <terry@santafe.edu> | |
6 | ;; Keywords: lisp | |
7 | ;; Created: 15 December 1995 | |
8 | ||
9 | ;; This file is part of GNU Emacs. | |
10 | ||
d6cba7ae | 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
50584ac0 | 12 | ;; it under the terms of the GNU General Public License as published by |
d6cba7ae GM |
13 | ;; the Free Software Foundation, either version 3 of the License, or |
14 | ;; (at your option) any later version. | |
50584ac0 KH |
15 | |
16 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
20 | ||
21 | ;; You should have received a copy of the GNU General Public License | |
d6cba7ae | 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
50584ac0 KH |
23 | |
24 | ;;; Commentary: | |
b578f267 | 25 | |
d15f9a2b | 26 | ;; The functions in this file detect (`load-path-shadows-find') |
50584ac0 KH |
27 | ;; and display (`list-load-path-shadows') potential load-path |
28 | ;; problems that arise when Emacs Lisp files "shadow" each other. | |
29 | ;; | |
30 | ;; For example, a file XXX.el early in one's load-path will shadow | |
31 | ;; a file with the same name in a later load-path directory. When | |
32 | ;; this is unintentional, it may result in problems that could have | |
33 | ;; been easily avoided. This occurs often (to me) when installing a | |
34 | ;; new version of emacs and something in the site-lisp directory | |
35 | ;; has been updated and added to the emacs distribution. The old | |
36 | ;; version, now outdated, shadows the new one. This is obviously | |
37 | ;; undesirable. | |
38 | ;; | |
39 | ;; The `list-load-path-shadows' function was run when you installed | |
40 | ;; this version of emacs. To run it by hand in emacs: | |
41 | ;; | |
50584ac0 KH |
42 | ;; M-x list-load-path-shadows |
43 | ;; | |
44 | ;; or run it non-interactively via: | |
45 | ;; | |
7ba65108 | 46 | ;; emacs -batch -f list-load-path-shadows |
50584ac0 | 47 | ;; |
8c106d17 | 48 | ;; Thanks to Francesco Potortì <pot@cnuce.cnr.it> for suggestions, |
50584ac0 KH |
49 | ;; rewritings & speedups. |
50 | ||
51 | ;;; Code: | |
52 | \f | |
4e2ad9ea | 53 | (defgroup lisp-shadow nil |
666b9413 | 54 | "Locate Emacs Lisp file shadowings." |
e5d49589 | 55 | :prefix "load-path-shadows-" |
666b9413 SE |
56 | :group 'lisp) |
57 | ||
e5d49589 GM |
58 | (define-obsolete-variable-alias 'shadows-compare-text-p |
59 | 'load-path-shadows-compare-text "23.3") | |
60 | ||
61 | (defcustom load-path-shadows-compare-text nil | |
7ba65108 | 62 | "If non-nil, then shadowing files are reported only if their text differs. |
666b9413 SE |
63 | This is slower, but filters out some innocuous shadowing." |
64 | :type 'boolean | |
4e2ad9ea | 65 | :group 'lisp-shadow) |
b7797a3e | 66 | |
d15f9a2b | 67 | (defun load-path-shadows-find (&optional path) |
50584ac0 KH |
68 | "Return a list of Emacs Lisp files that create shadows. |
69 | This function does the work for `list-load-path-shadows'. | |
70 | ||
71 | We traverse PATH looking for shadows, and return a \(possibly empty\) | |
72 | even-length list of files. A file in this list at position 2i shadows | |
73 | the file in position 2i+1. Emacs Lisp file suffixes \(.el and .elc\) | |
74 | are stripped from the file names in the list. | |
75 | ||
76 | See the documentation for `list-load-path-shadows' for further information." | |
50584ac0 KH |
77 | (let (true-names ; List of dirs considered. |
78 | shadows ; List of shadowings, to be returned. | |
79 | files ; File names ever seen, with dirs. | |
80 | dir ; The dir being currently scanned. | |
81 | curr-files ; This dir's Emacs Lisp files. | |
82 | orig-dir ; Where the file was first seen. | |
83 | files-seen-this-dir ; Files seen so far in this dir. | |
84 | file) ; The current file. | |
b16ff465 GM |
85 | (dolist (pp (or path load-path)) |
86 | (setq dir (directory-file-name (file-truename (or pp ".")))) | |
50584ac0 KH |
87 | (if (member dir true-names) |
88 | ;; We have already considered this PATH redundant directory. | |
f5bb9196 | 89 | ;; Show the redundancy if we are interactive, unless the PATH |
50584ac0 KH |
90 | ;; dir is nil or "." (these redundant directories are just a |
91 | ;; result of the current working directory, and are therefore | |
92 | ;; not always redundant). | |
93 | (or noninteractive | |
b16ff465 GM |
94 | (and pp |
95 | (not (string= pp ".")) | |
96 | (message "Ignoring redundant directory %s" pp))) | |
b7797a3e | 97 | |
50584ac0 | 98 | (setq true-names (append true-names (list dir))) |
b16ff465 | 99 | (setq dir (directory-file-name (or pp "."))) |
50584ac0 | 100 | (setq curr-files (if (file-accessible-directory-p dir) |
da49096f | 101 | (directory-files dir nil ".\\.elc?\\(\\.gz\\)?$" t))) |
50584ac0 KH |
102 | (and curr-files |
103 | (not noninteractive) | |
c2b7bdc2 | 104 | (message "Checking %d files in %s..." (length curr-files) dir)) |
b7797a3e | 105 | |
50584ac0 KH |
106 | (setq files-seen-this-dir nil) |
107 | ||
b16ff465 | 108 | (dolist (file curr-files) |
50584ac0 | 109 | |
da49096f AS |
110 | (if (string-match "\\.gz$" file) |
111 | (setq file (substring file 0 -3))) | |
50584ac0 KH |
112 | (setq file (substring |
113 | file 0 (if (string= (substring file -1) "c") -4 -3))) | |
114 | ||
5017dcaa RS |
115 | ;; FILE now contains the current file name, with no suffix. |
116 | (unless (or (member file files-seen-this-dir) | |
117 | ;; Ignore these files. | |
c94e6ee4 GM |
118 | (member file (list "subdirs" "leim-list" |
119 | (file-name-sans-extension | |
120 | dir-locals-file)))) | |
50584ac0 KH |
121 | ;; File has not been seen yet in this directory. |
122 | ;; This test prevents us declaring that XXX.el shadows | |
123 | ;; XXX.elc (or vice-versa) when they are in the same directory. | |
124 | (setq files-seen-this-dir (cons file files-seen-this-dir)) | |
a1506d29 | 125 | |
50584ac0 KH |
126 | (if (setq orig-dir (assoc file files)) |
127 | ;; This file was seen before, we have a shadowing. | |
b7797a3e KH |
128 | ;; Report it unless the files are identical. |
129 | (let ((base1 (concat (cdr orig-dir) "/" file)) | |
130 | (base2 (concat dir "/" file))) | |
e5d49589 GM |
131 | (if (not (and load-path-shadows-compare-text |
132 | (load-path-shadows-same-file-or-nonexistent | |
b7797a3e KH |
133 | (concat base1 ".el") (concat base2 ".el")) |
134 | ;; This is a bit strict, but safe. | |
e5d49589 | 135 | (load-path-shadows-same-file-or-nonexistent |
b7797a3e | 136 | (concat base1 ".elc") (concat base2 ".elc")))) |
3a6a40e5 RS |
137 | (setq shadows |
138 | (append shadows (list base1 base2))))) | |
50584ac0 KH |
139 | |
140 | ;; Not seen before, add it to the list of seen files. | |
b16ff465 | 141 | (setq files (cons (cons file dir) files))))))) |
50584ac0 KH |
142 | ;; Return the list of shadowings. |
143 | shadows)) | |
144 | ||
d15f9a2b GM |
145 | (define-obsolete-function-alias 'find-emacs-lisp-shadows |
146 | 'load-path-shadows-find "23.3") | |
147 | ||
b7797a3e KH |
148 | ;; Return true if neither file exists, or if both exist and have identical |
149 | ;; contents. | |
e5d49589 | 150 | (defun load-path-shadows-same-file-or-nonexistent (f1 f2) |
b7797a3e KH |
151 | (let ((exists1 (file-exists-p f1)) |
152 | (exists2 (file-exists-p f2))) | |
153 | (or (and (not exists1) (not exists2)) | |
154 | (and exists1 exists2 | |
155 | (or (equal (file-truename f1) (file-truename f2)) | |
156 | ;; As a quick test, avoiding spawning a process, compare file | |
157 | ;; sizes. | |
158 | (and (= (nth 7 (file-attributes f1)) | |
159 | (nth 7 (file-attributes f2))) | |
15502042 | 160 | (eq 0 (call-process "cmp" nil nil nil "-s" f1 f2)))))))) |
7b9235ad | 161 | |
06d9ef85 | 162 | (defvar load-path-shadows-font-lock-keywords |
511fd0b2 GM |
163 | ;; The idea is that shadows of files supplied with Emacs are more |
164 | ;; serious than various versions of external packages shadowing each | |
165 | ;; other. | |
7b9235ad | 166 | `((,(format "hides \\(%s.*\\)" |
511fd0b2 GM |
167 | (file-name-directory |
168 | (or (locate-library "simple") | |
169 | (file-name-as-directory | |
170 | (expand-file-name "../lisp" data-directory))))) | |
7b9235ad | 171 | . (1 font-lock-warning-face))) |
ac44263a | 172 | "Keywords to highlight in `load-path-shadows-mode'.") |
7b9235ad | 173 | |
ac44263a | 174 | (define-derived-mode load-path-shadows-mode fundamental-mode "LP-Shadows" |
7b9235ad GM |
175 | "Major mode for load-path shadows buffer." |
176 | (set (make-local-variable 'font-lock-defaults) | |
06d9ef85 | 177 | '((load-path-shadows-font-lock-keywords))) |
7b9235ad GM |
178 | (setq buffer-undo-list t |
179 | buffer-read-only t)) | |
180 | ||
181 | ;; TODO use text-properties instead, a la dired. | |
182 | (require 'button) | |
06d9ef85 | 183 | (define-button-type 'load-path-shadows-find-file |
7b9235ad GM |
184 | 'follow-link t |
185 | ;; 'face 'default | |
186 | 'action (lambda (button) | |
187 | (let ((file (concat (button-get button 'shadow-file) ".el"))) | |
188 | (or (file-exists-p file) | |
189 | (setq file (concat file ".gz"))) | |
190 | (if (file-readable-p file) | |
191 | (pop-to-buffer (find-file-noselect file)) | |
192 | (error "Cannot read file")))) | |
193 | 'help-echo "mouse-2, RET: find this file") | |
194 | ||
50584ac0 KH |
195 | \f |
196 | ;;;###autoload | |
7ba65108 | 197 | (defun list-load-path-shadows (&optional stringp) |
0f93e41f | 198 | "Display a list of Emacs Lisp files that shadow other files. |
50584ac0 | 199 | |
7ba65108 GM |
200 | If STRINGP is non-nil, returns any shadows as a string. |
201 | Otherwise, if interactive shows any shadows in a `*Shadows*' buffer; | |
202 | else prints messages listing any shadows. | |
203 | ||
f5bb9196 JB |
204 | This function lists potential load path problems. Directories in |
205 | the `load-path' variable are searched, in order, for Emacs Lisp | |
0f93e41f RS |
206 | files. When a previously encountered file name is found again, a |
207 | message is displayed indicating that the later file is \"hidden\" by | |
50584ac0 KH |
208 | the earlier. |
209 | ||
210 | For example, suppose `load-path' is set to | |
211 | ||
e6ea1f6c | 212 | \(\"/usr/share/emacs/site-lisp\" \"/usr/share/emacs/24.3/lisp\") |
50584ac0 KH |
213 | |
214 | and that each of these directories contains a file called XXX.el. Then | |
215 | XXX.el in the site-lisp directory is referred to by all of: | |
e6ea1f6c | 216 | \(require 'XXX), (autoload .... \"XXX\"), (load-library \"XXX\") etc. |
50584ac0 | 217 | |
e6ea1f6c GM |
218 | The first XXX.el file prevents Emacs from seeing the second (unless |
219 | the second is loaded explicitly via `load-file'). | |
50584ac0 KH |
220 | |
221 | When not intended, such shadowings can be the source of subtle | |
222 | problems. For example, the above situation may have arisen because the | |
32a37bc6 | 223 | XXX package was not distributed with versions of Emacs prior to |
e6ea1f6c | 224 | 24.3. A system administrator downloaded XXX from elsewhere and installed |
32a37bc6 | 225 | it. Later, XXX was updated and included in the Emacs distribution. |
e6ea1f6c GM |
226 | Unless the system administrator checks for this, the new version of XXX |
227 | will be hidden behind the old (which may no longer work with the new | |
228 | Emacs version). | |
50584ac0 KH |
229 | |
230 | This function performs these checks and flags all possible | |
231 | shadowings. Because a .el file may exist without a corresponding .elc | |
e6ea1f6c GM |
232 | \(or vice-versa), these suffixes are essentially ignored. A file |
233 | XXX.elc in an early directory (that does not contain XXX.el) is | |
50584ac0 KH |
234 | considered to shadow a later file XXX.el, and vice-versa. |
235 | ||
7ba65108 | 236 | Shadowings are located by calling the (non-interactive) companion |
d15f9a2b | 237 | function, `load-path-shadows-find'." |
50584ac0 | 238 | (interactive) |
e6ea1f6c GM |
239 | (let* ((shadows (load-path-shadows-find load-path)) |
240 | (n (/ (length shadows) 2)) | |
241 | (msg (format "%s Emacs Lisp load-path shadowing%s found" | |
242 | (if (zerop n) "No" (concat "\n" (number-to-string n))) | |
243 | (if (= n 1) " was" "s were")))) | |
244 | (with-temp-buffer | |
245 | (while shadows | |
246 | (insert (format "%s hides %s\n" (car shadows) | |
247 | (car (cdr shadows)))) | |
248 | (setq shadows (cdr (cdr shadows)))) | |
249 | (if stringp | |
250 | (buffer-string) | |
251 | (if (called-interactively-p 'interactive) | |
252 | ;; We are interactive. | |
253 | ;; Create the *Shadows* buffer and display shadowings there. | |
254 | (let ((string (buffer-string))) | |
255 | (with-current-buffer (get-buffer-create "*Shadows*") | |
256 | (display-buffer (current-buffer)) | |
257 | (load-path-shadows-mode) ; run after-change-major-mode-hook | |
258 | (let ((inhibit-read-only t)) | |
259 | (erase-buffer) | |
260 | (insert string) | |
261 | (insert msg "\n") | |
262 | (while (re-search-backward "\\(^.*\\) hides \\(.*$\\)" | |
263 | nil t) | |
264 | (dotimes (i 2) | |
265 | (make-button (match-beginning (1+ i)) | |
266 | (match-end (1+ i)) | |
267 | 'type 'load-path-shadows-find-file | |
268 | 'shadow-file | |
269 | (match-string (1+ i))))) | |
270 | (goto-char (point-max))))) | |
271 | ;; We are non-interactive, print shadows via message. | |
272 | (unless (zerop n) | |
273 | (message "This site has duplicate Lisp libraries with the same name. | |
5017dcaa RS |
274 | If a locally-installed Lisp library overrides a library in the Emacs release, |
275 | that can cause trouble, and you should probably remove the locally-installed | |
248a9f6d | 276 | version unless you know what you are doing.\n") |
e6ea1f6c GM |
277 | (goto-char (point-min)) |
278 | ;; Mimic the previous behavior of using lots of messages. | |
279 | ;; I think one single message would look better... | |
280 | (while (not (eobp)) | |
281 | (message "%s" (buffer-substring (line-beginning-position) | |
282 | (line-end-position))) | |
283 | (forward-line 1)) | |
284 | (message "%s" msg))))))) | |
50584ac0 KH |
285 | |
286 | (provide 'shadow) | |
287 | ||
288 | ;;; shadow.el ends here | |
8c106d17 GM |
289 | |
290 | ;; Local Variables: | |
291 | ;; coding: utf-8 | |
292 | ;; End: |