つーさにブログ

つうさにのメモ用ブログ

Emacsのcustom-theme-load-pathとload-theme

custom-theme-load-pathで気になったところ。

環境: Emacs 26.3

custom-theme-load-path変数

Emacsのzenburn-theme.el - つうさにメモブログの記事でEmacsにおけるカスタムテーマについて紹介したが、テーマ機能で用いられる変数としてcustom-theme-load-path変数がある。

custom-theme-load-pathディレクトリ名(もしくはt)を含むリストであり、このディレクトリ直下にないカスタムテーマをload-theme関数でロードすることはできない。つまり、カスタムテーマをEmacsで使うためにはなんとか-theme.elload-path下に置くのではなく、custom-theme-load-path下に置く必要がある。

しかし、package.elでインストールしたzenburnテーマはこの変数をなんやら設定しなくてもload-themeすることができた。これはどういうことか。

package.elのautoload処理

zenburn-theme.elを見ればわかるが、その設定はすでにzenburn-theme.el内に書かれている。そして、そのフォームにはautoloadクッキーがつけられている。やっていることは、load-theme-fileの値がnilでないとき、そのファイルが置かれているディレクトリをcustom-theme-load-pathに追加するというものだ。

;;;###autoload
(and load-file-name
     (boundp 'custom-theme-load-path)
     (add-to-list 'custom-theme-load-path
                  (file-name-as-directory
                   (file-name-directory load-file-name))))

package.elはパッケージのインストール時に、zenburn-theme.elと同じディレクトリにzenburn-theme-autoloads.el(autoloadsファイル)を作り、autoloadsファイルにこのフォームをコピーする。Emacs起動時に(package-initialize)1がautoloadsファイルをloadする2ため、Emacs起動後にはcustom-theme-load-pathにzenburn-themeのパスが追加された状態になっているのであった。

MELPAで配布されているカスタムテーマパッケージのほとんどは同様のテクニックを用いていて、ユーザにcustom-theme-load-pathを設定させる手間を無くしている。

custom-theme-load-pathが意図しない値になる

ところで、initファイルを以下のようにして、起動時にzenburnテーマをload-themeでロード(かつ有効化)するようにしたとする。

;; my init.el
(package-initialize)

;; ごにょごにょ

;; テーマ
(load-theme 'zenburn t)


;; ごにょごにょ

このとき、custom-theme-load-pathが意図しない値になっていることを発見した。

上記のようなinitファイルで起動したEmacscustom-theme-load-pathの値を見ると、明らかに意図しないディレクトリが追加されていたのだ(ユーザ名は<username>に置き換えて表記している)。

;; package.elでzenburnをインストールし、initファイルでzenburnテーマをロードした状態
custom-theme-load-path
("/Users/<username>/.emacs.d/" "/Users/<username>/.emacs.d/elpa/zenburn-theme-20190809.2224/" custom-theme-directory t)

"/Users/<username>/.emacs.d/"custom-theme-load-pathの要素なのは意図しないものである。なぜなら、initファイルにcustom-theme-load-pathをゴニョゴニョするコードは書いていないからだ。それに、そもそもcustom-theme-directory要素が~/.emacs.dを指すため、これは余計である。

なぜこうなってしまっているのかを調べた。

原因

原因はload-theme関数だった。

実はload-theme関数はload関数を使っていない。実装を見ればわかるが、やってることはtemp-bufferにファイルの内容をコピーして、それをeval-bufferしているのであった。

initファイルはEmacs起動時にloadされる。その際load-file-nameの値は~/.emacs.d/init.elになる。(load-theme 'zenburn t)loadを用いてファイルをロードするならload-file-nameにはまたそのファイルの絶対パスが入るが、上に書いたようにload-themeloadしていない。よってinitファイル内のload-themeでテーマファイルの中身が評価される際load-file-nameの値は~/.emacs.d/init.elであり、某テクニックのフォームが評価されて~/.emacs.dをcustom-theme-load-pathに追加してしまっていたのだ。

ちなみに、initファイルが~/.emacs.d/init.elではなく~/.emacsだった場合、同様のことが起こってcustom-theme-load-pathには~が追加される。

対策

謎が解けてスッキリした。

さてどうしたものかと思ったら、Emacs 27でのload-themeの実装 では、安全なテーマではload-themeloadするようになっていた!

ということで、Emacs 27以降では(load-theme 'zenburn t)loadするため、custom-theme-load-pathに意図せずinitファイルのディレクトリが追加されることはなくなった。めでたしめでたし。

終わり

Emacs 27は結構いろいろ変わってるようだ。リリースされるのが楽しみ。

補足

上の現象が起きるのは以下を全て満たすときである。

  • Emacs 26以下を使っている
  • initファイルで(load-theme '<theme-name> t)している (<theme-name>にはテーマ名が入る)(もちろんload-theme前にcustom-theme-load-pathが正しく設定されているものとする)
  • load-themeで評価されるカスタムテーマファイル(なんとか-theme.el)に某テクニックのフォームが書かれている

最後の条件を満たさないパッケージとして、doom-themesパッケージなどがある。doom-themesパッケージでは、カスタムテーマファイル自体にはcustom-theme-load-pathの設定をするフォームが書かれていないため、今回の記事で話したような意図しない現象は起こらない。


  1. initファイルに(package-initialize)が書かれている場合に限るが、普通これはinitファイルの先頭に書かれる。

  2. loadでファイルの中身が評価されるとき、load-file-name変数にはそのファイルの絶対パスが入ることに注意。