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