Mercurial > hg > xemacs-beta
comparison lisp/package-get.el @ 428:3ecd8885ac67 r21-2-22
Import from CVS: tag r21-2-22
author | cvs |
---|---|
date | Mon, 13 Aug 2007 11:28:15 +0200 |
parents | |
children | a5df635868b2 |
comparison
equal
deleted
inserted
replaced
427:0a0253eac470 | 428:3ecd8885ac67 |
---|---|
1 ;;; package-get.el --- Retrieve XEmacs package | |
2 | |
3 ;; Copyright (C) 1998 by Pete Ware | |
4 | |
5 ;; Author: Pete Ware <ware@cis.ohio-state.edu> | |
6 ;; Heavy-Modifications: Greg Klanderman <greg@alphatech.com> | |
7 ;; Jan Vroonhof <vroonhof@math.ethz.ch> | |
8 ;; Keywords: internal | |
9 | |
10 ;; This file is part of XEmacs. | |
11 | |
12 ;; XEmacs is free software; you can redistribute it and/or modify it | |
13 ;; under the terms of the GNU General Public License as published by | |
14 ;; the Free Software Foundation; either version 2, or (at your option) | |
15 ;; any later version. | |
16 | |
17 ;; XEmacs is distributed in the hope that it will be useful, but | |
18 ;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 ;; General Public License for more details. | |
21 | |
22 ;; You should have received a copy of the GNU General Public License | |
23 ;; along with XEmacs; see the file COPYING. If not, write to the Free | |
24 ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | |
25 ;; 02111-1307, USA. | |
26 | |
27 ;;; Synched up with: Not in FSF | |
28 | |
29 ;;; Commentary: | |
30 | |
31 ;; package-get - | |
32 ;; Retrieve a package and any other required packages from an archive | |
33 ;; | |
34 ;; | |
35 ;; Note (JV): Most of this no longer aplies! | |
36 ;; | |
37 ;; The idea: | |
38 ;; A new XEmacs lisp-only release is generated with the following steps: | |
39 ;; 1. The maintainer runs some yet to be written program that | |
40 ;; generates all the dependency information. This should | |
41 ;; determine all the require and provide statements and associate | |
42 ;; them with a package. | |
43 ;; 2. All the packages are then bundled into their own tar balls | |
44 ;; (or whatever format) | |
45 ;; 3. Maintainer automatically generates a new `package-get-base' | |
46 ;; data structure which contains information such as the | |
47 ;; package name, the file to be retrieved, an md5 checksum, | |
48 ;; etc (see `package-get-base'). | |
49 ;; 4. The maintainer posts an announcement with the new version | |
50 ;; of `package-get-base'. | |
51 ;; 5. A user/system manager saves this posting and runs | |
52 ;; `package-get-update' which uses the previously saved list | |
53 ;; of packages, `package-get-here' that the user/site | |
54 ;; wants to determine what new versions to download and | |
55 ;; install. | |
56 ;; | |
57 ;; A user/site manager can generate a new `package-get-here' structure | |
58 ;; by using `package-get-setup' which generates a customize like | |
59 ;; interface to the list of packages. The buffer looks something | |
60 ;; like: | |
61 ;; | |
62 ;; gnus - a mail and news reader | |
63 ;; [] Always install | |
64 ;; [] Needs updating | |
65 ;; [] Required by other [packages] | |
66 ;; version: 2.0 | |
67 ;; | |
68 ;; vm - a mail reader | |
69 ;; [] Always install | |
70 ;; [] Needs updating | |
71 ;; [] Required by other [packages] | |
72 ;; | |
73 ;; Where `[]' indicates a toggle box | |
74 ;; | |
75 ;; - Clicking on "Always install" puts this into | |
76 ;; `package-get-here' list. "Needs updating" indicates a new | |
77 ;; version is available. Anything already in | |
78 ;; `package-get-here' has this enabled. | |
79 ;; - "Required by other" means some other packages are going to force | |
80 ;; this to be installed. Clicking on [packages] gives a list | |
81 ;; of packages that require this. | |
82 ;; | |
83 ;; The `package-get-base' should be installed in a file in | |
84 ;; `data-directory'. The `package-get-here' should be installed in | |
85 ;; site-lisp. Both are then read at run time. | |
86 ;; | |
87 ;; TODO: | |
88 ;; - Implement `package-get-setup' | |
89 ;; - Actually put `package-get-base' and `package-get-here' into | |
90 ;; files that are read. | |
91 ;; - Allow users to have their own packages that they want installed | |
92 ;; in ~/.xemacs/. | |
93 ;; - SOMEONE needs to write the programs that generate the | |
94 ;; provides/requires database and makes it into a lisp data | |
95 ;; structure suitable for `package-get-base' | |
96 ;; - Handle errors such as no package providing a required symbol. | |
97 ;; - Tie this into the `require' function to download packages | |
98 ;; transparently. | |
99 | |
100 ;;; Change Log | |
101 | |
102 ;;; Code: | |
103 | |
104 (require 'package-admin) | |
105 ;; (require 'package-get-base) | |
106 | |
107 (defgroup package-tools nil | |
108 "Tools to manipulate packages." | |
109 :group 'emacs) | |
110 | |
111 (defgroup package-get nil | |
112 "Automatic Package Fetcher and Installer." | |
113 :prefix "package-get" | |
114 :group 'package-tools) | |
115 | |
116 ;;;###autoload | |
117 (defvar package-get-base nil | |
118 "List of packages that are installed at this site. | |
119 For each element in the alist, car is the package name and the cdr is | |
120 a plist containing information about the package. Typical fields | |
121 kept in the plist are: | |
122 | |
123 version - version of this package | |
124 provides - list of symbols provided | |
125 requires - list of symbols that are required. | |
126 These in turn are provided by other packages. | |
127 filename - name of the file. | |
128 size - size of the file (aka the bundled package) | |
129 md5sum - computed md5 checksum | |
130 description - What this package is for. | |
131 type - Whether this is a 'binary (default) or 'single file package | |
132 | |
133 More fields may be added as needed. An example: | |
134 | |
135 '( | |
136 (name | |
137 (version \"<version 2>\" | |
138 file \"filename\" | |
139 description \"what this package is about.\" | |
140 provides (<list>) | |
141 requires (<list>) | |
142 size <integer-bytes> | |
143 md5sum \"<checksum\" | |
144 type single | |
145 ) | |
146 (version \"<version 1>\" | |
147 file \"filename\" | |
148 description \"what this package is about.\" | |
149 provides (<list>) | |
150 requires (<list>) | |
151 size <integer-bytes> | |
152 md5sum \"<checksum\" | |
153 type single | |
154 ) | |
155 ... | |
156 )) | |
157 | |
158 For version information, it is assumed things are listed in most | |
159 recent to least recent -- in other words, the version names don't have to | |
160 be lexically ordered. It is debatable if it makes sense to have more than | |
161 one version of a package available.") | |
162 | |
163 (defcustom package-get-dir (temp-directory) | |
164 "*Where to store temporary files for staging." | |
165 :tag "Temporary directory" | |
166 :type 'directory | |
167 :group 'package-get) | |
168 | |
169 (define-widget 'host-name 'string | |
170 "A Host name." | |
171 :tag "Host") | |
172 | |
173 (defcustom package-get-remote nil | |
174 "*List of remote sites to contact for downloading packages. | |
175 List format is '(site-name directory-on-site). Each site is tried in | |
176 order until the package is found. As a special case, `site-name' can be | |
177 `nil', in which case `directory-on-site' is treated as a local directory." | |
178 :tag "Package repository" | |
179 :type '(repeat (choice (list :tag "Local" (const :tag "Local" nil) directory ) | |
180 (list :tag "Remote" host-name directory) )) | |
181 :group 'package-get) | |
182 | |
183 ;;;###autoload | |
184 (defcustom package-get-download-sites | |
185 '( | |
186 ;; North America | |
187 ("xemacs.org" "ftp.xemacs.org" "pub/xemacs/packages") | |
188 ("cso.uiuc.edu" "ftp.cso.uiuc.edu" "pub/packages/xemacs/packages") | |
189 | |
190 ;; South America | |
191 ("unicamp.br" "ftp.unicamp.br" "pub/xemacs/packages") | |
192 | |
193 ;; Europe | |
194 ("sunsite.cnlab-switch.ch" "sunsite.cnlab-switch.ch" "mirror/xemacs/packages") | |
195 ("tu-darmstadt.de" "ftp.tu-darmstadt.de" "pub/editors/xemacs/packages") | |
196 ("sunsite.auc.dk" "sunsite.auc.dk" "pub/emacs/xemacs/packages") | |
197 ("pasteur.fr" "ftp.pasteur.fr" "pub/computing/xemacs/packages") | |
198 ("cenatls.cena.dgac.fr" "ftp.cenatls.cena.dgac.fr" "pub/Emacs/xemacs/packages") | |
199 ("kfki.hu" "ftp.kfki.hu" "pub/packages/xemacs/packages") | |
200 ("uniroma2.it" "ftp.uniroma2.it" "unix/misc/dist/XEMACS/packages") | |
201 ("icm.edu.pl" "ftp.icm.edu.pl" "pub/unix/editors/xemacs/packages") | |
202 ("sunet.se" "ftp.sunet.se" "pub/gnu/xemacs/packages") | |
203 ("doc.ic.ac.uk" "sunsite.doc.ic.ac.uk" "packages/xemacs/packages") | |
204 ("srcc.msu.su" "ftp1.srcc.msu.su" "mirror/ftp.xemacs.org/packages") | |
205 | |
206 ;; Asia | |
207 ("usyd.edu.au" "ftp.usyd.edu.au" "pub/Xemacs/packages") | |
208 ("netlab.is.tsukuba.ac.jp" "ftp.netlab.is.tsukuba.ac.jp" "pub/GNU/xemacs/packages") | |
209 ("jaist.ac.jp" "ftp.jaist.ac.jp" "pub/GNU/xemacs/packages") | |
210 ("ring.aist.go.jp" "ring.aist.go.jp" "pub/text/xemacs/packages") | |
211 ("ring.asahi-net.or.jp" "ring.asahi-net.or.jp" "pub/text/xemacs/packages") | |
212 ("SunSITE.sut.ac.jp" "SunSITE.sut.ac.jp" "pub/archives/packages/xemacs/packages") | |
213 ("dti.ad.jp" "ftp.dti.ad.jp" "pub/unix/editor/xemacs/packages") | |
214 ("kreonet.re.kr" "ftp.kreonet.re.kr" "pub/tools/emacs/xemacs/packages") | |
215 ) | |
216 "*List of remote sites available for downloading packages. | |
217 List format is '(site-description site-name directory-on-site). | |
218 SITE-DESCRIPTION is a textual description of the site. SITE-NAME | |
219 is the internet address of the download site. DIRECTORY-ON-SITE | |
220 is the directory on the site in which packages may be found. | |
221 This variable is used to initialize `package-get-remote', the | |
222 variable actually used to specify package download sites." | |
223 :tag "Package download sites" | |
224 :type '(repeat (list hostname directory)) | |
225 :group 'package-get) | |
226 | |
227 (defcustom package-get-remove-copy t | |
228 "*After copying and installing a package, if this is t, then remove the | |
229 copy. Otherwise, keep it around." | |
230 :type 'boolean | |
231 :group 'package-get) | |
232 | |
233 ;; #### it may make sense for this to be a list of names. | |
234 ;; #### also, should we rename "*base*" to "*index*" or "*db*"? | |
235 ;; "base" is a pretty poor name. | |
236 (defcustom package-get-base-filename "package-index.LATEST.pgp" | |
237 "*Name of the default package-get database file. | |
238 This may either be a relative path, in which case it is interpreted | |
239 with respect to `package-get-remote', or an absolute path." | |
240 :type 'file | |
241 :group 'package-get) | |
242 | |
243 (defvar package-get-user-index-filename | |
244 (paths-construct-path (list user-init-directory package-get-base-filename)) | |
245 "Name for the user-specific location of the package-get database file.") | |
246 | |
247 (defcustom package-get-always-update nil | |
248 "*If Non-nil always make sure we are using the latest package index (base). | |
249 Otherwise respect the `force-current' argument of `package-get-require-base'." | |
250 :type 'boolean | |
251 :group 'package-get) | |
252 | |
253 (defcustom package-get-require-signed-base-updates t | |
254 "*If set to a non-nil value, require explicit user confirmation for updates | |
255 to the package-get database which cannot have their signature verified via PGP. | |
256 When nil, updates which are not PGP signed are allowed without confirmation." | |
257 :type 'boolean | |
258 :group 'package-get) | |
259 | |
260 (defvar package-get-was-current nil | |
261 "Non-nil we did our best to fetch a current database.") | |
262 | |
263 | |
264 ;Shouldn't this be in package-ui? | |
265 ;;;###autoload | |
266 (defun package-get-download-menu () | |
267 "Build the `Add Download Site' menu." | |
268 (mapcar (lambda (site) | |
269 (vector (car site) | |
270 `(if (member (quote ,(cdr site)) | |
271 package-get-remote) | |
272 (setq package-get-remote | |
273 (delete (quote ,(cdr site)) package-get-remote)) | |
274 (package-ui-add-site (quote ,(cdr site)))) | |
275 :style 'toggle | |
276 :selected `(member (quote ,(cdr site)) | |
277 package-get-remote))) | |
278 package-get-download-sites)) | |
279 | |
280 ;;;###autoload | |
281 (defun package-get-require-base (&optional force-current) | |
282 "Require that a package-get database has been loaded. | |
283 If the optional FORCE-CURRENT argument or the value of | |
284 `package-get-always-update' is Non-nil, try to update the database | |
285 from a location in `package-get-remote'. Otherwise a local copy is used | |
286 if available and remote access is never done. | |
287 | |
288 Please use FORCE-CURRENT only when the user is explictly dealing with packages | |
289 and remote access is likely in the near future." | |
290 (setq force-current (or force-current package-get-always-update)) | |
291 (unless (and (boundp 'package-get-base) | |
292 package-get-base | |
293 (or (not force-current) package-get-was-current)) | |
294 (package-get-update-base nil force-current)) | |
295 (if (or (not (boundp 'package-get-base)) | |
296 (not package-get-base)) | |
297 (error "Package-get database not loaded") | |
298 (setq package-get-was-current force-current))) | |
299 | |
300 (defconst package-get-pgp-signed-begin-line "^-----BEGIN PGP SIGNED MESSAGE-----" | |
301 "Text for start of PGP signed messages.") | |
302 (defconst package-get-pgp-signature-begin-line "^-----BEGIN PGP SIGNATURE-----" | |
303 "Text for beginning of PGP signature.") | |
304 (defconst package-get-pgp-signature-end-line "^-----END PGP SIGNATURE-----" | |
305 "Text for end of PGP signature.") | |
306 | |
307 ;;;###autoload | |
308 (defun package-get-update-base-entry (entry) | |
309 "Update an entry in `package-get-base'." | |
310 (let ((existing (assq (car entry) package-get-base))) | |
311 (if existing | |
312 (setcdr existing (cdr entry)) | |
313 (setq package-get-base (cons entry package-get-base)) | |
314 (package-get-custom-add-entry (car entry) (car (cdr entry)))))) | |
315 | |
316 (defun package-get-locate-file (file &optional nil-if-not-found no-remote) | |
317 "Locate an existing FILE with respect to `package-get-remote'. | |
318 If FILE is an absolute path or is not found, simply return FILE. | |
319 If optional argument NIL-IF-NOT-FOUND is non-nil, return nil | |
320 if FILE can not be located. | |
321 If NO-REMOTE is non-nil never search remote locations." | |
322 (if (file-name-absolute-p file) | |
323 file | |
324 (let ((entries package-get-remote) | |
325 (expanded nil)) | |
326 (while entries | |
327 (unless (and no-remote (caar entries)) | |
328 (let ((expn (package-get-remote-filename (car entries) file))) | |
329 (if (and expn (file-exists-p expn)) | |
330 (setq entries nil | |
331 expanded expn)))) | |
332 (setq entries (cdr entries))) | |
333 (or expanded | |
334 (and (not nil-if-not-found) | |
335 file))))) | |
336 | |
337 (defun package-get-locate-index-file (no-remote) | |
338 "Locate the package-get index file. Do not return remote paths if NO-REMOTE | |
339 is non-nil." | |
340 (or (package-get-locate-file package-get-base-filename t no-remote) | |
341 (if (file-exists-p package-get-user-index-filename) | |
342 package-get-user-index-filename))) | |
343 | |
344 (defun package-get-maybe-save-index (filename) | |
345 "Offer to save the current buffer as the local package index file, | |
346 if different." | |
347 (let ((location (package-get-locate-index-file t))) | |
348 (unless (and filename (equal filename location)) | |
349 (unless (and location | |
350 (equal (md5 (current-buffer)) | |
351 (with-temp-buffer | |
352 (insert-file-contents-literally location) | |
353 (md5 (current-buffer))))) | |
354 (unless (and location (file-writable-p location)) | |
355 (setq location package-get-user-index-filename)) | |
356 (when (y-or-n-p (concat "Update package index in" location "? ")) | |
357 (write-file location)))))) | |
358 | |
359 | |
360 ;;;###autoload | |
361 (defun package-get-update-base (&optional db-file force-current) | |
362 "Update the package-get database file with entries from DB-FILE. | |
363 Unless FORCE-CURRENT is non-nil never try to update the database." | |
364 (interactive | |
365 (let ((dflt (package-get-locate-index-file nil))) | |
366 (list (read-file-name "Load package-get database: " | |
367 (file-name-directory dflt) | |
368 dflt | |
369 t | |
370 (file-name-nondirectory dflt))))) | |
371 (setq db-file (expand-file-name (or db-file | |
372 (package-get-locate-index-file | |
373 (not force-current))))) | |
374 (if (not (file-exists-p db-file)) | |
375 (error "Package-get database file `%s' does not exist" db-file)) | |
376 (if (not (file-readable-p db-file)) | |
377 (error "Package-get database file `%s' not readable" db-file)) | |
378 (let ((buf (get-buffer-create "*package database*"))) | |
379 (unwind-protect | |
380 (save-excursion | |
381 (set-buffer buf) | |
382 (erase-buffer buf) | |
383 (insert-file-contents-internal db-file) | |
384 (package-get-update-base-from-buffer buf) | |
385 (if (file-remote-p db-file) | |
386 (package-get-maybe-save-index db-file))) | |
387 (kill-buffer buf)))) | |
388 | |
389 ;;;###autoload | |
390 (defun package-get-update-base-from-buffer (&optional buf) | |
391 "Update the package-get database with entries from BUFFER. | |
392 BUFFER defaults to the current buffer. This command can be | |
393 used interactively, for example from a mail or news buffer." | |
394 (interactive) | |
395 (setq buf (or buf (current-buffer))) | |
396 (let (content-beg content-end beg end) | |
397 (save-excursion | |
398 (set-buffer buf) | |
399 (goto-char (point-min)) | |
400 (setq content-beg (point)) | |
401 (setq content-end (save-excursion (goto-char (point-max)) (point))) | |
402 (when (re-search-forward package-get-pgp-signed-begin-line nil t) | |
403 (setq beg (match-beginning 0)) | |
404 (setq content-beg (match-end 0))) | |
405 (when (re-search-forward package-get-pgp-signature-begin-line nil t) | |
406 (setq content-end (match-beginning 0))) | |
407 (when (re-search-forward package-get-pgp-signature-end-line nil t) | |
408 (setq end (point))) | |
409 (if (not (and content-beg content-end beg end)) | |
410 (or (not package-get-require-signed-base-updates) | |
411 (yes-or-no-p "Package-get entries not PGP signed, continue? ") | |
412 (error "Package-get database not updated"))) | |
413 (if (and content-beg content-end beg end) | |
414 (if (not (condition-case nil | |
415 (or (fboundp 'mc-pgp-verify-region) | |
416 (load-library "mc-pgp") | |
417 (fboundp 'mc-pgp-verify-region)) | |
418 (error nil))) | |
419 (or (not package-get-require-signed-base-updates) | |
420 (yes-or-no-p | |
421 "No mailcrypt; can't verify package-get DB signature, continue? ") | |
422 (error "Package-get database not updated")))) | |
423 (if (and beg end | |
424 (fboundp 'mc-pgp-verify-region) | |
425 (or (not | |
426 (condition-case err | |
427 (mc-pgp-verify-region beg end) | |
428 (file-error | |
429 (and (string-match "No such file" (nth 2 err)) | |
430 (or (not package-get-require-signed-base-updates) | |
431 (yes-or-no-p | |
432 (concat "Can't find PGP, continue without " | |
433 "package-get DB verification? "))))) | |
434 (t nil))))) | |
435 (error "Package-get PGP signature failed to verify")) | |
436 ;; ToDo: We shoud call package-get-maybe-save-index on the region | |
437 (package-get-update-base-entries content-beg content-end) | |
438 (message "Updated package-get database")))) | |
439 | |
440 (defun package-get-update-base-entries (beg end) | |
441 "Update the package-get database with the entries found between | |
442 BEG and END in the current buffer." | |
443 (save-excursion | |
444 (goto-char beg) | |
445 (if (not (re-search-forward "^(package-get-update-base-entry" nil t)) | |
446 (error "Buffer does not contain package-get database entries")) | |
447 (beginning-of-line) | |
448 (let ((count 0)) | |
449 (while (and (< (point) end) | |
450 (re-search-forward "^(package-get-update-base-entry" nil t)) | |
451 (beginning-of-line) | |
452 (let ((entry (read (current-buffer)))) | |
453 (if (or (not (consp entry)) | |
454 (not (eq (car entry) 'package-get-update-base-entry))) | |
455 (error "Invalid package-get database entry found")) | |
456 (package-get-update-base-entry | |
457 (car (cdr (car (cdr entry))))) | |
458 (setq count (1+ count)))) | |
459 (message "Got %d package-get database entries" count)))) | |
460 | |
461 ;;;###autoload | |
462 (defun package-get-save-base (file) | |
463 "Write the package-get database to FILE. | |
464 | |
465 Note: This database will be unsigned of course." | |
466 (interactive "FSave package-get database to: ") | |
467 (package-get-require-base t) | |
468 (let ((buf (get-buffer-create "*package database*"))) | |
469 (unwind-protect | |
470 (save-excursion | |
471 (set-buffer buf) | |
472 (erase-buffer buf) | |
473 (goto-char (point-min)) | |
474 (let ((entries package-get-base) entry plist) | |
475 (insert ";; Package Index file -- Do not edit manually.\n") | |
476 (insert ";;;@@@\n") | |
477 (while entries | |
478 (setq entry (car entries)) | |
479 (setq plist (car (cdr entry))) | |
480 (insert "(package-get-update-base-entry (quote\n") | |
481 (insert (format "(%s\n" (symbol-name (car entry)))) | |
482 (while plist | |
483 (insert (format " %s%s %S\n" | |
484 (if (eq plist (car (cdr entry))) "(" " ") | |
485 (symbol-name (car plist)) | |
486 (car (cdr plist)))) | |
487 (setq plist (cdr (cdr plist)))) | |
488 (insert "))\n))\n;;;@@@\n") | |
489 (setq entries (cdr entries)))) | |
490 (insert ";; Package Index file ends here\n") | |
491 (write-region (point-min) (point-max) file)) | |
492 (kill-buffer buf)))) | |
493 | |
494 (defun package-get-interactive-package-query (get-version package-symbol) | |
495 "Perform interactive querying for package and optional version. | |
496 Query for a version if GET-VERSION is non-nil. Return package name as | |
497 a symbol instead of a string if PACKAGE-SYMBOL is non-nil. | |
498 The return value is suitable for direct passing to `interactive'." | |
499 (package-get-require-base t) | |
500 (let ( (table (mapcar '(lambda (item) | |
501 (let ( (name (symbol-name (car item))) ) | |
502 (cons name name) | |
503 )) | |
504 package-get-base)) | |
505 package package-symbol default-version version) | |
506 (save-window-excursion | |
507 (setq package (completing-read "Package: " table nil t)) | |
508 (setq package-symbol (intern package)) | |
509 (if get-version | |
510 (progn | |
511 (setq default-version | |
512 (package-get-info-prop | |
513 (package-get-info-version | |
514 (package-get-info-find-package package-get-base | |
515 package-symbol) nil) | |
516 'version)) | |
517 (while (string= | |
518 (setq version (read-string "Version: " default-version)) | |
519 "") | |
520 ) | |
521 (if package-symbol | |
522 (list package-symbol version) | |
523 (list package version)) | |
524 ) | |
525 (if package-symbol | |
526 (list package-symbol) | |
527 (list package))) | |
528 ))) | |
529 | |
530 ;;;###autoload | |
531 (defun package-get-delete-package (package &optional pkg-topdir) | |
532 "Delete an installation of PACKAGE below directory PKG-TOPDIR. | |
533 PACKAGE is a symbol, not a string. | |
534 This is just an interactive wrapper for `package-admin-delete-binary-package'." | |
535 (interactive (package-get-interactive-package-query nil t)) | |
536 (package-admin-delete-binary-package package pkg-topdir)) | |
537 | |
538 ;;;###autoload | |
539 (defun package-get-update-all () | |
540 "Fetch and install the latest versions of all currently installed packages." | |
541 (interactive) | |
542 (package-get-require-base t) | |
543 ;; Load a fresh copy | |
544 (catch 'exit | |
545 (mapcar (lambda (pkg) | |
546 (if (not (package-get (car pkg) nil 'never)) | |
547 (throw 'exit nil) ;; Bail out if error detected | |
548 )) | |
549 packages-package-list))) | |
550 | |
551 ;;;###autoload | |
552 (defun package-get-all (package version &optional fetched-packages install-dir) | |
553 "Fetch PACKAGE with VERSION and all other required packages. | |
554 Uses `package-get-base' to determine just what is required and what | |
555 package provides that functionality. If VERSION is nil, retrieves | |
556 latest version. Optional argument FETCHED-PACKAGES is used to keep | |
557 track of packages already fetched. Optional argument INSTALL-DIR, | |
558 if non-nil, specifies the package directory where fetched packages | |
559 should be installed. | |
560 | |
561 Returns nil upon error." | |
562 (interactive (package-get-interactive-package-query t nil)) | |
563 (let* ((the-package (package-get-info-find-package package-get-base | |
564 package)) | |
565 (this-package (package-get-info-version | |
566 the-package version)) | |
567 (this-requires (package-get-info-prop this-package 'requires)) | |
568 ) | |
569 (catch 'exit | |
570 (setq version (package-get-info-prop this-package 'version)) | |
571 (unless (package-get-installedp package version) | |
572 (if (not (package-get package version nil install-dir)) | |
573 (progn | |
574 (setq fetched-packages nil) | |
575 (throw 'exit nil)))) | |
576 (setq fetched-packages | |
577 (append (list package) | |
578 (package-get-info-prop this-package 'provides) | |
579 fetched-packages)) | |
580 ;; grab everything that this package requires plus recursively | |
581 ;; grab everything that the requires require. Keep track | |
582 ;; in `fetched-packages' the list of things provided -- this | |
583 ;; keeps us from going into a loop | |
584 (while this-requires | |
585 (if (not (member (car this-requires) fetched-packages)) | |
586 (let* ((reqd-package (package-get-package-provider | |
587 (car this-requires) t)) | |
588 (reqd-version (cadr reqd-package)) | |
589 (reqd-name (car reqd-package))) | |
590 (if (null reqd-name) | |
591 (error "Unable to find a provider for %s" | |
592 (car this-requires))) | |
593 (if (not (setq fetched-packages | |
594 (package-get-all reqd-name reqd-version | |
595 fetched-packages | |
596 install-dir))) | |
597 (throw 'exit nil))) | |
598 ) | |
599 (setq this-requires (cdr this-requires))) | |
600 ) | |
601 fetched-packages | |
602 )) | |
603 | |
604 ;;;###autoload | |
605 (defun package-get-dependencies (packages) | |
606 "Compute dependencies for PACKAGES. | |
607 Uses `package-get-base' to determine just what is required and what | |
608 package provides that functionality. Returns the list of packages | |
609 required by PACKAGES." | |
610 (package-get-require-base t) | |
611 (let ((orig-packages packages) | |
612 dependencies provided) | |
613 (while packages | |
614 (let* ((package (car packages)) | |
615 (the-package (package-get-info-find-package | |
616 package-get-base package)) | |
617 (this-package (package-get-info-version | |
618 the-package nil)) | |
619 (this-requires (package-get-info-prop this-package 'requires)) | |
620 (new-depends (set-difference | |
621 (mapcar | |
622 #'(lambda (reqd) | |
623 (let* ((reqd-package (package-get-package-provider reqd)) | |
624 (reqd-version (cadr reqd-package)) | |
625 (reqd-name (car reqd-package))) | |
626 (if (null reqd-name) | |
627 (error "Unable to find a provider for %s" reqd)) | |
628 reqd-name)) | |
629 this-requires) | |
630 dependencies)) | |
631 (this-provides (package-get-info-prop this-package 'provides))) | |
632 (setq dependencies | |
633 (union dependencies new-depends)) | |
634 (setq provided | |
635 (union provided (union (list package) this-provides))) | |
636 (setq packages | |
637 (union new-depends (cdr packages))))) | |
638 (set-difference dependencies orig-packages))) | |
639 | |
640 (defun package-get-load-package-file (lispdir file) | |
641 (let (pathname) | |
642 (setq pathname (expand-file-name file lispdir)) | |
643 (condition-case err | |
644 (progn | |
645 (load pathname t) | |
646 t) | |
647 (t | |
648 (message "Error loading package file \"%s\" %s!" pathname err) | |
649 nil)) | |
650 )) | |
651 | |
652 (defun package-get-init-package (lispdir) | |
653 "Initialize the package. | |
654 This really assumes that the package has never been loaded. Updating | |
655 a newer package can cause problems, due to old, obsolete functions in | |
656 the old package. | |
657 | |
658 Return `t' upon complete success, `nil' if any errors occurred." | |
659 (progn | |
660 (if (and lispdir | |
661 (file-accessible-directory-p lispdir)) | |
662 (progn | |
663 ;; Add lispdir to load-path if it doesn't already exist. | |
664 ;; NOTE: this does not take symlinks, etc., into account. | |
665 (if (let ( (dirs load-path) ) | |
666 (catch 'done | |
667 (while dirs | |
668 (if (string-equal (car dirs) lispdir) | |
669 (throw 'done nil)) | |
670 (setq dirs (cdr dirs)) | |
671 ) | |
672 t)) | |
673 (setq load-path (cons lispdir load-path))) | |
674 (if (not (package-get-load-package-file lispdir "auto-autoloads")) | |
675 (package-get-load-package-file lispdir "_pkg")) | |
676 t) | |
677 nil) | |
678 )) | |
679 | |
680 ;;;###autoload | |
681 (defun package-get (package &optional version conflict install-dir) | |
682 "Fetch PACKAGE from remote site. | |
683 Optional arguments VERSION indicates which version to retrieve, nil | |
684 means most recent version. CONFLICT indicates what happens if the | |
685 package is already installed. Valid values for CONFLICT are: | |
686 'always always retrieve the package even if it is already installed | |
687 'never do not retrieve the package if it is installed. | |
688 INSTALL-DIR, if non-nil, specifies the package directory where | |
689 fetched packages should be installed. | |
690 | |
691 The value of `package-get-base' is used to determine what files should | |
692 be retrieved. The value of `package-get-remote' is used to determine | |
693 where a package should be retrieved from. The sites are tried in | |
694 order so one is better off listing easily reached sites first. | |
695 | |
696 Once the package is retrieved, its md5 checksum is computed. If that | |
697 sum does not match that stored in `package-get-base' for this version | |
698 of the package, an error is signalled. | |
699 | |
700 Returns `t' upon success, the symbol `error' if the package was | |
701 successfully installed but errors occurred during initialization, or | |
702 `nil' upon error." | |
703 (interactive (package-get-interactive-package-query nil t)) | |
704 (catch 'skip-update | |
705 (let* ((this-package | |
706 (package-get-info-version | |
707 (package-get-info-find-package package-get-base | |
708 package) version)) | |
709 (latest (package-get-info-prop this-package 'version)) | |
710 (installed (package-get-key package :version)) | |
711 (this-requires (package-get-info-prop this-package 'requires)) | |
712 (found nil) | |
713 (search-dirs package-get-remote) | |
714 (base-filename (package-get-info-prop this-package 'filename)) | |
715 (package-status t) | |
716 filenames full-package-filename) | |
717 (if (null this-package) | |
718 (if package-get-remote | |
719 (error "Couldn't find package %s with version %s" | |
720 package version) | |
721 (error "No download sites or local package locations specified."))) | |
722 (if (null base-filename) | |
723 (error "No filename associated with package %s, version %s" | |
724 package version)) | |
725 (setq install-dir | |
726 (package-admin-get-install-dir package install-dir | |
727 (or (eq package 'mule-base) (memq 'mule-base this-requires)))) | |
728 | |
729 ;; If they asked for the latest using version=nil, don't get an older | |
730 ;; version than we already have. | |
731 (if installed | |
732 (if (> (if (stringp installed) | |
733 (string-to-number installed) | |
734 installed) | |
735 (if (stringp latest) | |
736 (string-to-number latest) | |
737 latest)) | |
738 (if (not (null version)) | |
739 (warn "Installing %s package version %s, you had a newer version %s" | |
740 package latest installed) | |
741 (warn "Skipping %s package, you have a newer version %s" | |
742 package installed) | |
743 (throw 'skip-update t)))) | |
744 | |
745 ;; Contrive a list of possible package filenames. | |
746 ;; Ugly. Is there a better way to do this? | |
747 (setq filenames (cons base-filename nil)) | |
748 (if (string-match "^\\(..*\\)\.tar\.gz$" base-filename) | |
749 (setq filenames (append filenames | |
750 (list (concat (match-string 1 base-filename) | |
751 ".tgz"))))) | |
752 | |
753 (setq version latest) | |
754 (unless (and (eq conflict 'never) | |
755 (package-get-installedp package version)) | |
756 ;; Find the package from the search list in package-get-remote | |
757 ;; and copy it into the staging directory. Then validate | |
758 ;; the checksum. Finally, install the package. | |
759 (catch 'done | |
760 (let (search-filenames current-dir-entry host dir current-filename | |
761 dest-filename) | |
762 ;; In each search directory ... | |
763 (while search-dirs | |
764 (setq current-dir-entry (car search-dirs) | |
765 host (car current-dir-entry) | |
766 dir (car (cdr current-dir-entry)) | |
767 search-filenames filenames | |
768 ) | |
769 | |
770 ;; Look for one of the possible package filenames ... | |
771 (while search-filenames | |
772 (setq current-filename (car search-filenames) | |
773 dest-filename (package-get-staging-dir current-filename)) | |
774 (cond | |
775 ;; No host means look on the current system. | |
776 ( (null host) | |
777 (setq full-package-filename | |
778 (substitute-in-file-name | |
779 (expand-file-name current-filename | |
780 (file-name-as-directory dir)))) | |
781 ) | |
782 | |
783 ;; If it's already on the disk locally, and the size is | |
784 ;; greater than zero ... | |
785 ( (and (file-exists-p dest-filename) | |
786 (let (attrs) | |
787 ;; file-attributes could return -1 for LARGE files, | |
788 ;; but, hopefully, packages won't be that large. | |
789 (and (setq attrs (file-attributes dest-filename)) | |
790 (> (nth 7 attrs) 0)))) | |
791 (setq full-package-filename dest-filename) | |
792 ) | |
793 | |
794 ;; If the file exists on the remote system ... | |
795 ( (file-exists-p (package-get-remote-filename | |
796 current-dir-entry current-filename)) | |
797 ;; Get it | |
798 (setq full-package-filename dest-filename) | |
799 (message "Retrieving package `%s' ..." | |
800 current-filename) | |
801 (sit-for 0) | |
802 (copy-file (package-get-remote-filename current-dir-entry | |
803 current-filename) | |
804 full-package-filename t) | |
805 ) | |
806 ) | |
807 | |
808 ;; If we found it, we're done. | |
809 (if (and full-package-filename | |
810 (file-exists-p full-package-filename)) | |
811 (throw 'done nil)) | |
812 ;; Didn't find it. Try the next possible filename. | |
813 (setq search-filenames (cdr search-filenames)) | |
814 ) | |
815 ;; Try looking in the next possible directory ... | |
816 (setq search-dirs (cdr search-dirs)) | |
817 ) | |
818 )) | |
819 | |
820 (if (or (not full-package-filename) | |
821 (not (file-exists-p full-package-filename))) | |
822 (if package-get-remote | |
823 (error "Unable to find file %s" base-filename) | |
824 (error | |
825 "No download sites or local package locations specified."))) | |
826 ;; Validate the md5 checksum | |
827 ;; Doing it with XEmacs removes the need for an external md5 program | |
828 (message "Validating checksum for `%s'..." package) (sit-for 0) | |
829 (with-temp-buffer | |
830 ;; What ever happened to i-f-c-literally | |
831 (let (file-name-handler-alist) | |
832 (insert-file-contents-internal full-package-filename)) | |
833 (if (not (string= (md5 (current-buffer)) | |
834 (package-get-info-prop this-package | |
835 'md5sum))) | |
836 (error "Package %s does not match md5 checksum" base-filename))) | |
837 | |
838 (package-admin-delete-binary-package package install-dir) | |
839 | |
840 (message "Installing package `%s' ..." package) (sit-for 0) | |
841 (let ((status | |
842 (package-admin-add-binary-package full-package-filename | |
843 install-dir))) | |
844 (if (= status 0) | |
845 (progn | |
846 ;; clear messages so that only messages from | |
847 ;; package-get-init-package are seen, below. | |
848 (clear-message) | |
849 (if (package-get-init-package (package-admin-get-lispdir | |
850 install-dir package)) | |
851 (progn | |
852 (message "Added package `%s'" package) | |
853 (sit-for 0) | |
854 ) | |
855 (progn | |
856 ;; display message only if there isn't already one. | |
857 (if (not (current-message)) | |
858 (progn | |
859 (message "Added package `%s' (errors occurred)" | |
860 package) | |
861 (sit-for 0) | |
862 )) | |
863 (if package-status | |
864 (setq package-status 'errors)) | |
865 )) | |
866 ) | |
867 (message "Installation of package %s failed." base-filename) | |
868 (sit-for 0) | |
869 (switch-to-buffer package-admin-temp-buffer) | |
870 (setq package-status nil) | |
871 )) | |
872 (setq found t)) | |
873 (if (and found package-get-remove-copy) | |
874 (delete-file full-package-filename)) | |
875 package-status | |
876 ))) | |
877 | |
878 (defun package-get-info-find-package (which name) | |
879 "Look in WHICH for the package called NAME and return all the info | |
880 associated with it. See `package-get-base' for info on the format | |
881 returned. | |
882 | |
883 To access fields returned from this, use | |
884 `package-get-info-version' to return information about particular a | |
885 version. Use `package-get-info-find-prop' to find particular property | |
886 from a version returned by `package-get-info-version'." | |
887 (interactive "xPackage list: \nsPackage Name: ") | |
888 (if which | |
889 (if (eq (caar which) name) | |
890 (cdar which) | |
891 (if (cdr which) | |
892 (package-get-info-find-package (cdr which) name))))) | |
893 | |
894 (defun package-get-info-version (package version) | |
895 "In PACKAGE, return the plist associated with a particular VERSION of the | |
896 package. PACKAGE is typically as returned by | |
897 `package-get-info-find-package'. If VERSION is nil, then return the | |
898 first (aka most recent) version. Use `package-get-info-find-prop' | |
899 to retrieve a particular property from the value returned by this." | |
900 (interactive (package-get-interactive-package-query t t)) | |
901 (while (and version package (not (string= (plist-get (car package) 'version) version))) | |
902 (setq package (cdr package))) | |
903 (if package (car package))) | |
904 | |
905 (defun package-get-info-prop (package-version property) | |
906 "In PACKAGE-VERSION, return the value associated with PROPERTY. | |
907 PACKAGE-VERSION is typically returned by `package-get-info-version' | |
908 and PROPERTY is typically (although not limited to) one of the | |
909 following: | |
910 | |
911 version - version of this package | |
912 provides - list of symbols provided | |
913 requires - list of symbols that are required. | |
914 These in turn are provided by other packages. | |
915 size - size of the bundled package | |
916 md5sum - computed md5 checksum" | |
917 (interactive "xPackage Version: \nSProperty") | |
918 (plist-get package-version property)) | |
919 | |
920 (defun package-get-info-version-prop (package-list package version property) | |
921 "In PACKAGE-LIST, search for PACKAGE with this VERSION and return | |
922 PROPERTY value." | |
923 (package-get-info-prop | |
924 (package-get-info-version | |
925 (package-get-info-find-package package-list package) version) property)) | |
926 | |
927 (defun package-get-set-version-prop (package-list package version | |
928 property value) | |
929 "A utility to make it easier to add a VALUE for a specific PROPERTY | |
930 in this VERSION of a specific PACKAGE kept in the PACKAGE-LIST. | |
931 Returns the modified PACKAGE-LIST. Any missing fields are created." | |
932 ) | |
933 | |
934 (defun package-get-staging-dir (filename) | |
935 "Return a good place to stash FILENAME when it is retrieved. | |
936 Use `package-get-dir' for directory to store stuff. | |
937 Creates `package-get-dir' it it doesn't exist." | |
938 (interactive "FPackage filename: ") | |
939 (if (not (file-exists-p package-get-dir)) | |
940 (make-directory package-get-dir)) | |
941 (expand-file-name | |
942 (file-name-nondirectory (or (and (fboundp 'efs-ftp-path) | |
943 (nth 2 (efs-ftp-path filename))) | |
944 filename)) | |
945 (file-name-as-directory package-get-dir))) | |
946 | |
947 (defun package-get-remote-filename (search filename) | |
948 "Return FILENAME as a remote filename. | |
949 It first checks if FILENAME already is a remote filename. If it is | |
950 not, then it uses the (car search) as the remote site-name and the (cadr | |
951 search) as the remote-directory and concatenates filename. In other | |
952 words | |
953 site-name:remote-directory/filename. | |
954 | |
955 If (car search) is nil, (cadr search is interpreted as a local directory). | |
956 " | |
957 (if (file-remote-p filename) | |
958 filename | |
959 (let ((dir (cadr search))) | |
960 (concat (when (car search) | |
961 (concat | |
962 (if (string-match "@" (car search)) | |
963 "/" | |
964 "/anonymous@") | |
965 (car search) ":")) | |
966 (if (string-match "/$" dir) | |
967 dir | |
968 (concat dir "/")) | |
969 filename)))) | |
970 | |
971 | |
972 (defun package-get-installedp (package version) | |
973 "Determine if PACKAGE with VERSION has already been installed. | |
974 I'm not sure if I want to do this by searching directories or checking | |
975 some built in variables. For now, use packages-package-list." | |
976 ;; Use packages-package-list which contains name and version | |
977 (equal (plist-get | |
978 (package-get-info-find-package packages-package-list | |
979 package) ':version) | |
980 (if (floatp version) version (string-to-number version)))) | |
981 | |
982 ;;;###autoload | |
983 (defun package-get-package-provider (sym &optional force-current) | |
984 "Search for a package that provides SYM and return the name and | |
985 version. Searches in `package-get-base' for SYM. If SYM is a | |
986 consp, then it must match a corresponding (provide (SYM VERSION)) from | |
987 the package. | |
988 | |
989 If FORCE-CURRENT is non-nil make sure the database is up to date. This might | |
990 lead to Emacs accessing remote sites." | |
991 (interactive "SSymbol: ") | |
992 (package-get-require-base force-current) | |
993 (let ((packages package-get-base) | |
994 (done nil) | |
995 (found nil)) | |
996 (while (and (not done) packages) | |
997 (let* ((this-name (caar packages)) | |
998 (this-package (cdr (car packages)))) ;strip off package name | |
999 (while (and (not done) this-package) | |
1000 (if (or (eq this-name sym) | |
1001 (eq (cons this-name | |
1002 (package-get-info-prop (car this-package) 'version)) | |
1003 sym) | |
1004 (member sym | |
1005 (package-get-info-prop (car this-package) 'provides))) | |
1006 (progn (setq done t) | |
1007 (setq found | |
1008 (list (caar packages) | |
1009 (package-get-info-prop (car this-package) 'version)))) | |
1010 (setq this-package (cdr this-package))))) | |
1011 (setq packages (cdr packages))) | |
1012 (when (interactive-p) | |
1013 (if found | |
1014 (message "%S" found) | |
1015 (message "No appropriate package found"))) | |
1016 found)) | |
1017 | |
1018 ;; | |
1019 ;; customize interfaces. | |
1020 ;; The group is in this file so that custom loads includes this file. | |
1021 ;; | |
1022 (defgroup packages nil | |
1023 "Configure XEmacs packages." | |
1024 :group 'emacs) | |
1025 | |
1026 ;;;###autoload | |
1027 (defun package-get-custom () | |
1028 "Fetch and install the latest versions of all customized packages." | |
1029 (interactive) | |
1030 (package-get-require-base t) | |
1031 (mapcar (lambda (pkg) | |
1032 (if (eval (intern (concat (symbol-name (car pkg)) "-package"))) | |
1033 (package-get (car pkg) nil)) | |
1034 t) | |
1035 package-get-base)) | |
1036 | |
1037 (defun package-get-ever-installed-p (pkg &optional notused) | |
1038 (string-match "-package$" (symbol-name pkg)) | |
1039 (custom-initialize-set | |
1040 pkg | |
1041 (if (package-get-info-find-package | |
1042 packages-package-list | |
1043 (intern (substring (symbol-name pkg) 0 (match-beginning 0)))) | |
1044 t))) | |
1045 | |
1046 (defvar package-get-custom-groups nil | |
1047 "List of package-get-custom groups") | |
1048 | |
1049 (defun package-get-custom-add-entry (package props) | |
1050 (let* ((category (plist-get props 'category)) | |
1051 (group (intern (concat category "-packages"))) | |
1052 (custom-var (intern (concat (symbol-name package) "-package"))) | |
1053 (description (plist-get props 'description))) | |
1054 (when (not (memq group package-get-custom-groups)) | |
1055 (setq package-get-custom-groups (cons group | |
1056 package-get-custom-groups)) | |
1057 (eval `(defgroup ,group nil | |
1058 ,(concat category " package group") | |
1059 :group 'packages))) | |
1060 (eval `(defcustom ,custom-var nil | |
1061 ,description | |
1062 :group ',group | |
1063 :initialize 'package-get-ever-installed-p | |
1064 :type 'boolean)))) | |
1065 | |
1066 | |
1067 (provide 'package-get) | |
1068 ;;; package-get.el ends here |