Archives 2025

On Emacs Theme Precedence

I’ve written before about themes in Emacs, and in particular about using them for something other than just colors. And naturally, you can have more than one theme active at the same time. So what happens when you have a variable set in two active themes?

The list of enabled themes is the custom-enabled-themes variable. And rather intuitively, earlier ones override later ones. Normally, (load-theme 'mytheme) prepends mytheme to custom-enabled-themes, so the settings you just loaded take effect, and last until you load some other settings on top of them, which makes sense.

There’s one twist, though: the settings you change through the “Customize Emacs” system also count as a theme, at least in some ways. If you look at the property list for a symbol defined in two themes, you might see something like

(symbol-plist 'my-var)

(theme-value
 ((user 'customized-value)
  (second-theme 'second-value)
  (first-theme 'first-value))
 saved-value 'customized-value
 ...)

This tells us that two themes, first-theme and second-theme, have both defined my-var, and set it to first-value and second-value respectively. The symbol’s value is resolved in the order in which it appears here, so second-value overrides first-value because it was loaded later.

But there’s that third user entry, which comes first, and thus overrides the other values: that comes from (customize-variable 'my-var). I find this counterintuitive, since I think of the customization variables as the defaults, to be overridden by themes.

The solution I came up with this was to define my own theme, arensb, to hold all of the settings that might be overridden by other themes, and to load it first. Actually, I already had this theme lying around to store my favorite colors and faces, but themes can hold other values, so it made sense to reuse it.

Return values from Ansible roles

Ansible roles aren’t functions, so they don’t actually return values. But what if they could?

I recently wrote a role to update a software package, which involved asking some questions:

  1. What’s the latest version?
  2. Which version, if any, is currently installed?

I wanted my role to be modular(ish), so it made sense to outsource those questions to other roles. But how would they return their results? I tried adding register: latest_version to the role invocation, but that didn’t work.

But you can set facts inside a role:

# ... Figure out installed version of Foo package...
- name: Return a value
  set_fact:
    installed_version: "1.2.3"

and if you know that this role sets installed_version, you can treat that as a return value, just as if it were a task with register: installed_version. Also, the fact that it sets, installed_version, is local to the client, so each host will have a copy in hostvars[ansible_host]. This makes sense: after all, you might want to know which of your machines are out of date.

The problem, though, is that installed_version is “global” to each client. So if you’re checking the installed version of multiple packages

- name: Upgrade if necessary
  roles:
    - role: check_version
      package: Foo
    - role: check_version
      package: Bar
    - role: check_version
      package: Baz
  tasks:
    - debug: var=installed_version

how do you know which package’s installed_version you’re dealing with?

The way to fix this is to realize that you can use string interpolation on the left side of the colon. When we invoke the role, let’s also give it the name of a fact to put the return value in:

- name: Upgrade if necessary
  roles:
    - role: check_version
      package: Foo
      return: foo_version
    - role: check_version
      package: Bar
      return: bar_version
    - role: check_version
      package: Baz
      return: baz_version

The check_version role then sees return as a role variable, and can use that in a play:

# ... Look up installed version of the given package...
- name: Return version number
  set_fact:
    "{{ return }}": "1.2.3"

And then just check the appropriate fact for each role invocation.

- debug: var=foo_version
- debug: var=bar_version
- debug: var=baz_version

Bear in mind that these variables have the same scope as other facts, so if two roles set installed_version, you’re back to the earlier problem of them stepping on each other’s feet. And yes, the whole thing feels like a hack. But it does mean you can return values from roles.