mirror of
https://github.com/doomemacs/doomemacs
synced 2025-09-18 16:06:56 -05:00
This prevents file errors when running the doctor from directories that don't exist or you don't have permissions for.
270 lines
10 KiB
Bash
Executable File
270 lines
10 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
":"; command -v emacs >/dev/null || { >&2 echo "Emacs isn't installed"; exit 1; } # -*-emacs-lisp-*-
|
|
":"; VERSION=$(emacs --version | head -n1)
|
|
":"; case $VERSION in *\ 2[0-2].[0-1].[0-9]) echo "You're running $VERSION"; echo "That version is too old to run the doctor. Check your PATH"; echo; exit 2 ;; esac
|
|
":"; exec emacs --quick --script "$0"; exit 0
|
|
|
|
;; The Doom doctor is essentially one big, self-contained elisp shell script
|
|
;; that uses a series of simple heuristics to diagnose common issues on your
|
|
;; system. Issues that could intefere with Doom Emacs.
|
|
;;
|
|
;; Doom modules may optionally have a doctor.el file to run their own heuristics
|
|
;; in. Doctor scripts may run in versions of Emacs as old as Emacs 23, so make
|
|
;; no assumptions about what's available in the standard library (e.g. avoid
|
|
;; cl/cl-lib, subr-x, map, seq, etc).
|
|
|
|
|
|
;; Ensure Doom doctor always runs out of the current Emacs directory (optionally
|
|
;; specified by the EMACSDIR envvar)
|
|
(setq user-emacs-directory
|
|
(or (getenv "EMACSDIR")
|
|
(expand-file-name "../" (file-name-directory load-file-name)))
|
|
default-directory user-emacs-directory)
|
|
|
|
(unless (file-directory-p user-emacs-directory)
|
|
(error "Couldn't find a Doom config!"))
|
|
(unless noninteractive
|
|
(error "This script must not be run from an interactive session."))
|
|
(when (getenv "DEBUG")
|
|
(setq debug-on-error t))
|
|
|
|
(require 'subr-x)
|
|
(require 'pp)
|
|
(load (expand-file-name "core/autoload/format" user-emacs-directory) nil t)
|
|
|
|
|
|
(defvar doom-init-p nil)
|
|
(defvar doom-warnings 0)
|
|
(defvar doom-errors 0)
|
|
|
|
|
|
;;; Helpers
|
|
|
|
(defun sh (cmd &rest args)
|
|
(ignore-errors
|
|
(string-trim-right
|
|
(shell-command-to-string (apply #'format cmd args)))))
|
|
|
|
(defun elc-check-dir (dir)
|
|
(dolist (file (directory-files-recursively dir "\\.elc$"))
|
|
(when (file-newer-than-file-p (concat (file-name-sans-extension file) ".el")
|
|
file)
|
|
(warn! "%s is out-of-date" (abbreviate-file-name file)))))
|
|
|
|
(defmacro assert! (condition message &rest args)
|
|
`(unless ,condition
|
|
(error! ,message ,@args)))
|
|
|
|
|
|
;;; Logging
|
|
|
|
(defvar indent 0)
|
|
(defvar prefix "")
|
|
|
|
(defmacro msg! (msg &rest args)
|
|
`(print!
|
|
(indent indent
|
|
(format (concat prefix ,msg)
|
|
,@args))))
|
|
|
|
(defmacro error! (&rest args)
|
|
`(progn (msg! (red ,@args))
|
|
(setq doom-errors (+ doom-errors 1))))
|
|
(defmacro warn! (&rest args)
|
|
`(progn (msg! (yellow ,@args))
|
|
(setq doom-warnings (+ doom-warnings 1))))
|
|
(defmacro success! (&rest args) `(msg! (green ,@args)))
|
|
(defmacro section! (&rest args) `(msg! (bold (blue ,@args))))
|
|
|
|
(defmacro explain! (&rest args)
|
|
`(msg! (indent (+ indent 2) (autofill ,@args))))
|
|
|
|
|
|
;;; Polyfills
|
|
;; early versions of emacs won't have this
|
|
(unless (fboundp 'string-match-p)
|
|
(defun string-match-p (regexp string &optional start)
|
|
(save-match-data
|
|
(string-match regexp string &optional start))))
|
|
|
|
;; subr-x don't exist in older versions of Emacs
|
|
(unless (fboundp 'string-trim-right)
|
|
(defsubst string-trim-right (string &optional regexp)
|
|
(if (string-match (concat "\\(?:" (or regexp "[ \t\n\r]+") "\\)\\'") string)
|
|
(replace-match "" t t string)
|
|
string)))
|
|
|
|
|
|
;;
|
|
;;; Basic diagnostics
|
|
|
|
(msg! (bold "Doom Doctor"))
|
|
(msg! "Emacs v%s" emacs-version)
|
|
(msg! "Doom v%s (%s)"
|
|
(or (let ((core-file (expand-file-name "core/core.el" user-emacs-directory)))
|
|
(and (file-exists-p core-file)
|
|
(ignore-errors
|
|
(with-temp-buffer
|
|
(insert-file-contents-literally core-file)
|
|
(goto-char (point-min))
|
|
(when (re-search-forward "doom-version" nil t)
|
|
(forward-char)
|
|
(sexp-at-point))))))
|
|
"???")
|
|
(if (and (executable-find "git")
|
|
(file-directory-p (expand-file-name ".git" user-emacs-directory)))
|
|
(sh "git log -1 --format=\"%D %h %ci\"")
|
|
"n/a"))
|
|
(msg! "shell: %s%s"
|
|
(getenv "SHELL")
|
|
(if (equal (getenv "SHELL") (sh "echo $SHELL"))
|
|
""
|
|
(red " (mismatch)")))
|
|
(when (boundp 'system-configuration-features)
|
|
(msg! "Compiled with:\n%s" (indent 2 system-configuration-features)))
|
|
(msg! "uname -msrv:\n%s\n" (indent 2 (sh "uname -msrv")))
|
|
|
|
|
|
;;
|
|
;;; Check if Emacs is set up correctly
|
|
|
|
(section! "Checking Emacs")
|
|
(let ((indent 2))
|
|
(section! "Checking your Emacs version is 25.3 or newer...")
|
|
(when (version< emacs-version "25.3")
|
|
(error! "Important: Emacs %s detected [%s]" emacs-version (executable-find "emacs"))
|
|
(explain!
|
|
"DOOM only supports >= 25.3. Perhaps your PATH wasn't set up properly."
|
|
(when (eq system-type 'darwin)
|
|
(concat "\nMacOS users should use homebrew (https://brew.sh) to install Emacs\n"
|
|
" brew install emacs --with-modules --with-imagemagick --with-cocoa"))))
|
|
|
|
(section! "Checking if your version of Emacs has changed recently...")
|
|
(let ((version-file (expand-file-name ".local/emacs-version.el" user-emacs-directory))
|
|
doom--last-emacs-version)
|
|
(when (and (load version-file 'noerror 'nomessage 'nosuffix)
|
|
(not (equal emacs-version doom--last-emacs-version)))
|
|
(warn! "Your version of Emacs has changed from %S to %S. Recompile your packages!"
|
|
doom--last-emacs-version
|
|
emacs-version)
|
|
(explain! "Byte-code compiled in one version of Emacs may not work in another version."
|
|
"It is recommended that you reinstall your plugins or recompile them with"
|
|
"`bin/doom rebuild'.")))
|
|
|
|
(section! "Checking for Emacs config conflicts...")
|
|
(when (file-exists-p "~/.emacs")
|
|
(warn! "Detected an ~/.emacs file, which may prevent Doom from loading")
|
|
(explain! "If Emacs finds an ~/.emacs file, it will ignore ~/.emacs.d, where Doom is "
|
|
"typically installed. If you're seeing a vanilla Emacs splash screen, this "
|
|
"may explain why. If you use Chemacs, you may ignore this warning."))
|
|
|
|
(section! "Checking for private config conflicts...")
|
|
(let ((xdg-dir (concat (or (getenv "XDG_CONFIG_HOME")
|
|
"~/.config")
|
|
"/doom/"))
|
|
(doom-dir (or (getenv "DOOMDIR")
|
|
"~/.doom.d/")))
|
|
(when (and (not (file-equal-p xdg-dir doom-dir))
|
|
(file-directory-p xdg-dir)
|
|
(file-directory-p doom-dir))
|
|
(warn! "Detected two private configs, in %s and %s"
|
|
(abbreviate-file-name xdg-dir)
|
|
doom-dir)
|
|
(explain! "The second directory will be ignored, as it has lower precedence.")))
|
|
|
|
(section! "Checking for stale elc files...")
|
|
(elc-check-dir user-emacs-directory))
|
|
|
|
|
|
;;
|
|
;;; Check if system environment is set up correctly
|
|
|
|
(section! "Checking your system...")
|
|
(let ((indent 2))
|
|
;; on windows?
|
|
(when (memq system-type '(windows-nt ms-dos cygwin))
|
|
(warn! "Warning: Windows detected")
|
|
(explain! "DOOM was designed for MacOS and Linux. Expect a bumpy ride!")))
|
|
|
|
|
|
;;
|
|
;;; Check if Doom Emacs is set up correctly
|
|
|
|
(condition-case-unless-debug ex
|
|
(let ((after-init-time (current-time))
|
|
(doom-format-backend 'ansi)
|
|
noninteractive)
|
|
(section! "Checking DOOM Emacs...")
|
|
(load (concat user-emacs-directory "core/core.el") nil t)
|
|
(unless (file-directory-p doom-private-dir)
|
|
(error "No DOOMDIR was found, did you run `doom install` yet?"))
|
|
|
|
(let ((indent 2))
|
|
;; Make sure Doom is initialized and loaded
|
|
(doom-initialize 'force)
|
|
(doom-initialize-core)
|
|
(success! "Initialized Doom Emacs %s" doom-version)
|
|
|
|
(doom-initialize-modules)
|
|
(if (hash-table-p doom-modules)
|
|
(success! "Initialized %d modules" (hash-table-count doom-modules))
|
|
(warn! "Failed to load any modules. Do you have an private init.el?"))
|
|
|
|
(doom-initialize-packages)
|
|
(success! "Initialized %d packages" (length doom-packages))
|
|
|
|
(section! "Checking Doom core for irregularities...")
|
|
(let ((indent (+ indent 2)))
|
|
(load (expand-file-name "doctor.el" doom-core-dir) nil 'nomessage))
|
|
|
|
(section! "Checking for stale elc files in your DOOMDIR...")
|
|
(when (file-directory-p doom-private-dir)
|
|
(let ((indent (+ indent 2)))
|
|
(elc-check-dir doom-private-dir)))
|
|
|
|
(when doom-modules
|
|
(section! "Checking your enabled modules...")
|
|
(let ((indent (+ indent 2)))
|
|
(advice-add #'require :around #'doom-shut-up-a)
|
|
(maphash
|
|
(lambda (key plist)
|
|
(let ((prefix (format! (bold "(%s %s) " (car key) (cdr key)))))
|
|
(condition-case-unless-debug ex
|
|
(let ((doctor-file (doom-module-path (car key) (cdr key) "doctor.el"))
|
|
(packages-file (doom-module-path (car key) (cdr key) "packages.el")))
|
|
(cl-loop for name in (let (doom-packages
|
|
doom-disabled-packages)
|
|
(load packages-file 'noerror 'nomessage)
|
|
(mapcar #'car doom-packages))
|
|
unless (or (doom-package-get name :disable)
|
|
(eval (doom-package-get name :ignore))
|
|
(doom-package-built-in-p name)
|
|
(doom-package-installed-p name))
|
|
do (error! "%s is not installed" name))
|
|
(load doctor-file 'noerror 'nomessage))
|
|
(file-missing (error! "%s" (error-message-string ex)))
|
|
(error (error! "Syntax error: %s" ex)))))
|
|
doom-modules)))))
|
|
(error
|
|
(warn! "Attempt to load DOOM failed\n %s\n"
|
|
(or (cdr-safe ex) (car ex)))
|
|
(setq doom-modules nil)))
|
|
|
|
|
|
;;
|
|
;;; Final report
|
|
|
|
(message "")
|
|
(dolist (msg (list (list doom-errors "error" 'red)
|
|
(list doom-warnings "warning" 'yellow)))
|
|
(when (> (car msg) 0)
|
|
(msg! (color (nth 2 msg)
|
|
(if (= (car msg) 1)
|
|
"There is %d %s!"
|
|
"There are %d %ss!")
|
|
(car msg) (nth 1 msg)))))
|
|
|
|
(when (and (zerop doom-errors)
|
|
(zerop doom-warnings))
|
|
(success! "Everything seems fine, happy Emacs'ing!"))
|