From 9acaf3899aedb69300a29a0d0c8b468e5f4ad729 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 3 May 2026 19:10:44 -0500 Subject: fix: expand local ELPA mirror paths with expand-file-name `(concat user-home-dir ".elpa-mirrors/")` was producing `/home/cjennings.elpa-mirrors/` because `getenv HOME` doesn't return a trailing slash on Linux. The local mirrors were silently dropping out of `package-archives` because `file-accessible-directory-p` couldn't find the bogus path. I replaced the `concat` calls for `elpa-mirror-location` and `localrepo-location` with `expand-file-name`, which handles the slash for us. I also lifted the four per-archive subdirs into their own constants (`elpa-mirror-gnu-location`, `nongnu`, `melpa`, `stable-melpa`) so the archive registration block stops splicing `concat` strings inline. I added `tests/test-early-init-paths.el`. It loads `early-init.el` against a temp HOME with the package side effects stubbed and asserts each constant and each `package-archives` entry resolves to the right path. --- early-init.el | 38 ++++++++++++---- tests/test-early-init-paths.el | 101 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 tests/test-early-init-paths.el diff --git a/early-init.el b/early-init.el index 9f90f292..49a9da92 100644 --- a/early-init.el +++ b/early-init.el @@ -116,14 +116,32 @@ Set to nil to use only local repos.") (defconst user-home-dir (getenv "HOME") "The user's home directory per the environment variable.") -(defconst elpa-mirror-location (concat user-home-dir ".elpa-mirrors/") +(defconst elpa-mirror-location + (expand-file-name ".elpa-mirrors/" user-home-dir) "The path to the elpa mirror location.") -(defconst localrepo-location (concat user-emacs-directory ".localrepo/") +(defconst localrepo-location + (expand-file-name ".localrepo/" user-emacs-directory) "The path to your local Emacs package repository. For more information about the local Emacs package repository, see comments in early-init.el.") +(defconst elpa-mirror-gnu-location + (expand-file-name "gnu/" elpa-mirror-location) + "The path to the local GNU ELPA mirror.") + +(defconst elpa-mirror-nongnu-location + (expand-file-name "nongnu/" elpa-mirror-location) + "The path to the local NonGNU ELPA mirror.") + +(defconst elpa-mirror-melpa-location + (expand-file-name "melpa/" elpa-mirror-location) + "The path to the local MELPA mirror.") + +(defconst elpa-mirror-stable-melpa-location + (expand-file-name "stable-melpa/" elpa-mirror-location) + "The path to the local MELPA Stable mirror.") + (setq package-archives nil) ;; package-archives will be added below ;; LOCAL REPOSITORY (packages in version control) @@ -132,20 +150,20 @@ early-init.el.") (add-to-list 'package-archive-priorities '("localrepo" . 200))) ;; LOCAL REPOSITORY ELPA MIRRORS -(when (file-accessible-directory-p (concat elpa-mirror-location "gnu")) - (add-to-list 'package-archives (cons "gnu-local" (concat elpa-mirror-location "gnu/")) t) +(when (file-accessible-directory-p elpa-mirror-gnu-location) + (add-to-list 'package-archives (cons "gnu-local" elpa-mirror-gnu-location) t) (add-to-list 'package-archive-priorities '("gnu-local" . 125))) -(when (file-accessible-directory-p (concat elpa-mirror-location "nongnu")) - (add-to-list 'package-archives (cons "nongnu-local" (concat elpa-mirror-location "nongnu/")) t) +(when (file-accessible-directory-p elpa-mirror-nongnu-location) + (add-to-list 'package-archives (cons "nongnu-local" elpa-mirror-nongnu-location) t) (add-to-list 'package-archive-priorities '("nongnu-local" . 120))) -(when (file-accessible-directory-p (concat elpa-mirror-location "melpa")) - (add-to-list 'package-archives (cons "melpa-local" (concat elpa-mirror-location "melpa/")) t) +(when (file-accessible-directory-p elpa-mirror-melpa-location) + (add-to-list 'package-archives (cons "melpa-local" elpa-mirror-melpa-location) t) (add-to-list 'package-archive-priorities '("melpa-local" . 115))) -(when (file-accessible-directory-p (concat elpa-mirror-location "stable-melpa")) - (add-to-list 'package-archives (cons "melpa-stable-local" (concat elpa-mirror-location "stable-melpa/")) t) +(when (file-accessible-directory-p elpa-mirror-stable-melpa-location) + (add-to-list 'package-archives (cons "melpa-stable-local" elpa-mirror-stable-melpa-location) t) (add-to-list 'package-archive-priorities '("melpa-stable-local" . 100))) ;; ONLINE REPOSITORIES diff --git a/tests/test-early-init-paths.el b/tests/test-early-init-paths.el new file mode 100644 index 00000000..2051da73 --- /dev/null +++ b/tests/test-early-init-paths.el @@ -0,0 +1,101 @@ +;;; test-early-init-paths.el --- Tests for early-init path construction -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Load early-init.el with package side effects stubbed so path construction can +;; be tested without touching real package archives or the network. + +;;; Code: + +(require 'cl-lib) +(require 'ert) +(require 'package) + +(defvar cj/use-online-repos nil + "Test binding for early-init online repository setup.") + +(defconst test-early-init-paths--repo-root + (file-name-directory + (directory-file-name + (file-name-directory (or load-file-name buffer-file-name)))) + "Repository root for early-init path tests.") + +(defun test-early-init-paths--load-with-temp-home (home emacs-home) + "Load early-init.el with HOME and EMACS-HOME as isolated roots." + (let ((process-environment (copy-sequence process-environment)) + (user-emacs-directory (file-name-as-directory emacs-home)) + (package-user-dir (expand-file-name "elpa/" emacs-home)) + (package-archives nil) + (package-archive-priorities nil) + (debug-on-error nil) + (debug-on-quit nil)) + (setq cj/use-online-repos nil) + (setenv "HOME" home) + (cl-letf (((symbol-function 'package-initialize) #'ignore) + ((symbol-function 'package-installed-p) (lambda (_package) t)) + ((symbol-function 'package-refresh-contents) #'ignore) + ((symbol-function 'package-install) #'ignore)) + (load (expand-file-name "early-init.el" test-early-init-paths--repo-root) nil t)) + (list :user-home-dir user-home-dir + :elpa-mirror-location elpa-mirror-location + :localrepo-location localrepo-location + :gnu elpa-mirror-gnu-location + :nongnu elpa-mirror-nongnu-location + :melpa elpa-mirror-melpa-location + :stable-melpa elpa-mirror-stable-melpa-location + :package-archives package-archives + :package-archive-priorities package-archive-priorities))) + +(ert-deftest test-early-init-paths-normal-expanded-under-home () + "Normal: local mirror paths expand under HOME, not beside it." + (let* ((home (make-temp-file "early-init-home-" t)) + (emacs-home (make-temp-file "early-init-emacs-" t)) + (_ (dolist (dir '(".localrepo" + ".elpa-mirrors/gnu" + ".elpa-mirrors/nongnu" + ".elpa-mirrors/melpa" + ".elpa-mirrors/stable-melpa")) + (make-directory (expand-file-name dir emacs-home) t))) + ;; Mirror directories intentionally live under HOME, while .localrepo + ;; lives under user-emacs-directory. + (_ (dolist (dir '(".elpa-mirrors/gnu" + ".elpa-mirrors/nongnu" + ".elpa-mirrors/melpa" + ".elpa-mirrors/stable-melpa")) + (make-directory (expand-file-name dir home) t))) + (result (test-early-init-paths--load-with-temp-home home emacs-home))) + (should (string= (plist-get result :user-home-dir) home)) + (should (string= (plist-get result :elpa-mirror-location) + (expand-file-name ".elpa-mirrors/" home))) + (should (string= (plist-get result :localrepo-location) + (expand-file-name ".localrepo/" emacs-home))) + (should (string= (plist-get result :gnu) + (expand-file-name ".elpa-mirrors/gnu/" home))) + (should-not (string= (plist-get result :elpa-mirror-location) + (concat home ".elpa-mirrors/"))))) + +(ert-deftest test-early-init-paths-normal-local-archives-use-expanded-paths () + "Normal: local package archives use expanded path constants." + (let* ((home (make-temp-file "early-init-home-" t)) + (emacs-home (make-temp-file "early-init-emacs-" t))) + (make-directory (expand-file-name ".localrepo/" emacs-home) t) + (dolist (dir '(".elpa-mirrors/gnu" + ".elpa-mirrors/nongnu" + ".elpa-mirrors/melpa" + ".elpa-mirrors/stable-melpa")) + (make-directory (expand-file-name dir home) t)) + (let* ((result (test-early-init-paths--load-with-temp-home home emacs-home)) + (archives (plist-get result :package-archives))) + (should (equal (cdr (assoc "localrepo" archives)) + (expand-file-name ".localrepo/" emacs-home))) + (should (equal (cdr (assoc "gnu-local" archives)) + (expand-file-name ".elpa-mirrors/gnu/" home))) + (should (equal (cdr (assoc "nongnu-local" archives)) + (expand-file-name ".elpa-mirrors/nongnu/" home))) + (should (equal (cdr (assoc "melpa-local" archives)) + (expand-file-name ".elpa-mirrors/melpa/" home))) + (should (equal (cdr (assoc "melpa-stable-local" archives)) + (expand-file-name ".elpa-mirrors/stable-melpa/" home)))))) + +(provide 'test-early-init-paths) +;;; test-early-init-paths.el ends here -- cgit v1.2.3