Commit | Line | Data |
---|---|---|
e8af40ee | 1 | ;;; shadow.el --- locate Emacs Lisp file shadowings |
50584ac0 KH |
2 | |
3 | ;; Copyright (C) 1995 Free Software Foundation, Inc. | |
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 | ||
11 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
12 | ;; it under the terms of the GNU General Public License as published by | |
13 | ;; the Free Software Foundation; either version 2, or (at your option) | |
14 | ;; any later version. | |
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 | |
b578f267 EN |
22 | ;; along with GNU Emacs; see the file COPYING. If not, write to the |
23 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
24 | ;; Boston, MA 02111-1307, USA. | |
50584ac0 KH |
25 | |
26 | ;;; Commentary: | |
b578f267 | 27 | |
50584ac0 KH |
28 | ;; The functions in this file detect (`find-emacs-lisp-shadows') |
29 | ;; and display (`list-load-path-shadows') potential load-path | |
30 | ;; problems that arise when Emacs Lisp files "shadow" each other. | |
31 | ;; | |
32 | ;; For example, a file XXX.el early in one's load-path will shadow | |
33 | ;; a file with the same name in a later load-path directory. When | |
34 | ;; this is unintentional, it may result in problems that could have | |
35 | ;; been easily avoided. This occurs often (to me) when installing a | |
36 | ;; new version of emacs and something in the site-lisp directory | |
37 | ;; has been updated and added to the emacs distribution. The old | |
38 | ;; version, now outdated, shadows the new one. This is obviously | |
39 | ;; undesirable. | |
40 | ;; | |
41 | ;; The `list-load-path-shadows' function was run when you installed | |
42 | ;; this version of emacs. To run it by hand in emacs: | |
43 | ;; | |
44 | ;; M-x load-library RET shadow RET | |
45 | ;; M-x list-load-path-shadows | |
46 | ;; | |
47 | ;; or run it non-interactively via: | |
48 | ;; | |
49 | ;; emacs -batch -l shadow.el -f list-load-path-shadows | |
50 | ;; | |
51 | ;; Thanks to Francesco Potorti` <pot@cnuce.cnr.it> for suggestions, | |
52 | ;; rewritings & speedups. | |
53 | ||
54 | ;;; Code: | |
55 | \f | |
4e2ad9ea | 56 | (defgroup lisp-shadow nil |
666b9413 SE |
57 | "Locate Emacs Lisp file shadowings." |
58 | :prefix "shadows-" | |
59 | :group 'lisp) | |
60 | ||
61 | (defcustom shadows-compare-text-p nil | |
b7797a3e | 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 | |
50584ac0 KH |
67 | (defun find-emacs-lisp-shadows (&optional path) |
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." | |
a1506d29 | 77 | |
50584ac0 KH |
78 | (or path (setq path load-path)) |
79 | ||
80 | (let (true-names ; List of dirs considered. | |
81 | shadows ; List of shadowings, to be returned. | |
82 | files ; File names ever seen, with dirs. | |
83 | dir ; The dir being currently scanned. | |
84 | curr-files ; This dir's Emacs Lisp files. | |
85 | orig-dir ; Where the file was first seen. | |
86 | files-seen-this-dir ; Files seen so far in this dir. | |
87 | file) ; The current file. | |
88 | ||
a1506d29 | 89 | |
50584ac0 KH |
90 | (while path |
91 | ||
b7797a3e | 92 | (setq dir (directory-file-name (file-truename (or (car path) ".")))) |
50584ac0 KH |
93 | (if (member dir true-names) |
94 | ;; We have already considered this PATH redundant directory. | |
95 | ;; Show the redundancy if we are interactiver, unless the PATH | |
96 | ;; dir is nil or "." (these redundant directories are just a | |
97 | ;; result of the current working directory, and are therefore | |
98 | ;; not always redundant). | |
99 | (or noninteractive | |
100 | (and (car path) | |
101 | (not (string= (car path) ".")) | |
c2b7bdc2 | 102 | (message "Ignoring redundant directory %s" (car path)))) |
b7797a3e | 103 | |
50584ac0 | 104 | (setq true-names (append true-names (list dir))) |
b7797a3e | 105 | (setq dir (directory-file-name (or (car path) "."))) |
50584ac0 | 106 | (setq curr-files (if (file-accessible-directory-p dir) |
b7797a3e | 107 | (directory-files dir nil ".\\.elc?$" t))) |
50584ac0 KH |
108 | (and curr-files |
109 | (not noninteractive) | |
c2b7bdc2 | 110 | (message "Checking %d files in %s..." (length curr-files) dir)) |
b7797a3e | 111 | |
50584ac0 KH |
112 | (setq files-seen-this-dir nil) |
113 | ||
114 | (while curr-files | |
115 | ||
116 | (setq file (car curr-files)) | |
117 | (setq file (substring | |
118 | file 0 (if (string= (substring file -1) "c") -4 -3))) | |
119 | ||
5017dcaa RS |
120 | ;; FILE now contains the current file name, with no suffix. |
121 | (unless (or (member file files-seen-this-dir) | |
122 | ;; Ignore these files. | |
123 | (member file '("subdirs"))) | |
50584ac0 KH |
124 | ;; File has not been seen yet in this directory. |
125 | ;; This test prevents us declaring that XXX.el shadows | |
126 | ;; XXX.elc (or vice-versa) when they are in the same directory. | |
127 | (setq files-seen-this-dir (cons file files-seen-this-dir)) | |
a1506d29 | 128 | |
50584ac0 KH |
129 | (if (setq orig-dir (assoc file files)) |
130 | ;; This file was seen before, we have a shadowing. | |
b7797a3e KH |
131 | ;; Report it unless the files are identical. |
132 | (let ((base1 (concat (cdr orig-dir) "/" file)) | |
133 | (base2 (concat dir "/" file))) | |
134 | (if (not (and shadows-compare-text-p | |
135 | (shadow-same-file-or-nonexistent | |
136 | (concat base1 ".el") (concat base2 ".el")) | |
137 | ;; This is a bit strict, but safe. | |
138 | (shadow-same-file-or-nonexistent | |
139 | (concat base1 ".elc") (concat base2 ".elc")))) | |
3a6a40e5 RS |
140 | (setq shadows |
141 | (append shadows (list base1 base2))))) | |
50584ac0 KH |
142 | |
143 | ;; Not seen before, add it to the list of seen files. | |
144 | (setq files (cons (cons file dir) files)))) | |
145 | ||
146 | (setq curr-files (cdr curr-files)))) | |
147 | (setq path (cdr path))) | |
148 | ||
149 | ;; Return the list of shadowings. | |
150 | shadows)) | |
151 | ||
b7797a3e KH |
152 | ;; Return true if neither file exists, or if both exist and have identical |
153 | ;; contents. | |
154 | (defun shadow-same-file-or-nonexistent (f1 f2) | |
155 | (let ((exists1 (file-exists-p f1)) | |
156 | (exists2 (file-exists-p f2))) | |
157 | (or (and (not exists1) (not exists2)) | |
158 | (and exists1 exists2 | |
159 | (or (equal (file-truename f1) (file-truename f2)) | |
160 | ;; As a quick test, avoiding spawning a process, compare file | |
161 | ;; sizes. | |
162 | (and (= (nth 7 (file-attributes f1)) | |
163 | (nth 7 (file-attributes f2))) | |
6b61353c | 164 | (eq 0 (call-process "cmp" nil nil nil "-s" f1 f2)))))))) |
50584ac0 KH |
165 | \f |
166 | ;;;###autoload | |
167 | (defun list-load-path-shadows () | |
0f93e41f | 168 | "Display a list of Emacs Lisp files that shadow other files. |
50584ac0 KH |
169 | |
170 | This function lists potential load-path problems. Directories in the | |
171 | `load-path' variable are searched, in order, for Emacs Lisp | |
0f93e41f RS |
172 | files. When a previously encountered file name is found again, a |
173 | message is displayed indicating that the later file is \"hidden\" by | |
50584ac0 KH |
174 | the earlier. |
175 | ||
176 | For example, suppose `load-path' is set to | |
177 | ||
178 | \(\"/usr/gnu/emacs/site-lisp\" \"/usr/gnu/emacs/share/emacs/19.30/lisp\"\) | |
179 | ||
180 | and that each of these directories contains a file called XXX.el. Then | |
181 | XXX.el in the site-lisp directory is referred to by all of: | |
182 | \(require 'XXX\), \(autoload .... \"XXX\"\), \(load-library \"XXX\"\) etc. | |
183 | ||
184 | The first XXX.el file prevents emacs from seeing the second \(unless | |
185 | the second is loaded explicitly via load-file\). | |
186 | ||
187 | When not intended, such shadowings can be the source of subtle | |
188 | problems. For example, the above situation may have arisen because the | |
189 | XXX package was not distributed with versions of emacs prior to | |
190 | 19.30. An emacs maintainer downloaded XXX from elsewhere and installed | |
191 | it. Later, XXX was updated and included in the emacs distribution. | |
192 | Unless the emacs maintainer checks for this, the new version of XXX | |
193 | will be hidden behind the old \(which may no longer work with the new | |
194 | emacs version\). | |
195 | ||
196 | This function performs these checks and flags all possible | |
197 | shadowings. Because a .el file may exist without a corresponding .elc | |
198 | \(or vice-versa\), these suffixes are essentially ignored. A file | |
199 | XXX.elc in an early directory \(that does not contain XXX.el\) is | |
200 | considered to shadow a later file XXX.el, and vice-versa. | |
201 | ||
202 | When run interactively, the shadowings \(if any\) are displayed in a | |
203 | buffer called `*Shadows*'. Shadowings are located by calling the | |
204 | \(non-interactive\) companion function, `find-emacs-lisp-shadows'." | |
a1506d29 | 205 | |
50584ac0 | 206 | (interactive) |
0cdbb11d RS |
207 | (let* ((path (copy-sequence load-path)) |
208 | (tem path) | |
209 | toplevs) | |
210 | ;; If we can find simple.el in two places, | |
211 | (while tem | |
212 | (if (file-exists-p (expand-file-name "simple.el" (car tem))) | |
213 | (setq toplevs (cons (car tem) toplevs))) | |
214 | (setq tem (cdr tem))) | |
215 | (if (> (length toplevs) 1) | |
216 | ;; Cut off our copy of load-path right before | |
8ab4da6c | 217 | ;; the last directory which has simple.el in it. |
0cdbb11d RS |
218 | ;; This avoids loads of duplications between the source dir |
219 | ;; and the dir where these files were copied by installation. | |
8ab4da6c | 220 | (let ((break (car toplevs))) |
0cdbb11d RS |
221 | (setq tem path) |
222 | (while tem | |
223 | (if (eq (nth 1 tem) break) | |
224 | (progn | |
225 | (setcdr tem nil) | |
226 | (setq tem nil))) | |
227 | (setq tem (cdr tem))))) | |
228 | ||
229 | (let* ((shadows (find-emacs-lisp-shadows path)) | |
230 | (n (/ (length shadows) 2)) | |
231 | (msg (format "%s Emacs Lisp load-path shadowing%s found" | |
232 | (if (zerop n) "No" (concat "\n" (number-to-string n))) | |
233 | (if (= n 1) " was" "s were")))) | |
234 | (if (interactive-p) | |
235 | (save-excursion | |
236 | ;; We are interactive. | |
237 | ;; Create the *Shadows* buffer and display shadowings there. | |
238 | (let ((output-buffer (get-buffer-create "*Shadows*"))) | |
239 | (display-buffer output-buffer) | |
240 | (set-buffer output-buffer) | |
241 | (erase-buffer) | |
242 | (while shadows | |
243 | (insert (format "%s hides %s\n" (car shadows) | |
244 | (car (cdr shadows)))) | |
245 | (setq shadows (cdr (cdr shadows)))) | |
246 | (insert msg "\n"))) | |
247 | ;; We are non-interactive, print shadows via message. | |
248 | (when shadows | |
249 | (message "This site has duplicate Lisp libraries with the same name. | |
5017dcaa RS |
250 | If a locally-installed Lisp library overrides a library in the Emacs release, |
251 | that can cause trouble, and you should probably remove the locally-installed | |
248a9f6d RS |
252 | version unless you know what you are doing.\n") |
253 | (while shadows | |
254 | (message "%s hides %s" (car shadows) (car (cdr shadows))) | |
255 | (setq shadows (cdr (cdr shadows)))) | |
256 | (message "%s" msg)))))) | |
50584ac0 KH |
257 | |
258 | (provide 'shadow) | |
259 | ||
6b61353c | 260 | ;;; arch-tag: 0480e8a7-62ed-4a12-a9f6-f44ded9b0830 |
50584ac0 | 261 | ;;; shadow.el ends here |