hammerspoon: hs.configdir resolution change in 0.9.79 breaks module loading for symlinked configs

Thanks @asmagill, @cmsj, @latenitefilms — and to everyone else who contributed to this new release! 🚀

I’ve been keeping my configs in sync between multiple Macs by symlinking init.lua (and other modules) into ~/.hammerspoon from a central config dir (~/Sync/Settings/Hammerpoon) using Resilio Sync.

My config failed to load when upgrading from 0.9.78. In the console, the following errors were logged:

...app/Contents/Resources/extensions/hs/_coresetup/init.lua:651: module 'keybinds' not found:
	no field package.preload['keybinds']
	no file '/Users/luke/Sync/Settings/Hammerspoon/keybinds.lua'
	no file '/Users/luke/Sync/Settings/Hammerspoon/keybinds/init.lua'
	no file '/Users/luke/Sync/Settings/Hammerspoon/Spoons/keybinds.spoon/init.lua'
        < ...snip... >
stack traceback:
	[C]: in function 'rawrequire'
	...app/Contents/Resources/extensions/hs/_coresetup/init.lua:651: in function 'require'
	(...tail calls...)
	[C]: in function 'xpcall'
	...app/Contents/Resources/extensions/hs/_coresetup/init.lua:514: in function <...app/Contents/Resources/extensions/hs/_coresetup/init.lua:494>

I eventually figured out that this was due to a change in the way hs.configdir is resolved. Previously, it seems like it would resolve to ~/.hammerspoon but now, if init.lua is itself a symlink, then hs.configdir is set to the parent directory of the absolute path of the real file. This is probably fine for 99.9% of users— but in my case, it broke my configuration.

I have the following symlinks:

~/.hammerspoon/init.lua       →  ~/Sync/Settings/Hammerspoon/init-XXX-YYY.lua
~/.hammerspoon/keybinds.lua   →  ~/Sync/Settings/Hammerspoon/keybinds-XXX-YYY.lua

(where XXX=hostname and YYY=machine serial number)

The reason I do this is so I can share chunks of configuration in init.lua between multiple Macs, but have the keybinds/module settings be specific to the machine that’s loading them. I symlink a custom file for each machine but they are all named e.g. “keybinds.lua”.

I “fixed” it by adding a function to my init.lua:

function require_safe(m)
  local rs_abspath = hs.fs.pathToAbsolute(os.getenv('HOME') .. '/.hammerspoon/' .. m .. '.lua')
  if not rs_abspath then return end
  local rs_basename = string.gsub(rs_abspath, "(.*/)(.*)", "%2")
  local rs_modname = rs_basename:sub(1, -5)
  return require(rs_modname)
end

-- instead of require('keybinds')
require_safe('keybinds')

This has gotten things working again for me. But I’m guessing there’s a cleaner way to handle this…?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 3
  • Comments: 16 (15 by maintainers)

Commits related to this issue

Most upvoted comments

I had the same problem since I use homeshick to manage my dotfiles in a git repo, and all of my dotfiles are symlinks.

If you want Spoons to load, you’ll need those in package.path as well:

hs.configdir = os.getenv('HOME') .. '/.hammerspoon'
package.path = hs.configdir .. '/?.lua;' .. hs.configdir .. '/?/init.lua;' .. hs.configdir .. '/Spoons/?.spoon/init.lua;' .. package.path

Automatically closed when pull merged; reopen if you still have an issue after trying a build off of master branch.