el-get/el-get-check.el
Stephan Creutz ccca97f226 Replace cl by cl-lib
Since Emacs 27 the package cl is deprecated, the replacement is
cl-lib, which is available since Emacs 24.3.

This patch replaces cl by cl-lib and drops support for Emacs versions
less than 24.3. Dropping older Emacsen is required, because cl-lib is
a builtin starting from version 24.3 and doesn't need an extra package
from ELPA.

Testcases for past issues still contain cl. Most of them seem to be
broken and need further investigation.

This patch is tested with test/run-ert.sh, which outputs:

Ran 10 tests, 10 results as expected, 0 unexpected (2021-01-30 13:24:54+0100, 0.672122 sec)
1 expected failures

and manually by daily usage for a month now.
2021-06-13 16:03:08 +02:00

226 lines
9.6 KiB
EmacsLisp

;;; el-get-recipes.el --- Manage the external elisp bits and pieces you depend upon
;;
;; Copyright (C) 2010-2011 Dimitri Fontaine
;;
;; Author: Dimitri Fontaine <dim@tapoueh.org>
;; URL: http://www.emacswiki.org/emacs/el-get
;; GIT: https://github.com/dimitri/el-get
;; Licence: WTFPL, grab your copy here: http://sam.zoy.org/wtfpl/
;;
;; This file is NOT part of GNU Emacs.
;;
;; Install
;; Please see the README.md file from the same distribution
;;; Commentary:
;;
;; el-get-check provides some functions to check for some errors in recipes.
;;
;;; Code:
(require 'cl-lib)
(require 'el-get-recipes)
(require 'el-get-build)
(defvar warning-minimum-log-level)
(defvar warning-minimum-level)
(declare-function warning-numeric-level "warnings" (level))
(defvar el-get-check--last-file-or-buffer nil
"The last file-or-buffer checked.")
(defun el-get-check-redo ()
"Rerun `el-get-check-recipe' with last recipe."
(interactive)
(when el-get-check--last-file-or-buffer
(el-get-check-recipe
el-get-check--last-file-or-buffer)))
(defvar el-get-check-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map special-mode-map)
(define-key map "g" #'el-get-check-redo)
map)
"Mode map for `el-get-check-mode'.")
(define-derived-mode el-get-check-mode special-mode "El-Get Check"
"Special mode for `el-get-check-recipe' buffers.
See Info node `(el-get) Authoring Recipes'.")
(defvar el-get-check-suppressed-warnings ()
"List of `el-get-check-recipe' warnings to suppress.
Current possibe elements are:
`features', `github', `autoloads'")
(defun el-get-check-recipe-batch-1 (recipe-file)
(let ((warning-prefix-function
(lambda (level entry)
(list level (format "%s:%s" el-get-check--last-file-or-buffer
(format (nth 1 entry) ""))))))
(condition-case err
(el-get-check-recipe (file-relative-name recipe-file))
(error (lwarn '(el-get) :emergency "%s" (error-message-string err))
1))))
(defun el-get-check-recipe-batch ()
"emacs -Q -batch -f el-get-check-recipe-batch [-W<:level>] [-Wno-<warning>...] *.rcp
<:level> can be any valid warning level, see `warning-levels'.
See `el-get-check-suppressed-warnings' for possible <warning> values."
(cl-assert noninteractive nil
"`el-get-check-recipe-batch' should only be used with -batch")
(setq vc-handled-backends nil) ; avoid loading VC during batch mode
(cl-loop for arg in command-line-args-left
if (string-match "\\`-Wno-\\(.*\\)" arg)
do (push (intern (match-string 1 arg)) el-get-check-suppressed-warnings)
else if (string-match "\\`-W\\(:[-a-z]*\\)" arg)
do (setq warning-minimum-log-level
(setq warning-minimum-level (intern (match-string 1 arg))))
else summing
(if (file-directory-p arg)
(cl-reduce #'+ (directory-files arg t "\\.rcp$" t)
:key #'el-get-check-recipe-batch-1 :initial-value 0)
(el-get-check-recipe-batch-1 arg))
into errors
finally (progn (message "%d warning/error(s) total." errors)
(kill-emacs (if (zerop errors) 0 1)))))
;;;###autoload
(defun el-get-check-recipe (file-or-buffer)
"Check the format of the recipe.
Please run this command before sending a pull request.
Usage: M-x el-get-check-recipe RET
You can run this function from checker script like this:
test/check-recipe.el PATH/TO/RECIPE.rcp
When used as a lisp function, FILE-OR-BUFFER must be a buffer
object or a file path."
(interactive (list (current-buffer)))
(setq el-get-check--last-file-or-buffer file-or-buffer)
(if (bufferp file-or-buffer)
(with-current-buffer file-or-buffer
(el-get-check-recipe-in-current-buffer (buffer-file-name)))
(with-temp-buffer
(insert-file-contents file-or-buffer)
(el-get-check-recipe-in-current-buffer file-or-buffer))))
(eval-and-compile
(unless (fboundp 'file-name-base) ; new in 24.3
(defun file-name-base (&optional filename)
"Return the base name of the FILENAME: no directory, no extension.
FILENAME defaults to `buffer-file-name'."
(file-name-sans-extension
(file-name-nondirectory (or filename (buffer-file-name)))))))
(defvar el-get-check-warning-buffer)
(defvar el-get-check-error-count)
(defun el-get-check-warning (level message &rest args)
(declare (indent 1))
(display-warning '(el-get recipe) (apply #'format message args)
level el-get-check-warning-buffer)
(when (>= (warning-numeric-level level)
(warning-numeric-level warning-minimum-level))
(cl-incf el-get-check-error-count)))
(defun el-get-check-recipe-in-current-buffer (recipe-file-name)
(let ((inhibit-read-only t)
(el-get-check-error-count 0)
(el-get-check-warning-buffer (get-buffer-create "*el-get check recipe*")))
(display-buffer el-get-check-warning-buffer)
(with-current-buffer el-get-check-warning-buffer
(erase-buffer)
(el-get-check-mode))
(let* ((recipe (save-excursion
(goto-char (point-min))
(prog1 (read (current-buffer))
(let ((lvl-err (condition-case err
(progn (read (current-buffer))
`(:warning . "Extra data following recipe"))
(end-of-file nil)
(error `(:error . ,(error-message-string err))))))
(when lvl-err
(let ((el-get-check--last-file-or-buffer
(format "%s:%d:%d" recipe-file-name
(line-number-at-pos) (current-column))))
(el-get-check-warning (car lvl-err) (cdr lvl-err))))))))
(el-get-sources (list recipe))
(pkg-name (plist-get recipe :name)))
(when (and recipe-file-name
(not (string= (file-name-base recipe-file-name) pkg-name)))
(el-get-check-warning :error
"File name should match recipe name."))
;; Check if userspace property is used.
(cl-loop for key in '(:before :after)
for alt in '(:prepare :post-init)
when (plist-get recipe key)
do (el-get-check-warning :warning
"Property %S is for user. Use %S instead."
key alt))
;; Check for misformatted plists
(cl-loop for key in recipe by #'cddr
unless (keywordp key)
do (el-get-check-warning :warning
"Property %S is not a keyword!"
key))
(cl-destructuring-bind (&key type url autoloads feats builtin
&allow-other-keys)
recipe
;; let-binding `features' causes `provide' to throw error
(setq feats (plist-get recipe :features))
;; Is github type used?
(when (and (not (memq 'github el-get-check-suppressed-warnings))
(eq type 'git) (string-match "//github.com/" url))
(el-get-check-warning :warning
"Use `:type github' for github type recipe"))
;; Warn when `:autoloads nil' is specified.
(when (and (not (memq 'autoloads el-get-check-suppressed-warnings))
(null autoloads) (plist-member recipe :autoloads))
(el-get-check-warning :warning
"Are you sure you don't need autoloads?
This property should be used only when the library takes care of
the autoload."))
;; Warn when `:features t' is specified
(when (and (not (memq 'features el-get-check-suppressed-warnings))
feats)
(el-get-check-warning :warning
"Are you sure you need features?
If this library has `;;;###autoload' comment (a.k.a autoload cookie),
you don't need `:features'."))
;; Check if `:builtin' is used with an integer
(when (integerp builtin)
(el-get-check-warning :warning
"Usage of integers for :builtin is obsolete.
Use a version string like \"24.3\" instead.")))
;; Check for shell interpolated :build commands
(let ((safe-functions '(backquote-list*
el-get-load-path el-get-package-exists-p
el-get-package-directory el-get-print-to-string
el-get-verbose-message
with-temp-buffer insert-file-contents
directory-files file-name-as-directory
expand-file-name shell-quote-argument)))
(dolist (sys '("" "/darwin" "/berkeley-unix" "/windows-nt"))
(let ((unsafe (catch 'unsafe-build
(when (cl-some #'stringp (el-get-build-commands pkg-name 'safe-eval sys))
(el-get-check-warning :warning
":build%s should be a *list* of string lists." sys))
nil)))
(when unsafe
(el-get-check-warning :debug ":build%s is unsafep: %s" sys unsafe)))))
;; Check for required properties.
(cl-loop for key in '(:description :name)
unless (plist-get recipe key)
do (el-get-check-warning
:error "Required property %S is not defined." key))
(with-current-buffer el-get-check-warning-buffer
(insert (format "\n%s: %s error(s) found." recipe-file-name el-get-check-error-count))))
el-get-check-error-count))
(provide 'el-get-check)
;;; el-get-check.el ends here