SXEmacs v22.1.17 is released!
[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 (HTTPS)"
215            "downloads.sxemacs.org" "xemacs-pkgs/packages" "https")
216           ))
217       ;; FTP Sites
218       ;; We don't have FTP (does anyone these days?) --SY
219       ;; ("SXEmacs Main Site (FTP)"
220       ;;  "ftp.sxemacs.org" "pub/packages" "ftp")
221       )
222   "*List of remote sites available for downloading packages.
223
224 List format is '(site-description site-name directory-on-site url-scheme).
225 SITE-DESCRIPTION is a textual description of the site.  SITE-NAME is
226 the internet address of the download site.  DIRECTORY-ON-SITE is the
227 directory on the site in which packages may be found.  URL-SCHEME is
228 the protocol such as `http', `ftp', etc.  This variable is used to
229 initialize `package-get-remote', the variable actually used to specify
230 package download sites."
231   :tag "Package download sites"
232   :type '(repeat (list (string :tag "Name")
233                        host-name directory url-scheme))
234   :group 'package-get)
235
236 ;;;###autoload
237 (defcustom package-get-site-release-download-sites nil
238   "*List of remote sites available for downloading \"Site Release\" packages.
239
240 List format is '(site-description site-name directory-on-site url-scheme).
241 SITE-DESCRIPTION is a textual description of the site.  SITE-NAME is
242 the internet address of the download site.  DIRECTORY-ON-SITE is the
243 directory on the site in which packages may be found.  URL-SCHEME is
244 the protocol such as `http', `ftp', etc.  This variable is used to
245 initialize `package-get-remote', the variable actually used to specify
246 package download sites."
247   :tag "Site Release Package download sites"
248   :type '(repeat (list (string :tag "Name")
249                        host-name directory url-scheme))
250   :group 'package-get)
251
252 (defcustom package-get-remove-copy t
253   "*After copying and installing a package, if this is t, then remove the
254 copy.  Otherwise, keep it around."
255   :type 'boolean
256   :group 'package-get)
257
258 ;; #### it may make sense for this to be a list of names.
259 ;; #### also, should we rename "*base*" to "*index*" or "*db*"?
260 ;;      "base" is a pretty poor name.
261 (defcustom package-get-base-filename "package-index"
262   "*Name of the default package-get database file.
263 This may either be a relative path, in which case it is interpreted
264 with respect to `package-get-remote', or an absolute path."
265   :type 'file
266   :group 'package-get)
267
268 (defcustom package-get-always-update nil
269   "*If Non-nil always make sure we are using the latest package index (base).
270 Otherwise respect the `force-current' argument of `package-get-require-base'."
271   :type 'boolean
272   :group 'package-get)
273
274 (defvar package-get-was-current nil
275   "Non-nil we did our best to fetch a current database.")
276
277 ;;;###autoload
278 (defun package-get-require-base (&optional force-current)
279   "Require that a package-get database has been loaded.
280 If the optional FORCE-CURRENT argument or the value of
281 `package-get-always-update' is Non-nil, try to update the database
282 from a location in `package-get-remote'. Otherwise a local copy is used
283 if available and remote access is never done.
284
285 Please use FORCE-CURRENT only when the user is explictly dealing with packages
286 and remote access is likely in the near future."
287   (setq force-current (or force-current package-get-always-update))
288   (unless (and (boundp 'package-get-base)
289                package-get-base
290                (or (not force-current) package-get-was-current))
291     (package-get-update-base nil force-current))
292   (if (or (not (boundp 'package-get-base))
293           (not package-get-base))
294       (error 'void-variable
295              "Package-get database not loaded")
296     (setq package-get-was-current force-current)))
297
298 ;;;###autoload
299 (defun package-get-update-base-entry (entry)
300   "Update an entry in `package-get-base'."
301   (let ((existing (assq (car entry) package-get-base)))
302     (if existing
303         (setcdr existing (cdr entry))
304       (setq package-get-base (cons entry package-get-base)))))
305
306 (defun package-get-locate-file (file &optional nil-if-not-found no-remote)
307   "Locate an existing FILE with respect to `package-get-remote'.
308 If FILE is an absolute path or is not found, simply return FILE.
309 If optional argument NIL-IF-NOT-FOUND is non-nil, return nil
310 if FILE can not be located.
311 If NO-REMOTE is non-nil never search remote locations."
312   (if (file-name-absolute-p file)
313       file
314     (let ((site package-get-remote)
315           (expanded nil))
316       (when site
317         (unless (and no-remote (caar (list site)))
318           (let ((expn (package-get-remote-filename (car (list site)) file)))
319             (if (and expn (file-exists-p expn))
320                 (setq site nil
321                       expanded expn)))))
322       (or expanded
323           (and (not nil-if-not-found)
324                file)))))
325
326 (defun package-get-locate-index-file (no-remote)
327   "Locate the package-get index file.
328
329 Do not return remote paths if NO-REMOTE is non-nil.  If the index
330 file doesn't exist in `package-get-package-index-file-location', ask
331 the user if one should be created using the index file in core as a
332 template."
333   (or (package-get-locate-file package-get-base-filename t no-remote)
334       (if (file-exists-p (expand-file-name package-get-base-filename
335                                            package-get-package-index-file-location))
336           (expand-file-name package-get-base-filename
337                             package-get-package-index-file-location)
338         (if (y-or-n-p (format "No index file, shall I create one in %s? "
339                               package-get-package-index-file-location))
340             (progn
341               (save-excursion
342                 (set-buffer
343                  (find-file-noselect (expand-file-name
344                                       package-get-base-filename
345                                       package-get-package-index-file-location)))
346                 (let ((coding-system-for-write 'binary))
347                   (erase-buffer)
348                   (insert-file-contents-literally
349                    (locate-data-file package-get-base-filename))
350                   (save-buffer (current-buffer))
351                   (kill-buffer (current-buffer))))
352               (expand-file-name package-get-base-filename
353                                 package-get-package-index-file-location))
354           (error 'search-failed
355                  "Can't locate a package index file.")))))
356
357 (defun package-get-maybe-save-index (filename)
358   "Offer to save the current buffer as the local package index file,
359 if different."
360   (let ((location (package-get-locate-index-file t)))
361     (unless (and filename (equal filename location))
362       (unless (and location
363                    (equal (md5 (current-buffer))
364                           (with-temp-buffer
365                             (insert-file-contents-literally location)
366                             (md5 (current-buffer)))))
367         (when (not (file-writable-p location))
368           (if (y-or-n-p (format "Sorry, %s is read-only, can I use %s? "
369                                 location user-packages-topdir))
370               (setq location (expand-file-name
371                               package-get-base-filename
372                               package-get-package-index-file-location))
373             (error 'file-error
374                    (format "%s is read-only" location))))
375         (when (y-or-n-p (concat "Update package index in " location "? "))
376           (let ((coding-system-for-write 'binary))
377             (write-file location)))))))
378
379 ;;;###autoload
380 (defun package-get-update-base (&optional db-file force-current)
381   "Update the package-get database file with entries from DB-FILE.
382 Unless FORCE-CURRENT is non-nil never try to update the database."
383   (interactive
384    (let* ((dflt (package-get-locate-index-file nil))
385           (match (not (string-match #r"^\(https?\|s?ftp\)://" dflt))))
386      (list (read-file-name "Load package-get database: "
387                            (file-name-directory dflt)
388                            dflt
389                            match
390                            (file-name-nondirectory dflt)))))
391   (setq db-file (expand-file-name (or db-file
392                                       (package-get-locate-index-file
393                                          (not force-current)))))
394   (if (not (file-exists-p db-file))
395       (error 'file-error
396              (format "Package-get database file `%s' does not exist" db-file)))
397   (if (not (file-readable-p db-file))
398       (error 'file-error
399              (format "Package-get database file `%s' not readable" db-file)))
400   (let ((buf (get-buffer-create "*package database*")))
401     (unwind-protect
402         (save-excursion
403           (set-buffer buf)
404           (erase-buffer buf)
405           (insert-file-contents-literally db-file)
406           (package-get-update-base-from-buffer buf)
407           (if (or (file-remote-p db-file)
408                   (and (string-match #r"^\(https?\|s?ftp\)://" db-file)
409                        package-get-have-curl))
410               (package-get-maybe-save-index db-file)))
411       (kill-buffer buf))))
412
413 ;; This is here because the `process-error' datum doesn't exist in
414 ;; 21.4. --SY.
415 (define-error 'process-error "Process error")
416
417 ;;;###autoload
418 (defun package-get-update-base-from-buffer (&optional buf)
419   "Update the package-get database with entries from BUFFER.
420 BUFFER defaults to the current buffer.  This command can be
421 used interactively, for example from a mail or news buffer."
422   (interactive)
423   (setq buf (or buf (current-buffer)))
424   (let ((coding-system-for-read 'binary)
425         (coding-system-for-write 'binary)
426         content-beg content-end)
427     (save-excursion
428       (set-buffer buf)
429       (goto-char (point-min))
430       (setq content-beg (point))
431       (setq content-end (save-excursion (goto-char (point-max)) (point)))
432       (package-get-update-base-entries content-beg content-end)
433       (message "Updated package database"))))
434
435 (defun package-get-update-base-entries (start end)
436   "Update the package-get database with the entries found between
437 START and END in the current buffer."
438   (save-excursion
439     (goto-char start)
440     (if (not (re-search-forward "^(package-get-update-base-entry" nil t))
441         (error 'search-failed
442                "Buffer does not contain package-get database entries"))
443     (beginning-of-line)
444     (let ((count 0))
445       (while (and (< (point) end)
446                   (re-search-forward "^(package-get-update-base-entry" nil t))
447         (beginning-of-line)
448         (let ((entry (read (current-buffer))))
449           (if (or (not (consp entry))
450                   (not (eq (car entry) 'package-get-update-base-entry)))
451               (error 'syntax-error
452                      "Invalid package-get database entry found"))
453           (package-get-update-base-entry
454            (car (cdr (car (cdr entry)))))
455           (setq count (1+ count))))
456       (message "Got %d package-get database entries" count))))
457
458 ;;;###autoload
459 (defun package-get-save-base (file)
460   "Write the package-get database to FILE.
461
462 Note: This database will be unsigned of course."
463   (interactive "FSave package-get database to: ")
464   (package-get-require-base t)
465   (let ((buf (get-buffer-create "*package database*")))
466     (unwind-protect
467         (save-excursion
468           (set-buffer buf)
469           (erase-buffer buf)
470           (goto-char (point-min))
471           (let ((entries package-get-base) entry plist)
472             (insert ";; Package Index file -- Do not edit manually.\n")
473             (insert ";;;@@@\n")
474             (while entries
475               (setq entry (car entries))
476               (setq plist (car (cdr entry)))
477               (insert "(package-get-update-base-entry (quote\n")
478               (insert (format "(%s\n" (symbol-name (car entry))))
479               (while plist
480                 (insert (format "  %s%s %S\n"
481                                 (if (eq plist (car (cdr entry))) "(" " ")
482                                 (symbol-name (car plist))
483                                 (car (cdr plist))))
484                 (setq plist (cdr (cdr plist))))
485               (insert "))\n))\n;;;@@@\n")
486               (setq entries (cdr entries))))
487           (insert ";; Package Index file ends here\n")
488           (write-region (point-min) (point-max) file))
489       (kill-buffer buf))))
490
491 (defun package-get-interactive-package-query (get-version package-symbol)
492   "Perform interactive querying for package and optional version.
493 Query for a version if GET-VERSION is non-nil.  Return package name as
494 a symbol instead of a string if PACKAGE-SYMBOL is non-nil.
495 The return value is suitable for direct passing to `interactive'."
496   (package-get-require-base t)
497   (let ((table (mapcar #'(lambda (item)
498                            (let ((name (symbol-name (car item))))
499                              (cons name name)))
500                        package-get-base))
501         package package-symbol default-version version)
502     (save-window-excursion
503       (setq package (completing-read "Package: " table nil t))
504       (setq package-symbol (intern package))
505       (if get-version
506           (progn
507             (setq default-version
508                   (package-get-info-prop
509                    (package-get-info-version
510                     (package-get-info-find-package package-get-base
511                                                    package-symbol) nil)
512                    'version))
513             (while (string=
514                     (setq version (read-string "Version: " default-version))
515                     ""))
516             (if package-symbol
517                 (list package-symbol version)
518               (list package version)))
519         (if package-symbol
520             (list package-symbol)
521           (list package))))))
522
523 ;;;###autoload
524 (defun package-get-delete-package (package &optional pkg-topdir)
525   "Delete an installation of PACKAGE below directory PKG-TOPDIR.
526 PACKAGE is a symbol, not a string.
527 This is just an interactive wrapper for `package-admin-delete-binary-package'."
528   (interactive (package-get-interactive-package-query nil t))
529   (package-admin-delete-binary-package package pkg-topdir))
530
531 ;;;###autoload
532 (defun package-get-update-all ()
533   "Fetch and install the latest versions of all currently installed packages."
534   (interactive)
535   (package-get-require-base t)
536   ;; Load a fresh copy
537   (catch 'exit
538     (mapcar (lambda (pkg)
539               (if (not (package-get (car pkg) nil 'never))
540                   (throw 'exit nil)))           ;; Bail out if error detected
541             packages-package-list)))
542
543 ;;;###autoload
544 (defun package-get-all (package version &optional fetched-packages install-dir)
545   "Fetch PACKAGE with VERSION and all other required packages.
546 Uses `package-get-base' to determine just what is required and what
547 package provides that functionality.  If VERSION is nil, retrieves
548 latest version.  Optional argument FETCHED-PACKAGES is used to keep
549 track of packages already fetched.  Optional argument INSTALL-DIR,
550 if non-nil, specifies the package directory where fetched packages
551 should be installed.
552
553 Returns nil upon error."
554   (interactive (package-get-interactive-package-query t nil))
555   (let* ((the-package (package-get-info-find-package package-get-base
556                                                      package))
557          (this-package (package-get-info-version
558                         the-package version))
559          (this-requires (package-get-info-prop this-package 'requires)))
560     (catch 'exit
561       (setq version (package-get-info-prop this-package 'version))
562       (unless (package-get-installedp package version)
563         (if (not (package-get package version nil install-dir))
564             (progn
565               (setq fetched-packages nil)
566               (throw 'exit nil))))
567       (setq fetched-packages
568             (append (list package)
569                     (package-get-info-prop this-package 'provides)
570                     fetched-packages))
571       ;; grab everything that this package requires plus recursively
572       ;; grab everything that the requires require.  Keep track
573       ;; in `fetched-packages' the list of things provided -- this
574       ;; keeps us from going into a loop
575       (while this-requires
576         (if (not (member (car this-requires) fetched-packages))
577             (let* ((reqd-package (package-get-package-provider
578                                   (car this-requires) t))
579                    (reqd-version (cadr reqd-package))
580                    (reqd-name (car reqd-package)))
581               (if (null reqd-name)
582                   (error 'search-failed
583                          (format "Unable to find a provider for %s"
584                                  (car this-requires))))
585               (if (not (setq fetched-packages
586                              (package-get-all reqd-name reqd-version
587                                               fetched-packages
588                                               install-dir)))
589                   (throw 'exit nil))))
590         (setq this-requires (cdr this-requires))))
591     fetched-packages))
592
593 ;;;###autoload
594 (defun package-get-dependencies (packages)
595   "Compute dependencies for PACKAGES.
596 Uses `package-get-base' to determine just what is required and what
597 package provides that functionality.  Returns the list of packages
598 required by PACKAGES."
599   (package-get-require-base t)
600   (let ((orig-packages packages)
601         dependencies provided)
602     (while packages
603       (let* ((package (car packages))
604              (the-package (package-get-info-find-package
605                            package-get-base package))
606              (this-package (package-get-info-version
607                             the-package nil))
608              (this-requires (package-get-info-prop this-package 'requires))
609              (new-depends   (set-difference
610                              (mapcar
611                               #'(lambda (reqd)
612                                   (let* ((reqd-package (package-get-package-provider reqd))
613                                          (reqd-name    (car reqd-package)))
614                                     (if (null reqd-name)
615                                         (error 'search-failed
616                                                (format "Unable to find a provider for %s" reqd)))
617                                     reqd-name))
618                               this-requires)
619                              dependencies))
620              (this-provides (package-get-info-prop this-package 'provides)))
621         (setq dependencies
622               (union dependencies new-depends))
623         (setq provided
624               (union provided (union (list package) this-provides)))
625         (setq packages
626               (union new-depends (cdr packages)))))
627     (set-difference dependencies orig-packages)))
628
629 (defun package-get-load-package-file (lispdir file)
630   (let (pathname)
631     (setq pathname (expand-file-name file lispdir))
632     (condition-case err
633         (progn
634           (load pathname t)
635           t)
636       (t
637        (message "Error loading package file \"%s\" %s!" pathname err)
638        nil))
639     ))
640
641 (defun package-get-init-package (lispdir)
642   "Initialize the package.
643 This really assumes that the package has never been loaded.  Updating
644 a newer package can cause problems, due to old, obsolete functions in
645 the old package.
646
647 Return `t' upon complete success, `nil' if any errors occurred."
648   (progn
649     (if (and lispdir
650              (file-accessible-directory-p lispdir))
651         (progn
652           ;; Add lispdir to load-path if it doesn't already exist.
653           ;; NOTE: this does not take symlinks, etc., into account.
654           (if (let ((dirs load-path))
655                 (catch 'done
656                   (while dirs
657                     (if (string-equal (car dirs) lispdir)
658                         (throw 'done nil))
659                     (setq dirs (cdr dirs)))
660                   t))
661               (setq load-path (cons lispdir load-path)))
662           (if (not (package-get-load-package-file lispdir "auto-autoloads"))
663               (package-get-load-package-file lispdir "_pkg"))
664           t)
665       nil)))
666
667 ;;;###autoload
668 (defun package-get-info (package information &optional arg remote)
669   "Get information about a package.
670
671 Quite similar to `package-get-info-prop', but can retrieve a lot more
672 information.
673
674 Argument PACKAGE is the name of an XEmacs package (a symbol).  It must
675 be a valid package, ie, a member of `package-get-base'.
676
677 Argument INFORMATION is a symbol that can be any one of:
678
679    standards-version     Package system version (not used).
680    version               Version of the XEmacs package.
681    author-version        The upstream version of the package.
682    date                  The date the package was last modified.
683    build-date            The date the package was last built.
684    maintainer            The maintainer of the package.
685    distribution          Will always be \"xemacs\" (not used).
686    priority              \"low\", \"medium\", or \"high\" (not used).
687    category              Either \"standard\", \"mule\", or \"unsupported\"..
688    dump                  Is the package dumped (not used).
689    description           A description of the package.
690    filename              The filename of the binary tarball of the package.
691    md5sum                The md5sum of filename.
692    size                  The size in bytes of filename.
693    provides              A list of symbols that this package provides.
694    requires              A list of packages that this package requires.
695    type                  Can be either \"regular\" or \"single-file\".
696
697 If optional argument ARG is non-nil insert INFORMATION into current
698 buffer at point.  This is very useful for doing things like inserting
699 a maintainer's email address into a mail buffer.
700
701 If optional argument REMOTE is non-nil use a package list from a
702 remote site.  For this to work `package-get-remote' must be non-nil.
703
704 If this function is called interactively it will display INFORMATION
705 in the minibuffer."
706   (interactive "SPackage: \nSInfo: \nP")
707     (if remote
708         (package-get-require-base t)
709       (package-get-require-base nil))
710     (let ((all-pkgs package-get-base)
711           info)
712       (loop until (equal package (caar all-pkgs))
713         do (setq all-pkgs (cdr all-pkgs))
714         do (if (not all-pkgs)
715                (error 'invalid-argument
716                       (format "%s is not a valid package" package))))
717       (setq info (plist-get (cadar all-pkgs) information))
718       (if (interactive-p)
719           (if arg
720               (insert (format "%s" info))
721             (if (package-get-key package :version)
722                 (message "%s" info)
723               (message "%s (Package: %s is not installed)" info package)))
724         (if arg
725             (insert (format "%s" info))
726           info))))
727
728 ;;;###autoload
729 (defun package-get-list-packages-where (item field &optional arg)
730   "Return a list of packages that fulfill certain criteria.
731
732 Argument ITEM, a symbol, is what you want to check for.  ITEM must be a
733 symbol even when it doesn't make sense to be a symbol \(think, searching
734 maintainers, descriptions, etc\).  The function will convert the symbol
735 to a string if a string is what is needed.  The downside to this is that
736 ITEM can only ever be a single word.
737
738 Argument FIELD, a symbol, is the field to check in.  You can specify
739 any one of:
740
741       Field            Sane or Allowable Content
742     description          any single word
743     category             `standard' or `mule'
744     maintainer           any single word
745     build-date           yyyy-mm-dd
746     date                 yyyy-mm-dd
747     type                 `regular' or `single'
748     requires             any package name
749     provides             any symbol
750     priority             `low', `medium', or `high'
751
752 Optional Argument ARG, a prefix arg, insert output at point in the
753 current buffer."
754   (interactive "SList packages that have (item): \nSin their (field): \nP")
755   (package-get-require-base nil)
756   (let ((pkgs package-get-base)
757         (strings '(description category maintainer build-date date))
758         (symbols '(type requires provides priority))
759         results)
760     (cond ((memq field strings)
761            (setq item (symbol-name item))
762            (while pkgs
763              (when (string-match item (package-get-info (caar pkgs) field))
764                (setq results (push (caar pkgs) results)))
765              (setq pkgs (cdr pkgs))))
766           ((memq field symbols)
767            (if (or (eq field 'type)
768                    (eq field 'priority))
769                (while pkgs
770                  (when (eq item (package-get-info (caar pkgs) field))
771                    (setq results (push (caar pkgs) results)))
772                  (setq pkgs (cdr pkgs)))
773              (while pkgs
774                (when (memq item (package-get-info (caar pkgs) field))
775                  (setq results (push (caar pkgs) results)))
776                (setq pkgs (cdr pkgs)))))
777           (t
778            (error 'wrong-type-argument field)))
779     (if (interactive-p)
780         (if arg
781             (insert (format "%s" results))
782           (message "%s" results)))
783     results))
784
785 ;;;###autoload
786 (defun package-get (package &optional version conflict install-dir)
787   "Fetch PACKAGE from remote site.
788 Optional arguments VERSION indicates which version to retrieve, nil
789 means most recent version.  CONFLICT indicates what happens if the
790 package is already installed.  Valid values for CONFLICT are:
791 'always always retrieve the package even if it is already installed
792 'never  do not retrieve the package if it is installed.
793 INSTALL-DIR, if non-nil, specifies the package directory where
794 fetched packages should be installed.
795
796 The value of `package-get-base' is used to determine what files should
797 be retrieved.  The value of `package-get-remote' is used to determine
798 where a package should be retrieved from.
799
800 Once the package is retrieved, its md5 checksum is computed.  If that
801 sum does not match that stored in `package-get-base' for this version
802 of the package, an error is signalled.
803
804 Returns `t' upon success, the symbol `error' if the package was
805 successfully installed but errors occurred during initialization, or
806 `nil' upon error."
807   (interactive (package-get-interactive-package-query nil t))
808   (catch 'skip-update
809   (let* ((this-package
810           (package-get-info-version
811            (package-get-info-find-package package-get-base
812                                           package) version))
813          (latest (package-get-info-prop this-package 'version))
814          (installed (package-get-key package :version))
815          (found nil)
816          (host nil)
817          (search-dir package-get-remote)
818          (base-filename (package-get-info-prop this-package 'filename))
819          (package-status t)
820          filenames full-package-filename)
821     (if (and (equal (package-get-info package 'category) "mule")
822              (not (featurep 'mule)))
823         (error 'invalid-state
824                "Mule packages can't be installed with a non-Mule SXEmacs"))
825     (if (null this-package)
826         (if package-get-remote
827             (error 'search-failed
828                    (format "Couldn't find package %s with version %s"
829                            package version))
830           (error 'syntax-error
831                  "No download site or local package location specified.")))
832     (if (null base-filename)
833         (error 'syntax-error
834                (format "No filename associated with package %s, version %s"
835                        package version)))
836     (setq install-dir (package-admin-get-install-dir package install-dir))
837
838     ;; If they asked for the latest using version=nil, don't get an older
839     ;; version than we already have.
840     (if installed
841         (if (> (if (stringp installed)
842                    (string-to-number installed)
843                  installed)
844                (if (stringp latest)
845                    (string-to-number latest)
846                  latest))
847             (if (not (null version))
848                 (warn "Installing %s package version %s, you had a newer version %s"
849                   package latest installed)
850               (warn "Skipping %s package, you have a newer version %s"
851                 package installed)
852               (throw 'skip-update t))))
853
854     ;; Contrive a list of possible package filenames.
855     ;; Ugly.  Is there a better way to do this?
856     (setq filenames (cons base-filename nil))
857     (if (string-match #r"^\(..*\)\.tar\.gz$" base-filename)
858         (setq filenames (append filenames
859                                 (list (concat (match-string 1 base-filename)
860                                               ".tgz")))))
861
862     (setq version latest)
863     (unless (and (eq conflict 'never)
864                  (package-get-installedp package version))
865       ;; Find the package from the search list in package-get-remote
866       ;; and copy it into the staging directory.  Then validate
867       ;; the checksum.  Finally, install the package.
868       (catch 'done
869         (let (search-filenames dir current-filename dest-filename)
870           ;; In each search directory ...
871           (when search-dir
872             (setq host (car search-dir)
873                   dir (car (cdr search-dir))
874                   search-filenames filenames)
875
876             ;; Look for one of the possible package filenames ...
877             (while search-filenames
878               (setq current-filename (car search-filenames)
879                     dest-filename (package-get-staging-dir current-filename))
880               (cond
881                ;; No host means look on the current system.
882                ((null host)
883                 (setq full-package-filename
884                       (substitute-in-file-name
885                        (expand-file-name current-filename
886                                          (file-name-as-directory dir)))))
887
888                ;; If it's already on the disk locally, and the size is
889                ;; correct
890                ((and (file-exists-p dest-filename)
891                      (eq (nth 7 (file-attributes dest-filename))
892                          (package-get-info package 'size)))
893                  (setq full-package-filename dest-filename))
894
895                ;; If the file exists on the remote system ...
896                ((file-exists-p (package-get-remote-filename
897                                 search-dir current-filename))
898                 ;; Get it
899                 (setq full-package-filename dest-filename)
900                 (message "Retrieving package `%s' ..."
901                          current-filename)
902                 (sit-for 0)
903                 (copy-file (package-get-remote-filename search-dir
904                                                         current-filename)
905                            full-package-filename t)))
906
907               ;; If we found it, we're done.
908               (if (and full-package-filename
909                        (file-exists-p full-package-filename))
910                   (throw 'done nil))
911               ;; Didn't find it.  Try the next possible filename.
912               (setq search-filenames (cdr search-filenames))))))
913
914       (if (or (not full-package-filename)
915               (not (file-exists-p full-package-filename)))
916           (if package-get-remote
917               (error 'search-failed
918                      (format "Unable to find file %s" base-filename))
919             (error 'syntax-error
920                    "No download sites or local package locations specified.")))
921       ;; Validate the md5 checksum
922       ;; Doing it with SXEmacs removes the need for an external md5 program
923       (message "Validating checksum for `%s'..." package) (sit-for 0)
924       (with-temp-buffer
925         (insert-file-contents-literally full-package-filename)
926         (if (not (string= (md5 (current-buffer))
927                           (package-get-info-prop this-package
928                                                  'md5sum)))
929             (progn
930               (unless (null host)
931                 (delete-file full-package-filename))
932               (error 'process-error
933                      (format "Package %s does not match md5 checksum %s has been deleted"
934                              base-filename full-package-filename)))))
935
936       (package-admin-delete-binary-package package install-dir)
937
938       (message "Installing package `%s' ..." package) (sit-for 0)
939       (let ((status
940              (package-admin-add-binary-package full-package-filename
941                                                install-dir)))
942         (if (= status 0)
943             (progn
944               ;; clear messages so that only messages from
945               ;; package-get-init-package are seen, below.
946               (clear-message)
947               (if (package-get-init-package (package-admin-get-lispdir
948                                              install-dir package))
949                   (progn
950                     (run-hook-with-args 'package-install-hook package install-dir)
951                     (message "Added package `%s'" package)
952                     (sit-for 0))
953                 (progn
954                   ;; display message only if there isn't already one.
955                   (if (not (current-message))
956                       (progn
957                         (message "Added package `%s' (errors occurred)"
958                                  package)
959                         (sit-for 0)))
960                   (if package-status
961                       (setq package-status 'errors)))))
962           (message "Installation of package %s failed." base-filename)
963           (sit-for 0)
964           (switch-to-buffer package-admin-temp-buffer)
965           ;; null host means a local package mirror
966           (unless (null host)
967             (delete-file full-package-filename))
968           (setq package-status nil)))
969       (setq found t))
970     (if (and found package-get-remove-copy (not (null host)))
971         (delete-file full-package-filename))
972     package-status)))
973
974 (defun package-get-info-find-package (which name)
975   "Look in WHICH for the package called NAME and return all the info
976 associated with it.  See `package-get-base' for info on the format
977 returned.
978
979  To access fields returned from this, use
980 `package-get-info-version' to return information about particular a
981 version.  Use `package-get-info-find-prop' to find particular property
982 from a version returned by `package-get-info-version'."
983   (interactive "xPackage list: \nsPackage Name: ")
984   (if which
985       (if (eq (caar which) name)
986           (cdar which)
987         (if (cdr which)
988             (package-get-info-find-package (cdr which) name)))))
989
990 (defun package-get-info-version (package version)
991   "In PACKAGE, return the plist associated with a particular VERSION of the
992   package.  PACKAGE is typically as returned by
993   `package-get-info-find-package'.  If VERSION is nil, then return the
994   first (aka most recent) version.  Use `package-get-info-find-prop'
995   to retrieve a particular property from the value returned by this."
996   (interactive (package-get-interactive-package-query t t))
997   (while (and version package (not (string= (plist-get (car package) 'version) version)))
998     (setq package (cdr package)))
999   (if package (car package)))
1000
1001 (defun package-get-info-prop (package-version property)
1002   "In PACKAGE-VERSION, return the value associated with PROPERTY.
1003 PACKAGE-VERSION is typically returned by `package-get-info-version'
1004 and PROPERTY is typically (although not limited to) one of the
1005 following:
1006
1007 version         - version of this package
1008 provides                - list of symbols provided
1009 requires                - list of symbols that are required.
1010                   These in turn are provided by other packages.
1011 size            - size of the bundled package
1012 md5sum          - computed md5 checksum"
1013   (interactive "xPackage Version: \nSProperty")
1014   (plist-get package-version property))
1015
1016 (defun package-get-info-version-prop (package-list package version property)
1017   "In PACKAGE-LIST, search for PACKAGE with this VERSION and return
1018   PROPERTY value."
1019   (package-get-info-prop
1020    (package-get-info-version
1021     (package-get-info-find-package package-list package) version) property))
1022
1023 (defun package-get-staging-dir (filename)
1024   "Return a good place to stash FILENAME when it is retrieved.
1025 Use `package-get-dir' for directory to store stuff.
1026 Creates `package-get-dir'  if it doesn't exist."
1027   (interactive "FPackage filename: ")
1028   (if (not (file-exists-p package-get-dir))
1029       (make-directory package-get-dir))
1030   (expand-file-name
1031    (file-name-nondirectory (or (and-fboundp 'efs-ftp-path
1032                                  (nth 2 (efs-ftp-path filename)))
1033                                filename))
1034    (file-name-as-directory package-get-dir)))
1035
1036 (defun package-get-remote-filename (search filename)
1037   "Return FILENAME as a remote filename.
1038 It first checks if FILENAME already is a remote filename.  If it is
1039 not, then it uses the (car search) as the remote site-name and the (cadr
1040 search) as the remote-directory and concatenates filename.  In other
1041 words:
1042
1043         site-name:remote-directory/filename.
1044
1045 If ffi-curl has been loaded then this will return a URL style name,
1046 for example:
1047
1048         http://site-name/remote-directory/filename
1049
1050 The url scheme to use in this case is from (third search).
1051
1052 If (car search) is nil, (cadr search is interpreted as a local
1053 directory)."
1054   (if (or (file-remote-p filename)
1055           (and (string-match #r"^\(https?\|s?ftp\)://" filename)
1056                package-get-have-curl))
1057       filename
1058     (let ((site (car search))
1059           (dir (cadr search))
1060           (scheme (third search)))
1061       (if (and site package-get-have-curl)
1062           (concat scheme "://" site "/" dir "/" filename)
1063         (concat (when site
1064                   (concat
1065                    (if (string-match "@" site)
1066                        "/"
1067                      "/anonymous@")
1068                    site ":"))
1069                 (if (string-match "/$" dir)
1070                     dir
1071                   (concat dir "/"))
1072                 filename)))))
1073
1074 (defun package-get-installedp (package version)
1075   "Determine if PACKAGE with VERSION has already been installed.
1076 I'm not sure if I want to do this by searching directories or checking
1077 some built in variables.  For now, use packages-package-list."
1078   ;; Use packages-package-list which contains name and version
1079   (equal (plist-get
1080           (package-get-info-find-package packages-package-list
1081                                          package) ':version)
1082          (if (floatp version)
1083              version
1084            (string-to-number version))))
1085
1086 ;;;###autoload
1087 (defun package-get-package-provider (sym &optional force-current)
1088   "Search for a package that provides SYM and return the name and
1089   version.  Searches in `package-get-base' for SYM.   If SYM is a
1090   consp, then it must match a corresponding (provide (SYM VERSION)) from
1091   the package.
1092
1093 If FORCE-CURRENT is non-nil make sure the database is up to date. This might
1094 lead to Emacs accessing remote sites."
1095   (interactive "SSymbol: ")
1096   (package-get-require-base force-current)
1097   (let ((packages package-get-base)
1098         (done nil)
1099         (found nil))
1100     (while (and (not done) packages)
1101       (let* ((this-name (caar packages))
1102              (this-package (cdr (car packages)))) ;strip off package name
1103         (while (and (not done) this-package)
1104           (if (or (eq this-name sym)
1105                   (eq (cons this-name
1106                           (package-get-info-prop (car this-package) 'version))
1107                       sym)
1108                   (member sym
1109                         (package-get-info-prop (car this-package) 'provides)))
1110               (progn (setq done t)
1111                      (setq found
1112                        (list (caar packages)
1113                          (package-get-info-prop (car this-package) 'version))))
1114             (setq this-package (cdr this-package)))))
1115       (setq packages (cdr packages)))
1116     (when (interactive-p)
1117       (if found
1118           (message "%S" found)
1119         (message "No appropriate package found")))
1120     found))
1121
1122 (defun package-get-ever-installed-p (pkg &optional notused)
1123   (string-match "-package$" (symbol-name pkg))
1124   (custom-initialize-set
1125    pkg
1126    (if (package-get-info-find-package
1127         packages-package-list
1128         (intern (substring (symbol-name pkg) 0 (match-beginning 0))))
1129        t)))
1130
1131 (provide 'package-get)
1132
1133 ;; On-load forms
1134 (unless (and (featurep 'package-ui)
1135              (fboundp 'loop))
1136   (require 'package-ui)
1137   (load "cl-macs"))
1138
1139 ;;; package-get.el ends here