О, use-package!

Это пакет, который буквально изменил всё. Если раньше init.el заполняли кодом в императивном стиле, то с появлением use-package очень многие пользователи Emacs стали описывать свои настройки в декларативном стиле.

Пакет оказал настолько большое влияние на управление настройками Emacs, что начиная с Emacs 29 use-package стал встроенным (builtin), т. е. уже не требует установки вручную (хотя его всё ещё можно установить или обновить из других источников).

На сайте документации GNU есть целый раздел, посвящённый use-package, и вроде бы всё хорошо, но... Я бы тогда не написал эту статью.

Неправильное использование :init и :config

Первая ошибка, которую делают пользователи use-package — путают :init и :config. Ссылку на документацию я дал выше, можете почитать в оригинале. Здесь же расскажу своими словами.

В блоке :init нужно добавлять код, который выполняется до загрузки пакета.

Допустим, у вас есть такой фрагмент кода:

(use-package anzu
  :init
  (global-anzu-mode 1))

Этот код эквивалентен такому коду в императивном стиле:

(global-anzu-mode 1)
(require 'anzu)

Как видите, функция из модуля вызывается до его загрузки. Надо ли говорить, что этот код не будет работать?

Правильное решение — размещение вызовов функций пакета внутри блока :config:

(use-package anzu
  :config
  (global-anzu-mode 1))

Злоупотребление use-package-always-ensure

В use-package есть такая встроенная функциональность, как автоматическая установка неустановленных пакетов. Можно включить глобальную настройку use-package-always-ensure, и тогда use-package будет пытаться установить все неустановленные пакеты автоматически:

(require 'use-package)
(custom-set-variables '(use-package-always-ensure t))

Всё хорошо, если вы используете Emacs только на локальном компьютере или одних и тех же типовых окружениях: дома Ubuntu 20.04 LTS и на работе на всех машинах тоже Ubuntu 20.04 LTS. В этом случае вы будете очень редко сталкиваться с тем, что use-package не может установить пакет из-за того, что автор пакета повысил требования к версии Emacs.

В этом случае каждое выполнение вашего init.el будет завершаться ошибкой установки пакетов.

Допустим, пакет markdown-mode.el с некоторых пор стал требовать Emacs 27.1 или более новый.

Наивное и неработающее решение (напоминаю, use-package-always-ensure t) c :if, :when или :unless:

(use-package ace-window
  :when (or ((> emacs-major-version 27)
           (and (= emacs-major-version 27)
                (>= emacs-minor-version 1)))))

Проблема в том, что весь код настройки пакета выполняется после его установки, и :if процессу установки никак не мешает:

The :if, :when, and :unless keywords predicates the loading and initialization of packages. They all accept one argument, an Emacs Lisp form that is evaluated at run-time.

:requires вам тоже не поможет. Код в этом блоке тоже выполняется после установки пакета.

Может, тогда не будем присваивать use-package-always-ensure значение t, а просто напишем в блоке :ensure нужное выражение? Давайте попробуем!

Исходные условия:

  • Emacs 29.3.

  • use-package v2.4.5.

Добавим в init.el такой код:

(use-package all-the-icons
  :ensure (> emacs-major-version 27))

Запускаем init.el с помощью eval-buffer... Ой!

⛔ Error (use-package): Failed to parse package all-the-icons: use-package: :ensure wants an optional package name (an unquoted symbol name), or (<symbol> :pin <string>)

Что же делать? Очевидно, проверять версию Emacs и другие требования до вызова use-package. Если у вас много пакетов, которые не работают в определённых окружениях, целесообразно написать функцию типа такой:

;; Возвращает t, если версия Emacs больше или равна указанной.
(defun emacs-version-not-less-than (major minor)
  "True when Emacs version is not less than MAJOR and MINOR versions."
  (or
    (> emacs-major-version major)
    (and (= emacs-major-version major)
      (>= emacs-minor-version minor))))

Использовать её нужно так:

(when (emacs-version-not-less-than 27 1)
  (use-package buffer-env
    :ensure t
    :pin "gnu"
    :defer t
    :after (files)
    :hook ((
             hack-local-variables
             comint-mode
             ) . buffer-env-update)))

(when (emacs-version-not-less-than 28 1)
  (use-package denote
    :pin "gnu"
    :ensure t
    :custom
    (denote-directory "~/Документы/Notes/" "Каталог для хранения заметок.")))

Пакет buffer-env будет установлен только если версия Emacs выше или равна 27.1, а пакет denote — 28.1.

Комментарии (0)