vcupp-batch.el

;;; vcupp-batch.el --- Shared batch helpers for vcupp -*- lexical-binding: t -*-

;; Author: Michael Olson
;; SPDX-License-Identifier: GPL-3.0-or-later
;; URL: https://github.com/mwolson/vcupp

;;; Commentary:

;; Shared infrastructure for `vcupp-install-packages' and `vcupp-native-comp'
;; batch entry points.  See the vcupp README for usage.

;;; Code:

(require 'cl-lib)
(require 'package)
(require 'subr-x)

(defconst vcupp-batch-default-load-files '("early-init.el" "init.el")
  "Default config files loaded by the batch helpers.")

(defvar vcupp-batch-args nil
  "Default plist of arguments consumed by the batch entry points.")

(defvar vcupp-batch-root
  (expand-file-name user-emacs-directory)
  "Root directory of the Emacs config being processed.")

(defvar vcupp-batch-load-files nil
  "Files to load before deferred tasks or package operations.
Entries may be absolute or relative to `vcupp-batch-root'.")

(defvar vcupp-batch-compile-files nil
  "Files to `native-compile' after loading the config.
Defaults to the value of variable `vcupp-batch-load-files'.")

(defvar vcupp-batch-setup-forms nil
  "Forms evaluated before the config files are loaded.")

(defvar vcupp-batch-preload-features nil
  "Features to `require' before the config files are loaded.")

(defvar vcupp-batch-delete-elc-globs nil
  "Glob patterns whose matching `.elc' files are deleted before load.
Patterns are resolved relative to `vcupp-batch-root'.")

(defvar vcupp-batch-post-load-forms nil
  "Forms run after the config files finish loading.")

(defvar vcupp-batch-post-install-forms nil
  "Forms run after install or upgrade completes.")

(defvar vcupp-batch-refresh-contents t
  "Whether to run `package-refresh-contents' before loading the config.")

(defvar vcupp-batch-package-native-compile t
  "Value to assign to `package-native-compile' during installs.")

(defun vcupp-batch--plist-value (args prop fallback)
  "Return PROP from ARGS if present, otherwise FALLBACK."
  (if (plist-member args prop)
      (plist-get args prop)
    fallback))

(defun vcupp-batch-effective-args (&optional args)
  "Return effective ARGS for batch entry points."
  (let* ((args (or args vcupp-batch-args))
         (root (expand-file-name
                (vcupp-batch--plist-value args :root vcupp-batch-root)))
         (load-files (or (vcupp-batch--plist-value args :load-files
                                                   vcupp-batch-load-files)
                         vcupp-batch-default-load-files))
         (compile-files (or (vcupp-batch--plist-value args :compile-files
                                                      vcupp-batch-compile-files)
                            load-files)))
    (list :root root
          :load-files load-files
          :compile-files compile-files
          :setup-forms (vcupp-batch--plist-value args :setup-forms
                                                 vcupp-batch-setup-forms)
          :preload-features (vcupp-batch--plist-value args :preload-features
                                                      vcupp-batch-preload-features)
          :delete-elc-globs (vcupp-batch--plist-value args :delete-elc-globs
                                                      vcupp-batch-delete-elc-globs)
          :post-load-forms (vcupp-batch--plist-value args :post-load-forms
                                                     vcupp-batch-post-load-forms)
          :post-install-forms (vcupp-batch--plist-value args :post-install-forms
                                                        vcupp-batch-post-install-forms)
          :refresh-contents (vcupp-batch--plist-value args :refresh-contents
                                                      vcupp-batch-refresh-contents)
          :package-native-compile (vcupp-batch--plist-value args :package-native-compile
                                                           vcupp-batch-package-native-compile))))

(defmacro vcupp-batch-with-effective-args (args &rest body)
  "Bind batch variables from ARGS, then run BODY."
  (declare (indent 1) (debug t))
  `(let* ((effective-args (vcupp-batch-effective-args ,args))
          (vcupp-batch-root (plist-get effective-args :root))
          (vcupp-batch-load-files (plist-get effective-args :load-files))
          (vcupp-batch-compile-files (plist-get effective-args :compile-files))
          (vcupp-batch-setup-forms (plist-get effective-args :setup-forms))
          (vcupp-batch-preload-features (plist-get effective-args :preload-features))
          (vcupp-batch-delete-elc-globs (plist-get effective-args :delete-elc-globs))
          (vcupp-batch-post-load-forms (plist-get effective-args :post-load-forms))
          (vcupp-batch-post-install-forms
           (plist-get effective-args :post-install-forms))
          (vcupp-batch-refresh-contents (plist-get effective-args :refresh-contents))
          (vcupp-batch-package-native-compile
           (plist-get effective-args :package-native-compile)))
     ,@body))

(defun vcupp-batch-expand-file (path)
  "Expand PATH relative to `vcupp-batch-root' when needed."
  (if (file-name-absolute-p path)
      path
    (expand-file-name path vcupp-batch-root)))

(defun vcupp-batch-load-files ()
  "Return config files to load for the active batch invocation."
  (or vcupp-batch-load-files
      (user-error "Set :load-files or `vcupp-batch-load-files' before loading this script")))

(defun vcupp-batch-compile-files ()
  "Return config files to `native-compile' for the active batch invocation."
  (or vcupp-batch-compile-files
      (vcupp-batch-load-files)))

(defun vcupp-batch-run-setup ()
  "Run preload features, setup forms, and cleanup."
  (setq load-prefer-newer t)
  (dolist (feature vcupp-batch-preload-features)
    (require feature))
  (dolist (form vcupp-batch-setup-forms)
    (eval form t))
  (dolist (pattern vcupp-batch-delete-elc-globs)
    (dolist (elc (file-expand-wildcards
                  (vcupp-batch-expand-file pattern) t))
      (delete-file elc))))

(defun vcupp-batch-load-config ()
  "Load config files and run optional post-load forms."
  (dolist (file (vcupp-batch-load-files))
    (load-file (vcupp-batch-expand-file file)))
  (dolist (entry vcupp-batch-post-load-forms)
    (if (functionp entry)
        (funcall entry)
      (eval entry t))))

(defun vcupp-batch-run-post-install ()
  "Run `vcupp-batch-post-install-forms' after install completes.
Each entry may be a function designator or a form to evaluate."
  (dolist (entry vcupp-batch-post-install-forms)
    (if (functionp entry)
        (funcall entry)
      (eval entry t))))

(provide 'vcupp-batch)
;;; vcupp-batch.el ends here