emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
blob 2816702bbd5f32c5924fd4a020b6dac30b2d34fc 8381 bytes (raw)
name: mk/epm.el 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
 
#!/bin/sh
":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
":"; exec emacs --script "$0" "$@"
;;; epm.el --- Emacs package management helper for Org Mode

;; Copyright (C) 2023 Free Software Foundation, Inc.

;; Author: Max Nikulin <manikulin@gmail.com>
;; Created: 2 May 2023
;; Keywords: maint, tools
;; Package-Requires: ((emacs "26.1"))
;; URL: https://orgmode.org
;; Version: 0.1

;; This file is not part of GNU Emacs.
;;
;; 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 of the License, 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.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; epm.el is an attempt to create a tool that allows simple package
;; management outside of interactive Emacs session.  Its purpose
;; is dependency management for make based workflow for building
;; and testing and for continuous integration (CI) systems.
;; It install not available yet packages from ELPA.
;;
;; Before compiling or running Org mode uncompiled it is necessary
;; to install dependencies.  If libraries are already available
;; in your `load-path' then the following commands should be no-op.
;;
;; As a user who prefers to use Org mode version from git
;; likely you would prefer default ~/emacs.d/elpa directory
;; for installed packages
;;
;;     mk/epm.el -q install compat
;;
;; If you are a developer and you need to separate environment from
;; main Emacs configuration, you may choose some alternative
;; package directory and use %e substitution for Emacs version.
;;
;;     export EPMDIR="$HOME/.cache/epm/emacs-%e"
;;     /path/to/emacs-src/emacs -q --script mk/epm.el install compat
;;
;; or
;;
;;     /path/to/emacs-src/emacs -q --script mk/epm.el \
;;         --epm-dir "$HOME/.cache/epm/emacs-%e" install compat
;;
;; Of course, you should specify location to overriden `package-user-dir'
;; in local.mk.
;;
;; When a bug is suspected, it is better to install dependencies
;; e.g. to TMPDIR to avoid issues with content of user init directory.
;;
;; If you prefer to avoid ELPA packages and this script, you still have
;; --directory/-L option and EMACSLOADPATH environment variable
;; to specify where required libraries may be loaded.
;;
;; Since required dependency may be installed e.g. as elpa-compat Debian
;; package, it is not recommended to use --quick/-Q or --no-site-lisp/-nsl
;; options, prefer --no-user-init/-q instead unless you suspect some
;; issue with site-lisp directories.
;;
;; Limitations:
;; - package.el allows to check if minimum version requirement is satisfied
;;   for a package, but I have not found API to check it for a library from
;;   `load-path'.
;; - Upgrading of a package is not implemented and I am unsure if convenient
;;   API exists.
;; - Ideally istead of library list it should be possible to specify .el file
;;   and dependencies should be taken from the Package-Requires header.
;;
;; To get list of libraries that are not available run
;;
;;     mk/epm.el -q missing compat
;;
;; Non-zero exit code means missing dependencies, its list is printed to stdout.
;; To check which file will be loaded try
;;
;;     mk/epm.el -q report htmlize compat
;;
;; Overview of available commands are provided by
;;
;;     mk/epm.el -q help

;;; Code:

(require 'format-spec)
(require 'package)
(require 'subr-x)

(defvar epm-dir nil
  "Overrides `package-user-dir' and EPMDIR environment.")

(defun epm--get-script-name ()
  "Guess command line argument spefifying this script.

Real argument is not available:
`argi' is \"-scriptload\", `argval' is local variable of `command-line-1',
`load-file-name' is absolute path, `file-relative-name' is too aggressive
and adds \"..\" to root."
  (let ((relative (file-relative-name load-file-name
                                      command-line-default-directory)))
    (if (string-suffix-p load-file-name relative)
        load-file-name
      relative)))

(defvar epm-script-name (epm--get-script-name)
  "Name of epm.el as it appears in Emacs command line options")

(defun epm-nonempty-p (s)
  (and s (not (string-empty-p s))))

