reangd (reangd) wrote in ru_emacs,
reangd
reangd
ru_emacs

Мой первый пакет для Emacs: multi-compile

(оригинал: ReanGDblog)
Я часто пишу заметки о Emacs, в формате: скопируйте вот эти строки подкорректируйте под себя, вставьте в конфиг, установите ещё вот этот плагин и т.п. Сегодня решил выйти на "новый уровень" и сделал полноценный пакет для Emacs и что бы нанести пользу максимально большой аудитории - разместил его в melpa. Конечно подготовить его несколько сложнее, чем просто пример конфига для блога, но для меня это своеобразное возвращение долгов за используемые мной open-source решения.


Начну с описания проблемы. Для компиляции проектов обычно используют стандартный модуль compile. Если вкратце то для его настройки нужно для интересующего вас режима переопределить локальную переменную compile-command командой, которую модуль compile выполнит при компиляции. Допустим для "c++-mode" она может быть вот такой:


make --no-print-directory -C 

Проблема подхода в том, что одной команды не хватает, часто помимо компиляции требуется ещё запуск тестов, компиляция в release\debug, запуск скомпилированного кода и т.п. Как вариант решения предлагают создавать для каждого режима свой набор функций, по одной на каждый подобный use case, где будет вызываться "compile" с нужными параметрами и потом вешать их на горячие клавиши. Это мягко говоря не удобно, поскольку все простые сочетания давно заняты, а запоминать 5-6 новых пальцедробительных не хочется.

Мне кажется удобным решением было бы при компиляции показывать меню со всеми вариантами действий, что собственно я и реализовал, выглядит это вот так:


Настройка для именно этого меню:


(require 'multi-compile)

(setq multi-compile-alist '(
    (rust-mode . (("rust-debug" . "cargo run")
                  ("rust-release" . "cargo run --release")
                  ("rust-test" . "cargo test")))
    ))


Ну и перед включением конечно нужно установить пакет, (предварительно подключив Melpa репозиторий, если его у вас нет):


M-x package-install multi-compile


В командах компиляции можно использовать шаблоны:


(setq multi-compile-alist '(
    (c++-mode . (("cpp-run" . "make --no-print-directory -C %make-dir")))
    (rust-mode . (("rust-debug" . "cargo run")
                  ("rust-release" . "cargo run --release")
                  ("rust-test" . "cargo test")))
    ))


Полный список реализованных шаблонов вот такой:

  • "%path" - "/tmp/prj/file.rs" - полный путь к открытому файлу.

  • "%dir" - "tmp/prj" - директория, где он лежит.

  • "%file-name" - "file.rs" - имя текущего файла.

  • "%file-sans" - "file" - имя без расширения.

  • "%file-ext" - "rs" - расширение файла.

  • "%make-dir" - "tmp" - просматривается вся иерархия директорий текущего файла и возвращается та, в которой лежит "Makefile".

Шаблоны файлов не обязательно задавать для всего режима, можно использовать регулярные выражения для имени файла:


(setq multi-compile-alist '(
    ("\\.txt\\'" . (("text-filename" . "echo %file-name")))
    (c++-mode . (("cpp-run" . "make --no-print-directory -C %make-dir")))
    (rust-mode . (("rust-debug" . "cargo run")
                  ("rust-release" . "cargo run --release")
                  ("rust-test" . "cargo test")))
    ))


Теперь по отображаемому меню: по умолчанию оно формируется через ido, но можно использовать helm, а для сторонников default конфигурации Emacs есть опция "default":


