#!/usr/bin/env bash ":"; command -v emacs >/dev/null || { >&2 echo "Emacs isn't installed"; exit 1; } # -*-emacs-lisp-*- ":"; VERSION=$(emacs --version | head -n1) ":"; [[ $VERSION == *\ 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; } ":"; 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)))) (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 'pp) (load (expand-file-name "core/autoload/message" user-emacs-directory) nil t) (defvar doom-init-p nil) (defvar doom-warnings 0) (defvar doom-errors 0) ;;; Helpers (defun sh (cmd) (string-trim-right (shell-command-to-string cmd))) (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) (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 compile :plugins'."))) (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!")) ;; are all default fonts present? (section! "Checking your fonts...") (if (not (fboundp 'find-font)) (progn (warn! "Warning: unable to detect font") (explain! "The `find-font' function is missing. This could indicate the incorrect " "version of Emacs is being used!")) ;; all-the-icons fonts (let ((font-dest (pcase system-type (`gnu/linux (concat (or (getenv "XDG_DATA_HOME") "~/.local/share") "/fonts/")) (`darwin "~/Library/Fonts/")))) (when (and font-dest (require 'all-the-icons nil t)) (dolist (font all-the-icons-font-names) (if (file-exists-p (expand-file-name font font-dest)) (success! "Found font %s" font) (warn! "Warning: couldn't find %s font in %s" font font-dest) (explain! "You can install it by running `M-x all-the-icons-install-fonts' within Emacs.\n\n" "This could also mean you've installed them in non-standard locations, in which " "case feel free to ignore this warning.")))))) ;; gnutls-cli & openssl (section! "Checking gnutls/openssl...") (cond ((executable-find "gnutls-cli")) ((executable-find "openssl") (let* ((output (sh "openssl ciphers -v")) (protocols (let (protos) (mapcar (lambda (row) (add-to-list 'protos (cadr (split-string row " " t)))) (split-string (sh "openssl ciphers -v") "\n")) (delq nil protos)))) (unless (or (member "TLSv1.1" protocols) (member "TLSv1.2" protocols)) (let ((version (cadr (split-string (sh "openssl version") " " t)))) (warn! "Warning: couldn't find gnutls-cli, and OpenSSL is out-of-date (v%s)" version) (explain! "This may not affect your Emacs experience, but there are security " "vulnerabilities in the SSL2/3 & TLS1.0 protocols. You should use " "TLS 1.1+, which wasn't introduced until OpenSSL v1.0.1.\n\n" "Please consider updating (or install gnutls-cli, which is preferred)."))))) (t (error! "Important: couldn't find either gnutls-cli nor openssl") (explain! "You won't be able to install/update packages because Emacs won't be able to " "verify HTTPS ELPA sources. Install gnutls-cli or openssl v1.0.0+. If for some " "reason you can't, you can bypass this verification with the INSECURE flag:\n\n" " INSECURE=1 make install\n\n" "Or change `package-archives' to use non-https sources.\n\n" "But remember that you're leaving your security in the hands of your " "network, provider, government, neckbearded mother-in-laws, geeky roommates, " "or just about anyone who knows more about computers than you do!"))) ;; are certificates validated properly? (section! "Testing your root certificates...") (cond ((not (ignore-errors (gnutls-available-p))) (warn! "Warning: Emacs wasn't installed with gnutls support") (explain! "This may cause 'pecular error' errors with the Doom doctor, and is likely to " "interfere with package management. Your mileage may vary." (when (eq system-type 'darwin) (concat "\nMacOS users are advised to install Emacs via homebrew with one of the following:\n" " brew install emacs --with-gnutls" " or" " brew tap d12frosted/emacs-plus" " brew install emacs-plus")))) ((not (fboundp 'url-retrieve-synchronously)) (error! "Can't find url-retrieve-synchronously function. Are you sure you're on Emacs 24+?")) ((or (executable-find "gnutls-cli") (executable-find "openssl")) (let ((tls-checktrust t) (gnutls-verify-error t)) (dolist (url '("https://elpa.gnu.org" "https://melpa.org")) (pcase (condition-case-unless-debug e (unless (let ((inhibit-message t)) (url-retrieve-synchronously url)) 'empty) ('timed-out 'timeout) ('error e)) (`nil nil) (`empty (error! "Couldn't reach %s" url)) (`timeout (error! "Timed out trying to contact %s" ex)) (_ (error! "Failed to validate %s" url) (explain! (pp-to-string it))))) (dolist (url '("https://self-signed.badssl.com" "https://wrong.host.badssl.com/")) (pcase (condition-case-unless-debug e (if (let ((inhibit-message t)) (url-retrieve-synchronously url)) t 'empty) ('timed-out 'timeout) ('error)) (`nil nil) (`empty (error! "Couldn't reach %s" url)) (`timeout (error! "Timed out trying to contact %s" ex)) (_ (error! "Validated %s (this shouldn't happen!)" url)))))) ((error! "Nope!"))) ;; which variant of tar is on your system? bsd or gnu tar? (section! "Checking for GNU/BSD tar...") (let ((tar-bin (or (executable-find "gtar") (executable-find "tar")))) (if tar-bin (unless (string-match-p "(GNU tar)" (sh (format "%s --version" tar-bin))) (warn! "Warning: BSD tar detected") (explain! "QUELPA (through package-build) uses the system tar to build plugins, but it " "expects GNU tar. BSD tar *could* cause errors during package installation or " "updating from non-ELPA sources." (when (eq system-type 'darwin) (concat "\nMacOS users can install gnu-tar via homebrew:\n" " brew install gnu-tar")))) (error! "Important: Couldn't find tar") (explain! "This is required by package.el and QUELPA to build packages and will " "prevent you from installing & updating packages.")))) ;; ;;; Check if Doom Emacs is set up correctly (condition-case-unless-debug ex (let ((after-init-time (current-time)) (doom-message-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 quickstart` yet?")) (let ((indent 2)) ;; Make sure everything is loaded (require 'core-cli) (require 'core-keybinds) (require 'core-ui) (require 'core-projects) (require 'core-editor) (require 'core-packages) ;; ...and initialized (doom-initialize) (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) (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 with doom--stage = 'packages for name in (let (doom-packages doom-disabled-packages) (load packages-file 'noerror 'nomessage) (mapcar #'car doom-packages)) unless (or (doom-package-prop name :disable) (doom-package-prop name :ignore t) (package-built-in-p name) (package-installed-p name)) do (error! "%s is not installed" name)) (let ((doom--stage 'doctor)) (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!"))