aboutsummaryrefslogtreecommitdiffstats
path: root/emacs/package-based-configuration.org
blob: 1704157ef879b21d0ff4b3544bfa357041d46c85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#+TITLE: Package-based Configuration
#+SUBTITLE: Let's get crazy!
#+OPTIONS: num:nil
#+STARTUP: showall
#+HTML_LINK_UP: ..
#+HTML_LINK_HOME: ..

I've been using a configuration based on using =package.el= to manage loading, installing, and configuring packages.

* Motivation

I've been configuring Emacs since around 2008. My configuration has grown quite a bit since I first started. I've been working in multiple languages, I've used multiple completion methods, and I've tried multiple ways of keeping my configuration in check.

For a long time I was using a git repository that contained all of my configuration. I tried my best to set everything up with autoloads and everything, and it worked well once everything was installed, but installing it on a new machine was a bit of a pain.

#+begin_src dot :exports results :file git-config-workflow.svg
  digraph {
    graph [bgcolor="#111114"]
    node [color="#bfbfbf", fontcolor="#bfbfbf", fontname="sans-serif"]
    edge [color="#bfbfbf", fontcolor="#bfbfbf", fontname="sans-serif"]
    label="Installing my configuration on a new machine"
    fontcolor="#bfbfbf"
    fontname="sans-serif"

    node[shape="box", style="rounded"]
      "git clone";
      work;
    node[shape="parallelogram", style=""]
      make; "install package"; "run Emacs";
    node[shape="diamond", style=""]
      compiletime[label="missing\ncompile-time\npackage?"];
      runtime[label="missing\nrun-time\npackage?"];

    "git clone" -> make -> compiletime;
    "install package" -> make;
    compiletime -> "install package"[label="yes"];
    compiletime -> "run Emacs"[label="no"];
    "run Emacs" -> runtime;
    runtime -> "install package"[label="yes"];
    runtime -> "work"[label="no"];
    "work" -> runtime

    {rank=same; make "install package"}
  }
#+end_src

(Apologies if this is a terrible excuse for a flowchart, I don't usually make any) I would clone my git repository, run make to byte-compile my configurations. During byte-compilation it would complain about being unable to load a certain package. Then I'd start Emacs without my configuration and install the package, run make again until and repeat until make finished without errors. Then I'd start Emacs and it would complain about being unable to load other packages. I'd install this package, restart Emacs, and repeat /this/ process until Emacs stopped complaining. That didn't necessarily mean that everything was fine, I might run into other packages that were missing later on.

This was frustrating. I wanted something more flexible and easier to install.

* A New Configuration

I had this crazy thought one day that I could use =package.el= to manage my configuration /and/ do fun things like run all kinds of tests on my configuration to make sure that everything still works as I expect it to. Even just for my configuration this has helped me prevent having to spend time during work to fix certain issues.

My new installation method consists of evaluating a little bit of code:

#+begin_src emacs-lisp
  (add-to-list 'package-archives '("oni" . "https://ryuslash.org/elpa/"))
#+end_src

Once this is done I install =oni-package= so that all the repositories that I use get added, and then I can just install each configuration as I want it. If I'm writing Python on this machine, I can install =oni-python=. This will install Yasnippet, Company, Flycheck, Lsp, etc.

It does this just through regular package dependencies. In the =oni-python= package definition I have just any old =Package-Requires= line:

#+begin_src emacs-lisp
  ;; Package-Requires: (oni-yasnippet oni-company oni-fci oni-flycheck oni-hydra oni-lsp rainbow-delimiters reformatter traad)
#+end_src

The =oni-= prefixed packages are my configuration for those particular modes. Because Company and friends are big enough projects that they'll have their own configuration that I want to include whenever I use that package.

* Autoloading magic

In order to allow me to just download the package and have my configuration work, I abuse the autoloading mechanism. The autoload cookie will add whatever form you put after it into the package autoloads file. There are certain forms (like =defun=) that add a special form to the autoloads file which is just enough to get things set up if it's called, but most forms are just copied over.

#+begin_src emacs-lisp
  ;;;###autoload(with-eval-after-load 'python (require 'oni-python))
#+end_src