The Boston Diaries

The ongoing saga of a programmer who doesn't live in Boston, nor does he even like Boston, but yet named his weblog/journal “The Boston Diaries.”

Go figure.

Friday, March 22, 2013

Preloading Lua modules

I'm tasked with testing the call processing on “Project: Wolowizard.” M suggested, and I concurred, that using Lua to manage the testing scripts would be a Good Thing™. Easier to write and modify the tests as needed. So over the past few years I've written a number of modules to handle the files and protocols used in the project (one side effect: by re-implemeting the code to read/write the various data files helped to verify the specification and flush out architectural dependencies in the binary formats).

But one problem did exist: Not all the systems I need to run the test on have Lua installed, and LuaRocks has … um … “issues” on our Solaris boxes (otherwise, it's not that bad a package manager). So I decided to build what I call “Kitchen Sink Lua”—a Lua interpreter that has the 47 modules required to run the testing scripts (okay, eight of the modules are already built into Lua).

It took some time to wrangle, as some of the modules were written in Lua (so the source needed to be embedded) and I had to figure out how to integrate some third party modules (like LuaCURL) into the build system, but perhaps the hardest bit was to ensure the modules were initialized properly. My first attempt, while it worked (mostly by accident) wasn't technically correct (as I realized when I read this message on a mailing list).

I then restructured my code, which not only made it correct, but smaller and clearer.

#include <stdlib.h>
#include <assert.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

/**************************************************************************/

typedef struct prelua_reg
{
  const char   *const name;
  const char   *const code;
  const size_t *const size;
} prelua_reg__t;

/*************************************************************************/

int	luaopen_org_conman_env		(lua_State *);
int	luaopen_org_conman_errno	(lua_State *);
int	luaopen_org_conman_fsys		(lua_State *);
int	luaopen_org_conman_math		(lua_State *);
int	luaopen_org_conman_syslog	(lua_State *);
int	luaopen_org_conman_hash		(lua_State *);
int	luaopen_org_conman_string_trim	(lua_State *);
int	luaopen_org_conman_string_wrap	(lua_State *);
int	luaopen_org_conman_string_remchar (lua_State *);
int	luaopen_org_conman_process	(lua_State *);
int	luaopen_org_conman_net		(lua_State *);
int	luaopen_org_conman_dns		(lua_State *);
int	luaopen_org_conman_sys		(lua_State *);
int	luaopen_org_conman_uuid		(lua_State *);
int	luaopen_lpeg			(lua_State *);
int	luaopen_LuaXML_lib		(lua_State *);
int	luaopen_cURL			(lua_State *);

/***********************************************************************/

	/*---------------------------------------------------------------
	; Modules written in Lua.  The build system takes the Lua code,
	; processes it through luac (the Lua compiler), then creates an
	; object file which exports a character array containing the byte
	; code, and a variable which gives the size of the bytecode array.
	;---------------------------------------------------------------*/

extern const char   c_org_conman_debug[];
extern const size_t c_org_conman_debug_size;
extern const char   c_org_conman_getopt[];
extern const size_t c_org_conman_getopt_size;
extern const char   c_org_conman_string[];
extern const size_t c_org_conman_string_size;
extern const char   c_org_conman_table[];
extern const size_t c_org_conman_table_size;
extern const char   c_org_conman_unix[];
extern const size_t c_org_conman_unix_size;
extern const char   c_re[];
extern const size_t c_re_size;
extern const char   c_LuaXml[];
extern const size_t c_LuaXml_size;

	/*----------------------------------------------------------------
	; Modules written in C.  We can use luaL_register() to load these
	; into package.preloaded[]
	;----------------------------------------------------------------*/

const luaL_Reg c_preload[] =
{
  { "org.conman.env"		, luaopen_org_conman_env		} ,
  { "org.conman.errno"		, luaopen_org_conman_errno		} ,
  { "org.conman.fsys"		, luaopen_org_conman_fsys		} ,
  { "org.conman.math"		, luaopen_org_conman_math		} ,
  { "org.conman.syslog"		, luaopen_org_conman_syslog		} ,
  { "org.conman.hash"		, luaopen_org_conman_hash		} ,
  { "org.conman.string.trim"	, luaopen_org_conman_string_trim	} ,
  { "org.conman.string.wrap"	, luaopen_org_conman_string_wrap	} ,
  { "org.conman.string.remchar"	, luaopen_org_conman_string_remchar	} ,
  { "org.conman.process"	, luaopen_org_conman_process		} ,
  { "org.conman.net"		, luaopen_org_conman_net		} ,
  { "org.conman.dns"		, luaopen_org_conman_dns		} ,
  { "org.conman.sys"		, luaopen_org_conman_sys		} ,
  { "org.conman.uuid"		, luaopen_org_conman_uuid		} ,
  { "lpeg"			, luaopen_lpeg				} ,
  { "LuaXML_lib"		, luaopen_LuaXML_lib			} ,
  { "cURL"			, luaopen_cURL				} ,
  { NULL			, NULL					}
};

	/*---------------------------------------------------------------
	; Modules written in Lua.  These need to be loaded and populated
	; into package.preloaded[] by some code provided in this file.
	;----------------------------------------------------------------

const prelua_reg__t c_luapreload[] =
{
  { "org.conman.debug"		, c_org_conman_debug	, &c_org_conman_debug_size	} ,
  { "org.conman.getopt"		, c_org_conman_getopt	, &c_org_conman_getopt_size	} ,
  { "org.conman.string"		, c_org_conman_string	, &c_org_conman_string_size	} ,
  { "org.conman.table"		, c_org_conman_table	, &c_org_conman_table_size	} ,
  { "org.conman.unix"		, c_org_conman_unix	, &c_org_conman_unix_size	} ,
  { "re"			, c_re			, &c_re_size			} ,
  { "LuaXml"			, c_LuaXml		, &c_LuaXml_size		} ,
  { NULL			, NULL			, NULL				}
};

/*************************************************************************/

void preload_lua(lua_State *const L)
{
  assert(L != NULL);
  
  lua_gc(L,LUA_GCSTOP,0);
  luaL_openlibs(L);
  lua_gc(L,LUA_GCRESTART,0);
  
  /*---------------------------------------------------------------
  ; preload all the modules.  This does does not initialize them, 
  ; just makes them available for require().  
  ;
  ; I'm doing it this way because of a recent email on the LuaJIT
  ; email list:
  ;
  ; http://www.freelists.org/post/luajit/Trivial-bug-in-bitops-bitc-luaopen-bit,4
  ;
  ; Pre-loading these modules in package.preload[] means that they're be
  ; initialized properly through the require() statement.
  ;---------------------------------------------------------------------*/
  
  lua_getglobal(L,"package");
  lua_getfield(L,-1,"preload");
  
  luaL_register(L,NULL,c_preload);
  for (size_t i = 0 ; c_luapreload[i].name != NULL ; i++)
  {
    int rc = luaL_loadbuffer(L,c_luapreload[i].code,*c_luapreload[i].size,c_luapreload[i].name);
    if (rc != 0)
    {
      const char *err;
      
      switch(rc)
      {
        case LUA_ERRRUN:    err = "runtime error"; break;
        case LUA_ERRSYNTAX: err = "syntax error";  break;
        case LUA_ERRMEM:    err = "memory error";  break;
        case LUA_ERRERR:    err = "generic error"; break;
        case LUA_ERRFILE:   err = "file error";    break;
        default:            err = "unknown error"; break;
      }
      
      fprintf(stderr,"%s: %s\n",c_luapreload[i].name,err);
      exit(EXIT_FAILURE);
    }
    lua_setfield(L,-2,c_luapreload[i].name);
  }
}

/*************************************************************************/

Yes, this is the code used in “Project: Wolowizard” (minus the proprietary modules) and is a good example of the module preload feature in Lua. The modules in C are easy to build (the following is from the Makefile):

obj/spc/process.o : $(LUASPC)/src/process.c     \
                $(LUA)/lua.h                    \
                $(LUA)/lauxlib.h
        $(CC) $(CFLAGS) -I$(LUA) -c -o $@ $<

While the Lua-based modules are a bit more involved:

obj/spc/unix.o : $(LUASPC)/lua/unix.lua $(BIN2C) $(LUAC)
        $(LUAC) -o tmp/unix.out $<
        $(BIN2C) -o tmp/unix.c -t org_conman_unix tmp/unix.out
        $(CC) $(CFLAGS) -c -o $@ tmp/unix.c

These modules are compiled using luac (which outputs the Lua byte code used by the core Lua VM), then through a program that converts this output into a C file, which is then compiled into an object file that can be linked into the final Kitchen Sink Lua interpreter.

Obligatory Picture

[It's the most wonderful time of the year!]

Obligatory Links

Obligatory Miscellaneous

You have my permission to link freely to any entry here. Go ahead, I won't bite. I promise.

The dates are the permanent links to that day's entries (or entry, if there is only one entry). The titles are the permanent links to that entry only. The format for the links are simple: Start with the base link for this site: http://boston.conman.org/, then add the date you are interested in, say 2000/08/01, so that would make the final URL:

http://boston.conman.org/2000/08/01

You can also specify the entire month by leaving off the day portion. You can even select an arbitrary portion of time.

You may also note subtle shading of the links and that's intentional: the “closer” the link is (relative to the page) the “brighter” it appears. It's an experiment in using color shading to denote the distance a link is from here. If you don't notice it, don't worry; it's not all that important.

It is assumed that every brand name, slogan, corporate name, symbol, design element, et cetera mentioned in these pages is a protected and/or trademarked entity, the sole property of its owner(s), and acknowledgement of this status is implied.

Copyright © 1999-2019 by Sean Conner. All Rights Reserved.