This commit was manufactured by cvs2svn to create branch 'chise-r21-4-18'.
[chise/xemacs-chise.git-] / lisp / packages.el
diff --git a/lisp/packages.el b/lisp/packages.el
new file mode 100644 (file)
index 0000000..7c7fd97
--- /dev/null
@@ -0,0 +1,530 @@
+;;; packages.el --- Low level support for XEmacs packages
+
+;; Copyright (C) 1997 Free Software Foundation, Inc.
+;; Copyright (C) 2002, 2003 Ben Wing.
+
+;; Author: Steven L Baur <steve@xemacs.org>
+;; Maintainer: Steven L Baur <steve@xemacs.org>
+;; Keywords: internal, lisp, dumped
+
+;; This file is part of XEmacs.
+
+;; XEmacs is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; XEmacs is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with XEmacs; see the file COPYING.  If not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+;; 02111-1307, USA.
+
+;;; Synched up with: Not in FSF
+
+;;; Commentary:
+
+;; This file is dumped with XEmacs.
+
+;; This file provides low level facilities for XEmacs startup --
+;; particularly regarding the package setup.  This code has to run in
+;; what we call "bare temacs" -- i.e. XEmacs without the usual Lisp
+;; environment.  Pay special attention:
+
+;; - not to use the `lambda' macro.  Use #'(lambda ...) instead.
+;;   (this goes for any package loaded before `subr.el'.)
+;;
+;; - not to use macros, because they are not yet available (and this
+;;   file must be loadable uncompiled.)  Built in macros, such as
+;;   `when' and `unless' are fine, of course.
+;;
+;; - not to use `defcustom'.  If you must add user-customizable
+;;   variables here, use `defvar', and add the variable to
+;;   `cus-start.el'.
+
+;; Because of all this, make sure that the stuff you put here really
+;; belongs here.
+
+;; This file requires find-paths.el.
+\f
+;;; Code:
+
+;;; Package versioning
+
+(defvar packages-package-list nil
+  "Database of installed packages and version numbers")
+
+(defvar packages-hierarchy-depth 1
+  "Depth of package hierarchies.")
+
+(defvar packages-load-path-depth 1
+  "Depth of load-path search in package hierarchies.")
+
+(defvar packages-data-path-depth 1
+  "Depth of data-path search in package hierarchies.")
+
+(defvar early-packages nil
+  "Packages early in the load path.")
+
+(defvar early-package-load-path nil
+  "Load path for packages early in the load path.")
+
+(defvar late-packages nil
+  "Packages late in the load path.")
+
+(defvar late-package-load-path nil
+  "Load path for packages late in the load path.")
+
+(defvar last-packages nil
+  "Packages last in the load path.")
+
+(defvar last-package-load-path nil
+  "Load path for packages last in the load path.")
+
+(defun packages-compute-package-locations (user-init-directory)
+  "Compute locations of the various package directories.
+This is a list each of whose elements describes one directory.
+A directory description is a three-element list.
+The first element is either an absolute path or a subdirectory
+in the XEmacs hierarchy.
+The second component is one of the symbols EARLY, LATE, LAST,
+depending on the load-path segment the hierarchy is supposed to
+show up in.
+The third component is a thunk which, if it returns NIL, causes
+the directory to be ignored."
+  (list
+   (list (paths-construct-path (list user-init-directory "site-packages"))
+        'early #'(lambda () t))
+   (list (paths-construct-path (list user-init-directory "infodock-packages"))
+        'early #'(lambda () (featurep 'infodock)))
+   (list (paths-construct-path (list user-init-directory "mule-packages"))
+        'early #'(lambda () (featurep 'mule)))
+   (list (paths-construct-path (list user-init-directory "xemacs-packages"))
+        'early #'(lambda () t))
+   (list "site-packages"     'late  #'(lambda () t))
+   (list "infodock-packages" 'late  #'(lambda () (featurep 'infodock)))
+   (list "mule-packages"     'late  #'(lambda () (featurep 'mule)))
+   (list "xemacs-packages"   'late  #'(lambda () t))))
+
+(defun package-get-key-1 (info key)
+  "Locate keyword `key' in list."
+  (cond ((null info)
+        nil)
+       ((eq (car info) key)
+        (nth 1 info))
+       (t (package-get-key-1 (cddr info) key))))
+
+(defun package-get-key (name key)
+  "Get info `key' from package `name'."
+  (let ((info (assq name packages-package-list)))
+    (when info
+      (package-get-key-1 (cdr info) key))))
+
+(defun package-provide (name &rest attributes)
+  (let ((info (if (and attributes (floatp (car attributes)))
+                 (list :version (car attributes))
+               attributes)))
+    (setq packages-package-list
+         (cons (cons name info) (remassq name packages-package-list)))))
+
+(defun package-require (name version)
+  (let ((pkg (assq name packages-package-list)))
+    (cond ((null pkg)
+          (error 'invalid-state
+                 (format "Package %s has not been loaded into this XEmacsen"
+                         name)))
+         ((< (package-get-key name :version) version)
+          (error 'search-failed
+                 (format "Need version %g of package %s, got version %g"
+                         version name (package-get-key name :version))))
+         (t t))))
+
+(defun package-delete-name (name)
+  (let (pkg)
+    ;; Delete ALL versions of package.
+    ;; This is pretty memory-intensive, as we use copy-alist when deleting
+    ;; package entries, to prevent side-effects in functions that call this
+    ;; one.
+    (while (setq pkg (assq name packages-package-list))
+      (setq packages-package-list (delete pkg (copy-alist
+                                              packages-package-list))))))
+
+;;; Build time stuff
+
+(defvar autoload-file-name "auto-autoloads.el"
+  "Filename that autoloads are expected to be found in.")
+
+;; Moved from help.el.
+;; Unlike the FSF version, our `locate-library' uses the `locate-file'
+;; primitive, which should make it lightning-fast.
+
+(defun locate-library (library &optional nosuffix path interactive-call)
+  "Show the precise file name of Emacs library LIBRARY.
+This command searches the directories in `load-path' like `M-x load-library'
+to find the file that `M-x load-library RET LIBRARY RET' would load.
+Optional second arg NOSUFFIX non-nil means don't add suffixes `.elc' or `.el'
+to the specified name LIBRARY.
+
+If the optional third arg PATH is specified, that list of directories
+is used instead of `load-path'."
+  (interactive (list (read-library-name "Locate library: ")
+                     nil nil
+                     t))
+  (let ((result
+        (locate-file
+         library
+         (or path load-path)
+         (cond ((or (rassq 'jka-compr-handler file-name-handler-alist)
+                    (and (boundp 'find-file-hooks)
+                         (member 'crypt-find-file-hook find-file-hooks)))
+                ;; Compression involved.
+                (if nosuffix
+                    '("" ".gz" ".Z" ".bz2")
+                  '(".elc" ".elc.gz" "elc.Z" ".elc.bz2"
+                    ".el" ".el.gz" ".el.Z" ".el.bz2"
+                    "" ".gz" ".Z" ".bz2")))
+               (t
+                ;; No compression.
+                (if nosuffix
+                    ""
+                  '(".elc" ".el" "")))))))
+    (and interactive-call
+        (if result
+            (message "Library is file %s" result)
+          (message "No library %s in search path" library)))
+    result))
+
+(defun packages-add-suffix (str)
+  (if (null (string-match "\\.el\\'" str))
+      (concat str ".elc")
+    str))
+
+(defun packages-list-autoloads-path ()
+  "List autoloads from precomputed load-path."
+  (let ((path load-path)
+       autoloads)
+    (while path
+      (if (file-exists-p (concat (car path)
+                                autoload-file-name))
+         (setq autoloads (cons (concat (car path)
+                                       autoload-file-name)
+                               autoloads)))
+      (setq path (cdr path)))
+    autoloads))
+
+(defun packages-list-autoloads (source-directory)
+  "List autoload files in (what will be) the normal lisp search path.
+This function is used during build to find where the global symbol files so
+they can be perused for their useful information."
+  (let ((files (directory-files (file-name-as-directory source-directory)
+                               t ".*"))
+       file autolist)
+    ;; (print (prin1-to-string source-directory))
+    ;; (print (prin1-to-string files))
+    (while (setq file (car-safe files))
+      (if (and (file-directory-p file)
+              (file-exists-p (concat (file-name-as-directory file)
+                                     autoload-file-name)))
+         (setq autolist (cons (concat (file-name-as-directory file)
+                                      autoload-file-name)
+                              autolist)))
+      (setq files (cdr files)))
+    autolist))
+
+;; The following function cannot be called from a bare temacs
+(defun packages-new-autoloads ()
+  "Return autoloads files that have been added or modified since XEmacs dump."
+  (require 'loadhist)
+  (let ((me (concat invocation-directory invocation-name))
+       (path load-path)
+       result dir)
+    (while path
+      (setq dir (file-truename (car path)))
+      (let ((autoload-file (file-name-sans-extension (concat
+                                                     dir
+                                                     autoload-file-name))))
+       ;; Check for:
+       ;; 1.  An auto-autoload file that hasn't provided a feature (because
+       ;;     it has been installed since XEmacs was dumped).
+       ;; 2.  auto-autoload.el being newer than the executable
+       ;; 3.  auto-autoload.elc being newer than the executable (the .el
+       ;;     could be missing or compressed)
+       (when (or (and (null (file-provides autoload-file))
+                      (or (file-exists-p (concat autoload-file ".elc"))
+                          (file-exists-p (concat autoload-file ".el"))))
+                 (and (file-newer-than-file-p (concat autoload-file ".el") me)
+                      (setq autoload-file (concat autoload-file ".el")))
+                 (and (file-newer-than-file-p (concat autoload-file
+                                                      ".elc")
+                                              me)
+                      (setq autoload-file (concat autoload-file ".elc"))))
+         (push autoload-file result)))
+      (setq path (cdr path)))
+    result))
+
+;; The following function cannot be called from a bare temacs
+(defun packages-reload-autoloads ()
+  "Reload new or updated auto-autoloads files.
+This is an extremely dangerous function to call after the user-init-files
+is run.  Don't call it or you'll be sorry."
+  (let ((autoload-list (packages-new-autoloads)))
+    (while autoload-list
+      (let* ((autoload-file (car autoload-list))
+            (feature (car-safe (file-provides autoload-file))))
+       (when feature
+         ;; (message "(unload-feature %S)" feature)
+         (unload-feature feature))
+       (condition-case nil
+           (load autoload-file)
+         (t nil)))
+      (setq autoload-list (cdr autoload-list)))))
+
+;; Data-directory is really a list now.  Provide something to search it for
+;; directories.
+
+(defun locate-data-directory-list (name &optional dir-list)
+  "Locate the matching list of directories in a search path DIR-LIST.
+If no DIR-LIST is supplied, it defaults to `data-directory-list'."
+  (unless dir-list
+    (setq dir-list data-directory-list))
+  (let (found found-dir found-dir-list)
+    (while dir-list
+      (setq found (file-name-as-directory (concat (car dir-list) name))
+           found-dir (file-directory-p found))
+      (and found-dir
+          (setq found-dir-list (cons found found-dir-list)))
+      (setq dir-list (cdr dir-list)))
+    (nreverse found-dir-list)))
+
+;; Data-directory is really a list now.  Provide something to search it for
+;; a directory.
+
+(defun locate-data-directory (name &optional dir-list)
+  "Locate a directory in a search path DIR-LIST (a list of directories).
+If no DIR-LIST is supplied, it defaults to `data-directory-list'."
+  (unless dir-list
+    (setq dir-list data-directory-list))
+  (let (found found-dir)
+    (while (and (null found-dir) dir-list)
+      (setq found (file-name-as-directory (concat (car dir-list) name))
+           found-dir (file-directory-p found))
+      (or found-dir
+         (setq found nil))
+      (setq dir-list (cdr dir-list)))
+    found))
+
+;; Data-directory is really a list now.  Provide something to search it for
+;; files.
+
+(defun locate-data-file (name &optional dir-list)
+  "Locate a file in a search path DIR-LIST (a list of directories).
+If no DIR-LIST is supplied, it defaults to `data-directory-list'.
+This function is basically a wrapper over `locate-file'."
+  (locate-file name (or dir-list data-directory-list)))
+
+;; Path setup
+
+(defun packages-find-package-directories (roots base)
+  "Find a set of package directories."
+  ;; make sure paths-find-version-directory and paths-find-site-directory
+  ;; don't both pick up version-independent directories ...
+  (let ((version-directory (paths-find-version-directory roots base nil nil t))
+       (site-directory (paths-find-site-directory roots base)))
+    (paths-uniq-append
+     (and version-directory (list version-directory))
+     (and site-directory (list site-directory)))))
+
+(defvar packages-special-base-regexp "^\\(etc\\|info\\|man\\|lisp\\|lib-src\\|bin\\|pkginfo\\)$"
+  "Special subdirectories of packages.")
+
+(defvar packages-no-package-hierarchy-regexp
+  (concat "\\(" paths-version-control-filename-regexp "\\)"
+         "\\|"
+         "\\(" packages-special-base-regexp "\\)")
+  "Directories which can't be the roots of package hierarchies.")
+
+(defun packages-find-packages-in-directories (directories)
+  "Find all packages underneath directories in DIRECTORIES."
+  (paths-find-recursive-path directories
+                            packages-hierarchy-depth
+                            packages-no-package-hierarchy-regexp))
+
+(defun packages-split-path (path)
+  "Split PATH at \"\", return pair with two components.
+The second component is shared with PATH."
+  (let ((reverse-tail '())
+       (rest path))
+    (while (and rest (null (string-equal "" (car rest))))
+      (setq reverse-tail (cons (car rest) reverse-tail))
+      (setq rest (cdr rest)))
+    (if (null rest)
+       (cons path nil)
+      (cons (nreverse reverse-tail) (cdr rest)))))
+
+(defun packages-split-package-path (package-path)
+  "Split up PACKAGE-PATH into early, late and last components.
+The separation is by \"\" components.
+This returns (LIST EARLY-PACKAGES LATE-PACKAGES LAST-PACKAGES)."
+  ;; When in doubt, it's late
+  (let* ((stuff (packages-split-path package-path))
+        (early (and (cdr stuff) (car stuff)))
+        (late+last (or (cdr stuff) (car stuff)))
+        (stuff (packages-split-path late+last))
+        (late (car stuff))
+        (last (cdr stuff)))
+    (list (packages-find-packages-in-directories early)
+         (packages-find-packages-in-directories late)
+         (packages-find-packages-in-directories last))))
+
+(defun packages-deconstruct (list consumer)
+  "Deconstruct LIST and feed it to CONSUMER."
+  (apply consumer list))
+
+(defun packages-find-packages-by-name (roots name)
+  "Find a package hierarchy by its name."
+  (packages-find-packages-in-directories
+   (if (and (file-name-absolute-p name)
+           (file-name-directory (expand-file-name name)))
+       (list (file-name-as-directory (expand-file-name name)))
+    (packages-find-package-directories roots name))))
+
+(defun packages-find-packages-at-time
+  (roots package-locations time &optional default)
+  "Find packages at given time.
+For the format of PACKAGE-LOCATIONS, see the global variable of the same name.
+TIME is either 'EARLY, 'LATE, or 'LAST.
+DEFAULT is a default list of packages."
+  (or default
+      (let ((packages '()))
+       (while package-locations
+         (packages-deconstruct
+          (car package-locations)
+          #'(lambda (name a-time thunk)
+              (if (and (eq time a-time)
+                       (funcall thunk))
+                  (setq packages
+                        (nconc packages
+                               (packages-find-packages-by-name roots name))))))
+         (setq package-locations (cdr package-locations)))
+       packages)))
+
+(defun packages-find-packages (roots package-locations)
+  "Find the packages."
+  (let ((envvar-value (getenv "EMACSPACKAGEPATH")))
+    (if envvar-value
+       (packages-split-package-path (paths-decode-directory-path envvar-value))
+      (packages-deconstruct
+       (packages-split-package-path configure-package-path)
+       #'(lambda (configure-early-packages
+                 configure-late-packages
+                 configure-last-packages)
+          (list (packages-find-packages-at-time roots package-locations 'early
+                                                configure-early-packages)
+                (packages-find-packages-at-time roots package-locations 'late
+                                                configure-late-packages)
+                (packages-find-packages-at-time roots package-locations 'last
+                                                configure-last-packages)))))))
+
+(defun packages-find-package-library-path (packages suffixes)
+  "Construct a path into a component of the packages hierarchy.
+PACKAGES is a list of package directories.
+SUFFIXES is a list of names of package subdirectories to look for."
+  (let ((directories
+        (apply
+         #'nconc
+         (mapcar #'(lambda (package)
+                     (mapcar #'(lambda (suffix)
+                                 (file-name-as-directory (concat package suffix)))
+                             suffixes))
+                 packages))))
+    (paths-directories-which-exist directories)))
+
+(defun packages-find-package-load-path (packages)
+  "Construct the load-path component for packages.
+PACKAGES is a list of package directories."
+  (paths-find-recursive-load-path
+   (packages-find-package-library-path packages
+                                      '("lisp"))
+   packages-load-path-depth))
+
+(defun packages-find-package-exec-path (packages)
+  "Construct the exec-path component for packages.
+PACKAGES is a list of package directories."
+  (packages-find-package-library-path packages
+                                     (list (paths-construct-path
+                                            (list "bin" system-configuration))
+                                           "lib-src")))
+
+(defun packages-find-package-info-path (packages)
+  "Construct the info-path component for packages.
+PACKAGES is a list of package directories."
+  (packages-find-package-library-path packages '("info")))
+
+(defun packages-find-package-data-path (packages)
+  "Construct the data-path component for packages.
+PACKAGES is a list of package directories."
+  (paths-find-recursive-load-path
+   (packages-find-package-library-path packages
+                                      '("etc"))
+   packages-data-path-depth))
+
+;; Loading package initialization files
+
+(defun packages-load-package-lisps (package-load-path base)
+  "Load all Lisp files of a certain name along a load path.
+BASE is the base name of the files."
+  (mapcar #'(lambda (dir)
+           (let ((file-name (expand-file-name base dir)))
+             (condition-case error
+                 (load file-name t t)
+               (error
+                (warn (format "Autoload error in: %s:\n\t%s"
+                              file-name
+                              (with-output-to-string
+                                (display-error error nil))))))))
+       package-load-path))
+
+(defun packages-load-package-auto-autoloads (package-load-path)
+  "Load auto-autoload files along a load path."
+  (packages-load-package-lisps package-load-path
+                              (file-name-sans-extension autoload-file-name)))
+
+(defun packages-handle-package-dumped-lisps (handle package-load-path)
+  "Load dumped-lisp.el files along a load path.
+Call HANDLE on each file off definitions of PACKAGE-LISP there."
+  (mapcar #'(lambda (dir)
+           (let ((file-name (expand-file-name "dumped-lisp.el" dir)))
+             (if (file-exists-p file-name)
+                 (let (package-lisp
+                       ;; 20.4 packages could set this
+                       preloaded-file-list)
+                   (load file-name)
+                   ;; dumped-lisp.el could have set this ...
+                   (if package-lisp
+                       (mapcar #'(lambda (base)
+                                 (funcall handle base))
+                             package-lisp))))))
+       package-load-path))
+
+(defun packages-load-package-dumped-lisps (package-load-path)
+  "Load dumped-lisp.el files along a load path.
+Also load files off PACKAGE-LISP definitions there."
+  (packages-handle-package-dumped-lisps #'load package-load-path))
+
+(defun packages-collect-package-dumped-lisps (package-load-path)
+  "Load dumped-lisp.el files along a load path.
+Return list of files off PACKAGE-LISP definitions there."
+  (let ((*files* '()))
+    (packages-handle-package-dumped-lisps
+     #'(lambda (file)
+        (setq *files* (cons file *files*)))
+     package-load-path)
+    (reverse *files*)))
+
+(provide 'packages)
+
+;;; packages.el ends here