;;; package-build.el --- Tools for curating the package archive ;; Copyright (C) 2011 Donald Ephraim Curtis ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Created: 2011-09-30 ;; Version: 0.1 ;; Keywords: tools ;; 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 ;; projects and repositories from which to get them. The term ;; "package" here is used to mean a specific version of a project that ;; is prepared for download and installation. ;; Currently only supports single-file projects stored in git. ;;; Code: ;; Since this library is not meant to be loaded by users ;; at runtime, use of cl functions should not be a problem. (require 'cl) (require 'package) (defvar package-build-working-dir (expand-file-name "working/") "Directory in which to keep checkouts.") (defvar package-build-archive-dir (expand-file-name "packages/") "Directory in which to keep compiled archives.") (defvar package-build-alist-file (expand-file-name "pkglist") "File containing pkg alist") (defun package-build-checkout-darcs (repo dir) "checkout an svn package" (with-current-buffer (get-buffer-create "*package-build-checkout*") (cond ((file-exists-p dir) (print "checkout directory exists, updating...") (let ((default-directory dir)) (process-file "darcs" nil (current-buffer) nil "pull"))) (t (print "cloning repository") (process-file "darcs" nil (current-buffer) nil "get" repo dir))) (let ((default-directory dir)) (process-file "darcs" nil (current-buffer) t "changes" "--last" "1")) (print (format-time-string "%Y%m%d" (date-to-time (print (progn (re-search-backward "\\([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\\}\\)") (match-string-no-properties 1)))))))) (defun package-build-checkout-svn (repo dir) "checkout an svn repo" (with-current-buffer (get-buffer-create "*package-build-checkout*") (goto-char (point-max)) (cond ((file-exists-p dir) (print "checkout directory exists, updating...") (let ((default-directory dir)) (process-file "svn" nil (current-buffer) nil "up"))) (t (print "cloning repository") (process-file "svn" nil (current-buffer) nil "checkout" (concat repo "/trunk") dir))) (let ((default-directory dir)) (process-file "svn" nil (current-buffer) t "info")) (print (format-time-string "%Y%m%d" (date-to-time (print (progn (re-search-backward "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)") (match-string-no-properties 1)))))))) (defun package-build-checkout-git (repo dir) "checkout an git repo" (with-current-buffer (get-buffer-create "*package-build-checkout*") (goto-char (point-max)) (cond ((file-exists-p dir) (print "checkout directory exists, updating...") (let ((default-directory dir)) (process-file "git" nil (current-buffer) nil "pull")) ) (t (print "cloning %s to %s" repo dir) (process-file "git" nil (current-buffer) nil "clone" repo dir))) (let ((default-directory dir)) (process-file "git" nil (current-buffer) t "show" "-s" "--format='\%ci'" "HEAD")) (print (format-time-string "%Y%m%d" (date-to-time (print (progn (re-search-backward "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)") (match-string-no-properties 1)))))))) (defun package-change-list-elt (lst idx newval) (if (zerop idx) (cons newval (cdr lst)) (cons (car lst) (package-change-list-elt (cdr lst) (1- idx) newval)))) (defun package-build-pkg-file (pkg-file file-name version homepage) "build the pkg file" (let ((print-level nil) (print-length nil) (pkglst (or (package-read-from-file pkg-file) (list 'define-package file-name version homepage (list 'quote (mapcar (lambda (elt) (list (car elt) (package-version-join (cadr elt)))) nil)))))) ;; set the packages version (setq pkglst (package-change-list-elt pkglst 2 version)) (write-region (concat (prin1-to-string pkglst ) "\n") nil pkg-file nil nil nil nil) pkglst)) (defun package-read-from-file (file-name) "read one lisp expression from a file" (cond ((file-exists-p file-name) (with-temp-buffer (insert-file-contents-literally file-name) (goto-char (point-min)) (car (read-from-string (buffer-substring-no-properties (point-min) (point-max)))))))) (defun package-build-get-config (file-name) "get the configuration information for the given file-name" (package-read-from-file (format "epkgs/%s/.config" file-name))) (defun package-build-get-master (file-name) "get the configuration information for the given file-name" (package-read-from-file (format "epkgs/%s/master" file-name))) (defun package-build-create-tar (dir file &optional files) "create a tar for the file-name with version" (let* ((default-directory package-build-working-dir)) (if files (setq files (mapcar (lambda (fn) (concat dir "/" fn)) files)) (setq files (list dir))) (print files) (print default-directory) (apply 'process-file "tar" nil (get-buffer-create "*package-build-checkout*") nil "-cvf" file "--exclude=.svn" "--exclude=.git*" "--exclude=_darcs" files))) (defun package-build-archives (&rest pkgs) "build archives" (interactive) (mapc 'package-build-archive pkgs) (package-build-dump-archive-contents)) (defun package-build-archive (file-name) "build a git package archive" (interactive) (let* ((name (intern file-name)) (cfg (cdr (assoc name package-build-alist))) (pkg-cwd (file-name-as-directory (expand-file-name file-name package-build-working-dir)))) (when cfg (let* ((repo-type (plist-get cfg :fetcher)) (repo-url (plist-get cfg :url)) (summary (plist-get cfg :summary)) (files (plist-get cfg :files))) (let* ((pkglst) (pkgdeps) (version (cond ((eq repo-type 'svn) (print 'Subversion) (package-build-checkout-svn repo-url pkg-cwd)) ((eq repo-type 'git) (print 'Git) (package-build-checkout-git repo-url pkg-cwd)) ((eq repo-type 'darcs) (print 'Darcs) (package-build-checkout-darcs repo-url pkg-cwd)))) (pkg-base-dir (concat file-name "-" version)) (pkg-file (concat file-name "-pkg.el")) (default-directory package-build-working-dir)) (cond ((= 1 (length files)) (copy-file (expand-file-name (car files) pkg-cwd) (expand-file-name (concat file-name "-" version ".el") package-build-archive-dir)) (package-build-add-to-archive-contents name version pkgdeps summary 'single)) (t (copy-directory file-name pkg-base-dir) (setq pkglst (package-build-pkg-file (expand-file-name pkg-file (concat (file-name-as-directory package-build-working-dir) (file-name-as-directory pkg-base-dir))) file-name version summary)) (setq pkgdeps (mapcar (lambda (elt) (list (car elt) (version-to-list (cadr elt)))) (eval (nth 4 pkglst)))) (when files (add-to-list 'files pkg-file)) (package-build-create-tar pkg-base-dir (expand-file-name (concat file-name "-" version ".tar") package-build-archive-dir) files) (delete-directory pkg-base-dir t nil) (package-build-add-to-archive-contents name version pkgdeps summary 'tar))))) (package-build-dump-archive-contents)))) (defvar package-build-alist (let ((pkg-file package-build-alist-file)) (when (file-exists-p pkg-file) (with-temp-buffer (insert-file-contents-literally pkg-file) (goto-char (point-min)) (read (current-buffer)))))) (defvar package-build-archive-alist (let ((archive-file (expand-file-name "archive-contents" package-build-archive-dir))) (when (file-exists-p archive-file) (with-temp-buffer (insert-file-contents-literally archive-file) (goto-char (point-min)) (let ((contents (read (current-buffer)))) (cdr contents)))))) (defun package-build-dump-archive-contents () "dump the archive contents back to the file" (write-region (concat (pp-to-string (cons 1 package-build-archive-alist)) "\n") nil (expand-file-name "archive-contents" package-build-archive-dir) nil nil nil nil)) (defun package-build-add-to-archive-contents (name version deps homepage type) "add an archive to the package-build-archive-contents" (let ((existing (assq name package-build-archive-alist))) (when existing (setq package-build-archive-alist (delq existing package-build-archive-alist))) (add-to-list 'package-build-archive-alist (cons name (vector (version-to-list version) deps homepage type)))))