User:Schievel/emacs-as-an-ebuild-IDE

From Gentoo Wiki
Jump to:navigation Jump to:search

(Doom) Emacs as an Ebuild-IDE

app-misc/emacs is a very powerful and flexible editor. It is ideal for customizing it into an 'IDE' for Ebuilds. I use Doom Emacs, the Doom in Doom Emacs is a configuration framework for Emacs. It sets certain variables to sane defaults, provides coherent keybindings across packages and has it's own sets of packages that belong together bundled and installable with one command. Read more about Doom Emacs here.

Tip
Doom Emacs is run best with emacs in GUI mode, so install emacs with USE=gui and one of USE=gtk, USE=athena or USE=motif. Additionally Doom needs USE=xft to render fonts correctly.

If you have any other cool things, helper functions etc. regarding ebuild writing in Emacs, let me know on the discussion page.. I will add that to this page then.

ebuild-mode

Ebuild mode is the center package of ebuild development on Gentoo. It sits on top of sh mode.

Tip
Read the documentation of ebuild-mode with
user $bzcat /usr/share/info/ebuild-mode.info.bz2
. (bzcat comes from app-arch/bzip2) Or follow this link.

Installation

Ebuild-mode is available on Gentoos main repository as app-misc/ebuild-mode, so install it like any other package with

root #emerge -a app-emacs/ebuild-mode

.

Ebuild mode package brings in several other modes apart from the ebuild-mode core, which are devbook-mode, gentoo-newsitem-mode and glep-mode. I don't use them, and therefore this article will focus on ebuild-mode itself. Ebuild mode is then installed into /usr/share/emacs/site-lisp/ebuild-mode, so we have to tell emacs to load elisp from there:

