14 May 2020

Recently, I’ve found myself in a position of writing and debugging some Lua based RPM macros and generators:

The capabilities of the embedded Lua interpreter in RPM are endless. My Lua skills are not. I need to test the code in small chunks to understand what am I doing.


When I need to do this in Python, I use the IPython console (my second favorite software in the Python ecosystem after pytest).

IPython console

For Lua, I’ve used the lua command for a while. It lets me test basic Lua concepts in an interactive console, but it’s not that powerful as the IPython console and it lacks the RPM provided Lua libraries.

Basic Lua console

I’ve asked Panu (my colleague and an RPM developer I work with when submitting changes to the RPM project) whether there is an interactive console for the Lua interpreter embedded in RPM and the answer was yes. I can use rpm --eval "%{lua:rpm.interactive()}". But it has some quirks.

Basic RPM Lua interactive shell

Mostly, there is no command history or even line editing, and the output is hard to get.


Naturally, I’ve searched for an IPython like Lua console and I’ve found the list of Jupyter Kerneles (IPython console is a frontend to Jupyter). There are 3 Lua kernels there:

ILua intrigued me mostly because it says right away: “Lua-implementation agnostic, should work with any Lua interpreter out of the box.” That’s exactly what I need. Maybe I can use it with rpm --eval "%{lua:rpm.interactive()}".

Turns out I can, but it’s not that simple. The used Lua interpreter needs to respect the $LUA_PATH environment variable and execute the file given to it as a command-line argument. Naturally, the simplistic rpm --eval "%{lua:rpm.interactive()}" does neither.

So I’ve created a wrapper:

exec rpm --eval '%{lua:package.path = "'${LUA_PATH}';" .. package.path;'"$(cat "[email protected]")"';rpm.interactive()}'

And it mostly worked. Except for the slight problem with missing print output. (The executed ILua script does the interactivity, so I’ve removed the rpm.interactive() call at the end.)

ILua using a Bash wrapper over RPM Lua

I’ve tried to figure out how to launch the RPM Lua interpreter, not in the RPM macro expansion mode (that thing eats all the print output until the end). I’ve done a little RPM symbols lookup and source reading and figured out there is an rpmluaRunScript() function in librpmio. So I’ve tried to use it:

from ctypes import cdll, c_char_p

librpmio = cdll.LoadLibrary("librpmio.so.9")
librpmio.rpmluaRunScript(None, c_char_p(b"rpm.interactive()"), None)

It works. A little tweaking to support ILua as well as other use cases:

import sys
from ctypes import cdll, c_char_p

librpm = cdll.LoadLibrary("librpm.so.9")
librpmio = cdll.LoadLibrary("librpmio.so.9")

# Load general configuration (such as macros defined in standard places)
# Second argument is target platform, NULL is the default
librpm.rpmReadConfigFiles(librpm.rpmcliRcfile, None)

adjust_path = b"""
if os.getenv("LUA_PATH") then
    package.path =  os.getenv("LUA_PATH") .. ";" .. package.path

# first argument is an "rpmlua" pointer, but uses global one when NULL
# second argument is code
# third argument is "name", used in errors, reasonable default when NULL
librpmio.rpmluaRunScript(None, c_char_p(adjust_path), None)

if len(sys.argv) > 1:
    sys.argv[-1] = '/dev/stdin' if sys.argv[-1] == '-' else sys.argv[-1]
    # first argument as above, second argument is path
    librpmio.rpmluaRunScriptFile(None, c_char_p(sys.argv[-1].encode("utf-8")))
    librpmio.rpmluaRunScript(None, c_char_p(b"rpm.interactive()"), None)

Note by Panu: rpmluaRunScript() and rpmluaRunScriptFile() are not considered public API and are not available in the public headers on C side, although the symbols are accessible in the ABI. So they are subject to change without further notice, although the likelihood of that happening doesn’t seem that great, they’ve been exactly the way are since their inception 16 years ago.

And now I have an interactive IPython-like shell for RPM embedded Lua with command history, line editing, completion and more:

ILua using a Python ctypes wrapper over RPM Lua

Enjoy! PS: ILua is on package review for Fedora, but can be safely pip-installed in the meantime.

blog comments powered by Disqus