2013-05-13 07:16:06 +00:00
|
|
|
|
;;; package-build.el --- Tools for assembling a package archive
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2013-04-25 10:58:37 +00:00
|
|
|
|
;; Copyright (C) 2011-2013 Donald Ephraim Curtis <dcurtis@milkbox.net>
|
2014-02-21 09:35:14 +00:00
|
|
|
|
;; Copyright (C) 2012-2014 Steve Purcell <steve@sanityinc.com>
|
2011-10-05 05:49:38 +00:00
|
|
|
|
;; Copyright (C) 2009 Phil Hagelberg <technomancy@gmail.com>
|
|
|
|
|
|
|
|
|
|
;; Author: Donald Ephraim Curtis <dcurtis@milkbox.net>
|
|
|
|
|
;; Created: 2011-09-30
|
|
|
|
|
;; Version: 0.1
|
|
|
|
|
;; Keywords: tools
|
2013-04-01 20:16:46 +00:00
|
|
|
|
;; Package-Requires: ((cl-lib "0.2"))
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
|
|
|
|
;; This file is not (yet) part of GNU Emacs.
|
|
|
|
|
;; However, it is distributed under the same license.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs 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 3, or (at your option)
|
|
|
|
|
;; any later version.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
|
|
|
|
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
|
;; Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; This file allows a curator to publish an archive of Emacs packages.
|
|
|
|
|
|
|
|
|
|
;; The archive is generated from an index, which contains a list of
|
2012-05-01 13:09:50 +00:00
|
|
|
|
;; projects and repositories from which to get them. The term
|
2011-10-05 05:49:38 +00:00
|
|
|
|
;; "package" here is used to mean a specific version of a project that
|
|
|
|
|
;; is prepared for download and installation.
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
;; Since this library is not meant to be loaded by users
|
|
|
|
|
;; at runtime, use of cl functions should not be a problem.
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(require 'cl-lib)
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
|
|
|
|
(require 'package)
|
2012-12-04 19:56:35 +00:00
|
|
|
|
(require 'lisp-mnt)
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2013-03-24 13:08:19 +00:00
|
|
|
|
(defconst pb/this-dir (file-name-directory (or load-file-name (buffer-file-name))))
|
|
|
|
|
|
|
|
|
|
(defcustom package-build-working-dir (expand-file-name "working/" pb/this-dir)
|
2012-01-22 05:13:19 +00:00
|
|
|
|
"Directory in which to keep checkouts."
|
|
|
|
|
:group 'package-build
|
|
|
|
|
:type 'string)
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2013-03-24 13:08:19 +00:00
|
|
|
|
(defcustom package-build-archive-dir (expand-file-name "packages/" pb/this-dir)
|
2012-01-22 05:13:19 +00:00
|
|
|
|
"Directory in which to keep compiled archives."
|
|
|
|
|
:group 'package-build
|
|
|
|
|
:type 'string)
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2013-03-24 13:08:19 +00:00
|
|
|
|
(defcustom package-build-recipes-dir (expand-file-name "recipes/" pb/this-dir)
|
2012-04-06 19:49:39 +00:00
|
|
|
|
"Directory containing recipe files."
|
2012-01-22 05:13:19 +00:00
|
|
|
|
:group 'package-build
|
|
|
|
|
:type 'string)
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(defvar package-build-verbose t
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"When non-nil, `package-build' feels free to print information about its progress.")
|
2012-02-03 14:46:55 +00:00
|
|
|
|
|
2014-03-27 12:38:07 +00:00
|
|
|
|
(defvar package-build-stable nil
|
|
|
|
|
"When non-nil, `package-build' tries to build packages from versions-tagged code.")
|
|
|
|
|
|
2013-03-09 15:12:50 +00:00
|
|
|
|
;;; Internal Variables
|
2012-02-03 14:46:55 +00:00
|
|
|
|
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(defvar pb/recipe-alist nil
|
|
|
|
|
"Internal list of package build specs.
|
|
|
|
|
|
2014-02-16 09:26:21 +00:00
|
|
|
|
Do not use this directly. Use `package-build-recipe-alist'
|
2013-03-09 15:12:50 +00:00
|
|
|
|
function.")
|
|
|
|
|
|
2013-03-28 21:52:52 +00:00
|
|
|
|
(defvar pb/recipe-alist-initialized nil
|
|
|
|
|
"Determines if `pb/recipe-alist` has been initialized.")
|
|
|
|
|
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(defvar pb/archive-alist nil
|
|
|
|
|
"Internal list of already-built packages, in the standard package.el format.
|
|
|
|
|
|
2014-02-16 09:26:21 +00:00
|
|
|
|
Do not use this directly. Use `package-build-archive-alist'
|
2013-03-09 15:12:50 +00:00
|
|
|
|
function for access to this function")
|
2012-12-31 00:58:34 +00:00
|
|
|
|
|
2013-03-28 21:52:52 +00:00
|
|
|
|
(defvar pb/archive-alist-initialized nil
|
|
|
|
|
"Determines if pb/archive-alist has been initialized.")
|
|
|
|
|
|
2013-12-28 12:14:54 +00:00
|
|
|
|
(defconst package-build-default-files-spec
|
|
|
|
|
'("*.el" "*.el.in" "dir"
|
|
|
|
|
"*.info" "*.texi" "*.texinfo"
|
2014-01-27 18:04:25 +00:00
|
|
|
|
"doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo"
|
2014-02-07 04:34:56 +00:00
|
|
|
|
(:exclude ".dir-locals.el" "tests.el" "*-test.el" "*-tests.el"))
|
2013-03-24 13:17:25 +00:00
|
|
|
|
"Default value for :files attribute in recipes.")
|
|
|
|
|
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(defun pb/message (format-string &rest args)
|
|
|
|
|
"Log a message using FORMAT-STRING and ARGS as per `message'."
|
|
|
|
|
(when package-build-verbose
|
|
|
|
|
(apply 'message format-string args)))
|
2012-02-03 14:46:55 +00:00
|
|
|
|
|
2012-09-15 12:14:52 +00:00
|
|
|
|
(defun pb/slurp-file (file-name)
|
|
|
|
|
"Return the contents of FILE-NAME as a string, or nil if no such file exists."
|
|
|
|
|
(when (file-exists-p file-name)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert-file-contents-literally file-name)
|
|
|
|
|
(buffer-substring-no-properties (point-min) (point-max)))))
|
|
|
|
|
|
|
|
|
|
(defun pb/string-rtrim (str)
|
|
|
|
|
"Remove trailing whitespace from `STR'."
|
|
|
|
|
(replace-regexp-in-string "[ \t\n]*$" "" str))
|
|
|
|
|
|
2012-04-20 15:48:30 +00:00
|
|
|
|
(defun pb/parse-time (str)
|
2012-09-01 14:37:26 +00:00
|
|
|
|
"Parse STR as a time, and format as a YYYYMMDD.HHMM string."
|
|
|
|
|
;; We remove zero-padding the HH portion, as it is lost
|
|
|
|
|
;; when stored in the archive-contents
|
2012-10-20 19:22:14 +00:00
|
|
|
|
(let* ((s (substring-no-properties str))
|
2013-07-10 07:08:31 +00:00
|
|
|
|
(time (date-to-time
|
|
|
|
|
(if (string-match "^\\([0-9]\\{4\\}\\)/\\([0-9]\\{2\\}\\)/\\([0-9]\\{2\\}\\) \\([0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)$" str)
|
|
|
|
|
(concat (match-string 1 str) "-" (match-string 2 str) "-"
|
|
|
|
|
(match-string 3 str) " " (match-string 4 str))
|
|
|
|
|
str))))
|
2012-09-06 13:23:41 +00:00
|
|
|
|
(concat (format-time-string "%Y%m%d." time)
|
2012-12-02 16:05:44 +00:00
|
|
|
|
(format "%d" (or (string-to-number (format-time-string "%H%M" time)) 0)))))
|
2012-04-20 15:48:30 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/string-match-all (regex str &optional group)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Find every match for `REGEX' within `STR', returning the full match string or group `GROUP'."
|
2012-04-20 15:48:30 +00:00
|
|
|
|
(let (result
|
|
|
|
|
(pos 0)
|
|
|
|
|
(group (or group 0)))
|
|
|
|
|
(while (string-match regex str pos)
|
|
|
|
|
(push (match-string group str) result)
|
|
|
|
|
(setq pos (match-end group)))
|
|
|
|
|
result))
|
|
|
|
|
|
|
|
|
|
(defun pb/find-parse-time (regex &optional bound)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Find REGEX in current buffer and format as a time version, optionally looking only as far as BOUND."
|
2012-04-20 15:48:30 +00:00
|
|
|
|
(pb/parse-time (progn (re-search-backward regex bound)
|
|
|
|
|
(match-string-no-properties 1))))
|
|
|
|
|
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(defun pb/valid-version-string (str)
|
|
|
|
|
"Returns true if STR is a valid version, otherwise return nil."
|
|
|
|
|
(ignore-errors (version-to-list str)))
|
|
|
|
|
|
|
|
|
|
(defun pb/find-parse-version-newest (regex &optional bound)
|
|
|
|
|
"Find the newest version matching REGEX, optionally looking only as far as BOUND."
|
|
|
|
|
(let* ((text (buffer-substring-no-properties
|
|
|
|
|
(or bound (point-min)) (point)))
|
|
|
|
|
(times (cl-remove-if-not 'pb/valid-version-string
|
|
|
|
|
(pb/string-match-all regex text 1))))
|
|
|
|
|
(car (nreverse (sort times 'version<)))))
|
|
|
|
|
|
2012-04-20 15:48:30 +00:00
|
|
|
|
(defun pb/find-parse-time-latest (regex &optional bound)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Find the latest timestamp matching REGEX, optionally looking only as far as BOUND."
|
2012-04-20 15:48:30 +00:00
|
|
|
|
(let* ((text (buffer-substring-no-properties
|
|
|
|
|
(or bound (point-min)) (point)))
|
|
|
|
|
(times (mapcar 'pb/parse-time (pb/string-match-all regex text 1))))
|
|
|
|
|
(car (nreverse (sort times 'string<)))))
|
2012-01-29 01:26:20 +00:00
|
|
|
|
|
2013-07-24 14:40:59 +00:00
|
|
|
|
(defun pb/run-process (dir command &rest args)
|
|
|
|
|
"In DIR (or `default-directory' if unset) run COMMAND with ARGS.
|
2012-01-31 10:49:49 +00:00
|
|
|
|
Output is written to the current buffer."
|
2014-01-31 08:39:28 +00:00
|
|
|
|
(let* ((default-directory (file-name-as-directory (or dir default-directory)))
|
2013-07-24 14:40:59 +00:00
|
|
|
|
(have-timeout (executable-find "timeout"))
|
|
|
|
|
(argv (if have-timeout
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(append (list "timeout" "-k" "60" "600" command) args)
|
2013-07-24 14:40:59 +00:00
|
|
|
|
(cons command args))))
|
2014-01-31 08:20:39 +00:00
|
|
|
|
(unless (file-directory-p default-directory)
|
|
|
|
|
(error "Can't run process in non-existent directory: %s" default-directory))
|
2013-07-24 14:40:59 +00:00
|
|
|
|
(let ((exit-code (apply 'process-file (car argv) nil (current-buffer) t (cdr argv))))
|
2012-03-16 14:30:13 +00:00
|
|
|
|
(unless (zerop exit-code)
|
2014-01-28 18:06:30 +00:00
|
|
|
|
(error "Command '%s' exited with non-zero status %d: %s"
|
|
|
|
|
argv exit-code (buffer-string))))))
|
2012-01-29 13:09:07 +00:00
|
|
|
|
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(defun pb/run-process-match (regex dir prog &rest args)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Find match for REGEX when - in DIR, or `default-directory' if unset - we run PROG with ARGS."
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(apply 'pb/run-process dir prog args)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(re-search-forward regex)
|
|
|
|
|
(match-string-no-properties 1)))
|
|
|
|
|
|
2013-12-28 12:14:54 +00:00
|
|
|
|
(defun package-build-checkout (package-name config working-dir)
|
|
|
|
|
"Check out source for PACKAGE-NAME with CONFIG under WORKING-DIR.
|
|
|
|
|
In turn, this function uses the :fetcher option in the CONFIG to
|
2012-01-31 10:49:49 +00:00
|
|
|
|
choose a source-specific fetcher function, which it calls with
|
2013-12-28 12:14:54 +00:00
|
|
|
|
the same arguments.
|
|
|
|
|
|
|
|
|
|
Returns a last-modification timestamp for the :files listed in
|
|
|
|
|
CONFIG, if any, or `package-build-default-files-spec' otherwise."
|
2012-01-30 12:46:44 +00:00
|
|
|
|
(let ((repo-type (plist-get config :fetcher)))
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Fetcher: %s" repo-type)
|
2012-04-21 19:36:37 +00:00
|
|
|
|
(unless (eq 'wiki repo-type)
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Source: %s\n" (or (plist-get config :repo) (plist-get config :url))))
|
2014-01-30 14:42:58 +00:00
|
|
|
|
(funcall (intern (format "pb/checkout-%s" repo-type))
|
2014-01-31 08:17:06 +00:00
|
|
|
|
package-name config (file-name-as-directory working-dir))))
|
2012-01-30 12:46:44 +00:00
|
|
|
|
|
2012-03-19 12:50:51 +00:00
|
|
|
|
(defvar pb/last-wiki-fetch-time 0
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"The time at which an emacswiki URL was last requested.
|
|
|
|
|
This is used to avoid exceeding the rate limit of 1 request per 2
|
2012-03-19 12:50:51 +00:00
|
|
|
|
seconds; the server cuts off after 10 requests in 20 seconds.")
|
|
|
|
|
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(defvar pb/wiki-min-request-interval 3
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"The shortest permissible interval between successive requests for Emacswiki URLs.")
|
2012-03-19 12:50:51 +00:00
|
|
|
|
|
2012-03-19 14:17:16 +00:00
|
|
|
|
(defmacro pb/with-wiki-rate-limit (&rest body)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Rate-limit BODY code passed to this macro to match EmacsWiki's rate limiting."
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(let ((now (cl-gensym))
|
|
|
|
|
(elapsed (cl-gensym)))
|
2012-03-19 12:50:51 +00:00
|
|
|
|
`(let* ((,now (float-time))
|
|
|
|
|
(,elapsed (- ,now pb/last-wiki-fetch-time)))
|
2012-03-19 14:17:16 +00:00
|
|
|
|
(when (< ,elapsed pb/wiki-min-request-interval)
|
|
|
|
|
(let ((wait (- pb/wiki-min-request-interval ,elapsed)))
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Waiting %.2f secs before hitting Emacswiki again" wait)
|
2012-03-19 12:50:51 +00:00
|
|
|
|
(sleep-for wait)))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn ,@body)
|
|
|
|
|
(setq pb/last-wiki-fetch-time (float-time))))))
|
|
|
|
|
|
|
|
|
|
(defun pb/grab-wiki-file (filename)
|
|
|
|
|
"Download FILENAME from emacswiki, returning its last-modified time."
|
2012-04-21 17:08:45 +00:00
|
|
|
|
(let* ((download-url
|
|
|
|
|
(format "http://www.emacswiki.org/emacs/download/%s" filename))
|
|
|
|
|
(wiki-url
|
|
|
|
|
(format "http://www.emacswiki.org/emacs/%s" filename)))
|
2012-03-19 14:17:16 +00:00
|
|
|
|
(pb/with-wiki-rate-limit
|
2012-03-19 12:50:51 +00:00
|
|
|
|
(url-copy-file download-url filename t))
|
2012-04-24 19:07:44 +00:00
|
|
|
|
(when (zerop (nth 7 (file-attributes filename)))
|
|
|
|
|
(error "Wiki file %s was empty - has it been removed?" filename))
|
2012-12-04 20:26:25 +00:00
|
|
|
|
;; The Last-Modified response header for the download is actually
|
|
|
|
|
;; correct for the file, but we have no access to that
|
|
|
|
|
;; header. Instead, we must query the non-raw emacswiki page for
|
|
|
|
|
;; the file.
|
|
|
|
|
;; Since those Emacswiki lookups are time-consuming, we maintain a
|
|
|
|
|
;; foo.el.stamp file containing ("SHA1" . "PARSED_TIME")
|
|
|
|
|
(let* ((new-content-hash (secure-hash 'sha1 (pb/slurp-file filename)))
|
|
|
|
|
(stamp-file (concat filename ".stamp"))
|
2012-12-11 11:01:05 +00:00
|
|
|
|
(stamp-info (pb/read-from-file stamp-file))
|
|
|
|
|
(prev-content-hash (car stamp-info)))
|
|
|
|
|
(if (and prev-content-hash
|
|
|
|
|
(string-equal new-content-hash prev-content-hash))
|
2012-12-04 20:26:25 +00:00
|
|
|
|
;; File has not changed, so return old timestamp
|
|
|
|
|
(progn
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "%s is unchanged" filename)
|
2012-12-04 20:26:25 +00:00
|
|
|
|
(cdr stamp-info))
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "%s has changed - checking mod time" filename)
|
2012-12-04 20:26:25 +00:00
|
|
|
|
(let ((new-timestamp
|
|
|
|
|
(with-current-buffer (pb/with-wiki-rate-limit
|
|
|
|
|
(url-retrieve-synchronously wiki-url))
|
2013-11-05 14:25:25 +00:00
|
|
|
|
(unless (= 200 url-http-response-status)
|
|
|
|
|
(error "HTTP error %s fetching %s" url-http-response-status wiki-url))
|
|
|
|
|
(goto-char (point-max))
|
2012-12-04 20:26:25 +00:00
|
|
|
|
(pb/find-parse-time
|
2013-10-20 09:26:19 +00:00
|
|
|
|
"Last edited \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\} [A-Z]\\{3\\}\\)"
|
|
|
|
|
url-http-end-of-headers))))
|
2012-12-04 20:26:25 +00:00
|
|
|
|
(pb/dump (cons new-content-hash new-timestamp) stamp-file)
|
|
|
|
|
new-timestamp)))))
|
2012-03-19 12:50:51 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/checkout-wiki (name config dir)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Checkout package NAME with config CONFIG from the EmacsWiki into DIR."
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(unless package-build-stable
|
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(unless (file-exists-p dir)
|
|
|
|
|
(make-directory dir))
|
|
|
|
|
(let ((files (or (plist-get config :files)
|
|
|
|
|
(list (format "%s.el" name))))
|
|
|
|
|
(default-directory dir))
|
|
|
|
|
(car (nreverse (sort (mapcar 'pb/grab-wiki-file files) 'string-lessp)))))))
|
2012-01-30 13:56:23 +00:00
|
|
|
|
|
2012-03-17 18:45:55 +00:00
|
|
|
|
(defun pb/darcs-repo (dir)
|
|
|
|
|
"Get the current darcs repo for DIR."
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(pb/run-process-match "Default Remote: \\(.*\\)" dir "darcs" "show" "repo"))
|
2012-03-17 18:45:55 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/checkout-darcs (name config dir)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Check package NAME with config CONFIG out of darcs into DIR."
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(unless package-build-stable
|
|
|
|
|
(let ((repo (plist-get config :url)))
|
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(cond
|
|
|
|
|
((and (file-exists-p (expand-file-name "_darcs" dir))
|
|
|
|
|
(string-equal (pb/darcs-repo dir) repo))
|
|
|
|
|
(pb/princ-exists dir)
|
|
|
|
|
(pb/run-process dir "darcs" "pull"))
|
|
|
|
|
(t
|
|
|
|
|
(when (file-exists-p dir)
|
|
|
|
|
(delete-directory dir t))
|
|
|
|
|
(pb/princ-checkout repo dir)
|
|
|
|
|
(pb/run-process nil "darcs" "get" repo dir)))
|
|
|
|
|
(apply 'pb/run-process dir "darcs" "changes" "--max-count" "1"
|
|
|
|
|
(pb/expand-source-file-list dir config))
|
|
|
|
|
(pb/find-parse-time
|
|
|
|
|
"\\([a-zA-Z]\\{3\\} [a-zA-Z]\\{3\\} \\( \\|[0-9]\\)[0-9] [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\} [A-Za-z]\\{3\\} [0-9]\\{4\\}\\)")))))
|
2012-01-30 12:46:44 +00:00
|
|
|
|
|
2012-03-17 18:45:55 +00:00
|
|
|
|
(defun pb/svn-repo (dir)
|
2012-04-30 00:54:35 +00:00
|
|
|
|
"Get the current svn repo for DIR."
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(pb/run-process-match "URL: \\(.*\\)" dir "svn" "info"))
|
2012-03-17 18:45:55 +00:00
|
|
|
|
|
2012-04-17 20:42:36 +00:00
|
|
|
|
(defun pb/trim (str &optional chr)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Return a copy of STR without any trailing CHR (or space if unspecified)."
|
|
|
|
|
(if (equal (elt str (1- (length str))) (or chr ? ))
|
2012-04-17 20:42:36 +00:00
|
|
|
|
(substring str 0 (1- (length str)))
|
|
|
|
|
str))
|
|
|
|
|
|
2012-04-21 19:36:37 +00:00
|
|
|
|
(defun pb/princ-exists (dir)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Print a message that the contents of DIR will be updated."
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Updating %s" dir))
|
2012-04-21 19:36:37 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/princ-checkout (repo dir)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Print a message that REPO will be checked out into DIR."
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Cloning %s to %s" repo dir))
|
2012-04-21 19:36:37 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/checkout-svn (name config dir)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Check package NAME with config CONFIG out of svn into DIR."
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(unless package-build-stable
|
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(let ((repo (pb/trim (plist-get config :url) ?/))
|
|
|
|
|
(bound (goto-char (point-max))))
|
|
|
|
|
(cond
|
|
|
|
|
((and (file-exists-p (expand-file-name ".svn" dir))
|
|
|
|
|
(string-equal (pb/svn-repo dir) repo))
|
|
|
|
|
(pb/princ-exists dir)
|
|
|
|
|
(pb/run-process dir "svn" "up"))
|
|
|
|
|
(t
|
|
|
|
|
(when (file-exists-p dir)
|
|
|
|
|
(delete-directory dir t))
|
|
|
|
|
(pb/princ-checkout repo dir)
|
|
|
|
|
(pb/run-process nil "svn" "checkout" repo dir)))
|
|
|
|
|
(apply 'pb/run-process dir "svn" "info"
|
|
|
|
|
(pb/expand-source-file-list dir config))
|
|
|
|
|
(or (pb/find-parse-time-latest "Last Changed Date: \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)" bound)
|
|
|
|
|
(error "No valid timestamps found!"))))))
|
|
|
|
|
|
2012-01-30 12:46:44 +00:00
|
|
|
|
|
2012-09-14 16:35:16 +00:00
|
|
|
|
(defun pb/cvs-repo (dir)
|
|
|
|
|
"Get the current CVS root and repository for DIR.
|
|
|
|
|
|
|
|
|
|
Return a cons cell whose `car' is the root and whose `cdr' is the repository."
|
2012-09-15 12:14:52 +00:00
|
|
|
|
(apply 'cons
|
|
|
|
|
(mapcar (lambda (file)
|
|
|
|
|
(pb/string-rtrim (pb/slurp-file (expand-file-name file dir))))
|
|
|
|
|
'("CVS/Root" "CVS/Repository"))))
|
2012-09-14 16:35:16 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/checkout-cvs (name config dir)
|
2013-07-09 06:51:56 +00:00
|
|
|
|
"Check package NAME with config CONFIG out of cvs into DIR."
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(unless package-build-stable
|
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(let ((root (pb/trim (plist-get config :url) ?/))
|
|
|
|
|
(repo (or (plist-get config :module) (symbol-name name)))
|
|
|
|
|
(bound (goto-char (point-max))))
|
|
|
|
|
(cond
|
|
|
|
|
((and (file-exists-p (expand-file-name "CVS" dir))
|
|
|
|
|
(equal (pb/cvs-repo dir) (cons root repo)))
|
|
|
|
|
(pb/princ-exists dir)
|
|
|
|
|
(pb/run-process dir "cvs" "update" "-dP"))
|
|
|
|
|
(t
|
|
|
|
|
(when (file-exists-p dir)
|
|
|
|
|
(delete-directory dir t))
|
|
|
|
|
(pb/princ-checkout (format "%s from %s" repo root) dir)
|
|
|
|
|
;; CVS insists on relative paths as target directory for checkout (for
|
|
|
|
|
;; whatever reason), and puts "CVS" directories into every subdirectory
|
|
|
|
|
;; of the current working directory given in the target path. To get CVS
|
|
|
|
|
;; to just write to DIR, we need to execute CVS from the parent
|
|
|
|
|
;; directory of DIR, and specific DIR as relative path. Hence all the
|
|
|
|
|
;; following mucking around with paths. CVS is really horrid.
|
|
|
|
|
(let* ((dir (directory-file-name dir))
|
|
|
|
|
(working-dir (file-name-directory dir))
|
|
|
|
|
(target-dir (file-name-nondirectory dir)))
|
|
|
|
|
(pb/run-process working-dir "env" "TZ=UTC" "cvs" "-z3" "-d" root "checkout"
|
|
|
|
|
"-d" target-dir repo))))
|
|
|
|
|
(apply 'pb/run-process dir "cvs" "log"
|
|
|
|
|
(pb/expand-source-file-list dir config))
|
|
|
|
|
(or (pb/find-parse-time-latest "date: \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)" bound)
|
|
|
|
|
(pb/find-parse-time-latest "date: \\([0-9]\\{4\\}/[0-9]\\{2\\}/[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\);" bound)
|
|
|
|
|
(error "No valid timestamps found!"))
|
|
|
|
|
))))
|
|
|
|
|
|
2012-09-14 16:35:16 +00:00
|
|
|
|
|
2012-03-17 18:45:55 +00:00
|
|
|
|
(defun pb/git-repo (dir)
|
|
|
|
|
"Get the current git repo for DIR."
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(pb/run-process-match
|
|
|
|
|
"Fetch URL: \\(.*\\)" dir "git" "remote" "show" "-n" "origin"))
|
2012-03-17 18:45:55 +00:00
|
|
|
|
|
2012-09-16 23:11:21 +00:00
|
|
|
|
(defun pb/git-head-branch (dir)
|
|
|
|
|
"Get the current git repo for DIR."
|
2012-09-17 00:23:39 +00:00
|
|
|
|
(or (ignore-errors
|
|
|
|
|
(pb/run-process-match
|
|
|
|
|
"HEAD branch: \\(.*\\)" dir "git" "remote" "show" "origin"))
|
|
|
|
|
"master"))
|
2012-09-16 23:11:21 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/checkout-git (name config dir)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Check package NAME with config CONFIG out of git into DIR."
|
2012-01-30 12:46:44 +00:00
|
|
|
|
(let ((repo (plist-get config :url))
|
2014-02-06 20:27:54 +00:00
|
|
|
|
(commit (or (plist-get config :commit)
|
|
|
|
|
(let ((branch (plist-get config :branch)))
|
|
|
|
|
(when branch
|
|
|
|
|
(concat "origin/" branch))))))
|
2012-01-30 12:46:44 +00:00
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(cond
|
2012-03-17 18:45:55 +00:00
|
|
|
|
((and (file-exists-p (expand-file-name ".git" dir))
|
|
|
|
|
(string-equal (pb/git-repo dir) repo))
|
2012-04-21 19:36:37 +00:00
|
|
|
|
(pb/princ-exists dir)
|
2012-09-15 19:27:09 +00:00
|
|
|
|
(pb/run-process dir "git" "remote" "update"))
|
2012-01-30 12:46:44 +00:00
|
|
|
|
(t
|
2012-03-17 18:39:26 +00:00
|
|
|
|
(when (file-exists-p dir)
|
2014-02-15 13:52:19 +00:00
|
|
|
|
(delete-directory dir t))
|
2012-04-21 19:36:37 +00:00
|
|
|
|
(pb/princ-checkout repo dir)
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(pb/run-process nil "git" "clone" repo dir)))
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(if package-build-stable
|
|
|
|
|
(let ((bound (goto-char (point-max)))
|
|
|
|
|
version-tag)
|
|
|
|
|
(pb/run-process dir "git" "tag")
|
|
|
|
|
(setq version-tag
|
|
|
|
|
(or (pb/find-parse-version-newest "^\\([^ \t\n]+\\)$" bound)
|
|
|
|
|
(error "No valid stable versions found for %s" name)))
|
|
|
|
|
;; Using reset --hard here to comply with what's used for
|
|
|
|
|
;; unstable, but maybe this should be a checkout?
|
|
|
|
|
(pb/run-process dir "git" "reset" "--hard" (concat "tags/" version-tag))
|
|
|
|
|
(pb/run-process dir "git" "submodule" "update" "--init" "--recursive")
|
|
|
|
|
version-tag)
|
|
|
|
|
(pb/run-process dir "git" "reset" "--hard"
|
|
|
|
|
(or commit (concat "origin/" (pb/git-head-branch dir))))
|
|
|
|
|
(pb/run-process dir "git" "submodule" "update" "--init" "--recursive")
|
|
|
|
|
(apply 'pb/run-process dir "git" "log" "--first-parent" "-n1" "--pretty=format:'\%ci'"
|
|
|
|
|
(pb/expand-source-file-list dir config))
|
|
|
|
|
(pb/find-parse-time
|
|
|
|
|
"\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)")))))
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2012-04-09 19:37:45 +00:00
|
|
|
|
(defun pb/checkout-github (name config dir)
|
|
|
|
|
"Check package NAME with config CONFIG out of github into DIR."
|
|
|
|
|
(let* ((url (format "git://github.com/%s.git" (plist-get config :repo))))
|
|
|
|
|
(pb/checkout-git name (plist-put (copy-sequence config) :url url) dir)))
|
|
|
|
|
|
2012-04-30 21:03:22 +00:00
|
|
|
|
(defun pb/bzr-expand-repo (repo)
|
|
|
|
|
"Get REPO expanded name."
|
2012-04-30 22:24:48 +00:00
|
|
|
|
(pb/run-process-match "\\(?:branch root\\|repository branch\\): \\(.*\\)" nil "bzr" "info" repo))
|
2012-04-21 17:08:45 +00:00
|
|
|
|
|
2012-04-11 11:21:57 +00:00
|
|
|
|
(defun pb/bzr-repo (dir)
|
|
|
|
|
"Get the current bzr repo for DIR."
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(pb/run-process-match "parent branch: \\(.*\\)" dir "bzr" "info"))
|
2012-04-11 11:21:57 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/checkout-bzr (name config dir)
|
|
|
|
|
"Check package NAME with config CONFIG out of bzr into DIR."
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(unless package-build-stable
|
|
|
|
|
(let ((repo (pb/bzr-expand-repo (plist-get config :url))))
|
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(cond
|
|
|
|
|
((and (file-exists-p (expand-file-name ".bzr" dir))
|
|
|
|
|
(string-equal (pb/bzr-repo dir) repo))
|
|
|
|
|
(pb/princ-exists dir)
|
|
|
|
|
(pb/run-process dir "bzr" "merge"))
|
|
|
|
|
(t
|
|
|
|
|
(when (file-exists-p dir)
|
|
|
|
|
(delete-directory dir t))
|
|
|
|
|
(pb/princ-checkout repo dir)
|
|
|
|
|
(pb/run-process nil "bzr" "branch" repo dir)))
|
|
|
|
|
(apply 'pb/run-process dir "bzr" "log" "-l1"
|
|
|
|
|
(pb/expand-source-file-list dir config))
|
|
|
|
|
(pb/find-parse-time
|
|
|
|
|
"\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)")))))
|
2012-04-11 11:21:57 +00:00
|
|
|
|
|
2012-04-11 11:56:29 +00:00
|
|
|
|
(defun pb/hg-repo (dir)
|
|
|
|
|
"Get the current hg repo for DIR."
|
2012-04-30 20:59:33 +00:00
|
|
|
|
(pb/run-process-match "default = \\(.*\\)" dir "hg" "paths"))
|
2012-04-11 11:56:29 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/checkout-hg (name config dir)
|
|
|
|
|
"Check package NAME with config CONFIG out of hg into DIR."
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(unless package-build-stable
|
|
|
|
|
(let ((repo (plist-get config :url)))
|
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-checkout*")
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(cond
|
|
|
|
|
((and (file-exists-p (expand-file-name ".hg" dir))
|
|
|
|
|
(string-equal (pb/hg-repo dir) repo))
|
|
|
|
|
(pb/princ-exists dir)
|
|
|
|
|
(pb/run-process dir "hg" "pull")
|
|
|
|
|
(pb/run-process dir "hg" "update"))
|
|
|
|
|
(t
|
|
|
|
|
(when (file-exists-p dir)
|
|
|
|
|
(delete-directory dir t))
|
|
|
|
|
(pb/princ-checkout repo dir)
|
|
|
|
|
(pb/run-process nil "hg" "clone" repo dir)))
|
|
|
|
|
(apply 'pb/run-process dir "hg" "log" "--style" "compact" "-l1"
|
|
|
|
|
(pb/expand-source-file-list dir config))
|
|
|
|
|
(pb/find-parse-time
|
|
|
|
|
"\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)")))))
|
2012-04-11 11:21:57 +00:00
|
|
|
|
|
2013-11-13 20:07:00 +00:00
|
|
|
|
(defun pb/dump (data file &optional pretty-print)
|
|
|
|
|
"Write DATA to FILE as a Lisp sexp.
|
|
|
|
|
Optionally PRETTY-PRINT the data."
|
|
|
|
|
(with-temp-file file
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "File: %s" file)
|
2013-11-13 20:07:00 +00:00
|
|
|
|
(if pretty-print
|
|
|
|
|
(pp data (current-buffer))
|
|
|
|
|
(print data (current-buffer)))))
|
2012-01-30 19:54:04 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/write-pkg-file (pkg-file pkg-info)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Write PKG-FILE containing PKG-INFO."
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(pb/dump
|
2012-01-30 19:54:04 +00:00
|
|
|
|
`(define-package
|
|
|
|
|
,(aref pkg-info 0)
|
|
|
|
|
,(aref pkg-info 3)
|
|
|
|
|
,(aref pkg-info 2)
|
2012-02-02 19:20:43 +00:00
|
|
|
|
',(mapcar
|
2012-01-30 19:54:04 +00:00
|
|
|
|
(lambda (elt)
|
|
|
|
|
(list (car elt)
|
|
|
|
|
(package-version-join (cadr elt))))
|
2014-04-02 14:47:09 +00:00
|
|
|
|
(aref pkg-info 1))
|
|
|
|
|
;; Append our extra information
|
|
|
|
|
,@(apply #'append (mapcar (lambda (entry)
|
|
|
|
|
(let ((value (cdr entry)))
|
|
|
|
|
(when (or (symbolp value) (listp value))
|
|
|
|
|
;; We must quote lists and symbols,
|
|
|
|
|
;; because Emacs 24.3 and earlier evaluate
|
|
|
|
|
;; the package information, which would
|
|
|
|
|
;; break for unquoted symbols or lists
|
|
|
|
|
(setq value (list 'quote value)))
|
|
|
|
|
(list (car entry) value)))
|
2014-04-04 15:54:06 +00:00
|
|
|
|
(when (> (length pkg-info) 4)
|
|
|
|
|
(aref pkg-info 4)))))
|
2013-11-13 20:07:00 +00:00
|
|
|
|
pkg-file
|
|
|
|
|
t))
|
2011-11-14 02:47:03 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/read-from-file (file-name)
|
2012-09-15 12:14:52 +00:00
|
|
|
|
"Read and return the Lisp data stored in FILE-NAME, or nil if no such file exists."
|
2012-01-29 12:01:12 +00:00
|
|
|
|
(when (file-exists-p file-name)
|
2012-09-15 12:14:52 +00:00
|
|
|
|
(car (read-from-string (pb/slurp-file file-name)))))
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/create-tar (file dir &optional files)
|
2013-09-15 00:15:43 +00:00
|
|
|
|
"Create a tar FILE containing the contents of DIR, or just FILES if non-nil."
|
|
|
|
|
(apply 'process-file
|
|
|
|
|
"tar" nil
|
|
|
|
|
(get-buffer-create "*package-build-checkout*")
|
|
|
|
|
nil "-cvf"
|
|
|
|
|
file
|
|
|
|
|
"--exclude=.svn"
|
|
|
|
|
"--exclude=CVS"
|
|
|
|
|
"--exclude=.git*"
|
|
|
|
|
"--exclude=_darcs"
|
|
|
|
|
"--exclude=.bzr"
|
|
|
|
|
"--exclude=.hg"
|
|
|
|
|
(or (mapcar (lambda (fn) (concat dir "/" fn)) files) (list dir))))
|
2011-10-05 05:49:38 +00:00
|
|
|
|
|
2012-11-08 21:32:05 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/find-package-commentary (file-path)
|
|
|
|
|
"Get commentary section from FILE-PATH."
|
|
|
|
|
(when (file-exists-p file-path)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert-file-contents file-path)
|
|
|
|
|
(lm-commentary))))
|
|
|
|
|
|
2014-02-04 16:43:13 +00:00
|
|
|
|
(defun pb/write-pkg-readme (target-dir commentary file-name)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"In TARGET-DIR, write COMMENTARY to a -readme.txt file prefixed with FILE-NAME."
|
2012-11-08 21:32:05 +00:00
|
|
|
|
(when commentary
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert commentary)
|
|
|
|
|
;; Adapted from `describe-package-1'.
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(save-excursion
|
2012-11-29 05:36:12 +00:00
|
|
|
|
(when (re-search-forward "^;;; Commentary:\n" nil t)
|
2012-11-08 21:32:05 +00:00
|
|
|
|
(replace-match ""))
|
|
|
|
|
(while (re-search-forward "^\\(;+ ?\\)" nil t)
|
2012-11-29 05:36:12 +00:00
|
|
|
|
(replace-match ""))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(when (re-search-forward "\\`\\( *\n\\)+" nil t)
|
2012-11-08 21:32:05 +00:00
|
|
|
|
(replace-match "")))
|
|
|
|
|
(delete-trailing-whitespace)
|
|
|
|
|
(let ((coding-system-for-write buffer-file-coding-system))
|
|
|
|
|
(write-region nil nil
|
2014-02-04 16:43:13 +00:00
|
|
|
|
(pb/readme-file-name target-dir file-name))))))
|
2013-03-28 22:04:56 +00:00
|
|
|
|
|
2014-02-04 16:43:13 +00:00
|
|
|
|
(defun pb/readme-file-name (target-dir file-name)
|
|
|
|
|
"Name of the readme file in TARGET-DIR for the package FILE-NAME."
|
2013-03-28 22:04:56 +00:00
|
|
|
|
(expand-file-name (concat file-name "-readme.txt")
|
2014-02-04 16:43:13 +00:00
|
|
|
|
target-dir))
|
2012-11-08 21:32:05 +00:00
|
|
|
|
|
2013-03-24 13:24:58 +00:00
|
|
|
|
(defun pb/update-or-insert-version (version)
|
|
|
|
|
"Ensure current buffer has a \"Version: VERSION\" header."
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(if (re-search-forward "^;;;* *Version: *" nil t)
|
2013-04-07 10:02:27 +00:00
|
|
|
|
(progn
|
|
|
|
|
(move-beginning-of-line nil)
|
2013-04-07 10:05:06 +00:00
|
|
|
|
(search-forward "V" nil t)
|
|
|
|
|
(backward-char)
|
2013-04-07 10:02:27 +00:00
|
|
|
|
(insert "X-Original-")
|
|
|
|
|
(move-beginning-of-line nil))
|
2013-03-24 13:24:58 +00:00
|
|
|
|
(forward-line))
|
2013-03-26 20:24:02 +00:00
|
|
|
|
(insert (format ";; Version: %s" version))
|
2013-03-24 13:24:58 +00:00
|
|
|
|
(newline))
|
|
|
|
|
|
2013-03-28 15:22:15 +00:00
|
|
|
|
(defun pb/ensure-ends-here-line (file-path)
|
|
|
|
|
"Add a 'FILE-PATH ends here' trailing line if missing."
|
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(let* ((fname (file-name-nondirectory file-path))
|
|
|
|
|
(trailer (concat ";;; " fname " ends here")))
|
|
|
|
|
(unless (search-forward trailer nil t)
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(newline)
|
|
|
|
|
(insert trailer)
|
|
|
|
|
(newline)))))
|
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/get-package-info (file-path)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Get a vector of package info from the docstrings in FILE-PATH."
|
2012-01-22 18:19:04 +00:00
|
|
|
|
(when (file-exists-p file-path)
|
2012-01-22 05:13:19 +00:00
|
|
|
|
(ignore-errors
|
2012-04-21 16:57:18 +00:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert-file-contents file-path)
|
2012-04-21 20:07:24 +00:00
|
|
|
|
;; next few lines are a hack for some packages that aren't
|
2012-01-22 18:19:04 +00:00
|
|
|
|
;; commented properly.
|
2013-03-24 13:24:58 +00:00
|
|
|
|
(pb/update-or-insert-version "0")
|
2013-03-28 15:22:15 +00:00
|
|
|
|
(pb/ensure-ends-here-line file-path)
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(cl-flet ((package-strip-rcs-id (str) "0"))
|
2013-07-22 09:10:28 +00:00
|
|
|
|
(pb/package-buffer-info-vec))))))
|
2012-01-22 05:13:19 +00:00
|
|
|
|
|
2012-02-09 21:34:32 +00:00
|
|
|
|
(defun pb/get-pkg-file-info (file-path)
|
2012-01-31 10:49:49 +00:00
|
|
|
|
"Get a vector of package info from \"-pkg.el\" file FILE-PATH."
|
2012-01-22 18:19:04 +00:00
|
|
|
|
(when (file-exists-p file-path)
|
2012-04-24 07:11:16 +00:00
|
|
|
|
(let ((package-def (pb/read-from-file file-path)))
|
|
|
|
|
(if (eq 'define-package (car package-def))
|
|
|
|
|
(let ((pkgfile-info (cdr package-def)))
|
|
|
|
|
(vector
|
|
|
|
|
(nth 0 pkgfile-info)
|
|
|
|
|
(mapcar
|
|
|
|
|
(lambda (elt)
|
|
|
|
|
(list (car elt) (version-to-list (cadr elt))))
|
|
|
|
|
(eval (nth 3 pkgfile-info)))
|
|
|
|
|
(nth 2 pkgfile-info)
|
|
|
|
|
(nth 1 pkgfile-info)))
|
|
|
|
|
(error "No define-package found in %s" file-path)))))
|
2012-01-22 03:49:46 +00:00
|
|
|
|
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(defun pb/merge-package-info (pkg-info name version)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Return a version of PKG-INFO updated with NAME, VERSION and info from CONFIG.
|
2012-01-31 14:23:27 +00:00
|
|
|
|
If PKG-INFO is nil, an empty one is created."
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(let* ((merged (or (copy-sequence pkg-info)
|
2012-04-11 23:10:01 +00:00
|
|
|
|
(vector name nil "No description available." version))))
|
2012-05-29 19:53:19 +00:00
|
|
|
|
(aset merged 0 name)
|
2012-04-11 23:10:01 +00:00
|
|
|
|
(aset merged 3 version)
|
2012-02-09 21:01:39 +00:00
|
|
|
|
merged))
|
2012-01-31 14:23:27 +00:00
|
|
|
|
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(defun pb/archive-entry (pkg-info type)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"Return the archive-contents cons cell for PKG-INFO and TYPE."
|
2012-02-11 11:49:02 +00:00
|
|
|
|
(let* ((name (intern (aref pkg-info 0)))
|
|
|
|
|
(requires (aref pkg-info 1))
|
|
|
|
|
(desc (or (aref pkg-info 2) "No description available."))
|
2014-04-02 13:36:09 +00:00
|
|
|
|
(version (aref pkg-info 3))
|
2014-04-04 15:54:06 +00:00
|
|
|
|
(extras (when (> (length pkg-info) 4)
|
|
|
|
|
(aref pkg-info 4))))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(cons name
|
|
|
|
|
(vector (version-to-list version)
|
|
|
|
|
requires
|
|
|
|
|
desc
|
2014-04-02 13:36:09 +00:00
|
|
|
|
type
|
|
|
|
|
extras))))
|
2012-02-11 11:49:02 +00:00
|
|
|
|
|
2012-04-17 11:00:31 +00:00
|
|
|
|
(defun pb/archive-file-name (archive-entry)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Return the path of the file in which the package for ARCHIVE-ENTRY is stored."
|
2013-03-28 21:53:58 +00:00
|
|
|
|
(let* ((name (car archive-entry))
|
|
|
|
|
(pkg-info (cdr archive-entry))
|
|
|
|
|
(version (package-version-join (aref pkg-info 0)))
|
|
|
|
|
(flavour (aref pkg-info 3)))
|
|
|
|
|
(expand-file-name
|
|
|
|
|
(format "%s-%s.%s" name version (if (eq flavour 'single) "el" "tar"))
|
|
|
|
|
package-build-archive-dir)))
|
2012-04-17 11:00:31 +00:00
|
|
|
|
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(defun pb/entry-file-name (archive-entry)
|
|
|
|
|
"Return the path of the file in which the package for ARCHIVE-ENTRY is stored."
|
|
|
|
|
(let* ((name (car archive-entry))
|
|
|
|
|
(pkg-info (cdr archive-entry))
|
|
|
|
|
(version (package-version-join (aref pkg-info 0))))
|
|
|
|
|
(expand-file-name
|
|
|
|
|
(format "%s-%s.entry" name version)
|
|
|
|
|
package-build-archive-dir)))
|
|
|
|
|
|
|
|
|
|
(defun pb/delete-file-if-exists (file)
|
|
|
|
|
"Delete FILE if it exists."
|
|
|
|
|
(when (file-exists-p file)
|
|
|
|
|
(delete-file file)))
|
|
|
|
|
|
|
|
|
|
(defun pb/remove-archive-files (archive-entry)
|
2012-04-17 11:00:31 +00:00
|
|
|
|
"Remove ARCHIVE-ENTRY from archive-contents, and delete associated file.
|
|
|
|
|
Note that the working directory (if present) is not deleted by
|
|
|
|
|
this function, since the archive list may contain another version
|
|
|
|
|
of the same-named package which is to be kept."
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Removing archive: %s" archive-entry)
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(mapcar 'pb/delete-file-if-exists
|
|
|
|
|
(list (pb/archive-file-name archive-entry)
|
|
|
|
|
(pb/entry-file-name archive-entry))))
|
2012-12-31 01:11:25 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/read-recipe (file-name)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"Return the plist of recipe info for the package called FILE-NAME."
|
2012-12-31 01:11:25 +00:00
|
|
|
|
(let ((pkg-info (pb/read-from-file file-name)))
|
|
|
|
|
(if (string= (symbol-name (car pkg-info))
|
|
|
|
|
(file-name-nondirectory file-name))
|
|
|
|
|
pkg-info
|
|
|
|
|
(error "Recipe '%s' contains mismatched package name '%s'"
|
|
|
|
|
(file-name-nondirectory file-name)
|
|
|
|
|
(car pkg-info)))))
|
|
|
|
|
|
2012-04-06 19:49:39 +00:00
|
|
|
|
(defun pb/read-recipes ()
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Return a list of data structures for all recipes in `package-build-recipes-dir'."
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(cl-loop for file-name in (directory-files package-build-recipes-dir t "^[^.]")
|
|
|
|
|
collect (pb/read-recipe file-name)))
|
2012-12-31 01:11:25 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/read-recipes-ignore-errors ()
|
|
|
|
|
"Return a list of data structures for all recipes in `package-build-recipes-dir'."
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(cl-loop for file-name in (directory-files package-build-recipes-dir t "^[^.]")
|
|
|
|
|
for pkg-info = (condition-case err (pb/read-recipe file-name)
|
2014-03-30 19:05:11 +00:00
|
|
|
|
(error (pb/message "Error reading recipe %s: %s"
|
|
|
|
|
file-name
|
|
|
|
|
(error-message-string err))
|
2013-04-01 20:16:46 +00:00
|
|
|
|
nil))
|
|
|
|
|
when pkg-info
|
|
|
|
|
collect pkg-info))
|
2012-04-06 19:49:39 +00:00
|
|
|
|
|
2012-05-04 01:15:52 +00:00
|
|
|
|
|
2014-01-03 19:04:21 +00:00
|
|
|
|
(defun package-build-expand-file-specs (dir specs &optional subdir allow-empty)
|
2012-05-06 08:38:43 +00:00
|
|
|
|
"In DIR, expand SPECS, optionally under SUBDIR.
|
|
|
|
|
The result is a list of (SOURCE . DEST), where SOURCE is a source
|
2014-01-03 18:27:32 +00:00
|
|
|
|
file path and DEST is the relative path to which it should be copied.
|
|
|
|
|
|
|
|
|
|
If the resulting list is empty, an error will be reported. Pass t
|
|
|
|
|
for ALLOW-EMPTY to prevent this error."
|
2012-05-06 08:38:43 +00:00
|
|
|
|
(let ((default-directory dir)
|
2013-05-04 02:44:56 +00:00
|
|
|
|
(prefix (if subdir (format "%s/" subdir) ""))
|
|
|
|
|
(lst))
|
|
|
|
|
(dolist (entry specs lst)
|
|
|
|
|
(setq lst
|
|
|
|
|
(if (consp entry)
|
|
|
|
|
(if (eq :exclude (car entry))
|
|
|
|
|
(cl-nset-difference lst
|
2014-01-03 19:04:21 +00:00
|
|
|
|
(package-build-expand-file-specs dir (cdr entry) nil t)
|
2013-05-04 02:44:56 +00:00
|
|
|
|
:key 'car
|
|
|
|
|
:test 'equal)
|
|
|
|
|
(nconc lst
|
2014-01-03 19:04:21 +00:00
|
|
|
|
(package-build-expand-file-specs
|
2014-01-03 17:35:15 +00:00
|
|
|
|
dir
|
|
|
|
|
(cdr entry)
|
2014-01-03 18:27:32 +00:00
|
|
|
|
(concat prefix (car entry))
|
|
|
|
|
t)))
|
2013-05-04 02:44:56 +00:00
|
|
|
|
(nconc
|
|
|
|
|
lst (mapcar (lambda (f)
|
2013-07-22 09:11:44 +00:00
|
|
|
|
(let ((destname)))
|
|
|
|
|
(cons f
|
|
|
|
|
(concat prefix
|
|
|
|
|
(replace-regexp-in-string
|
|
|
|
|
"\\.in\\'"
|
|
|
|
|
""
|
|
|
|
|
(file-name-nondirectory f)))))
|
2014-01-03 18:27:32 +00:00
|
|
|
|
(file-expand-wildcards entry))))))
|
|
|
|
|
(when (and (null lst) (not allow-empty))
|
|
|
|
|
(error "No matching file(s) found in %s: %s" dir specs))
|
|
|
|
|
lst))
|
2013-05-04 02:44:56 +00:00
|
|
|
|
|
2012-05-04 01:15:52 +00:00
|
|
|
|
|
2014-01-03 18:27:32 +00:00
|
|
|
|
(defun pb/config-file-list (config)
|
|
|
|
|
"Get the :files spec from CONFIG, or return `package-build-default-files-spec'."
|
|
|
|
|
(or (plist-get config :files) package-build-default-files-spec))
|
2012-05-04 01:15:52 +00:00
|
|
|
|
|
2012-05-05 07:46:31 +00:00
|
|
|
|
(defun pb/expand-source-file-list (dir config)
|
2012-05-06 08:38:43 +00:00
|
|
|
|
"Shorthand way to expand paths in DIR for source files listed in CONFIG."
|
2014-01-03 19:04:21 +00:00
|
|
|
|
(mapcar 'car (package-build-expand-file-specs dir (pb/config-file-list config))))
|
2012-05-04 01:15:52 +00:00
|
|
|
|
|
2013-05-22 17:57:19 +00:00
|
|
|
|
(defun pb/generate-info-files (files source-dir target-dir)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"Create .info files from any .texi files listed in FILES.
|
2013-05-27 16:09:59 +00:00
|
|
|
|
|
2014-02-16 09:26:21 +00:00
|
|
|
|
The source and destination file paths are expanded in SOURCE-DIR
|
|
|
|
|
and TARGET-DIR respectively.
|
|
|
|
|
|
|
|
|
|
Any of the original .texi(nfo) files found in TARGET-DIR are
|
|
|
|
|
deleted."
|
2013-05-27 17:04:32 +00:00
|
|
|
|
(dolist (spec files)
|
|
|
|
|
(let* ((source-file (car spec))
|
2014-02-15 04:48:24 +00:00
|
|
|
|
(source-path (expand-file-name source-file source-dir))
|
2013-05-27 17:04:32 +00:00
|
|
|
|
(dest-file (cdr spec))
|
|
|
|
|
(info-path (expand-file-name
|
|
|
|
|
(concat (file-name-sans-extension dest-file) ".info")
|
|
|
|
|
target-dir)))
|
|
|
|
|
(when (string-match ".texi\\(nfo\\)?$" source-file)
|
|
|
|
|
(when (not (file-exists-p info-path))
|
2013-05-29 08:29:48 +00:00
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-info*")
|
2013-07-09 06:51:56 +00:00
|
|
|
|
(ignore-errors
|
|
|
|
|
(pb/run-process
|
2014-02-15 04:48:24 +00:00
|
|
|
|
(file-name-directory source-path)
|
2013-07-09 06:51:56 +00:00
|
|
|
|
"makeinfo"
|
2014-02-15 04:48:24 +00:00
|
|
|
|
source-path
|
2013-07-09 06:51:56 +00:00
|
|
|
|
"-o"
|
|
|
|
|
info-path)
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Created %s" info-path))))
|
|
|
|
|
(pb/message "Removing %s" (expand-file-name dest-file target-dir))
|
2013-05-27 17:04:32 +00:00
|
|
|
|
(delete-file (expand-file-name dest-file target-dir))))))
|
2013-05-22 17:57:19 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/generate-dir-file (files target-dir)
|
|
|
|
|
"Create dir file from any .info files listed in FILES in TARGET-DIR."
|
2013-05-27 17:04:32 +00:00
|
|
|
|
(dolist (spec files)
|
|
|
|
|
(let* ((source-file (car spec))
|
|
|
|
|
(dest-file (cdr spec))
|
|
|
|
|
(info-path (expand-file-name
|
|
|
|
|
(concat (file-name-sans-extension dest-file) ".info")
|
|
|
|
|
target-dir)))
|
|
|
|
|
(when (and (or (string-match ".info$" source-file)
|
|
|
|
|
(string-match ".texi\\(nfo\\)?$" source-file))
|
|
|
|
|
(file-exists-p info-path))
|
2013-05-29 08:29:48 +00:00
|
|
|
|
(with-current-buffer (get-buffer-create "*package-build-info*")
|
|
|
|
|
(ignore-errors
|
|
|
|
|
(pb/run-process
|
|
|
|
|
nil
|
|
|
|
|
"install-info"
|
|
|
|
|
(concat "--dir=" (expand-file-name "dir" target-dir))
|
|
|
|
|
info-path)))))))
|
2013-05-22 17:57:19 +00:00
|
|
|
|
|
2012-05-06 08:38:43 +00:00
|
|
|
|
(defun pb/copy-package-files (files source-dir target-dir)
|
|
|
|
|
"Copy FILES from SOURCE-DIR to TARGET-DIR.
|
|
|
|
|
FILES is a list of (SOURCE . DEST) relative filepath pairs."
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(cl-loop for (source-file . dest-file) in files
|
|
|
|
|
do (pb/copy-file
|
|
|
|
|
(expand-file-name source-file source-dir)
|
|
|
|
|
(expand-file-name dest-file target-dir))))
|
2012-05-04 01:15:52 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/copy-file (file newname)
|
|
|
|
|
"Copy FILE to NEWNAME and create parent directories for NEWNAME if they don't exist."
|
|
|
|
|
(let ((newdir (file-name-directory newname)))
|
|
|
|
|
(unless (file-exists-p newdir)
|
|
|
|
|
(make-directory newdir t)))
|
2012-04-30 00:26:28 +00:00
|
|
|
|
(cond
|
2012-05-04 01:15:52 +00:00
|
|
|
|
((file-regular-p file)
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "%s -> %s" file newname)
|
2012-05-04 01:15:52 +00:00
|
|
|
|
(copy-file file newname))
|
|
|
|
|
((file-directory-p file)
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "%s => %s" file newname)
|
2012-05-04 01:15:52 +00:00
|
|
|
|
(copy-directory file newname))))
|
|
|
|
|
|
2012-02-11 11:49:02 +00:00
|
|
|
|
|
2012-05-06 11:13:12 +00:00
|
|
|
|
(defun pb/package-name-completing-read ()
|
|
|
|
|
"Prompt for a package name, returning a symbol."
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(intern (completing-read "Package: " (package-build-recipe-alist))))
|
2012-05-06 11:13:12 +00:00
|
|
|
|
|
2012-07-03 17:22:45 +00:00
|
|
|
|
(defun pb/find-source-file (target files)
|
|
|
|
|
"Search for source of TARGET in FILES."
|
|
|
|
|
(let* ((entry (rassoc target files)))
|
|
|
|
|
(when entry (car entry))))
|
|
|
|
|
|
2013-03-26 21:09:50 +00:00
|
|
|
|
(defun pb/find-package-file (name)
|
|
|
|
|
"Return the filename of the most recently built package of NAME."
|
2013-03-28 21:53:58 +00:00
|
|
|
|
(pb/archive-file-name (assoc name (package-build-archive-alist))))
|
2013-03-26 21:09:50 +00:00
|
|
|
|
|
2013-07-22 09:10:28 +00:00
|
|
|
|
(defun pb/package-buffer-info-vec ()
|
|
|
|
|
"Return a vector of package info.
|
|
|
|
|
`package-buffer-info' returns a vector in older Emacs versions,
|
|
|
|
|
and a cl struct in Emacs HEAD. This wrapper normalises the results."
|
2014-04-02 13:35:51 +00:00
|
|
|
|
(let ((desc (package-buffer-info))
|
|
|
|
|
(keywords (lm-keywords-list)))
|
2013-07-22 09:10:28 +00:00
|
|
|
|
(if (fboundp 'package-desc-create)
|
2014-04-02 13:35:51 +00:00
|
|
|
|
(let ((extras (package-desc-extras desc)))
|
|
|
|
|
(when (and keywords (not (assq :keywords extras)))
|
|
|
|
|
;; Add keywords to package properties, if not already present
|
|
|
|
|
(push (cons :keywords keywords) extras))
|
|
|
|
|
(vector (package-desc-name desc)
|
|
|
|
|
(package-desc-reqs desc)
|
|
|
|
|
(package-desc-summary desc)
|
|
|
|
|
(package-desc-version desc)
|
|
|
|
|
extras))
|
|
|
|
|
;; The regexp and the processing is taken from `lm-homepage' in Emacs 24.4
|
|
|
|
|
(let* ((page (lm-header "\\(?:x-\\)?\\(?:homepage\\|url\\)"))
|
|
|
|
|
(homepage (if (and page (string-match "^<.+>$" page))
|
|
|
|
|
(substring page 1 -1)
|
|
|
|
|
page))
|
|
|
|
|
extras)
|
|
|
|
|
(when keywords (push (cons :keywords keywords) extras))
|
|
|
|
|
(when homepage (push (cons :url homepage) extras))
|
|
|
|
|
(vector (aref desc 0)
|
|
|
|
|
(aref desc 1)
|
|
|
|
|
(aref desc 2)
|
|
|
|
|
(aref desc 3)
|
|
|
|
|
extras)))))
|
2013-03-26 21:09:50 +00:00
|
|
|
|
|
2013-09-15 00:15:43 +00:00
|
|
|
|
|
2012-04-30 00:26:28 +00:00
|
|
|
|
;;; Public interface
|
2012-08-11 10:43:10 +00:00
|
|
|
|
;;;###autoload
|
2012-04-21 19:36:37 +00:00
|
|
|
|
(defun package-build-archive (name)
|
2012-05-01 13:09:50 +00:00
|
|
|
|
"Build a package archive for package NAME."
|
2012-05-06 11:13:12 +00:00
|
|
|
|
(interactive (list (pb/package-name-completing-read)))
|
2012-04-21 19:36:37 +00:00
|
|
|
|
(let* ((file-name (symbol-name name))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(rcp (or (cdr (assoc name (package-build-recipe-alist)))
|
2012-02-11 16:12:43 +00:00
|
|
|
|
(error "Cannot find package %s" file-name)))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(pkg-working-dir
|
2012-01-21 18:04:55 +00:00
|
|
|
|
(file-name-as-directory
|
|
|
|
|
(expand-file-name file-name package-build-working-dir))))
|
|
|
|
|
|
2014-01-26 05:28:18 +00:00
|
|
|
|
(unless (file-exists-p package-build-archive-dir)
|
|
|
|
|
(pb/message "Creating directory %s" package-build-archive-dir)
|
|
|
|
|
(make-directory package-build-archive-dir))
|
2013-03-28 22:05:47 +00:00
|
|
|
|
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "\n;;; %s\n" file-name)
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(let* ((version (or (package-build-checkout name rcp pkg-working-dir)
|
|
|
|
|
(error "No valid package version found!")))
|
2012-12-05 14:07:53 +00:00
|
|
|
|
(default-directory package-build-working-dir)
|
2013-04-08 04:58:33 +00:00
|
|
|
|
(start-time (current-time))
|
2014-01-03 17:35:15 +00:00
|
|
|
|
(archive-entry (package-build-package (symbol-name name)
|
2014-01-03 18:27:32 +00:00
|
|
|
|
version
|
|
|
|
|
(pb/config-file-list rcp)
|
2014-01-03 17:35:15 +00:00
|
|
|
|
pkg-working-dir
|
|
|
|
|
package-build-archive-dir)))
|
2014-01-14 21:21:40 +00:00
|
|
|
|
(pb/dump archive-entry (pb/entry-file-name archive-entry))
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "Built in %.3fs, finished at %s"
|
|
|
|
|
(time-to-seconds (time-since start-time))
|
|
|
|
|
(current-time-string))
|
2012-04-21 18:56:19 +00:00
|
|
|
|
file-name)))
|
2012-01-21 18:04:55 +00:00
|
|
|
|
|
2014-01-03 12:49:02 +00:00
|
|
|
|
;;;###autoload
|
2014-01-03 18:27:32 +00:00
|
|
|
|
(defun package-build-package (package-name version file-specs source-dir target-dir)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"Create PACKAGE-NAME with VERSION.
|
|
|
|
|
|
|
|
|
|
The information in FILE-SPECS is used to gather files from
|
|
|
|
|
SOURCE-DIR.
|
2014-01-03 18:27:32 +00:00
|
|
|
|
|
|
|
|
|
The resulting package will be stored as a .el or .tar file in
|
|
|
|
|
TARGET-DIR, depending on whether there are multiple files.
|
2014-01-03 17:35:15 +00:00
|
|
|
|
|
2014-01-03 18:27:32 +00:00
|
|
|
|
Argument FILE-SPECS is a list of specs for source files, which
|
|
|
|
|
should be relative to SOURCE-DIR. The specs can be wildcards,
|
|
|
|
|
and optionally specify different target paths. They extended
|
|
|
|
|
syntax is currently only documented in the MELPA README. You can
|
|
|
|
|
simply pass `package-build-default-files-spec' in most cases.
|
2013-09-15 00:15:43 +00:00
|
|
|
|
|
|
|
|
|
Returns the archive entry for the package."
|
2014-01-28 18:04:50 +00:00
|
|
|
|
(when (symbolp package-name)
|
|
|
|
|
(setq package-name (symbol-name package-name)))
|
2014-01-03 19:04:21 +00:00
|
|
|
|
(let ((files (package-build-expand-file-specs source-dir file-specs)))
|
2014-02-21 10:01:54 +00:00
|
|
|
|
(unless (equal file-specs package-build-default-files-spec)
|
|
|
|
|
(when (equal files (package-build-expand-file-specs
|
|
|
|
|
source-dir package-build-default-files-spec nil t))
|
|
|
|
|
(pb/message "Note: this :files spec is equivalent to the default.")))
|
|
|
|
|
(cond
|
2014-01-26 04:57:34 +00:00
|
|
|
|
((not version)
|
|
|
|
|
(error "Unable to check out repository for %s" package-name))
|
|
|
|
|
((= 1 (length files))
|
|
|
|
|
(let* ((pkg-source (expand-file-name (caar files) source-dir))
|
|
|
|
|
(pkg-target (expand-file-name
|
|
|
|
|
(concat package-name "-" version ".el")
|
|
|
|
|
target-dir))
|
|
|
|
|
(pkg-info (pb/merge-package-info
|
|
|
|
|
(pb/get-package-info pkg-source)
|
|
|
|
|
package-name
|
|
|
|
|
version)))
|
|
|
|
|
(unless (string-equal (downcase (concat package-name ".el"))
|
|
|
|
|
(downcase (file-name-nondirectory pkg-source)))
|
|
|
|
|
(error "Single file %s does not match package name %s"
|
|
|
|
|
(file-name-nondirectory pkg-source) package-name))
|
|
|
|
|
(when (file-exists-p pkg-target)
|
|
|
|
|
(delete-file pkg-target t))
|
|
|
|
|
(copy-file pkg-source pkg-target)
|
|
|
|
|
(let ((enable-local-variables nil)
|
|
|
|
|
(make-backup-files nil))
|
|
|
|
|
(with-current-buffer (find-file pkg-target)
|
|
|
|
|
(pb/update-or-insert-version version)
|
|
|
|
|
(pb/ensure-ends-here-line pkg-source)
|
|
|
|
|
(write-file pkg-target nil)
|
|
|
|
|
(condition-case err
|
|
|
|
|
(pb/package-buffer-info-vec)
|
|
|
|
|
(error
|
|
|
|
|
(pb/message "Warning: %S" err)))
|
|
|
|
|
(kill-buffer)))
|
|
|
|
|
|
|
|
|
|
(pb/write-pkg-readme target-dir
|
2014-04-02 14:27:57 +00:00
|
|
|
|
(pb/find-package-commentary pkg-source)
|
2014-01-26 04:57:34 +00:00
|
|
|
|
package-name)
|
|
|
|
|
(pb/archive-entry pkg-info 'single)))
|
|
|
|
|
((< 1 (length files))
|
|
|
|
|
(let* ((tmp-dir (file-name-as-directory (make-temp-file package-name t)))
|
|
|
|
|
(pkg-dir-name (concat package-name "-" version))
|
|
|
|
|
(pkg-tmp-dir (expand-file-name pkg-dir-name tmp-dir))
|
|
|
|
|
(pkg-file (concat package-name "-pkg.el"))
|
|
|
|
|
(pkg-file-source (or (pb/find-source-file pkg-file files)
|
|
|
|
|
pkg-file))
|
|
|
|
|
(file-source (concat package-name ".el"))
|
|
|
|
|
(pkg-source (or (pb/find-source-file file-source files)
|
|
|
|
|
file-source))
|
|
|
|
|
(pkg-info (pb/merge-package-info
|
|
|
|
|
(let ((default-directory source-dir))
|
|
|
|
|
(or (pb/get-pkg-file-info pkg-file-source)
|
|
|
|
|
;; some packages (like magit) provide name-pkg.el.in
|
|
|
|
|
(pb/get-pkg-file-info
|
|
|
|
|
(expand-file-name (concat pkg-file ".in")
|
|
|
|
|
(file-name-directory pkg-source)))
|
|
|
|
|
(pb/get-package-info pkg-source)))
|
|
|
|
|
package-name
|
|
|
|
|
version)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(pb/copy-package-files files source-dir pkg-tmp-dir)
|
2014-03-25 05:29:45 +00:00
|
|
|
|
(pb/write-pkg-file (expand-file-name pkg-file
|
2014-01-26 04:57:34 +00:00
|
|
|
|
(file-name-as-directory pkg-tmp-dir))
|
|
|
|
|
pkg-info)
|
|
|
|
|
|
|
|
|
|
(pb/generate-info-files files source-dir pkg-tmp-dir)
|
|
|
|
|
(pb/generate-dir-file files pkg-tmp-dir)
|
|
|
|
|
|
|
|
|
|
(let ((default-directory tmp-dir))
|
|
|
|
|
(pb/create-tar (expand-file-name (concat package-name "-" version ".tar")
|
|
|
|
|
target-dir)
|
|
|
|
|
pkg-dir-name))
|
|
|
|
|
|
|
|
|
|
(let ((default-directory source-dir))
|
2014-03-25 05:33:24 +00:00
|
|
|
|
(pb/write-pkg-readme target-dir
|
|
|
|
|
(pb/find-package-commentary pkg-source)
|
2014-01-26 04:57:34 +00:00
|
|
|
|
package-name))
|
|
|
|
|
|
|
|
|
|
(delete-directory pkg-tmp-dir t nil)
|
|
|
|
|
(pb/archive-entry pkg-info 'tar)))
|
|
|
|
|
|
|
|
|
|
(t (error "Unable to find files matching recipe patterns")))))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
|
|
|
|
|
|
2013-03-24 13:08:19 +00:00
|
|
|
|
|
|
|
|
|
;;; Helpers for recipe authors
|
|
|
|
|
|
|
|
|
|
(defvar package-build-minor-mode-map
|
|
|
|
|
(let ((m (make-sparse-keymap)))
|
|
|
|
|
(define-key m (kbd "C-c C-c") 'package-build-current-recipe)
|
|
|
|
|
m)
|
|
|
|
|
"Keymap for `package-build-minor-mode'.")
|
|
|
|
|
|
|
|
|
|
(define-minor-mode package-build-minor-mode
|
|
|
|
|
"Helpful functionality for building packages."
|
|
|
|
|
nil
|
|
|
|
|
" PBuild"
|
|
|
|
|
package-build-minor-mode-map)
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun package-build-create-recipe (name fetcher)
|
|
|
|
|
"Create a new recipe for package NAME using FETCHER."
|
|
|
|
|
(interactive
|
|
|
|
|
(list (intern (read-string "Package name: "))
|
|
|
|
|
(intern
|
|
|
|
|
(let ((fetcher-types (mapcar #'symbol-name '(github git wiki bzr hg cvs svn))))
|
|
|
|
|
(completing-read
|
|
|
|
|
"Fetcher: "
|
|
|
|
|
fetcher-types
|
|
|
|
|
nil t nil nil (car fetcher-types))))))
|
|
|
|
|
(let ((recipe-file (expand-file-name (symbol-name name) package-build-recipes-dir)))
|
|
|
|
|
(when (file-exists-p recipe-file)
|
|
|
|
|
(error "Recipe already exists"))
|
|
|
|
|
(find-file recipe-file)
|
|
|
|
|
(let* ((extra-params
|
|
|
|
|
(cond
|
|
|
|
|
((eq 'github fetcher) '(:repo "USER/REPO"))
|
|
|
|
|
((eq 'wiki fetcher) '())
|
|
|
|
|
(t '(:url "SCM_URL_HERE"))))
|
|
|
|
|
(template `(,name :fetcher ,fetcher ,@extra-params)))
|
|
|
|
|
(insert (pp-to-string template))
|
|
|
|
|
(emacs-lisp-mode)
|
|
|
|
|
(package-build-minor-mode)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
(goto-char (point-min)))))
|
2013-03-24 13:08:19 +00:00
|
|
|
|
|
2012-08-11 10:42:44 +00:00
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun package-build-current-recipe ()
|
|
|
|
|
"Build archive for the recipe defined in the current buffer."
|
|
|
|
|
(interactive)
|
|
|
|
|
(unless (and (buffer-file-name)
|
2014-03-31 13:55:43 +00:00
|
|
|
|
(file-equal-p (file-name-directory (buffer-file-name))
|
2012-08-11 10:42:44 +00:00
|
|
|
|
package-build-recipes-dir))
|
|
|
|
|
(error "Buffer is not visiting a recipe"))
|
|
|
|
|
(when (buffer-modified-p)
|
|
|
|
|
(if (y-or-n-p (format "Save file %s? " buffer-file-name))
|
|
|
|
|
(save-buffer)
|
|
|
|
|
(error "Aborting")))
|
2013-08-06 14:15:54 +00:00
|
|
|
|
(check-parens)
|
2012-12-31 00:58:34 +00:00
|
|
|
|
(package-build-reinitialize)
|
2013-03-26 21:09:50 +00:00
|
|
|
|
(let ((pkg-name (intern (file-name-nondirectory (buffer-file-name)))))
|
|
|
|
|
(package-build-archive pkg-name)
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(package-build-dump-archive-contents)
|
2013-11-13 20:12:14 +00:00
|
|
|
|
(with-output-to-temp-buffer "*package-build-result*"
|
|
|
|
|
(pp (assoc pkg-name (package-build-archive-alist))))
|
2013-03-26 21:09:50 +00:00
|
|
|
|
(when (yes-or-no-p "Install new package? ")
|
|
|
|
|
(package-install-file (pb/find-package-file pkg-name)))))
|
2012-08-11 10:42:44 +00:00
|
|
|
|
|
2012-04-17 09:42:51 +00:00
|
|
|
|
(defun package-build-archive-ignore-errors (pkg)
|
|
|
|
|
"Build archive for package PKG, ignoring any errors."
|
2012-05-06 11:13:12 +00:00
|
|
|
|
(interactive (list (pb/package-name-completing-read)))
|
2012-05-04 19:33:25 +00:00
|
|
|
|
(let* ((debug-on-error t)
|
|
|
|
|
(debug-on-signal t)
|
|
|
|
|
(pb/debugger-return nil)
|
|
|
|
|
(debugger (lambda (&rest args)
|
|
|
|
|
(setq pb/debugger-return (with-output-to-string
|
|
|
|
|
(backtrace))))))
|
2012-05-04 01:44:46 +00:00
|
|
|
|
(condition-case err
|
|
|
|
|
(package-build-archive pkg)
|
2013-03-30 21:39:10 +00:00
|
|
|
|
(error
|
2013-12-28 12:23:37 +00:00
|
|
|
|
(pb/message "%s" (error-message-string err))
|
2012-05-04 02:04:06 +00:00
|
|
|
|
nil))))
|
2012-03-17 17:39:44 +00:00
|
|
|
|
|
2013-03-24 13:08:19 +00:00
|
|
|
|
|
|
|
|
|
|
2012-08-11 10:43:10 +00:00
|
|
|
|
;;;###autoload
|
2012-02-11 11:49:02 +00:00
|
|
|
|
(defun package-build-all ()
|
2013-03-09 15:12:50 +00:00
|
|
|
|
"Build all packages in the `package-build-recipe-alist'."
|
2012-02-11 11:49:02 +00:00
|
|
|
|
(interactive)
|
2013-04-01 20:16:46 +00:00
|
|
|
|
(let ((failed (cl-loop for pkg in (mapcar 'car (package-build-recipe-alist))
|
|
|
|
|
when (not (package-build-archive-ignore-errors pkg))
|
|
|
|
|
collect pkg)))
|
2012-05-04 19:34:28 +00:00
|
|
|
|
(if (not failed)
|
2012-04-21 18:56:19 +00:00
|
|
|
|
(princ "\nSuccessfully Compiled All Packages\n")
|
|
|
|
|
(princ "\nFailed to Build the Following Packages\n")
|
2012-07-24 22:10:50 +00:00
|
|
|
|
(princ (mapconcat 'symbol-name failed "\n"))
|
|
|
|
|
(princ "\n")))
|
2012-04-17 11:00:31 +00:00
|
|
|
|
(package-build-cleanup))
|
|
|
|
|
|
|
|
|
|
(defun package-build-cleanup ()
|
|
|
|
|
"Remove previously-built packages that no longer have recipes."
|
|
|
|
|
(interactive)
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(let* ((known-package-names (mapcar 'car (package-build-recipe-alist)))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(stale-archives (cl-loop for built in (pb/archive-entries)
|
2013-04-01 20:16:46 +00:00
|
|
|
|
when (not (memq (car built) known-package-names))
|
|
|
|
|
collect built)))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(mapc 'pb/remove-archive-files stale-archives)
|
|
|
|
|
(package-build-dump-archive-contents)))
|
2012-01-31 10:49:49 +00:00
|
|
|
|
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(defun package-build-recipe-alist ()
|
|
|
|
|
"Retun the list of avalailable packages."
|
2013-03-28 21:52:52 +00:00
|
|
|
|
(unless pb/recipe-alist-initialized
|
|
|
|
|
(setq pb/recipe-alist (pb/read-recipes-ignore-errors)
|
|
|
|
|
pb/recipe-alist-initialized t))
|
|
|
|
|
pb/recipe-alist)
|
2013-03-09 15:12:50 +00:00
|
|
|
|
|
|
|
|
|
(defun package-build-archive-alist ()
|
|
|
|
|
"Return the archive list."
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(cdr (pb/read-from-file
|
|
|
|
|
(expand-file-name "archive-contents"
|
|
|
|
|
package-build-archive-dir))))
|
2013-03-09 15:12:50 +00:00
|
|
|
|
|
2012-12-31 00:58:34 +00:00
|
|
|
|
(defun package-build-reinitialize ()
|
2014-02-16 09:26:21 +00:00
|
|
|
|
"Forget any information about packages which have already been built."
|
2012-12-31 00:58:34 +00:00
|
|
|
|
(interactive)
|
2013-09-15 00:15:43 +00:00
|
|
|
|
(setq pb/recipe-alist-initialized nil))
|
|
|
|
|
|
2014-02-16 09:26:21 +00:00
|
|
|
|
(defun package-build-dump-archive-contents (&optional file-name)
|
|
|
|
|
"Dump the list of built packages to FILE-NAME.
|
|
|
|
|
|
|
|
|
|
If FILE-NAME is not specified, the default archive-contents file is used."
|
|
|
|
|
(pb/dump (cons 1 (pb/archive-entries))
|
|
|
|
|
(or file-name
|
|
|
|
|
(expand-file-name "archive-contents" package-build-archive-dir))))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
|
|
|
|
|
(defun pb/archive-entries ()
|
|
|
|
|
"Read all .entry files from the archive directory and return a list of all entries."
|
|
|
|
|
(let ((entries '()))
|
|
|
|
|
(dolist (new (mapcar 'pb/read-from-file
|
|
|
|
|
(directory-files package-build-archive-dir t
|
|
|
|
|
".*\.entry$"))
|
|
|
|
|
entries)
|
|
|
|
|
(let ((old (assq (car new) entries)))
|
|
|
|
|
(when old
|
2014-03-27 12:38:26 +00:00
|
|
|
|
(when (version-list-< (elt (cdr new) 0)
|
|
|
|
|
(elt (cdr old) 0))
|
2013-09-15 00:15:43 +00:00
|
|
|
|
;; swap old and new
|
|
|
|
|
(cl-rotatef old new))
|
|
|
|
|
(pb/remove-archive-files old)
|
|
|
|
|
(setq entries (remove old entries)))
|
|
|
|
|
(add-to-list 'entries new)))))
|
2012-12-31 00:58:34 +00:00
|
|
|
|
|
2012-02-03 14:46:55 +00:00
|
|
|
|
|
2012-07-19 18:41:51 +00:00
|
|
|
|
;; Utility functions
|
2013-03-24 13:08:19 +00:00
|
|
|
|
(require 'json)
|
2013-12-28 08:50:35 +00:00
|
|
|
|
(load (expand-file-name "json-fix" pb/this-dir) nil 'nomessage)
|
2012-07-19 18:41:51 +00:00
|
|
|
|
|
2014-02-16 09:26:21 +00:00
|
|
|
|
(defun package-build-recipe-alist-as-json (file-name)
|
|
|
|
|
"Dump the recipe list to FILE-NAME as json."
|
2012-07-19 18:41:51 +00:00
|
|
|
|
(interactive)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
(with-temp-file file-name
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(insert (json-encode (package-build-recipe-alist)))))
|
2012-07-19 18:41:51 +00:00
|
|
|
|
|
2014-02-16 09:26:21 +00:00
|
|
|
|
(defun package-build-archive-alist-as-json (file-name)
|
|
|
|
|
"Dump the build packages list to FILE-NAME as json."
|
2012-07-19 18:41:51 +00:00
|
|
|
|
(interactive)
|
2014-02-16 09:26:21 +00:00
|
|
|
|
(with-temp-file file-name
|
2013-03-09 15:12:50 +00:00
|
|
|
|
(insert (json-encode (package-build-archive-alist)))))
|
2012-07-19 18:41:51 +00:00
|
|
|
|
|
|
|
|
|
|
2012-02-11 16:17:24 +00:00
|
|
|
|
(provide 'package-build)
|
2012-05-07 10:43:07 +00:00
|
|
|
|
|
|
|
|
|
;; Local Variables:
|
|
|
|
|
;; coding: utf-8
|
|
|
|
|
;; eval: (checkdoc-minor-mode 1)
|
|
|
|
|
;; End:
|
|
|
|
|
|
2012-01-31 10:49:49 +00:00
|
|
|
|
;;; package-build.el ends here
|