はじめに
この記事は「Emacs Advent Calendar 2019」の4日目の記事として書いたものです。
昨日は私の「leaf.elに依存したEmacs設定ファイル「init.el」をバイトコンパイルして爆速にする」でした。
まだ空きがあるので、ぜひ参加頂ければと思います!
去年のディレクトリ構造
去年、私は「Makefileで.emacs.dの理想的なディレクトリ構造を生成する話」を「Emacs Advent Calendar 2018」に投稿しました。
そこで提案したのは以下の構造でした。
.
├── Makefile
├── Makefunc.mk
├── init.el
├── conf/
│ ├── 00_leaf.el
│ ├── 01_core-emacs.el
│ ├── 10_standard-elisp.el
│ ├── 20_editor.el
│ ├── 30_utility.el
│ └── 40_major-mode.el
├── site-lisp/
│ ├── cort.el/
│ ├── dash.el/
│ ├── f.el/
│ ├── feather.el/
│ ├── leaf.el/
│ ├── org-mode/
│ ├── orglyth.el/
│ ├── s.el/
│ └── use-package/
├── snippets/
├── templete/
└── local/
├── conao-mixed-raw.el
├── conao-mixed.el
├── 22.1/
│ ├── build/
│ │ ├── conao-mixed.el -> ../../conao-mixed.el
│ │ └── conao-mixed.elc
│ │ snippets/ -> ../../snippets/
│ │ templete/ -> ../../templete/
│ └── site-lisp/
│ ├── cort.el/
│ ├── ...
│ └── use-pacakge/
├── 23.4/
├── 24.5/
├── 25.3/
└── 26.1/
また、自動生成される前はこの構造です。
.
├── Makefile
├── Makefunc.mk
├── init.el
├── conf/
│ ├── 00_leaf.el
│ ├── 01_core-emacs.el
│ ├── 10_standard-elisp.el
│ ├── 20_editor.el
│ ├── 30_utility.el
│ └── 40_major-mode.el
├── snippets/
└── templete/
今年のディレクトリ構造
さて、1年使ってみると考えも変わってきます。大きく変わったのは「モダンなinit.elはファイル分割する」という観点です。実際、ファイル分割によって見通しのよいinit.el群の管理と読み込み順の管理、さらには条件付きの設定が実現できていました。
しかし、leaf.elの開発によって、巨大なinit.elを一つだけもっていたとしてもimenuですぐにジャンプできますし、leaf.elには強力な条件分岐キーワードがあります。
よって、現在ではこのようなディレクトリ構造になっています
.
├── Makefile
├── Makefunc.mk
├── init.el
├── conf.el
├── site-lisp/
│ ├── cort-test.el/
│ ├── feather.el/
│ ├── orglyth.el/
│ └── leaf.el/
├── snippets/
├── templetes/
└── local/
├── 22.1/
├── 23.4/
├── 24.5/
├── 25.3/
├── 26.1/
├── 26.2/
└── 26.3/
├── Makefile
├── conf.el -> ../../conf.el
├── conf.elc
├── elpa/
├── snippets/ -> ../../snippets/
├── templetes/ -> ../../templetes/
└── site-lisp/ -> ../../site-lisp/
なお自動生成前はこのようなディレクトリ構造です。つまりこの構造がGitHubで見えています。
.
├── Makefile
├── Makefile-local.mk
├── init.el
├── conf.el
├── snippets/
└── templete/
このディレクトリ構造は以下の特徴を持っています。
- メジャーバージョンの異なる、複数のEmacsを同時に使用できる。
- (M)ELPAからダウンロード、コンパイルしたパッケージは
local/[ver]/elpa/
以下に置かれる。(異なるバージョン間で混同しない) - site-lispは全バージョンで同じディレクトリを使用する。(開発中のパッケージを置いてるので、いちいちコピーするのが面倒)
- Emacsパッケージが保存するセンシティブな情報を一括で.gitignoreできる。
去年は気づいていませんでいませんでしたが、特に最後の特徴は重要な意味を持つことに気付きました。データの永続化のために user-emacs-directory
(通常は $HOME/.emacs.d
)にファイルを保存するEmacsパッケージは多く存在しており、その情報は取り扱いに注意が必要なものもあります。
やばそう(普通他人に見せない)なものの代表例はこちらです。
- session
- .emacs.desktop
- tramp
- recentf
- jisyo (ddskk)
これらはおそらく .gitignore
漏れだと思います。(意図してgit管理している場合もあるのでしょうが。。)
重要なのは、「どのEmacsパッケージが、どんな名前で情報を保存するのかは分からない」という点です。
この状況でブラックリスト管理するのは無理があります。たしかに .gitignore
にもホワイトリスト管理する方法がありますが、簡単なのは 「 user-emacs-directory
を local
以下に変更して、 local
を .gitignore
する」という方法です。
これなら user-emacs-directory
以下にどれだけセンシティブな情報を保存したとしてもまとめてignoreされます。
Makefile
現在のMakefileを紹介します。これを見ると、去年よりは簡単になっています。
## Makefile
all:
XARGS := xargs $(shell if ( echo | xargs -r > /dev/null 2>&1 ); then echo "-r"; else echo ""; fi)
DEV_PKGS := leaf.el leaf-keywords.el orglyth.el cort-test.el seml-mode.el navbar.el
DEV_PKGS += feather.el feather-server.el leaf-browser.el solarized-emacs
DEV_PKGS += liskk.el
DEV_PKGS += phantom-inline-comment annotate.el point-history
DEV_PKGS += ivy-posframe helm-swoop flylint
EMACS_LST_RAW := 22.0 22.1 22.2 22.3
EMACS_LST_RAW += 23.0 23.1 23.2 23.3 23.4
EMACS_LST_RAW += 24.0 24.1 24.2 24.3 24.4 24.5
EMACS_LST_RAW += 25.0 25.1 25.2 25.3
EMACS_LST_RAW += 26.0 26.1 26.2 26.3
EMACS_LST_RAW += 27.0
EMACS_LST := $(shell echo $(EMACS_LST_RAW) | sed "s/ /\n/g" | $(XARGS) -I% sh -c "if type emacs-% >/dev/null 2>&1; then echo %; fi")
DIRS := local
export XARGS
export DEV_PKGS
##################################################
.PHONY: all clean
.PRECIOUS: site-lisp/%
all: $(DIRS) $(DEV_PKGS:%=site-lisp/%) $(EMACS_LST:%=local/%)
@$(call ECHO_YELLOW,"make job:all completed!!","\n","\n")
$(DIRS):
mkdir -p $@
##############################
site-lisp/%:
@mkdir -p $(@D)
$(if $(wildcard ~/dev/repos/$*), cd site-lisp; ln -sf ~/dev/repos/$*,\
$(if $(wildcard ~/dev/forks/$*), cd site-lisp; ln -sf ~/dev/forks/$*,\
cd site-lisp; git clone https://github.com/conao3/$*.git))
local/%: local conf.el Makefile-local.mk
mkdir -p $@
cp -f Makefile-local.mk $@/Makefile
cp -f conf.el $@/conf.el
$(MAKE) -C $@
##############################
clean-elc:
find site-lisp -follow -name "*.elc" | $(XARGS) rm -rf
clean:
-rm -rf local
@$(call ECHO_CYAN,"make job:clean completed!!","\n","\n")
## Makefile-local
all:
EMACS ?= emacs-$(shell basename $$(pwd))
XARGS ?=
SYMS := latex-math-preview-cache lice snippets templates conf.el
##################################################
all: conf.elc $(SYMS)
%.elc: %.el site-lisp
$(EMACS) -Q --batch -f batch-byte-compile $<
%.el: ../../%.el
$(SYMS):
ln -sf ../../$@
バイトコンパイルについて
昨日の記事、「leaf.elに依存したemacs設定ファイル「init.el」をバイトコンパイルして爆速にする」のまとめでも触れましたが、バイトコンパイルした後の elc
はメジャーバージョンをまたいでの互換性はありません。
つまり異なるメジャーバージョンのEmacsを同時に動かそうとすると elc
にコンパイルできませんでした。
しかし、 .emacs.d/init.el
はバージョン間で共通にしておいて、本体を別のファイルにしておけば、そのファイルはバイトコンパイルできることに気付きました。
ということでディレクトリ構成もそうなっていて、 .emacs.d/init.el
は全バージョン共通(バイトコンパイルしない)、conf.el
は .emacs.d/local/{{ver}}/conf.el
からシンボリックリンクを張り、それぞれのバージョンで conf.elc
にバイトコンパイルすることになっています。
よって、init.elは以下のように、 user-emacs-directory
を変更して、 conf.elc
を読み込むだけです。
;;; init.el
(prog1 "Change user-emacs-directory"
;; enable debug
(setq debug-on-error t
init-file-debug t)
;; you can run like 'emacs -q -l ~/hoge/init.el'
(when load-file-name
(setq user-emacs-directory
(expand-file-name (file-name-directory load-file-name))))
;; change user-emacs-directory
(setq user-emacs-directory
(expand-file-name
(format "local/%s.%s/"
emacs-major-version emacs-minor-version)
user-emacs-directory)))
(condition-case err
(load (expand-file-name "conf" user-emacs-directory))
(error
(error "You need `make' before running Emacs! Raw err: %s" err)))
conf.elの先頭ではleafの初期化を行って、あとは気のむくままにパッケージの設定を書いていきます。
注意点として、私はleafの開発版をsite-lispに持っているので、少しインストールコードが異なっています。
(eval-when-compile
(setq user-emacs-directory
(expand-file-name (file-name-directory default-directory))))
(eval-and-compile
(defvar package-archives)
(setq package-archives '(("org" . "https://orgmode.org/elpa/")
("melpa" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")))
(package-initialize)
(add-to-list 'load-path (locate-user-emacs-file "site-lisp/leaf.el"))
(add-to-list 'load-path (locate-user-emacs-file "site-lisp/leaf-keywords.el"))
(require 'leaf)
(leaf leaf
:config
(leaf leaf-keywords
:require t
:init
(leaf package
:init
(leaf *elpa-workaround
:when (or (version= "26.1" emacs-version)
(version= "26.2" emacs-version))
:custom ((gnutls-algorithm-priority . "NORMAL:-VERS-TLS1.3")))
:config
(leaf *elsa-workaround
:custom ((package-user-dir . "/home/conao/.emacs.d/local/26.3/elpa"))))
(leaf hydra
:ensure t
:config
(leaf *hydra-posframe
:when (version<= "26.1" emacs-version)
:when window-system
:custom `((hydra-hint-display-type . 'posframe)
(hydra-posframe-show-params
. ',(list :internal-border-width 1
:internal-border-color "red"
:poshandler 'posframe-poshandler-window-bottom-right-corner)))))
(leaf el-get
:ensure t
:init (unless (executable-find "git")
(warn "'git' couldn't found. el-get can't download any packages"))
:custom ((el-get-git-shallow-clone . t)))
(leaf diminish
:ensure t)
(leaf cus-edit
:custom `((custom-file . ,(locate-user-emacs-file "custom.el"))))
:config
(leaf-keywords-init)))
(leaf *libraries
:config
(leaf dash :ensure t :require t)
(leaf ts :ensure t :require t)
(leaf buttercup :ensure t :require t)
(leaf ert-async :ensure t :require t)))
;; あとはパッケージの設定を書く
(leaf ...)
まとめ
色んなバージョンのEmacsを使うために、このディレクトリ構造にしましたが、思いがけず不要なファイルをgit管理しない構造になっていたことを発見しました。
この構造にメリットを見いだす人はわずかだとは思いますが、備忘録として残しておきます。
なお、Emacs-27からは early-init.el
のロード追加されており、クラスタは色めきあっています。さらにEmacs-27からXDG Base Directory Specificationにのっとり、 .config/emacs/init.el
を最優先で読み込むようになっているようです。(該当コミット / NEWS)
(tomoyaさんが最初の報告、コミットとNEWSはelimさんが探してくだだり、emacs-jpのSlackで共有して頂いた情報です。)
ArchのWikiでもEmacsは「ハードコードされているソフトウェア」にリストされていますが、修正が必要になるかもしれません。
これらのupstreamの修正により私のdotfilesも変更を迫られるでしょうが、過度に保守的にならず、現在も修正が加えられているというのは喜ばしいことです。
Emacs-27ももうすぐリリースされるようです(tak.kunihiroさんに共有して頂いた情報)。Emacsコントリビュータの人に感謝しつつ、いつか本体に貢献したいなと思っています。
最後になりますが、Patreonでご支援を頂ける方を募集しています。普段はleafなどのElispパッケージなどを中心にOSS活動をしつつ、学生をしています。ぜひよろしくお願いします!