(defun epm-init ()
  (unless (epm-nonempty-p epm-dir)
    (setq epm-dir (getenv "EPMDIR")))
  (when (epm-nonempty-p epm-dir)
    (let* ((fmt-expanded (format-spec epm-dir `((?e . ,emacs-version))))
	   (dir (directory-file-name (expand-file-name fmt-expanded command-line-default-directory))))
      ;; `package-user-dir' ~/.emacs.d/elpa by default even with -Q
      ;; `package-directory-list' does not include `package-user-dir'.
      (setq package-user-dir dir)))
  ;; TODO (load site-run-file 'no-error 'no-message)
  ;; may be necessary to load elpa-* deb packages when -Q option
  ;; is used. See Info node "(elisp) Init File".
  (package-initialize))

(defun epm-library-unavailable-p (lib)
  (unless (locate-library lib)
    lib))

(defun epm-missing (libs)
  ;; TODO consider `require' catching load errors
  (delq nil (mapcar #'epm-library-unavailable-p libs)))

(defun epm-cmd-help (_cmd _args)
  "List commands."
  (princ (format "\
Usage: %s [--dbg|--debug-on-error] [--epm-dir DIR] COMMAND ARGS...
   or: %s --script %s [--dbg|--debug-on-error] [--epm-dir DIR] COMMAND ARGS...

A CLI tool to install ELPA packages.

Any Emacs option may be specified, e.g. --quck,-Q, --no-init-file,-q,
or --directory,-L DIR

--dbg, --debug-on-error
    Enable `debug-on-error'

--epm-dir DIR
    Set `package-user-dir'.
    \"%%e\" is replaced by `emacs-version'.
    Alternatively EPMDIR environment variable may be specified.
\n"
                 epm-script-name (car command-line-args) epm-script-name))
  (pcase-dolist (`(,name . ,func) epm-commands)
    (princ name)
    (terpri)
    (princ
     (replace-regexp-in-string
      "\\`\\|\n" "\\1    "
      (documentation func) 'fixedcase nil))
    (terpri)
    (terpri)))

(defun epm-cmd-missing (_ libs)
  "Report not installed libraries and exit with non-zero code."
  (let ((missing (epm-missing libs)))
    (when missing
      (princ (mapconcat #'identity missing " "))
      (terpri)
      (kill-emacs 1))))

(defun epm-cmd-install (_ libs)
  "Install packages from LIBS that are not available yet"
  ;; TODO force option or update command
  (let ((missing (epm-missing libs)))
    (when missing
      (package-refresh-contents)
      (make-directory package-user-dir 'parents))
    (dolist (pkg missing)
      (package-install (intern pkg)))))

(defun epm-cmd-report (_ libs)
  "Report paths of available libraries"
  (princ (format "package-user-dir: %s\n" package-user-dir))
  ;; (princ (format "load-path: %s\n" load-path))
  (dolist (name libs)
    ;; (version-to-list version)
    (princ (format "%-20s %s " name
		   (if (package-installed-p (intern name))
		       "package "
		     "        ")))
    (princ (locate-library name))
    (terpri)))

(defvar epm-commands
  '(("help" . epm-cmd-help)
    ("install" . epm-cmd-install)
    ("missing" . epm-cmd-missing)
    ("report" . epm-cmd-report)))

(defun epm-command-line-function ()
  "Handle command line options and arguments specific to epm.

Implements a handler for `command-line-functions'."
  ;; There is no easy to determine if "--" argument has been processed earlier.
  ;; TODO "--option=value" is handled by `command-line-1' only for standard arguments.
  (pcase argi
    ("--epm-dir"
     (setq epm-dir (pop command-line-args-left))
     t)
    ((or "--dbg" "--debug-on-error")
     ;; -d is handled as --display, --debug as --debug-init
     (setq debug-on-error t)
     t)
    ((pred (string-match-p "\\`[^-]"))
     (epm-init)
     (let ((func (cdr (assoc argi epm-commands)))
           (cmd-args command-line-args-left))
       (if (not func)
           (error "Unknown command %s" argi)
         (setq command-line-args-left nil)
         (funcall func argi cmd-args)))
     t)))

(push #'epm-command-line-function command-line-functions)

;; Local Variables:
;; no-byte-compile: t
;; End:
;;; epm.el ends here

debug log:

solving 2816702bb ...
found 2816702bb in https://yhetil.org/orgmode/u2tj7b$10i4$1@ciao.gmane.io/

applying [1/1] https://yhetil.org/orgmode/u2tj7b$10i4$1@ciao.gmane.io/
diff --git a/mk/epm.el b/mk/epm.el
new file mode 100755
index 000000000..2816702bb

Checking patch mk/epm.el...
Applied patch mk/epm.el cleanly.

index at:
100755 2816702bbd5f32c5924fd4a020b6dac30b2d34fc	mk/epm.el

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).