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