11 minutes
Iterate Quickly with LuaRocks, Ship Reproducibly with Guix
a hybrid Lua dependency management workflow
I set out with a simple goal: use Guix to install what I can for my Lua project, then let LuaRocks handle the rest, installing local dependencies into the .luarocks directory. I began full of hope that these would work together seamlessly, but things did not go so smoothly. Hours later I had resolved five errors:
linux/limits.h: No such file or directoryopenssl/crypto.h not foundld: unrecognized option '-Wl,-rpath,...'gccworked,gcc-toolchaindidn't (because they provide different major versions ofgcc!)undefined symbol: lua_newuserdata
Every problem turned out to be a different mismatch between assumptions made by different tools.
tl;dr to make this work, I had to:
- pin
luato 5.3 to match theluarockspackage - add
linux-libre-headers,openssl, andgcc-toolchain@14(specifically!) as dependencies - export explicit
CC,LD,LDSHARED,OPENSSL_DIR, andCRYPTO_DIR
Here's how I got there.
The initial setup
I've been writing Fennel lately. Since it builds on Lua, interacting with LuaRocks is hard to avoid. To get my dev tools into my environment, I began with this .envrc:
if has guix; then
use guix lua fennel fennel-ls luarocks
fiUsing envrc, this automates setting up my environment with my Lua dependencies whenever I switch into my project directory.
I then went to add a Lua dependency to the project using LuaRocks.1 I picked lua-http since it seemed like a nice easy place to start, and ran luarocks install http.
Can't find Linux headers
The first issue I ran into was the inability to find Linux headers:
$ luarocks --tree=.luarocks install http
# [snip]
/gnu/store/bmyld857bf6z790fgkmw4dazdfrxs61z-glibc-2.41/include/bits/local_lim.h:38:10: fatal error: linux/limits.h: No such file or directory
38 | #include <linux/limits.h>
| ^~~~~~~~~~~~~~~~
lua-http itself is a library written in Lua, but it pulls in various transitive dependencies that LuaRocks is attempting to build on my machine. This error is originating from that compilation process.
The first thing I tried was adding linux-libre-headers to my guix shell, but that didn't help. My breakthrough came when running which gcc: LuaRocks was compiling with /usr/bin/gcc from my distro, which isn't aware of my Guix environment. Adding the gcc-toolchain package to my setup got me past that issue.
Process improvement ideas
I would have discovered this issue earlier if I'd been using containerized Guix shells, which exclude everything you didn't explicitly ask for in your environment manifest. In that scenario, I would have failed to invoke gcc at all, instead of getting a distro-provided one. This is a constant risk of using hybrid Guix & system shells: command invocations can easily "escape" the curated Guix environment, as LuaRocks did by invoking /usr/bin/gcc here.
In this case, as in many where I'm doing exploratory development, I didn't use a containerized Guix shell because its strictness cuts me off from all the normal utilities I have installed in my base system, and which I don't necessarily anticipate needing until the situation calls for them. I didn't know until gathering feedback for this piece2 that there's a way to add dependencies from inside a running Guix container, which means that in the past, when the desire struck me to use some tool I hadn't thought to include, I either had to suppress it or else exit the shell, start a new one with the tool included, and resume where I'd left off. That was an unnecessary speedbump for experimentation, and so I typically forego the Guix container until I'm at a later stage of development.
What I've just learned, though, is that the guix shell command can provide for nested access to the Guix daemon using the --nesting flag, which allows for adding new tools to the environment at the point of need. That could be a big process improvement for me and I'll be trying it out.
Another way this failure case could have been avoided would be for the LuaRocks Guix package to depend on gcc-toolchain internally. If installing rocks commonly requires compiling C code, it would make sense for it to declare a runtime dependency on the compiler and invoke it explicitly from the Guix store instead of looking for any compiler in the user's environment. However, it would make the LuaRocks package in Guix more complicated, so that would have to be a discussion about trade-offs within the maintainers and Lua packagers in the Guix commons.
Can't find SSL headers
At this point, my .envrc looks like this:
if has guix; then
use guix lua fennel fennel-ls luarocks \
linux-libre-headers gcc-toolchain
fiI now got stopped on an error saying:
$ luarocks --tree=.luarocks install http
# [snip]
Could not find header file for CRYPTO
No file openssl/crypto.h in /usr/local/include
No file openssl/crypto.h in /include
I first tried adding openssl to my Guix shell, but that didn't resolve the issue. I had to tell LuaRocks explicitly where to find them, like so:
luarocks --tree=.luarocks \
install http \
CRYPTO_DIR=/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8 \
OPENSSL_DIR=/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8
That worked, but obviously, hard-coding the directory from the Guix store is obnoxious and fragile, so I added pkg-config to my Guix shell and refactored to use that, avoiding the hard-coded paths:
luarocks --tree=.luarocks \
install http \
CRYPTO_DIR=$(pkg-config --variable=prefix openssl) \
OPENSSL_DIR=$(pkg-config --variable=prefix openssl)
An interesting wrinkle here was that I tried OPENSSL_DIR, then CRYPTO_DIR, and neither worked, at which point I thought I was stumped; but using both got me to the next stage.
Flag confusion: ld rejects '-Wl,-rpath,...'
Now I was getting this error:
# [snip]
/gnu/store/y9lqfynp8a48qf350c1haba3wpv2sa6v-binutils-2.44/bin/ld -shared -o _openssl.so src/openssl.o vendor/compat53/c-api/compat-5.3.o -L/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8/lib -L/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8/lib -Wl,-rpath,/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8/lib -Wl,-rpath,/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8/lib -lssl -lcrypto -lpthread -lm -ldl
/gnu/store/y9lqfynp8a48qf350c1haba3wpv2sa6v-binutils-2.44/bin/ld: unrecognized option '-Wl,-rpath,/gnu/store/wf6y8389zb1vz9448xzfy0l1b0fnvs06-openssl-3.0.8/lib'
/gnu/store/y9lqfynp8a48qf350c1haba3wpv2sa6v-binutils-2.44/bin/ld: use the --help option for usage information
Error: Failed installing dependency: https://luarocks.org/luaossl-20250929-0.src.rock - Build error: Failed compiling module _openssl.so
Note that -Wl,... are flags meant for gcc, not for ld. gcc will strip these flags before invoking the linker, but in this case the linker was being called directly. It's been pointed out to me that LuaRocks is trying to do the correct thing by default here, but Guix is unhelpfully setting LD to the wrong value in this case, overriding the default. The workaround: provide LuaRocks with explicit tool invocations, like so:
luarocks --tree=.luarocks \
CC=$(guix build gcc)/bin/gcc \
LD=$(guix build gcc)/bin/gcc \
LDSHARED="$(guix build gcc)/bin/gcc -shared" \
install http \
CRYPTO_DIR=$(pkg-config --variable=prefix openssl) \
OPENSSL_DIR=$(pkg-config --variable=prefix openssl)This worked, so I wrote down instructions for how to reproduce my success, and then started refactoring for better readability.
Surprise! Couldn't reproduce
I refactored to use command instead of guix build to find GCC in my Guix environment, giving an invocation like this:
luarocks --tree=.luarocks \
CC=$(command -v gcc) \
LD=$(command -v gcc) \
LDSHARED="$(command -v gcc) -shared" \
install http \
CRYPTO_DIR=$(pkg-config --variable=prefix openssl) \
OPENSSL_DIR=$(pkg-config --variable=prefix openssl)However, to my consternation, this didn't work! I then fell back to the command invocation I'd written down as working, which was:
luarocks --tree=.luarocks \
CC=$(guix build gcc-toolchain)/bin/gcc \
LD=$(guix build gcc-toolchain)/bin/gcc \
LDSHARED="$(guix build gcc-toolchain)/bin/gcc -shared" \
install http \
CRYPTO_DIR=$(pkg-config --variable=prefix openssl) \
OPENSSL_DIR=$(pkg-config --variable=prefix openssl)
Spot the difference from what I reported in the previous section: I'd replaced guix build gcc with guix build gcc-toolchain, because guix build gcc gives a warning saying guix build: package 'gcc' has been superseded by 'gcc-toolchain'.
I mistakenly thought that meant I should just switch to using gcc-toolchain instead and that it would give the same package output, but that was not the case. gcc gives you toolchain version 14, while gcc-toolchain gives you toolchain version 153, and LuaRocks was not successful at building the necessary dependencies using GCC 15.
My solution was to pin the version in my .envrc file to gcc-toolchain@14. I'm curious but have not yet investigated whether this is a GCC regression, a LuaRocks issue with newer GCC, or something else entirely.
Can't load http
Having got my command invocation refactored for better readability and working reproducibly, I updated my .envrc to put my LuaRocks deps into my environment, then went to the lua repl to make sure I could actually load my dependency.
My .envrc at this point looked like so:
if has guix; then
use guix lua fennel fennel-ls luarocks \
readline linux-libre-headers \
gcc-toolchain@14 openssl pkg-config
fi
if has luarocks; then
eval "$(luarocks --tree=.luarocks path --bin)"
fiAnd then I ran:
$ lua
Lua 5.4.8 Copyright (C) 1994-2025 Lua.org, PUC-Rio
> local request = require "http.request"
error loading module 'lpeg' from file '/home/ryan/dev/scratch/.luarocks/lib/lua/5.3/lpeg.so':
/home/ryan/dev/scratch/.luarocks/lib/lua/5.3/lpeg.so: undefined symbol: lua_newuserdataIf you're sharp-eyed you'll spot, without taking the 5 minutes I took, that we're running a Lua 5.4.x shell, but we're loading a library for Lua 5.3.x.4 I spent a long time trying to figure out if I somehow had a Lua 5.3 installed somewhere, and I tried telling LuaRocks explicitly to install the Lua 5.4 version of the dependencies, but that didn't work.
I eventually looked at the Guix package for luarocks and noticed that it depends explicitly on lua@5.3:
$ guix show luarocks
name: luarocks
version: 3.9.2
outputs:
+ out: everything
systems: x86_64-linux
dependencies: bash-minimal@5.2.37 binutils@2.44 bzip2@1.0.8
+ coreutils@9.1 curl@8.6.0 findutils@4.10.0 gcc@14.3.0 git@2.52.0
+ gnupg@2.4.8 gzip@1.14 lua@5.3.5
# [snip]
I could build a new LuaRocks with a dependency override to force 5.45, but for the time being I've got it all working on 5.3. I'm going to want to make progress here before too long, as 5.3 is end-of-life. After pinning (lua@5.3 in my .envrc), I was finally able to load my dependency in the REPL.
Final environment
Final .envrc:
if has guix; then
use guix lua@5.3 fennel fennel-ls luarocks \
readline linux-libre-headers \
gcc-toolchain@14 openssl pkg-config
fi
if has luarocks; then
eval "$(luarocks --tree=.luarocks path --bin)"
fi
Final luarocks command invocation:
luarocks --tree=.luarocks \
--lua-version=5.3 \
CC=$(command -v gcc) \
LD=$(command -v gcc) \
LDSHARED="$(command -v gcc) -shared" \
install http \
CRYPTO_DIR=$(pkg-config --variable=prefix openssl) \
OPENSSL_DIR=$(pkg-config --variable=prefix openssl)Reflection
In hindsight, I see the pieces coming together to explain how this wasn't the slam dunk it could have been.
Small disappointments add up
luarocksandluapackages don't have a common version out of the box in Guix- Guix-packaged
luarocksrequired explicitly setting the compiler and linker to invoke Guix-packagedgcc-toolchainat runtime - Guix
gccandgcc-toolchainyield different major versions of the toolchain, and warning text elides this detail when stating that the latter supersedes the former - LuaRocks doesn't find my Guix-provided libopenssl, even though
pkg-configcan find it fine
Nonetheless, the issues I hit did not require deep research or software patching, they were rough edges that we can address with effort. That makes this an opportunity to pitch in upstream in Guix and LuaRocks and potentially improve the experience. This pull request in Guix already contains some good ideas, although it's stalled at present.
Open investigations
- What blockers exist to upgrading
luarocksin Guix to Lua 5.4, and/or provide versioned variants? - Why is LuaRocks invoking
lddirectly in a way that conflicts with GCC-style linker flags? Is this a Guix-specific issue? - Does the GCC 15 toolchain expose an issue with one of the rocks involved (
luaosslperhaps suspect?) โฆor did something else change in the toolchain? - How do other language package managers in Guix solve access to a C compiler when native extensions need to be built?
- What's the vision of the Guix Lua team for a more polished experience?
Looking forward
Since I worked past the hurdles, I've been rewarded with a nice workflow. I can manage project dependencies hygienically, utilizing Guix to install the language runtime and LuaRocks, which takes over from there.
Once I'm ready to ship a Lua application, I do look forward to packaging all the dependencies in Guix so that I can deploy without using LuaRocks at all. That's ultimately a desirable setup, optimizing for better reproducibility and simplicity in the maintenance and deployment stage, after doing initial development using the hybrid approach's development agility and large LuaRocks package repository.
If you've run into similar issues, I'd be interested to hear if this helped, or didn't. If you know the answer to some question above, or end up contributing a fix in Guix or LuaRocks, that's also relevant to my interest. I'd especially love to update this post with corrections or write a follow-up with better approaches as I learn them. Eventually, with more Lua packages included in Guix and an improved onboarding flow, I can see us bringing together an effective Lua development story with Guix.
Of course, I could have added lua-http as a dependency using Guix, if it were available as a Guix package, but as of publication time it's not. I know how to write Guix packages, but taking a break from development to package a library and all its transitive dependencies can be a major hit to productivity, so it's convenient to have the option to mix packages from the LuaRocks repository into a Guix environment.
This surprised me. It seems like either gcc should give the same version as gcc-toolchain, or it should say specifically "this is superseded by gcc-toolchain@14" so that you don't invite a major version boundary crossing there. I'll bring it up with the Guix crew.
In terms of technical details here, lua_newuserdata disappeared in the Lua 5.4 C API, replaced by lua_newuserdatauv, making this an ABI mismatch issue. Long time Lua heads may have clocked this right away, but I did not.
The --with-input package transformation could potentially make it trivial to build LuaRocks with a different version of Lua, but there could be further wrinkles or incompatibilities lurking. I haven't tried it yet, but this would be on the list of chores for upgrading my toolchain to Lua 5.4 or later.
lualuarocksguixautomationdevopsback-endtroubleshootingpackage-management
2278 Words
2026-07-02 04:15 +0000