CODE
(add-to-list 'load-path "/usr/share/emacs/site-lisp/ebuild-mode")

Put that into the config. There are several different ways how emacs loads configs. For Doom this is usually ~/.doom.d/config.el.

For the pkgdev and pkgcheck commands you would need dev-util/pkgdev and dev-util/pkgcheck.

Configuration

To make emacs start ebuild-mode every time an .ebuild-File is opened, we should add this to the config as well:

CODE
(add-to-list 'load-path "/usr/share/emacs/site-lisp/ebuild-mode")
(autoload 'ebuild-mode "ebuild-mode"
  "Major mode for Portage .ebuild and .eclass files." t)
(autoload 'ebuild-repo-mode "ebuild-mode"
  "Minor mode for files in an ebuild repository." t)
(autoload 'ebuild-repo-mode-maybe-enable "ebuild-mode")
(autoload 'devbook-mode "devbook-mode"
  "Major mode for editing the Gentoo Devmanual." t)
(autoload 'gentoo-newsitem-mode "gentoo-newsitem-mode"
  "Major mode for Gentoo GLEP 42 news items." t)
(autoload 'glep-mode "glep-mode"
  "Major mode for Gentoo Linux Enhancement Proposals." t)
(add-to-list 'auto-mode-alist '("\\.\\(ebuild\\|eclass\\)\\'" . ebuild-mode))
(add-to-list 'auto-mode-alist '("*.ebuild" . ebuild-mode))
(add-to-list 'auto-mode-alist '("/devmanual.*\\.xml\\'" . devbook-mode))
(add-to-list 'auto-mode-alist
       '("/[0-9]\\{4\\}-[01][0-9]-[0-3][0-9]-.+\\.[a-z]\\{2\\}\\.txt\\'"
               . gentoo-newsitem-mode))
(add-to-list 'auto-mode-alist '("/glep.*\\.rst\\'" . glep-mode))
(add-to-list 'auto-mode-alist
       '("/\\(package\\.\\(mask\\|unmask\\|use\\|env\
\\|license\\|properties\\|accept_\\(keywords\\|restrict\\)\\)\
\\|\\(package\\.\\)?use.\\(stable\\.\\)?\\(force\\|mask\\)\\)\\'"
               . conf-space-mode))
(add-to-list 'auto-mode-alist
       '("/make\\.\\(conf\\|}defaults\\)\\'" . conf-unix-mode))
(add-to-list 'interpreter-mode-alist '("openrc-run" . sh-mode))
(add-to-list 'interpreter-mode-alist '("runscript" . sh-mode))
(add-hook 'find-file-hook #'ebuild-repo-mode-maybe-enable)
(modify-coding-system-alist 'file "\\.\\(ebuild\\|eclass\\)\\'" 'utf-8)

Keybindings

By default ebuild mode uses conventional emacs keybindings, you can look them up by pressing SPC h m while being in ebuild-mode.

CODE
key             binding
---             -------
C-c C-b         ebuild-mode-all-keywords-unstable
C-c C-e         ebuild-run-command
C-c C-k         ebuild-mode-keyword
C-c C-n         ebuild-mode-insert-skeleton
C-c C-y         ebuild-mode-ekeyword
C-c -           ebuild-mode-insert-tag-line
C-c C-p         ebuild-mode-run-pkgdev
C-c C-q         ebuild-mode-run-pkgcheck

I remapped them so that they are more coherent with Doom Emacs:

CODE
(map! :localleader
      :map ebuild-mode-map
      "r" #'ebuild-run-command                ;; run a provided phase of the currently open ebuild
      "k" #'ebuild-mode-keyword               ;; change status of a single keyword e.g. from unstable to stable
      "s" #'ebuild-mode-insert-skeleton       ;; insert a skeleton of an ebuild to work from
      "u" #'ebuild-mode-all-keywords-unstable ;; mark all keywords unstable (~)
      "e" #'ebuild-mode-ekeyword              ;; run ekeyword on the current ebuild.
      "p" #'ebuild-mode-run-pkgdev            ;; run pkgdev command
      "c" #'ebuild-mode-run-pkgcheck          ;; run pkgcheck command
      "t" #'ebuild-mode-insert-tag-line)      ;; Insert a tag with name and email
Tip
When using helm and running ebuild-run-mode, you can easily jump to the history with C-o. This is very useful when trying things out and you need to run the same ebuild commands again and again.

For the tag line ebuild mode uses the variables user-full-name and user-email-address. They are present in the default config of Doom Emacs. If you want to use a different name or address for ebuilds than for the rest of your Emacs life (e.g. you have a dedicated mail address for Gentoo related work) set the variables ebuild-mode-full-name and ebuild-mode-mail-address.

ebuild-run-mode

Ebuild run mode provides the functionality to jump directly from an error in the output of ebuild-run-command to the location of the code snippet that produced the error.

Installation

Ebuild-run-mode is available in akaters ebuild repository. So activate that using

root #eselect repository enable akater

} (if using eselect repository, or run the corresponding layman command) then

root #emaint sync -r akater

and install ebuild-run-mode with

root #emerge -a app-emacs/ebuild-run-mode

.

Ebuild-run-mode is installed into /usr/share/emacs/site-lisp/ebuild-run-mode, so we need to tell emacs to load elisp from that path:

CODE
(add-to-list 'load-path "/usr/share/emacs/site-lisp/ebuild-run-mode")

Ebuild-run-mode sits on top of ebuild-mode, so only run that after ebuild-mode is loaded:

CODE
(eval-after-load 'ebuild-mode `(setq ebuild-log-buffer-mode 'ebuild-run-mode))

Now, when an ebuild was run using ebuild-run-command a buffer with the output will pop up. When there is an error, place the point (the cursor) on it and press return (bound to compile-goto-error) to jump to the line that produces the error in the packages code.

Little helpers

Put that in config.el/ init.el (for Doom that is ~/.doom.d/config.el).

Tabs instead of spaces in ebuild-mode

By convention ebuilds use Tabs, not spaces, for indentation. This is important, because pkgcheck will get upset when we use spaces or indentation. I usually use spaces in shell-Scripts, and since ebuilds are shell-scripts (for emacs at least they are), emacs would insert spaces when I press tab all the time. So I also put this into the config:

CODE
(after! ebuild-mode
  (setq indent-tabs-mode t))

Tag line everywere

Because this is really useful even outside of ebuilds (in patches for example) I changed the ebuild-mode-insert-tag-line a little and bound it to a key that is always available instead of binding it to a key in ebuild-mode-map.

CODE
(defun ebuilds/insert-tag-line ()
  "Insert a tag line with the user's name, e-mail address and date.
Format is \"# Larry The Cow <larry@gentoo.org> (2019-07-01)\"."
  (interactive)
  (beginning-of-line)
  (insert (format "%s%s <%s> (%s)\n"
		  (if comment-start (concat comment-start " ") "")
		  ebuild-mode-full-name ebuild-mode-mail-address
		  (format-time-string "%F" nil t))))

This makes sure that a comment sign is only put into that tagline when a comment sign is known. Before when the line was inserted into a .patch-file, it would put nil at the start, because .patch-files do not have comment signs. (instead only lines with a space in front are not comments.) You could also use user-full-name and user-mail-address here instead of their ebuild-mode counterparts. For me those are the same so it does not really matter.

I bound that to SPC T.

CODE
(map! :leader
        :desc "Insert my tagline"           "T" #'ebuilds/insert-tag-line)

Environment variables

To test ebuilds we often need to set environment variables like USE and CC for example. To set them in Emacs we use M-x setenv. To make things a little easier we can define little functions to set sets of environment variables and bind them to keys in ebuild-mode-map or call them with M-x. E.g. for Clang16 bugs I made this:

CODE
(defun ebuild-mode-set-CC-clang ()
    (interactive)
    (setenv "CC" "clang")
    (setenv "CXX" "clang++"))

(defun ebuild-mode-set-CC-gcc ()
    (interactive)
    (setenv "CC" "gcc")
    (setenv "CXX" "g++"))

Call scrub-patch from emacs

According to the Gentoo Dev Manual patches should meet certain QA conditions as well. Therefore we have a neat little program called scrub-patch which is part of app-portage/iwdevtools, so we should install that using

root #emerge -a app-portage/iwdevtools

This thing scrubs the patch thoroughly and all we have to do is insert a short description what this patch is for, references to bugs and our tag. I wrote a function for Emacs to call scrub-patch on the current buffer or on the marked file in dired, so we don't have to go to the shell that often. In dired we mark files with m then call M-x ebuilds/scrub-patch. Or, if we have a patch-file open in the currently open buffer, just M-x ebuilds/scrub-patch.

CODE
(defun ebuilds/scrub-patch (&optional @fname)
"Call scrub-patch on marked file in dired or on current file.
 Needs app-portage/iwdevtools.
 Got this from xah lee, modified a bit
 URL `http://xahlee.info/emacs/emacs/emacs_dired_open_file_in_ext_apps.html'"
  (interactive)
  (let* (
         ($file-list
          (if @fname
              (progn (list @fname))
            (if (string-equal major-mode "dired-mode")
                (dired-get-marked-files)
              (list (buffer-file-name)))))
         ($do-it-p (if (<= (length $file-list) 5)
                       t
                     (y-or-n-p "Scrub more than 5 files? "))))
    (when $do-it-p
        (mapc
         (lambda ($fpath)
           (shell-command
            (concat "scrub-patch -c -i " (shell-quote-argument $fpath))))  $file-list)
        (when (not (string-equal major-mode "dired-mode"))
            (revert-buffer)))))

Go to build directory of the current ebuild

Sometimes I run into the problem that I want to go to the $WORKDIR of a build. Normally we would either need to type that long path to it (/var/tmp/portage...) or we could run the ebuild and produce an error somehow, than use "ebuild-run-mode" from above to go from that build error to the file. But what I the build has no error or we just want to run the prepare phase to begin writing patches for the package. Therefore I wrote this little function. When it is called with an ebuild in the current buffer, it will open helm-find-file (or ivy or vertico or whatever is used) in the $WORKDIR (/var/tmp/portage/$CATEGORY/$PACKAGENAME-VERSION/work) of that ebuild. Of course at least the unpack-phase must have ran before. Be aware that this one only works in Doom, because we use the Doom specific function (doom/find-file-in-other-project) but if your Emacs has a similar function, tinker around with that function a bit. (Look for provided functions of helm, vertico or ido or whatever you use.) Also it is assumed that portages tempdir is /var/tmp/portage.

CODE
(defun ebuilds/goto-work-dir ()
"In a buffer with an open .ebuild file, go to the work dir of the build"
  (interactive)
  (let* '(currentpath (nreverse (split-string (buffer-file-name) "\/")))
    (if (string-match-p (regexp-quote ".ebuild") (car currentpath))
        (doom/find-file-in-other-project (concat
                                          "/var/tmp/portage/"
                                          (nth 2 currentpath)
                                          "/"
                                          (substring (car currentpath) 0 -7)
                                          "/work/"))
      (print "Current file does not look like an ebuild"))))

I use that so often, I bound it to a key in ebuild mode:

CODE
(map! :localleader
      :map ebuild-mode-map
      "w" #'ebuilds/goto-work-dir)      ;; go to work dir of ebuild

See also