#!/usr/bin/env bash ":"; command -v emacs >/dev/null || { >&2 echo "Emacs isn't installed"; exit 1; } # -*-emacs-lisp-*- ":"; [[ $(emacs --version | head -n1) == *\ 2[0-2].[0-1].[0-9] ]] && { echo "You're running $(emacs --version | head -n1)"; echo "That version is too old to run the doctor. Check your PATH"; echo; exit 2; } || exec emacs --quick --script "$0" ;; Uses a couple simple heuristics to locate issues with your environment that ;; could interfere with running or setting up DOOM Emacs. ;; In case it isn't defined (in really old versions of Emacs, like the one that ;; ships with MacOS). (defvar user-emacs-directory (expand-file-name "~/.emacs.d/")) (unless (equal (expand-file-name user-emacs-directory) (expand-file-name "~/.emacs.d/")) (error "Couldn't find ~/.emacs.d")) (require 'pp) ;; (defvar doom-init-p nil) (defvar doom-errors 0) (defmacro check! (cond &rest body) (declare (indent defun)) `(when ,cond ,@body (setq doom-errors (1+ doom-errors)))) (defun indented (spc msg) (declare (indent defun)) (with-temp-buffer (insert msg) (indent-rigidly (point-min) (point-max) spc) (buffer-string))) (defun autofill (&rest msgs) (declare (indent defun)) (let ((fill-column 70)) (with-temp-buffer (dolist (line msgs) (when line (insert line))) (fill-region (point-min) (point-max)) (buffer-string)))) (defun columns (cols length strings) (declare (indent defun)) (with-temp-buffer (let ((sub-format (format "%%-%ds " (1- length))) col-format) (dotimes (i (1- cols)) (setq col-format (concat col-format sub-format))) (setq col-format (concat col-format "%s")) (while strings (insert (apply #'format col-format (let (args) (dotimes (i cols (nreverse args)) (push (if strings (pop strings) "") args)))) "\n"))) (buffer-string))) (defmacro wait-for! (var if-body &optional else-body) (declare (indent defun)) `(let ((i 0)) (while (and (not ,var) (< i 5)) (sleep-for 1) (setq i (1+ i))) (if ,var ,if-body ,else-body))) (defun color (code msg &rest args) (format "\e[%dm%s\e[%dm" code (apply #'format msg args) 0)) (defalias 'msg! #'message) (defmacro error! (&rest args) `(message (color 1 (color 31 ,@args)))) (defmacro warn! (&rest args) `(message (color 1 (color 33 ,@args)))) (defmacro success! (&rest args) `(message (color 1 (color 32 ,@args)))) (defmacro log! (&rest args) `(if doom-debug-mode (message (color 34 ,@args)))) (defmacro explain! (&rest args) `(message (indented 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)))) ;; --- start a'doctorin' -------------------------------------- (msg! "%s\nRunning Emacs v%s, commit %s" (color 1 "DOOM Doctor") (color 1 emacs-version) (if (executable-find "git") (shell-command-to-string "git rev-parse HEAD") "n/a")) (when (boundp 'system-configuration-features) (msg! "Compiled with:\n%s" (indented 2 (autofill system-configuration-features)))) (msg! "uname -a:\n%s" (indented 2 (autofill (shell-command-to-string "uname -a")))) (let (doom-core-packages doom-debug-mode) (condition-case ex (progn (let ((inhibit-message t) noninteractive) (load "~/.emacs.d/init.el" nil t)) (doom-initialize-packages) (doom|finalize) (success! "Attempt to load DOOM: success! Loaded v%s" doom-version) (when (executable-find "git") (msg! "Revision %s" (or (ignore-errors (let ((default-directory user-emacs-directory)) (shell-command-to-string "git rev-parse HEAD"))) "\n")))) ('error (warn! "Attempt to load DOOM: failed\n %s\n" (or (cdr-safe ex) (car ex)))))) (msg! "----\n") ;; --- is emacs set up properly? ------------------------------ (log! "test-emacs") (check! (version< emacs-version "25.1") (error! "Important: Emacs %s detected [%s]" emacs-version (executable-find "emacs")) (explain! "DOOM only supports >= 25.1. 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")))) ;; --- is the environment set up properly? -------------------- ;; windows? windows (log! "test-windows") (check! (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 (log! "test-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") (concat (getenv "HOME") "/.local/share")) "/fonts/")) ('darwin (concat (getenv "HOME") "/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, ignore this warning.")))))) ;; gnutls-cli & openssl (log! "test-gnutls") (cond ((executable-find "gnutls-cli")) ((executable-find "openssl") (let* ((output (shell-command-to-string "openssl ciphers -v")) (protocols (let (protos) (mapcar (lambda (row) (add-to-list 'protos (cadr (split-string row " " t)))) (split-string (shell-command-to-string "openssl ciphers -v") "\n")) (delq nil protos)))) (check! (not (or (member "TLSv1.1" protocols) (member "TLSv1.2" protocols))) (let ((version (cadr (split-string (shell-command-to-string "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 (check! 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!")))) (log! "test-tls") (cond ((not (string-match-p "\\_" system-configuration-features)) (warn! "Warning: You didn't install Emacs 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")))) ((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")) (condition-case-unless-debug ex (let (result) (let ((inhibit-message t)) (url-retrieve url (lambda (status &rest _) (setq result status)))) (wait-for! result (when (getenv "DEBUG") (success! "Verified %s" (nth 2 (split-string url "/")))) (signal 'timed-out url))) ('timed-out (error! "Timed out trying to contact %s" ex)) ('error (check! t (error! "Rejected %s" url) (explain! (pp-to-string ex)))))) (dolist (url '("https://self-signed.badssl.com" "https://wrong.host.badssl.com/")) (condition-case-unless-debug ex (let (result) (let ((inhibit-message t)) (url-retrieve url (lambda (status &rest _) (setq result status)))) (wait-for! result (check! t (warn! "Verified %s (this shouldn't happen!)" (nth 2 (split-string url "/"))) (explain! (pp-to-string result))) (signal 'timed-out url))) ('timed-out (error! "Timed out trying to contact %s" ex)) ('error (when (getenv "DEBUG") (success! "Rejected %s (a good thing!)" url) (explain! (pp-to-string ex)))))))) (t (error! "Nope!"))) ;; bsd vs gnu tar (log! "test-tar") (let ((tar-bin (or (executable-find "gtar") (executable-find "tar")))) (if tar-bin (check! (not (string-match-p "(GNU tar)" (shell-command-to-string (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")))) (check! t ; very unlikely (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.")))) ;; --- report! ------------------------------------------------ (when (getenv "DEBUG") (msg! "\n====\nHave some debug information:\n") (when (bound-and-true-p doom-modules) (msg! " + enabled modules:\n%s" (indented 4 (columns 3 23 (mapcar (lambda (x) (format "+%s" x)) (mapcar #'cdr (doom-module-pairs))))))) (when (and (bound-and-true-p doom-packages) (require 'package nil t)) (msg! " + enabled packages:\n%s" (indented 4 (columns 2 35 (mapcar (lambda (pkg) (let ((desc (cadr (assq pkg package-alist)))) (when desc (package-desc-full-name desc)))) (sort (mapcar #'car doom-packages) #'string-lessp)))))) (msg! " + byte-compiled files:\n%s" (indented 4 (columns 2 39 (let ((files (append (directory-files-recursively doom-core-dir ".elc$") (directory-files-recursively doom-modules-dir ".elc$")))) (or (and files (mapcar (lambda (file) (file-relative-name file doom-emacs-dir)) (nreverse files))) (list "n/a")))))) (msg! " + exec-path:\n%s" (indented 4 (columns 1 79 exec-path))) (msg! " + PATH:\n%s" (indented 4 (columns 1 79 (split-string (getenv "PATH") ":"))))) ;; (if (= doom-errors 0) (success! "Everything seems fine, happy Emacs'ing!") (message "\n----") (warn! "There were issues!") (unless (getenv "DEBUG") (msg! "\nHopefully these can help you find problems. If not, run this doctor again with DEBUG=1:") (msg! "\n DEBUG=1 make doctor\n") (msg! "And file a bug report with its output at https://github.com/hlissner/.emacs.d/issues")))