I run two instances of Emacs on my MacOS laptop ... one for "work" and another for IRC, et.al. This explains some of what I'm doing.
273 lines
11 KiB
Org Mode
273 lines
11 KiB
Org Mode
#+TITLE: My Emacs Bootstrap
|
||
#+AUTHOR: Howard X. Abrams
|
||
#+DATE: 2021-10-08
|
||
|
||
A literate programming file for bootstraping my Emacs Configuration.
|
||
|
||
#+begin_src emacs-lisp :exports none
|
||
;;; bootstrap.el --- file for bootstraping my Emacs Configuration
|
||
;;
|
||
;; © 2021-2022 Howard X. Abrams
|
||
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
||
;; See http://creativecommons.org/licenses/by/4.0/
|
||
;;
|
||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||
;; Maintainer: Howard X. Abrams
|
||
;; Created: October 8, 2021
|
||
;;
|
||
;; This file is not part of GNU Emacs.
|
||
;;
|
||
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||
;; ~/other/hamacs/bootstrap.org
|
||
;; And tangle the file to recreate this one.
|
||
;;
|
||
;;; Code:
|
||
#+end_src
|
||
* Introduction
|
||
This file contains all the variable definitions and library loading for the other files in my project.
|
||
** Straight Package Installer
|
||
I'm going to be installing everything using the [[https://github.com/raxod502/straight.el#getting-started][straight.el]] for package installation and management. Before I can /tangle/ these files, I need =straight= to grab the latest =org=, so the following initialization code is actually in [[file:initialize][initialize]], but if you are reading this online, configuring =straight= amounts to the following:
|
||
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defvar bootstrap-version)
|
||
|
||
(let ((bootstrap-file
|
||
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
|
||
(bootstrap-version 5))
|
||
(unless (file-exists-p bootstrap-file)
|
||
(with-current-buffer
|
||
(url-retrieve-synchronously
|
||
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
|
||
'silent 'inhibit-cookies)
|
||
(goto-char (point-max))
|
||
(eval-print-last-sexp)))
|
||
(load bootstrap-file nil 'nomessage))
|
||
#+end_src
|
||
|
||
To get the Straight project working with =use-package=:
|
||
#+begin_src emacs-lisp :tangle no
|
||
(straight-use-package 'use-package)
|
||
#+end_src
|
||
|
||
While the above code enables the =:straight t= extension to =use-package=, let's have that as the default:
|
||
#+begin_src emacs-lisp :tangle no
|
||
(use-package straight
|
||
:custom (straight-use-package-by-default t
|
||
straight-default-vc 'git))
|
||
#+end_src
|
||
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
||
|
||
** OS Path and Native Compilation
|
||
Helper functions to allow code for specific operating systems:
|
||
#+begin_src emacs-lisp
|
||
(defun ha-running-on-macos? ()
|
||
"Return non-nil if running on Mac OS systems."
|
||
(equal system-type 'darwin))
|
||
|
||
(defun ha-running-on-linux? ()
|
||
"Return non-nil if running on Linux systems."
|
||
(equal system-type 'gnu/linux))
|
||
#+end_src
|
||
|
||
With the way I start Emacs, I may not have the =PATH= I /actually/ use (from the shell) available, so we'll force it (code taken [[https://www.emacswiki.org/emacs/ExecPath][from here]]):
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun set-exec-path-from-shell ()
|
||
"Set up Emacs' `exec-path' and PATH environment variable to match
|
||
that used by the user's shell.
|
||
|
||
The MacOS, where GUI apps are not started from a shell, requires this."
|
||
(interactive)
|
||
(let* ((path-from-shell (shell-command-to-string "echo $PATH"))
|
||
(trimmed-path (replace-regexp-in-string (rx (zero-or-more space) eol)
|
||
"" path-from-shell))
|
||
(in-fish? (string-match (rx "fish" eol)
|
||
(shell-command-to-string "echo $SHELL")))
|
||
(separator (if in-fish? " " ":"))
|
||
(env-path (if in-fish? (replace-regexp-in-string " " ":" trimmed-path) trimmed-path)))
|
||
(message "PATH=%s" path-from-shell)
|
||
(setenv "PATH" env-path)
|
||
(setq exec-path (split-string trimmed-path separator))))
|
||
#+end_src
|
||
|
||
Clear up a Mac-specific issue that sometimes arises since I'm switching to [[http://akrl.sdf.org/gccemacs.html][native compilation project]], as the =Emacs.app= that I use doesn't have its =bin= directory, e.g. =Emacs.app/Contents/MacOS/bin=:
|
||
|
||
#+begin_src emacs-lisp
|
||
(when (ha-running-on-macos?)
|
||
(add-to-list 'exec-path "/usr/local/bin")
|
||
(add-to-list 'exec-path (concat invocation-directory "bin") t))
|
||
#+end_src
|
||
|
||
Getting tired off all the packages that I load spewing a bunch of warnings that I can't do anything about:
|
||
#+begin_src emacs-lisp
|
||
(when (and (fboundp 'native-comp-available-p)
|
||
(native-comp-available-p))
|
||
(setq native-comp-async-report-warnings-errors nil
|
||
native-comp-deferred-compilation t))
|
||
#+end_src
|
||
** GNU Pretty Good Privacy
|
||
On Linux, GPG is pretty straight-forward, but on the Mac, I often have troubles doing:
|
||
#+begin_src sh
|
||
brew install gpg
|
||
#+end_src
|
||
Next, on every reboot, start the agent:
|
||
#+begin_src sh
|
||
/usr/local/Cellar/gnupg/2.3.6/bin/gpg-agent --daemon
|
||
#+end_src
|
||
|
||
Since =brew link gpg= doesn’t always work, this helper function may find the executable:
|
||
#+begin_src emacs-lisp
|
||
(defun executable (path)
|
||
"Return PATH if executable, see `file-executable-p'."
|
||
(let ((epath (first (file-expand-wildcards path))))
|
||
(when (file-executable-p epath) epath)))
|
||
|
||
(use-package epa-file
|
||
:straight (:type built-in)
|
||
:custom (epg-gpg-program (or (executable "/usr/local/Cellar/gnupg/*/bin/gpg")
|
||
(executable "/usr/local/bin/gpg")
|
||
(executable "/usr/local/opt/gpg")
|
||
(executable "/usr/bin/pgp")))
|
||
:config (epa-file-enable))
|
||
#+end_src
|
||
** Basic Libraries
|
||
The following packages come with Emacs, but seems like they still need loading:
|
||
#+begin_src emacs-lisp
|
||
(use-package cl-lib
|
||
:straight (:type built-in)
|
||
:init (defun first (elt) (car elt))
|
||
:commands (first))
|
||
|
||
(require 'subr-x)
|
||
#+end_src
|
||
Ugh. Why am I getting a missing =first= function error? I define a simple implementation, that the CL library will overwrite ... at some point.
|
||
|
||
While most libraries will take care of their dependencies, I want to install /my dependent libraries/, e.g, [[https://github.com/magnars/.emacs.d/][Magnar Sveen]]'s Clojure-inspired [[https://github.com/magnars/dash.el][dash.el]] project:
|
||
#+begin_src emacs-lisp
|
||
(use-package dash)
|
||
#+end_src
|
||
Sure this package is essentially syntactic sugar, and to help /share/ my configuration, I attempt to use =thread-last= instead of =->>=, but, I still like it.
|
||
|
||
The [[https://github.com/magnars/s.el][s.el]] project is a simpler string manipulation library that I (and other projects) use:
|
||
#+begin_src emacs-lisp
|
||
(use-package s)
|
||
#+end_src
|
||
|
||
Manipulate file paths with the [[https://github.com/rejeep/f.el][f.el]] project:
|
||
#+begin_src emacs-lisp
|
||
(use-package f)
|
||
#+end_src
|
||
** My Code Location
|
||
Much of my more complicated code comes from my website essays and other projects. The destination shows up here:
|
||
#+begin_src emacs-lisp
|
||
(add-to-list 'load-path (f-expand "~/.emacs.d/elisp"))
|
||
#+end_src
|
||
|
||
Hopefully, this will tie me over while I transition.
|
||
** Emacs Server Control
|
||
Sure the Emacs application will almost always have the =server-start= going, but I need to control it (because I often have two instances running on some of my machines). What /defines/ the Emacs instance for work changes ... often:
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun ha-emacs-for-work? ()
|
||
"Return non-nil when the Emacs application's location matches as one for work.
|
||
This is the `emacs-plus@28' app that I have built with
|
||
the native-comp model, but I reserve the right to change this."
|
||
(and (f-dir? "~/work")
|
||
;; (string-match "emacs-plus@28" exec-directory)
|
||
(string-match "emacs-plus@29" exec-directory)))
|
||
#+end_src
|
||
|
||
And now start the server with an appropriate tag name:
|
||
#+begin_src emacs-lisp
|
||
(if (not (ha-emacs-for-work?))
|
||
(setq server-name "personal")
|
||
(setq server-name "work")
|
||
(when (ha-running-on-macos?)
|
||
(set-exec-path-from-shell)))
|
||
|
||
(server-start)
|
||
#+end_src
|
||
* Load the Rest
|
||
The following loads the rest of my org-mode literate files. I add new filesas they are /ready/:
|
||
#+begin_src emacs-lisp
|
||
(defvar ha-hamacs-files (flatten-list
|
||
`("ha-private.org"
|
||
"ha-config.org"
|
||
,(when (display-graphic-p)
|
||
"ha-display.org")
|
||
"ha-org.org"
|
||
,(when (display-graphic-p)
|
||
"ha-org-word-processor.org")
|
||
"ha-org-clipboard.org"
|
||
"ha-capturing-notes.org"
|
||
"ha-agendas.org"
|
||
"ha-passwords.org"
|
||
"ha-remoting.org"
|
||
"ha-programming.org"
|
||
"ha-programming-elisp.org"
|
||
"ha-programming-python.org"
|
||
,(if (ha-emacs-for-work?)
|
||
'("ha-org-sprint.org" "ha-work.org")
|
||
;; Personal Editor
|
||
'("ha-org-journaling.org"
|
||
"ha-irc.org"
|
||
"ha-org-publishing.org"
|
||
"ha-email.org"
|
||
"ha-aux-apps.org"
|
||
"ha-feed-reader.org"))))
|
||
"List of org files that complete the hamacs project.")
|
||
#+end_src
|
||
|
||
We can test/debug/reload any individual file, via:
|
||
#+begin_src emacs-lisp
|
||
(defun ha-hamacs-load (file)
|
||
"Load or reload an org-mode FILE containing literate Emacs configuration code."
|
||
(interactive (list (completing-read "Org file: " ha-hamacs-files)))
|
||
(let ((full-file (f-join hamacs-source-dir file)))
|
||
(when (f-exists? full-file)
|
||
(org-babel-load-file full-file))))
|
||
#+end_src
|
||
|
||
And we can now load everything:
|
||
#+begin_src emacs-lisp
|
||
(defun ha-hamacs-reload-all ()
|
||
"Reload our entire ecosystem of configuration files."
|
||
(interactive)
|
||
(dolist (file ha-hamacs-files)
|
||
(unless (equal file "bootstrap.org")
|
||
(ha-hamacs-load file))))
|
||
#+end_src
|
||
|
||
And do it:
|
||
#+begin_src emacs-lisp
|
||
(ha-hamacs-reload-all)
|
||
#+end_src
|
||
|
||
Once we have loaded /my world/, let’s add every other Org file in the project to the list, so that I can load newly created files that I don’t want to commit:
|
||
#+begin_src emacs-lisp
|
||
(setq ha-hamacs-files
|
||
(->> (rx ".org" string-end)
|
||
(directory-files "~/other/hamacs" nil)
|
||
(append ha-hamacs-files)
|
||
(--filter (not (string-match (rx "README") it)))
|
||
(-uniq)))
|
||
#+end_src
|
||
* Technical Artifacts :noexport:
|
||
Let's provide a name so we can =require= this file:
|
||
#+begin_src emacs-lisp :exports none
|
||
(provide 'bootstrap)
|
||
;;; bootstrap.el ends here
|
||
#+end_src
|
||
|
||
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
||
|
||
#+DESCRIPTION: A literate programming file for bootstrapping my environment.
|
||
|
||
#+PROPERTY: header-args:sh :tangle no
|
||
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
||
|
||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|