;;(setq multi-compile-completion-system 'ido)
(setq multi-compile-completion-system 'helm)
;; (setq multi-compile-completion-system 'default)


Ну и собственно вызов меню компиляции осуществляется через "M-x multi-compile-run". Какие-то горячие клавиши я добавлять не стал, каждый добавит свои по вкусу.

Если кому интересно, то исходники лежат на github и вот ссылка на сам пакет multi-compile на Melpa.
Tags: emacs
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 7 comments
удобная штука, спасибо
Есть ощущение, что не хватает второй вариативности - какой именно файл искать в иерархии.

Потому что помимо Makefile бывает еще makefile и GNUmakefile, не говоря уже обо всяких хаскелях, где ищется *.cabal. В связи с последним, насколько я понимаю, haskell-mode реализует свою отдельную haskell-compile. Там, впрочем, логика и того хитрее - если *.cabal найден, то выполняется одна команда (и выполняется там, где он найден), а если нет, то другая (и выполняется, что естественно, там, где файл). Но я бы сказал, что для C/C++ это тоже вполне актуально - если найден мейкфайл, то make там, а если не найден, то gcc тут :)

Я, кстати, несколько лет назад находил mode-compile.el, который реализовывал часть "выбор по major mode" (но не реализовывал меню). Так у него хотя бы для мейкфайла регексп есть...
Насчёт регекспов согласен, с ними наверное будет удобнее и сделать думаю не сложно, нужно будет добавить. А насчет особой логики, тут нужно подумать. Как насчет такого варианта: добавляем ещё один шаблон "%user_defined" и заводим локальную переменную, пусть ее пользователь переопределяет как в стандартном модуле "compile" и ее значение будет подставляться, вместо шаблона? В переменной можно уже будет на полноценном lisp написать любую логику.

besm6

October 6 2015, 23:19:28 UTC 2 years ago Edited:  October 6 2015, 23:21:21 UTC

Есть ощущение, что описанная "особая логика" на самом деле как раз обычная, просто compile слишком тупой.

А именно:

1. Есть механизм сборки проекта. Для начала он зависит от регекспа файла управления. Затем уже, при том же регекспе, идут возможные для него варианты сборки, определяемые юзером по вкусу, но с некоторыми осмысленными умолчаниями. Команда запускается там, где файл. (По опыту: вероятно, она должна начинаться с cd, поскольку другие способы смены директории emacs ловит плохо, и потом C-x ` не находит файл.)

2. Далее, есть механизм сборки одного файла. Его может хотеться применить и внутри проекта, но редко, поэтому по умолчанию при наличии файла управления проектом этот вариант не рассматривается, а рассматривается только как fallback, если файл управления не нашелся, но можно сконфигурировать (file variable) или через префикс включить. Этот механизм определяется major mode, а команда выполняется в директории текущего файла.

3. По major mode (или dir-local variable, или buffer-local) определяется, какие файлы управления стоит искать (список регекспов из п.1). Поднимаемся по директориям, в каждой ищем все файлы, подходящие к какому-нибудь регекспу. Как только нашли хотя бы один, останавливаемся и считаем эту директорию корнем проекта. Предлагаем все варианты для всех найденных файлов, плюс, если просили, вариант для сборки одного текущего файла. Если полученный набор содержит ровно один вариант, не предлагаем выбор из одного, а сразу его выполняем.

Если механизм сборки одного файла включен через file variable, то файл управления проектом не ищем. В остальном точно так же, вариантов сборки одного файла тоже может быть меню.

4. Бьютифаинг. Чтобы emacs не дергался зря на безопасность file variables и dir variables, стоит ввести еще один уровень косвенности: сами команды определяются в глобальном списке, а file variable содержит ключи этого списка. Тогда эти переменные можно пометить как safe (вариант: "safe if value satisfies symbolp", я такое в документации видел).

Ну а если нужна нестандартная логика, то вместо списка ключей — символ функции, которая возвращает варианты для меню. Опять же, для нестандартной (а равно и стандартной) логики можно в качестве собственно действия указать не команду ОС, а функцию.
Отлично, такое впечатление, что мысли по этому поводу зрели давно. Попробую в каком-то виде реализовать. Единственное - не понял насчет локальных переменных, я не помню, что бы хоть раз Emacs "дергался" на них, это поведение по умолчанию отключено?
В современном емаксе как раз по умолчанию включено. Но это именно file- и dir-local переменные. Те, которые устанавливаются в открываемом на редактирование файле. Просто buffer-local, установленная из хука, написанного в конфиге, не вызывает проверку на safety.

Вполне понятная мера безопасности.
Да, мысли зрели давно, даже пытался делать пару подходов. Но тоже давно. Как-то на практике M-x haskell-compile и так нетрудно набрать, а уж вытащить из истории и того проще. А поскольку многоязыковых зоопарков я довольно давно уже не программировал, то как-то так руки и не дошли сделать.

С меню вот идея интересная, мне она как-то в голову не приходила. Впрочем, у меня как-то и проектов таких сейчас нет, чтобы у них был отдельный релизный билд, отдельный development, и релизный при этом запускался достаточно часто, чтобы имело смысл запускать его из емакса.

Вот искалку мейкфайла вверх по директориям и запускалку найденного я писал. Но что-то я тогда емаксу объяснить не смог. То ли за мейком он по директориям ходил успешно, а за кем-то еще нет, то ли что... Вот, кстати, и сейчас - сделал haskell-build (он стартует в директории с cabal-файлом, ищет отсюда вверх по директориям stack.yaml, и если находит, то запускает stack, а не cabal). Изначально он стартовал где попало, а директорию с cabal-файлом получал параметром - "haskell-build %s". Сам по себе нормально работал, честно рассказывал, куда делает cd, но упс! емакс не находит файл с ошибкой. Поменял команду запуска на "cd %s; haskell-build" - заработало. Надо разбираться, что там у емакса в compilation-mode со слежением за директориями.