10
3

More than 1 year has passed since last update.

新しいパッケージマネージャー 【Elpaca】

Last updated at Posted at 2023-03-30

背景

これまで様々なパッケージマネージャが登場していましたが、現在use-packageを除くとpackage.elstraight.elが人気のようです。

Emacs User Survey — 2022
Emacsパッケージマネージャ決定版:これからのパッケージ管理はstraight.elで決まり!

私も以下の点からstraight.elを愛用していました。

  • package.elは一度インストールしたパッケージは、ちゃんと削除しないとロードされてしまう。
  • melpaがたまに壊れていることがあり、アップデートを行うとEmacsが動かなくなることがある。

しかし、straight.elはインストールとアップデートに時間がかかり、またWindowsだとstraight-pull-allのあと自動でビルドしてくれないのが不満に感じていました。

このpackage.elstraight.elの不満を解消する方法はないかと思っていたところ、elpacaというパッケージを見つけました。

elpacaとは

elpacastraight.elと同様にgit cloneでパッケージをインストール・ビルドを行いますが、非同期で処理することで高速にインストールされるようになっています。
straight.elよりも高速にインストール・更新が可能になっています。

【Windows】シンボリックリンクを有効にする

Elpacaではシンボリックリンクが必須となっていますので、下記の方法を使用する必要があります。

方法1:開発者モードを利用する

Emacs30を利用すると特別な設定もなく開発者モードを有効にするだけでシンボリックリンクが使えましたので、私はEmacs30+開発者モードにしています。

Scoopのインストール

$ Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
$ irm get.scoop.sh | iex

Emacs30のインストール(Emacs Wiki

$ scoop bucket add kiennq-scoop https://github.com/kiennq/scoop-misc
$ scoop install emacs-k

方法2:管理者権限で実行する

Windowsでシンボリックリンク有効にするには管理者権限で実行する必要があります。
アプリケーションを管理者権限で実行する方法(Windows 10/8/7/Vista)

elpacaのインストール

下記のコードをinit.elに追加して、elpacaをインストールします。

init.el
(defvar elpaca-installer-version 0.5)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil
                              :files (:defaults (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                 ((zerop (call-process "git" nil buffer t "clone"
                                       (plist-get order :repo) repo)))
                 ((zerop (call-process "git" nil buffer t "checkout"
                                       (or (plist-get order :ref) "--"))))
                 (emacs (concat invocation-directory invocation-name))
                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                 ((require 'elpaca))
                 ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))

また一度に多くのパッケージをインストール・アップデートする場合、too many open filesというエラーが出る可能性が高いので、下記のコードを追加しキューのリミットを設定しておきます。
※端末のスペックに応じて変更してください)

init.el
(setq elpaca-queue-limit 15)

パッケージのインストール

elpacaqueueに溜まったパッケージを非同期にインストール・ロードしていきます。
下記のように記述するとqueueにパッケージや処理が溜まっていきます。

init.el
(elpaca org)

(elpaca xxx
  (setq xxx-yyy nil)
  (hogehoge)
  (fogafoga))

また、use-packageも下記のようにインストールすることで利用できるようになります。

init.el
;; Install use-package support
(elpaca elpaca-use-package
  ;; Enable :elpaca use-package keyword.
  (elpaca-use-package-mode)
  ;; Assume :elpaca t unless otherwise specified.
  (setq elpaca-use-package-by-default t))

elpaca-wait

elpaca-waitはパッケージのインストール・ロードが完了するまで、処理を待ってくれます。use-package等、init.elの構成に使用しているパッケージはelpaca-waitを使用してあらかじめインストールしておきます。

init.el
;; Install use-package support
(elpaca elpaca-use-package
  ;; Enable :elpaca use-package keyword.
  (elpaca-use-package-mode)
  ;; Assume :elpaca t unless otherwise specified.
  (setq elpaca-use-package-by-default t))

(elpaca general)

;; Block until current queue processed.
(elpaca-wait)

elpaca-process-queues

elpaca-process-queuesはqueueに溜まっているパッケージの設定を非同期に実行してくれるコマンドです。公式では下記のようにafter-init-hookのあとに実行するようになっています。

init.el
(add-hook 'after-init-hook #'elpaca-process-queues)

しかし、dashboard等のパッケージを利用している場合、上記のタイミングだと都合が悪い場合があるので、私はinit.elの末尾に直接elpaca-process-queuesを呼び出しています。

init.el
;;; package --- Summary
;;; Commentary:
;;; Code:

;; 1.elpacaのインストール
;; 2.init.elの設定に使用するパッケージ
(elpaca-wait)

;; 3.各設定の記述
(use-package hogehoge)
(use-package fugafuga

;; 3で記述された処理を非同期に実行
(elpaca-process-queues)

(provide 'init)
;;; init.el ends here

設定例

init.el
;;; package --- Summary
;;; Commentary:
;;; Code:

(defvar elpaca-installer-version 0.5)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil
                              :files (:defaults (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                 ((zerop (call-process "git" nil buffer t "clone"
                                       (plist-get order :repo) repo)))
                 ((zerop (call-process "git" nil buffer t "checkout"
                                       (or (plist-get order :ref) "--"))))
                 (emacs (concat invocation-directory invocation-name))
                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                 ((require 'elpaca))
                 ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (load "./elpaca-autoloads")))
;; init.elの最後に実行
;; (add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))

;; queueの上限設定
(setq elpaca-queue-limit 15)

;; use-packageなどの設定に使用するパッケージを記述
(elpaca elpaca-use-package
  (elpaca-use-package-mode)
  (setq elpaca-use-package-by-default t))
(elpaca general)

;; 設定に利用するパッケージをセットアップ
(elpaca-wait)

;;各パッケージの設定を記述
(use-package evil
  :config
  (evil-mode +1))

(use-package vertico
  :config
  (vertico-mode +1))

;; 設定を非同期でロード
(elpaca-process-queues)

(provide 'init)
;;; init.el ends here

実際の様子

elpacaはUIも提供してくれているので、進捗状況を一覧で確認することができます。

qiita-output.gif

注意点

【2023/03/30】init.elをバイトコンパイルしてinit.elcの状態でelpacaを呼び出すとエラーがでます。修正されるまでバイトコンパイルせずに運用したほうが良さそうです。

所感

最初はエラーが色々出ていたり、導入に手こずっていましたが、
慣れると非常に快適になりました。

開発が活発に行われているので、今後更に機能が改善されることを楽しみにしています。

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3