diff --git a/.gitignore b/.gitignore index fc8681e922..d379d253c2 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,12 @@ tests/tests_* tests/*.log tests/*.trs test-suite.log + +# Cached files +.cache/ +compile_commands.json + +# LuaRocks development files +/luarocks +/lua_modules +/.luarocks diff --git a/Makefile.am b/Makefile.am index 2a4f988503..75d27f4d8c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -86,6 +86,20 @@ python_crun_la_LDFLAGS = -avoid-version -module $(PYTHON_LDFLAGS) python_crun_la_LIBADD = libcrun.la $(PYTHON_LIBS) $(FOUND_LIBS) $(maybe_libyajl.la) endif +if LUA_BINDINGS +noinst_LTLIBRARIES = libcrun-bundled.la +luaexec_LTLIBRARIES = luacrun.la +luacrun_la_SOURCES = lua/lua_crun.c +luacrun_la_CFLAGS = -I $(abs_top_srcdir)/libocispec/src -I $(abs_top_builddir)/libocispec/src -I $(abs_top_builddir)/src $(LUA_INCLUDE) +luacrun_la_LDFLAGS = -avoid-version -module +luacrun_la_LIBADD = libcrun-bundled.la $(LUA_LIB) $(FOUND_LIBS) + +libcrun_bundled_la_SOURCES = $(libcrun_SOURCES) +libcrun_bundled_la_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -fvisibility=hidden +libcrun_bundled_la_LIBADD = libocispec/libocispec.la $(FOUND_LIBS) $(maybe_libyajl.la) +libcrun_bundled_la_LDFLAGS = -Wl,--version-script=$(abs_top_srcdir)/libcrun.lds +endif + crun_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -D CRUN_LIBDIR="\"$(CRUN_LIBDIR)\"" crun_SOURCES = src/crun.c src/run.c src/delete.c src/kill.c src/pause.c src/unpause.c src/spec.c \ src/exec.c src/list.c src/create.c src/start.c src/state.c src/update.c src/ps.c \ diff --git a/README.md b/README.md index f9bd8fa4c2..64b01977f4 100644 --- a/README.md +++ b/README.md @@ -176,3 +176,7 @@ $ sudo su - # molecule converge # molecule verify ``` + +## Lua bindings + +A Lua binding is available. See [the README](lua/README.md) for more information. diff --git a/configure.ac b/configure.ac index 53440cd06f..a5d17504bc 100644 --- a/configure.ac +++ b/configure.ac @@ -173,6 +173,15 @@ AS_IF([test "x$with_python_bindings" = "xyes"], [ use_fPIC=yes ]) +AC_ARG_WITH([lua-bindings], AS_HELP_STRING([--with-lua-bindings], [build the Lua bindings])) + +AS_IF([test "x$with_lua_bindings" = "xyes"], [ + AX_PROG_LUA([5.4], [5.5], [], [AC_MSG_ERROR([*** lua interpreter not found])]) + AX_LUA_HEADERS([], [AC_MSG_ERROR([*** lua headers not found])]) + AX_LUA_LIBS([], [AC_MSG_ERROR([*** lua libs not found])]) + use_fPIC=yes +]) + AS_IF([test "x$use_fPIC" = "xyes"], [ # configure should not touch CFLAGS/LDFLAGS but we need it to propagate it # to libocispec. @@ -253,6 +262,7 @@ fi AC_SEARCH_LIBS([argp_parse], [argp], [], [AC_MSG_ERROR([*** argp functions not found - install libargp or argp_standalone])]) AM_CONDITIONAL([PYTHON_BINDINGS], [test "x$with_python_bindings" = "xyes"]) +AM_CONDITIONAL([LUA_BINDINGS], [test "x$with_lua_bindings" = "xyes"]) AM_CONDITIONAL([CRIU_SUPPORT], [test "x$have_criu" = "xyes"]) AM_CONDITIONAL([SHARED_LIBCRUN], [test "x$enable_shared" = "xyes"]) diff --git a/lua/README.md b/lua/README.md new file mode 100644 index 0000000000..99c94cf191 --- /dev/null +++ b/lua/README.md @@ -0,0 +1,120 @@ +# Lua binding for libcrun + +Bare libcrun interface for Lua. + +There are some problems still and the API is a subject to change. + +## Build + +Only build static archive, to be bundled with another program: + +````sh +./configure --with-lua-bindings +make && make install +```` + +Since the final product bundle libcrun, you don't need to link libcrun at runtime. + + +For the library using at runtime, add option `--enable-shared`: +````sh +./configure --with-lua-bindings --enable-shared +make && make install +```` + +Other options to build libcrun may affect the bundled libcrun. + +## Usage + +See `luacrun.d.tl`. + +## Works with your LuaRocks project + +You can configure the prefix as your `lua_modules` to access this library in your project. + +````sh +./configure --with-lua-bindings --enable-shared --prefix $(pwd)/lua_modules +```` + +## Interpreter may restart? + +Related issue: [#695: [Python bindings] Python interpreter restarts (?) after first import of python_crun](https://github.com/containers/crun/issues/695) + +The lua interpreter may restart at the first open of the library (may not happen if statically linked into your program). It's side effect of [a protection (click for the code)](https://github.com/containers/crun/blob/923447b691dbd7c5bffbaee1427460d62d848047/src/libcrun/linux.c#L3881-L3891) to avoid attacks like [CVE-2019-5736](https://nvd.nist.gov/vuln/detail/CVE-2019-5736). + +To ease the hurt, always place the `require` call at the start of your program: + +````lua +-- entry point module for luabundler +require "luacrun" + +return function(...) -- the entry point + print("Hello World!") +end +```` + +It's not required to use the library at the moment. Since it is cached, the `require()` will not open the library again. + +If the entry point is at the C-side, you may use the `luaL_requiref` to open the library instead of `require()` call in Lua code. +````c +#include +#include + +extern int luaopen_luacrun(lua_State *S); + +int main(int argc, char *argv[]) { + lua_State *S = luaL_newstate(); + // Open the library before any actual logic, + // to make sure users will not notice the program have actually started twice + luaL_requiref(S, "luacrun", &luaopen_luacrun, false); + lua_pop(S, 1); + // ...your code +} +```` + +The protection might cause another problem in REPL: +```` +$ lua +Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio +> luacrun = require "luacrun" +Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio +> luacrun +nil +> +```` + +When you call `require "luacrun"` at the first time, the REPL restarted and the state have been reset. + +The workaround is `require "luacrun"` again and you get the library this time. The protection will not apply again if it's already applied. +```` +$ lua +Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio +> luacrun = require "luacrun" +Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio +> luacrun +nil +> luacrun = require "luacrun" +> luacrun +table: 0x561edad470d0 +> +```` + +It's safe to use luacrun in multi-state usage, the program restarts only once. + + +## Test + +You need [busted](https://lunarmodules.github.io/busted/) and below dependencies to run tests: + +- [dkjson](https://luarocks.org/modules/dhkolf/dkjson) +- [luaposix](https://luaposix.github.io) + +````sh +luarocks install busted dkjson luaposix +```` + +The tests assume environment variable `INIT` exists. It's the `init` program compiled in `tests`. + +````sh +INIT=$(pwd)/tests/init lua_modules/bin/busted lua +```` diff --git a/lua/lua_crun.c b/lua/lua_crun.c new file mode 100644 index 0000000000..33425c122d --- /dev/null +++ b/lua/lua_crun.c @@ -0,0 +1,823 @@ +/* + *crun - OCI runtime written in C + * + *Copyright (C) Rubicon Rowe + *crun is free software; you can redistribute it and/or modify + *it under the terms of the GNU Lesser General Public License as published by + *the Free Software Foundation; either version 2.1 of the License, or + *(at your option) any later version. + * + *crun is distributed in the hope that it will be useful, + *but WITHOUT ANY WARRANTY; without even the implied warranty of + *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *GNU Lesser General Public License for more details. + * + *You should have received a copy of the GNU Lesser General Public License + *along with crun. If not, see . + */ + +/* This library is a bare libcrun interface and can be further wrapped by other libraries. + * + * There are some problems still and the API is a subject to change. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *LUA_CRUN_TAG_CTX = "crun-ctx"; +static const char *LUA_CRUN_TAG_CONT = "crun-container"; +static const char *LUA_CRUN_TAG_CONTS_ITER = "crun-containers-iterator"; + +#define luacrunL_optboolean(L, n, d) luaL_opt (S, lua_toboolean, n, d) + +// Soft error = return an error. +// When `expr` is false, run `onfailed` and push the string from `crun_err`. +// Return `addret + 1`. +#define luacrun_SoftErrIf(S, expr, crun_err, onfailed, addret) \ + if (expr) \ + { \ + onfailed; \ + return luacrun_error (S, crun_err) + addret; \ + } + +#if __STDC_VERSION__ < 201112L +# define LUACRUN_NoRet +#elif __STDC_VERSION__ < 202300L +# define LUACRUN_NoRet _Noreturn +#else +# define LUACRUN_NoRet [[noreturn]] +#endif + +extern LUACRUN_NoRet int lua_error (lua_State *L); +extern LUACRUN_NoRet int luaL_error (lua_State *L, const char *fmt, ...); + +/* Build the error string, push onto stack. */ +LUA_API int +luacrun_error (lua_State *S, libcrun_error_t *err) +{ + luaL_checkstack (S, 1, NULL); + if ((*err)->status == 0) + { + lua_pushfstring (S, "crun: %s", (*err)->msg); + } + else + { + lua_pushfstring (S, "crun: %s(%s)", (*err)->msg, strerror ((*err)->status)); + } + libcrun_error_release (err); + return 1; +} + +LUA_API LUACRUN_NoRet void +luacrun_set_error (lua_State *S, libcrun_error_t *err) +{ + luacrun_error (S, err); + lua_error (S); +} + +/* This is a custom version of `xstrdup`(in src/libcrun/utils.h), return a Lua userdata. + * Push the userdata onto stack, or nil if `s` is `NULL`. + * This function does not check stack. + * -0, +1, - + */ +static char * +luacrun_xstrdup (lua_State *S, const char *s) +{ + if (s != NULL) + { + size_t size = strlen (s) + 1; + char *ret = lua_newuserdata (S, size); + /* `lua_newuserdatauv` always return a valid address, + no need to check if the allocation is success */ + memcpy (ret, s, size); + return ret; + } + else + { + lua_pushnil (S); + return NULL; + } +} + +struct luacrun_args_holder +{ + const char **argv; + int argc; +}; + +/* uservalues used by the ctx, the index + 1 is the uservalue idx. +the definition here is not stable. */ +const char *luacrun_ctx_uservalues[] = { + "state_root", + "id", + "bundle", + "console_socket", + "pid_file", + "notify_socket", + "handler", + "args", // argv and argc +}; + +#define luacrun_CtxSetupStringField(S, ret, ctxidx, tabidx, field_name, name, uvalidx) \ + ret = lua_getfield (S, tabidx, field_name); \ + if (ret == LUA_TSTRING) \ + { \ + ctx->name = luacrun_xstrdup (S, lua_tostring (S, -1)); \ + } \ + else if (ret == LUA_TNIL) \ + { \ + lua_pushnil (S); \ + ctx->name = NULL; \ + } \ + else \ + { \ + lua_pop (S, 1); \ + luaL_error (S, "unknown type %s for field \"%s\"", lua_typename (S, ret), field_name); \ + } \ + lua_setiuservalue (S, ctxidx, uvalidx); \ + lua_pop (S, 1) + +#define luacrun_CtxSetupBoolField(S, ret, ctxidx, tabidx, field_name, name) \ + ret = lua_getfield (S, tab_idx, field_name); \ + if (ret == LUA_TBOOLEAN) \ + { \ + ctx->name = lua_toboolean (S, -1); \ + } \ + else if (ret != LUA_TNIL) \ + { \ + lua_pop (S, 1); \ + luaL_error (S, "unknown type %s for field \"%s\"", lua_typename (S, ret), field_name); \ + } \ + lua_pop (S, 1) + +/* Setup context_t by a table. The table must be the stack top. [-0, +0] */ +static void +luacrun_ctx_setup (lua_State *S, int ctxidx, int tab_idx) +{ + libcrun_context_t *ctx = lua_touserdata (S, ctxidx); + + int ret; + + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "state_root", state_root, 1); + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "id", id, 2); + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "bundle", bundle, 3); + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "console_socket", console_socket, 4); + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "pid_file", pid_file, 5); + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "notify_socket", notify_socket, 6); + luacrun_CtxSetupStringField (S, ret, ctxidx, tab_idx, "handler", handler, 7); + + luacrun_CtxSetupBoolField (S, ret, ctxidx, tabidx, "systemd_cgroup", systemd_cgroup); + luacrun_CtxSetupBoolField (S, ret, ctxidx, tabidx, "detach", detach); + + ret = lua_getfield (S, tab_idx, "args"); + if (ret == LUA_TTABLE) + { + lua_Integer length = luaL_len (S, -1); + if (length < 0 || length > (INT_MAX - 1)) + { /* A userdata can have INT_MAX uservalues */ + luaL_error (S, "field \"args\": length should be <= %d and > 0", INT_MAX - 1); + } + int argc = (int) length; + const char **argv = lua_newuserdatauv (S, sizeof (char *) * argc, argc); + int argv_idx = lua_gettop (S); + for (int i = argc; i > 0; i--) + { + lua_geti (S, tab_idx, i); + const char *arg = luaL_tolstring (S, -1, NULL); + if (arg != NULL) + { + const char *copy = luacrun_xstrdup (S, arg); + argv[i] = copy; + lua_setiuservalue (S, argv_idx, i); + lua_pop (S, 2); /* pop arg and result from lua_geti */ + } + else + { + luaL_error (S, "field \"args\": failed to convert value (index %d) to string", i); + } + } + /* Stack top: argv */ + + struct luacrun_args_holder *args = lua_newuserdatauv (S, sizeof (struct luacrun_args_holder), 1); + int args_idx = lua_gettop (S); + args->argc = argc; + args->argv = argv; + lua_pushvalue (S, argv_idx); + lua_setiuservalue (S, args_idx, 1); + /* Stack top: args */ + lua_setiuservalue (S, ctxidx, 8); /* -1 */ + lua_pop (S, 1); + } + else if (ret != LUA_TNIL) + { + lua_pop (S, 1); + luaL_error (S, "unknown type %s for field \"%s\"", lua_typename (S, ret), "args"); + } + lua_pop (S, 1); +} + +/* Create a crun context. + */ +LUA_API int +luacrun_new_ctx (lua_State *S) +{ + if (! (lua_isnil (S, 1) || lua_istable (S, 1))) + { + luaL_typeerror (S, 1, "table or nil"); + } + + luaL_checkstack (S, 1, NULL); + /* Lua does not guarantee that string addresses will be valid for the lifetime of libcrun_context_t, + * but we must respect the memory management function set by the user in lua_State. + * (For the string memory guarantees: https://www.lua.org/manual/5.4/manual.html#4.1.3) + * + * - Use userdata as string to ensure it will not be moved. + * ("Lua ensures that this address is valid as long as the corresponding userdata is alive": + * https://www.lua.org/manual/5.4/manual.html#lua_newuserdatauv) + * - Use uservalue to prevent being collected by GC. + * + */ + libcrun_context_t *ctx = lua_newuserdatauv (S, sizeof (libcrun_context_t), 8); + int ctx_idx = lua_gettop (S); + memset (ctx, 0, sizeof (libcrun_context_t)); + ctx->fifo_exec_wait_fd = -1; + if (lua_istable (S, 1)) + { + luacrun_ctx_setup (S, ctx_idx, 1); + } + luaL_setmetatable (S, LUA_CRUN_TAG_CTX); + return 1; +} + +/*Grab a basic container spec.*/ +LUA_API int +luacrun_container_spec (lua_State *S) +{ + bool rootless = luacrunL_optboolean (S, 1, true); + libcrun_error_t crun_err = NULL; + luaL_checkstack (S, 1, NULL); + char buf[4096] = {}; + FILE *memfile = fmemopen (buf, 4095, "w"); + int ret = libcrun_container_spec (rootless, memfile, &crun_err); // the crun_err is not used + fclose (memfile); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushnil (S), 1); + lua_pushlstring (S, buf, ret); + return 1; +} + +LUA_API int +luacrun_new_container_from_string (lua_State *S) +{ + libcrun_error_t crun_err = NULL; + const char *def = luaL_checkstring (S, 1); + libcrun_container_t **cont = lua_newuserdata (S, sizeof (libcrun_container_t *)); + luaL_setmetatable (S, LUA_CRUN_TAG_CONT); + *cont = libcrun_container_load_from_memory (def, &crun_err); + if (cont == NULL) + { + lua_pushnil (S); + return luacrun_error (S, &crun_err) + 1; + } + return 1; +} + +LUA_API int +luacrun_new_container_from_file (lua_State *S) +{ + libcrun_error_t crun_err = NULL; + const char *path = luaL_checkstring (S, 1); + libcrun_container_t **cont = lua_newuserdata (S, sizeof (libcrun_container_t *)); + luaL_setmetatable (S, LUA_CRUN_TAG_CONT); + // create the userdata before calling crun, so we don't need to cleanup when Lua failed + *cont = libcrun_container_load_from_file (path, &crun_err); + if (cont == NULL) + { + lua_pushnil (S); + return luacrun_error (S, &crun_err) + 1; + } + return 1; +} + +/*Release resource linked with container userdata. Double use is supported.*/ +LUA_API int +luacrun_container_finalizer (lua_State *S) +{ + libcrun_container_t **cont = luaL_checkudata (S, 1, LUA_CRUN_TAG_CONT); + if (*cont != NULL) + { + free_runtime_spec_schema_config_schema ((*cont)->container_def); + *cont = NULL; + } + return 0; +} + +LUA_API int +luacrun_set_verbosity (lua_State *S) +{ + lua_Integer verbosity = luaL_checkinteger (S, 1); + if (verbosity >= INT_MIN && verbosity <= INT_MAX) + { + libcrun_set_verbosity (verbosity); + } + else + { + luaL_error (S, "verbosity should be >= %d and <= %d", INT_MIN, INT_MAX); + } + return 0; +} + +LUA_API int +luacrun_get_verbosity (lua_State *S) +{ + int verbosity = libcrun_get_verbosity (); + lua_pushinteger (S, verbosity); + return 1; +} + +static unsigned int +luacrun_build_run_flags (lua_State *S, int idx) +{ + luaL_checktype (S, idx, LUA_TTABLE); + lua_getfield (S, idx, "prefork"); + bool prefork = lua_toboolean (S, -1); + lua_pop (S, 1); + return (prefork ? LIBCRUN_RUN_OPTIONS_PREFORK : 0) | 0; +} + +LUA_API int +luacrun_ctx_run (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + libcrun_container_t **cont = luaL_checkudata (S, 2, LUA_CRUN_TAG_CONT); + unsigned int flags = luaL_opt (S, luacrun_build_run_flags, 3, 0); + + libcrun_error_t crun_err = NULL; + luaL_checkstack (S, 1, NULL); + int ret = libcrun_container_run (ctx, *cont, flags, &crun_err); + if (ret < 0) + { + lua_pushnil (S); + return luacrun_error (S, &crun_err) + 1; + } + else + { + lua_pushinteger (S, ret); + return 1; + } +} + +LUA_API int +luacrun_ctx_create_container (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + libcrun_container_t **cont = luaL_checkudata (S, 2, LUA_CRUN_TAG_CONT); + unsigned int flags = luaL_opt (S, luacrun_build_run_flags, 3, LIBCRUN_RUN_OPTIONS_PREFORK); + + libcrun_error_t crun_err = NULL; + luaL_checkstack (S, 1, NULL); + int ret = libcrun_container_create (ctx, *cont, flags, &crun_err); + if (ret < 0) + { + lua_pushnil (S); + return luacrun_error (S, &crun_err) + 1; + } + else + { + lua_pushinteger (S, ret); + return 1; + } +} + +LUA_API int +luacrun_ctx_delete_container (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + const char *id = luaL_checkstring (S, 2); + bool force = luaL_opt (S, lua_toboolean, 3, false); + + libcrun_error_t crun_err = NULL; + luaL_checkstack (S, 1, NULL); + int ret = libcrun_container_delete (ctx, NULL, id, force, &crun_err); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushboolean (S, false), 1); + lua_pushboolean (S, true); + return 1; +} + +LUA_API int +luacrun_ctx_kill_container (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + const char *id = luaL_checkstring (S, 2); + const char *signame = luaL_checkstring (S, 3); + + libcrun_error_t crun_err = NULL; + luaL_checkstack (S, 1, NULL); + int ret = libcrun_container_kill (ctx, id, signame, &crun_err); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushboolean (S, false), 1); + lua_pushboolean (S, true); + return 1; +} + +/* Get the container status. (ctx: userdata, id: string) [-0, +1, -] + +This function is a rewrite of `libcrun_container_state` for Lua. +`libcrun_container_state` receives `FILE*` and writes JSON. +We could not use `fmemopen` like `luacrun_container_spec` since the final size of +the string is 100% unpredictable. +*/ +LUA_API int +luacrun_ctx_status_container (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + const char *id = luaL_checkstring (S, 2); + + luaL_checkstack (S, 3, NULL); + + libcrun_error_t crun_err = NULL; + int ret; + lua_createtable (S, 0, 0); + int tabidx = lua_gettop (S); + + // We know here are two frames available on the stack from this point. + + lua_pushstring (S, "1.0.0"); + lua_setfield (S, tabidx, "ociVersion"); + lua_pushvalue (S, 2); + lua_setfield (S, tabidx, "id"); + + libcrun_container_status_t status = {}; + const char *state_root = ctx->state_root; + ret = libcrun_read_container_status (&status, state_root, id, &crun_err); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushnil (S), 1); + + const char *container_status = NULL; + int running; + ret = libcrun_get_container_state_string (id, &status, state_root, &container_status, &running, &crun_err); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushnil (S), 1); + + lua_pushinteger (S, running ? status.pid : 0); + lua_setfield (S, tabidx, "pid"); + struct luacrun_string_pair + { + const char *k; + const char *v; + }; + const struct luacrun_string_pair values[] = { + { "status", container_status }, + { "bundle", status.bundle }, + { "rootfs", status.rootfs }, + { "created", status.created }, + { "systemd-scope", status.scope }, /* maybe NULL*/ + { "owner", status.owner }, /* maybe NULL */ + { NULL, NULL }, + }; + for (int i = 0; values[i].k != NULL; i++) + { + const struct luacrun_string_pair p = values[i]; + if (p.v != NULL) + { + lua_pushstring (S, p.v); + lua_setfield (S, tabidx, p.k); + } + } + + { + cleanup_container libcrun_container_t *container = NULL; + cleanup_free char *dir = NULL; + + dir = libcrun_get_state_directory (state_root, id); + if (dir == NULL) + { + lua_pushnil (S); + lua_pushstring (S, "cannot get state directory"); + return 2; + } + + const char *config_file = lua_pushfstring (S, "%s/%s", dir, "config.json"); + + container = libcrun_container_load_from_file (config_file, &crun_err); + lua_pop (S, 1); + if (container == NULL) + { + lua_pushnil (S); + lua_pushstring (S, "error loading config.json"); + return 2; + } + + if (container->container_def->annotations && container->container_def->annotations->len) + { + /* Check stack again, we need three available frames here. */ + luaL_checkstack (S, 3, NULL); + lua_createtable (S, 0, container->container_def->annotations->len); + for (size_t i = 0; i < container->container_def->annotations->len; i++) + { + const char *key = container->container_def->annotations->keys[i]; + const char *val = container->container_def->annotations->values[i]; + lua_pushstring (S, val); + lua_setfield (S, tabidx, key); + } + lua_setfield (S, tabidx, "annotations"); + } + } + return 1; +} + +LUA_API int +luacrun_ctx_start_container (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + const char *id = luaL_checkstring (S, 2); + + libcrun_error_t crun_err = NULL; + luaL_checkstack (S, 2, NULL); + int ret = libcrun_container_start (ctx, id, &crun_err); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushboolean (S, false), 1); + lua_pushboolean (S, true); + return 1; +} + +struct luacrun_ctx_containers_iterator +{ + bool closed; // flag for if the libcrun_container_list_t free'd + libcrun_container_list_t *start; + libcrun_container_list_t *curr; + lua_Integer counter; +}; + +static int +luacrun_ctx_containers_iteratorf (lua_State *S) +{ + // params: userdata integer + luaL_checktype (S, 1, LUA_TUSERDATA); + struct luacrun_ctx_containers_iterator *it = lua_touserdata (S, 1); + luaL_checkstack (S, 2, NULL); + if (it->curr != NULL) + { + lua_pushinteger (S, ++(it->counter)); + lua_pushstring (S, it->curr->name); + it->curr = it->curr->next; + return 2; + } + else + { + it->closed = true; + libcrun_free_containers_list (it->start); + lua_pushnil (S); + return 1; + } +} + +static int +luacrun_ctx_containers_finalizer (lua_State *S) +{ + luaL_checktype (S, 1, LUA_TUSERDATA); + struct luacrun_ctx_containers_iterator *iter = lua_touserdata (S, 1); + if (! iter->closed) + { + libcrun_free_containers_list (iter->start); + } + return 0; +} + +static const luaL_Reg luacrun_ctx_containers_iterator_metamethods[] = { + { "__gc", &luacrun_ctx_containers_finalizer }, + { NULL, NULL }, +}; + +static int +luacrun_setup_ctx_iter_metatable (lua_State *S) +{ + luaL_newmetatable (S, LUA_CRUN_TAG_CONTS_ITER); + luaL_setfuncs (S, luacrun_ctx_containers_iterator_metamethods, 0); + lua_pop (S, 1); + return 0; +} + +LUA_API int +luacrun_ctx_iter_containers (lua_State *S) +{ + libcrun_error_t crun_err = NULL; + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + + luaL_checkstack (S, 4, NULL); + lua_pushcfunction (S, &luacrun_ctx_containers_iteratorf); + + libcrun_container_list_t *containers; + int ret = libcrun_get_containers_list (&containers, ctx->state_root, &crun_err); + if (ret < 0) + luacrun_set_error (S, &crun_err); + struct luacrun_ctx_containers_iterator *it = lua_newuserdata (S, sizeof (struct luacrun_ctx_containers_iterator)); + *it = (struct luacrun_ctx_containers_iterator){ + .closed = false, + .counter = 0, + .curr = containers, + .start = containers, + }; + + luaL_setmetatable (S, LUA_CRUN_TAG_CONTS_ITER); + lua_pushinteger (S, it->counter); + lua_pushnil (S); + return 4; +} + +LUA_API int +luacrun_ctx_update_container (lua_State *S) +{ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); + const char *id = luaL_checkstring (S, 2); + const char *content = luaL_checkstring (S, 3); + luaL_checkstack (S, 2, NULL); + + char errbuf[1024] = {}; + yajl_val parsed_json = yajl_tree_parse (content, errbuf, sizeof (errbuf)); + if (parsed_json == NULL) + { + lua_pushboolean (S, false); + lua_pushfstring (S, "cannot parse the data: \"%s\"", errbuf); + return 2; + } + + struct parser_context parser_ctx = { .options = 0, .errfile = stderr }; + runtime_spec_schema_config_schema_process *rt_sepc_process; + parser_error p_err = NULL; + rt_sepc_process = make_runtime_spec_schema_config_schema_process (parsed_json, &parser_ctx, &p_err); + yajl_tree_free (parsed_json); + if (rt_sepc_process == NULL) + { + lua_pushboolean (S, false); + lua_pushfstring (S, "cannot parse process: \"%s\"", p_err); + free (p_err); + return 2; + } + + libcrun_error_t crun_err = NULL; + int ret = libcrun_container_exec (ctx, id, rt_sepc_process, &crun_err); + free_runtime_spec_schema_config_schema_process (rt_sepc_process); + luacrun_SoftErrIf (S, ret < 0, &crun_err, lua_pushboolean (S, false), 1); + + lua_pushboolean (S, true); + return 1; +} + +#define luacrun_CtxStringAccessor(name, uval_idx) \ + LUA_API int luacrun_ctx_get_##name (lua_State *S) \ + { \ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); \ + if (ctx->name != NULL) \ + { \ + luaL_checkstack (S, 1, NULL); \ + lua_pushstring (S, ctx->name); \ + return 1; \ + } \ + else \ + { \ + return 0; \ + } \ + } \ + LUA_API int luacrun_ctx_set_##name (lua_State *S) \ + { \ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); \ + const char *val = luaL_optstring (S, 2, NULL); \ + luaL_checkstack (S, 2, NULL); \ + if (ctx->name != NULL) \ + { \ + lua_pushstring (S, ctx->name); \ + } \ + else \ + { \ + lua_pushnil (S); \ + } \ + const char *copy = luacrun_xstrdup (S, val); \ + ctx->name = copy; \ + lua_setiuservalue (S, 1, uval_idx); \ + return 1; \ + } + +#define luacrun_CtxBoolAccessor(name) \ + LUA_API int luacrun_ctx_get_##name (lua_State *S) \ + { \ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); \ + luaL_checkstack (S, 1, NULL); \ + lua_pushboolean (S, ctx->name); \ + return 1; \ + } \ + LUA_API int luacrun_ctx_set_##name (lua_State *S) \ + { \ + libcrun_context_t *ctx = luaL_checkudata (S, 1, LUA_CRUN_TAG_CTX); \ + luaL_checktype (S, 2, LUA_TBOOLEAN); \ + luaL_checkstack (S, 1, NULL); \ + bool oldval = ctx->name; \ + ctx->name = lua_toboolean (S, 2); \ + lua_pushboolean (S, oldval); \ + return 1; \ + } + +luacrun_CtxStringAccessor (state_root, 1); +luacrun_CtxStringAccessor (id, 2); +luacrun_CtxStringAccessor (bundle, 3); +luacrun_CtxStringAccessor (console_socket, 4); +luacrun_CtxStringAccessor (pid_file, 5); +luacrun_CtxStringAccessor (notify_socket, 6); +luacrun_CtxStringAccessor (handler, 7); + +luacrun_CtxBoolAccessor (systemd_cgroup); + +#define luacrun_RegAddCtxAccessor(method_name, name) \ + { method_name, &luacrun_ctx_get_##name }, \ + { \ + "set_" method_name, &luacrun_ctx_set_##name \ + } + +static const luaL_Reg luacrun_ctx_index[] + = { + { "run", &luacrun_ctx_run }, + { "create", &luacrun_ctx_create_container }, + { "delete", &luacrun_ctx_delete_container }, + { "kill", &luacrun_ctx_kill_container }, + { "start", &luacrun_ctx_start_container }, + { "status", &luacrun_ctx_status_container }, + { "iter_names", &luacrun_ctx_iter_containers }, + { "update", &luacrun_ctx_update_container }, + luacrun_RegAddCtxAccessor ("state_root", state_root), + luacrun_RegAddCtxAccessor ("id", id), + luacrun_RegAddCtxAccessor ("bundle", bundle), + luacrun_RegAddCtxAccessor ("console_socket", console_socket), + luacrun_RegAddCtxAccessor ("pid_file", pid_file), + luacrun_RegAddCtxAccessor ("notify_socket", notify_socket), + luacrun_RegAddCtxAccessor ("handler", handler), + luacrun_RegAddCtxAccessor ("systemd_cgroup", systemd_cgroup), + { NULL, NULL }, + }; + +LUA_API int +luacrun_setup_ctx_metatable (lua_State *S) +{ + luaL_checkstack (S, 3, NULL); + luaL_newmetatable (S, LUA_CRUN_TAG_CTX); + int mtab_idx = lua_gettop (S); + lua_newtable (S); + luaL_setfuncs (S, luacrun_ctx_index, 0); + lua_setfield (S, mtab_idx, "__index"); + lua_pop (S, 1); + return 0; +} + +LUA_API int +luacrun_setup_cont_metatable (lua_State *S) +{ + luaL_checkstack (S, 2, NULL); + luaL_newmetatable (S, LUA_CRUN_TAG_CONT); + int mtab_idx = lua_gettop (S); + lua_pushcfunction (S, &luacrun_container_finalizer); + lua_setfield (S, mtab_idx, "__gc"); + // Can we do better than a finalizer? + // Indirect pointer and wild memory make + // Lua GC could not regconize the memory usage. + lua_pop (S, 1); + return 0; +} + +static const luaL_Reg luacrun_library_reg[] = { + { .name = "new_ctx", .func = &luacrun_new_ctx }, + { .name = "container_spec", .func = &luacrun_container_spec }, + { .name = "new_container_from_string", .func = &luacrun_new_container_from_string }, + { .name = "new_container_from_file", .func = &luacrun_new_container_from_file }, + { .name = "get_verbosity", .func = &luacrun_get_verbosity }, + { .name = "set_verbosity", .func = &luacrun_set_verbosity }, + { .name = "run", .func = &luacrun_ctx_run }, + { .name = "create_container", .func = &luacrun_ctx_create_container }, + { .name = "delete_container", .func = &luacrun_ctx_delete_container }, + { .name = "kill_container", .func = &luacrun_ctx_kill_container }, + { .name = "start_container", .func = &luacrun_ctx_start_container }, + { .name = "status_container", .func = &luacrun_ctx_status_container }, + { .name = "iter_container_names", .func = &luacrun_ctx_iter_containers }, + { .name = "update_container", .func = &luacrun_ctx_update_container }, + { NULL, NULL }, +}; + +LUA_API int +luaopen_luacrun (lua_State *S) +{ + luaL_checkstack (S, 2, NULL); + luaL_newlib (S, luacrun_library_reg); + int libtab_idx = lua_gettop (S); + + lua_pushinteger (S, LIBCRUN_VERBOSITY_ERROR); + lua_setfield (S, libtab_idx, "VERBOSITY_ERROR"); + lua_pushinteger (S, LIBCRUN_VERBOSITY_WARNING); + lua_setfield (S, libtab_idx, "VERBOSITY_WARNING"); + + luacrun_setup_ctx_metatable (S); + luacrun_setup_cont_metatable (S); + luacrun_setup_ctx_iter_metatable (S); + return 1; +} diff --git a/lua/luacrun.d.tl b/lua/luacrun.d.tl new file mode 100644 index 0000000000..00f37d65fd --- /dev/null +++ b/lua/luacrun.d.tl @@ -0,0 +1,164 @@ +--[[ +Copyright (c) Rubicon Rowe + +This file is part of crun. You can choose one of the following licenses for this file. + +1. The MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +2. GNU Lesser General Public License v2.1 or later +crun is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +crun is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + NU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with crun. If not, see . +]] + +local record luacrun + VERBOSITY_ERROR: integer + VERBOSITY_WARNING: integer + + enum ContainerWorkState + "stopped" + "created" + "paused" + "running" + end + + record Ctx userdata + run: (function (ctx: Ctx, cont: Container, flags: ContainerRunFlags | nil): number | nil, string | nil) + create: (function (ctx: Ctx, cont: Container, flags: ContainerRunFlags | nil): number | nil, string | nil) + delete: (function (ctx: Ctx, id: string, force: boolean | nil): boolean, string | nil) + kill: (function (ctx: Ctx, id: string, signame: string): boolean, string | nil) + status: (function (ctx: Ctx, id: string): ContainerStat | nil, string | nil) + start: (function (ctx: Ctx, id: string): boolean, string | nil) + iter_names: (function (ctx: Ctx): any...) + update: (function (ctx: Ctx, id: string, content: string): boolean, string | nil) + + -- Accessors + -- All setters will return the old value. + state_root: function(ctx: Ctx): string | nil + set_state_root: function(ctx: Ctx, val: string | nil): string | nil + id: function(ctx: Ctx): string | nil + set_id: function(ctx: Ctx, val: string | nil): string | nil + bundle: function(ctx: Ctx) : string | nil + set_bundle: function(ctx: Ctx, val: string | nil): string | nil + console_socket: function(ctx: Ctx): string | nil + set_console_socket: function(ctx: Ctx, val: string | nil): string | nil + pid_file: function(ctx: Ctx): string | nil + set_pid_file: function(ctx: Ctx, val: string | nil): string | nil + notify_socket: function(ctx: Ctx): string | nil + set_notify_socket: function(ctx: Ctx, val: string | nil): string | nil + handler: function(ctx: Ctx): string | nil + set_handler: function(ctx: Ctx, val: string | nil): string | nil + systemd_cgroup: function(ctx: Ctx): boolean + set_systemd_cgroup: function(ctx: Ctx, val: boolean): boolean + end + + record Container userdata + end + + record ContainerStat + ociVersion: string + id: string + pid: integer + status: ContainerWorkState + bundle: string + rootfs: string + ["systemd-scope"]: string | nil + owner: string | nil + annotations: {string: string} | nil + end + + record ContainerRunFlags + prefork: boolean | nil + end + + record CtxOpts + state_root: string | nil + id: string | nil + bundle: string | nil + console_socket: string | nil + pid_file: string | nil + notify_socket: string | nil + handler: string | nil + systemd_cgroup: boolean | nil + detach: boolean | nil + args: {string} + end + + new_ctx: (function ( + opts: CtxOpts + ): Ctx) + + container_spec: (function (rootless: boolean | nil): string) + + -- New container object from spec string or spec file. + -- These functions does not create container in context (and in environment), + -- just make a new object for the spec. + new_container_from_string: (function (json_string: string): Container) + new_container_from_file: (function (file_path: string): Container) + + get_verbosity: (function (): integer) + set_verbosity: (function (new_verbosity: integer): nil) + + -- Run a container in a context. + -- Return 1 if success; `nil` and the error message if failed. + run: (function (ctx: Ctx, cont: Container, flags: ContainerRunFlags | nil): number | nil, string | nil) + + -- Create the container described by `cont`. + -- Return number (>= 0) if success; `nil` and the error message if failed + create_container: (function (ctx: Ctx, cont: Container, flags: ContainerRunFlags | nil): number | nil, string | nil) + + -- Delete the container `id`. + -- Return `true` if success; `false` and the error message if failed. + delete_container: (function (ctx: Ctx, id: string, force: boolean | nil): boolean, string | nil) + + -- Kill the container `id` by signal `signame`. + -- `signame` can be any name of supported Linux signals, or a string of the integer. + -- If it's the name, it should be upper case (`"KILL"` not `"kill"`). + -- Return `true` if success; `false` and the error message if failed. + kill_container: (function (ctx: Ctx, id: string, signame: string): boolean, string | nil) + + -- Get the container status. + -- Return a table if success; `nil` and the error message if failed. + status_container: (function (ctx: Ctx, id: string): ContainerStat | nil, string | nil) + + -- Start the container. + -- Return `true` if success; `false` and the error messgae if failed + start_container: (function (ctx: Ctx, id: string): boolean, string | nil) + + -- Iterate all container names in `ctx`. + -- Return iterator. + iter_containers_names: (function (ctx: Ctx): any...) + + -- Update the container. + -- Return `true` if success, `false` and the error message if failed. + update_container: (function (ctx: Ctx, id: string, content: string): boolean, string | nil) +end + + +return luacrun diff --git a/lua/luacrun_spec.lua b/lua/luacrun_spec.lua new file mode 100644 index 0000000000..7522317e85 --- /dev/null +++ b/lua/luacrun_spec.lua @@ -0,0 +1,176 @@ +local dkjson = require "dkjson" +local posix = require "posix" +local stdlib = require "posix.stdlib" +local unistd = require "posix.unistd" +local libgen = require "posix.libgen" + +local function is_file_exists(path) + local f = io.open(path, 'rb') + if f then + f:close() + return true + else + return false + end +end + +local function makedirs(path) + if not is_file_exists(path) then + makedirs(libgen.dirname(path)) + local path, errmsg = posix.mkdir(path) + assert(path, errmsg) + end +end + +local function cp(src, dest) + local stat, exitm, retc = os.execute( + string.format("cp \"%s\" \"%s\"", src, dest)) + return retc +end + +local function mktestenv() + local format = string.format + local init_path = os.getenv("INIT") + assert(init_path, "no INIT env var for init program") + local path, errmsg = stdlib.mkdtemp("/tmp/luacrun-test-XXXXXX") + assert(path, errmsg) + local rootfs_path = string.format("%s/%s", path, "rootfs") + makedirs(rootfs_path) + for i, p in ipairs({ + "usr/bin", "etc", "var", "lib", "lib64", "usr/share/zoneinfo/Europe", + "proc", "sys", "dev" + }) do makedirs(string.format("%s/%s", rootfs_path, p)) end + local sbin_path = string.format("%s/%s", rootfs_path, "sbin") + makedirs(sbin_path) + + local retc = cp(init_path, format("%s/%s", rootfs_path, "init")) + assert(retc == 0, "cp init to rootfs return " .. retc) + local retc = cp(init_path, format("%s/%s", sbin_path, "init")) + assert(retc == 0, "cp init to sbin return " .. retc) + + unistd.link("../usr/share/zoneinfo/Europe/Rome", + format("%s/%s", rootfs_path, "etc/localtime")) + + local passwd, errmsg = io.open(format("%s/%s", rootfs_path, + "usr/share/passwd"), "w") + assert(passwd, errmsg) + passwd, errmsg = passwd:write("root:x:0:0:root:/root:/bin/bash\n") + assert(passwd, errmsg) + passwd:close() + unistd.link("../usr/share/passwd", + format("%s/%s", rootfs_path, "etc/passwd")) + return path +end + +insulate("luacrun", function() + local luacrun = require "luacrun" + + describe("container_spec", function() + it("returns a string", function() + local s = luacrun.container_spec() + assert.are.equals("string", type(s)) + end) + + it("returns a json object", function() + local s = luacrun.container_spec() + local t = dkjson.decode(s) + assert.are.equals("table", type(t)) + end) + end) + + describe("new_ctx", function() + it("can create context with only id", function() + local ctx = luacrun.new_ctx {id = "luacrun-test"} + assert.are.equals("userdata", type(ctx)) + end) + end) + + describe("new_container_from_string", function() + it("can create container object from json string", function() + local spec = dkjson.decode(luacrun.container_spec()) + spec.root.path = "/" + spec.process.args = {'/bin/echo', "Hello World!"} + local cont, err = luacrun.new_container_from_string(dkjson.encode( + spec)) + assert(cont, err) + end) + end) + + describe("create_container", function() + it("is same to ctx.create", function() + local ctx = luacrun.new_ctx {id = "luacrun-test"} + assert.are.equals(ctx.create, luacrun.create_container) + end) + end) + + it("can create container and delete container", function() + local temproot = mktestenv() + local ctx = luacrun.new_ctx {state_root = temproot, id = "luacrun-test"} + assert(ctx) + local spec = + dkjson.decode(luacrun.container_spec(unistd.geteuid() == 0)) + spec.root = { + path = string.format("%s/%s", temproot, "rootfs"), + readonly = true + } + spec.process.args = {'/init', "true"} + spec.process.terminal = false + spec.process.user = {uid = unistd.geteuid(), gid = unistd.getegid()} + spec.linux.rootfsPropagation = "rprivate" + local json_spec = dkjson.encode(spec) + local cont, err = luacrun.new_container_from_string(json_spec) + assert(cont, err) + local stat, err = luacrun.create_container(ctx, cont) + assert(stat == 0, string.format("%s, %d", err, stat)) + local names = {} + for i, name in ctx:iter_names() do names[#names + 1] = name end + assert(#names == 1, string.format("names length is %d", #names)) + local status, err = ctx:status(names[1]) + assert(status, err) + assert.are.equals("created", status.status) + local stat, err = ctx:start(names[1]) + assert(stat, err) + local status, err = ctx:status(names[1]) + assert(status, err) + assert.are.equals("running", status.status) + while true do -- wait for container exited + local status, err = ctx:status(names[1]) + assert(status, err) + if status.status == "created" or status.status == "stopped" then + break + end + end + local stat, err = luacrun.delete_container(ctx, names[1]) + assert(stat, err) + end) + + describe("Ctx", function() + it("id() can get id", function() + local ctx = luacrun.new_ctx {id = "luacrun-test"} + assert.are.equals("luacrun-test", ctx:id()) + end) + + it("id() can read the id set by set_id()", function() + local ctx = luacrun.new_ctx {id = "luacrun-test1"} + assert.are.equals("luacrun-test1", ctx:id()) + ctx:set_id("luacrun-test2") + assert.are.equals("luacrun-test2", ctx:id()) + end) + + it("systemd_cgroup() can get a boolean", function() + local ctx = luacrun.new_ctx {id = "luacrun-test"} + assert.are.equals("boolean", type(ctx:systemd_cgroup())) + end) + + it("systemd_cgroup() can read the value set by set_system_cgroupd()", + function() + local ctx = luacrun.new_ctx { + id = "luacrun-test", + systemd_cgroup = false + } + assert.is_false(ctx:systemd_cgroup()) + ctx:set_systemd_cgroup(true) + assert.is_true(ctx:systemd_cgroup()) + end) + end) +end) diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4 new file mode 100644 index 0000000000..f007053f10 --- /dev/null +++ b/m4/ax_lua.m4 @@ -0,0 +1,663 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_lua.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# +# DESCRIPTION +# +# Detect a Lua interpreter, optionally specifying a minimum and maximum +# version number. Set up important Lua paths, such as the directories in +# which to install scripts and modules (shared libraries). +# +# Also detect Lua headers and libraries. The Lua version contained in the +# header is checked to match the Lua interpreter version exactly. When +# searching for Lua libraries, the version number is used as a suffix. +# This is done with the goal of supporting multiple Lua installs (5.1, +# 5.2, and 5.3 side-by-side). +# +# A note on compatibility with previous versions: This file has been +# mostly rewritten for serial 18. Most developers should be able to use +# these macros without needing to modify configure.ac. Care has been taken +# to preserve each macro's behavior, but there are some differences: +# +# 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as +# AX_PROG_LUA with no arguments. +# +# 2) AX_LUA_HEADERS now checks that the version number defined in lua.h +# matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore +# unnecessary, so it is deprecated and does not expand to anything. +# +# 3) The configure flag --with-lua-suffix no longer exists; the user +# should instead specify the LUA precious variable on the command line. +# See the AX_PROG_LUA description for details. +# +# Please read the macro descriptions below for more information. +# +# This file was inspired by Andrew Dalke's and James Henstridge's +# python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4 +# (serial 17). Basically, this file is a mash-up of those two files. I +# like to think it combines the best of the two! +# +# AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua +# paths. Adds precious variable LUA, which may contain the path of the Lua +# interpreter. If LUA is blank, the user's path is searched for an +# suitable interpreter. +# +# If MINIMUM-VERSION is supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION will be accepted. If +# TOO-BIG-VERSION is also supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION and less than +# TOO-BIG-VERSION will be accepted. +# +# The Lua version number, LUA_VERSION, is found from the interpreter, and +# substituted. LUA_PLATFORM is also found, but not currently supported (no +# standard representation). +# +# Finally, the macro finds four paths: +# +# luadir Directory to install Lua scripts. +# pkgluadir $luadir/$PACKAGE +# luaexecdir Directory to install Lua modules. +# pkgluaexecdir $luaexecdir/$PACKAGE +# +# These paths are found based on $prefix, $exec_prefix, Lua's +# package.path, and package.cpath. The first path of package.path +# beginning with $prefix is selected as luadir. The first path of +# package.cpath beginning with $exec_prefix is used as luaexecdir. This +# should work on all reasonable Lua installations. If a path cannot be +# determined, a default path is used. Of course, the user can override +# these later when invoking make. +# +# luadir Default: $prefix/share/lua/$LUA_VERSION +# luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION +# +# These directories can be used by Automake as install destinations. The +# variable name minus 'dir' needs to be used as a prefix to the +# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES. +# +# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT- +# FOUND is blank, then it will default to printing an error. To prevent +# the default behavior, give ':' as an action. +# +# AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_INCLUDE, which +# may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If +# LUA_INCLUDE is blank, then this macro will attempt to find suitable +# flags. +# +# LUA_INCLUDE can be used by Automake to compile Lua modules or +# executables with embedded interpreters. The *_CPPFLAGS variables should +# be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE). +# +# This macro searches for the header lua.h (and others). The search is +# performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE. +# If the search is unsuccessful, then some common directories are tried. +# If the headers are then found, then LUA_INCLUDE is set accordingly. +# +# The paths automatically searched are: +# +# * /usr/include/luaX.Y +# * /usr/include/lua/X.Y +# * /usr/include/luaXY +# * /usr/local/include/luaX.Y +# * /usr/local/include/lua-X.Y +# * /usr/local/include/lua/X.Y +# * /usr/local/include/luaXY +# +# (Where X.Y is the Lua version number, e.g. 5.1.) +# +# The Lua version number found in the headers is always checked to match +# the Lua interpreter's version number. Lua headers with mismatched +# version numbers are not accepted. +# +# If headers are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_LIB, which may +# contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank, +# then this macro will attempt to find suitable flags. +# +# LUA_LIB can be used by Automake to link Lua modules or executables with +# embedded interpreters. The *_LIBADD and *_LDADD variables should be used +# for this purpose, e.g. mymod_LIBADD = $(LUA_LIB). +# +# This macro searches for the Lua library. More technically, it searches +# for a library containing the function lua_load. The search is performed +# with a combination of LIBS, LIBRARY_PATH, and LUA_LIB. +# +# If the search determines that some linker flags are missing, then those +# flags will be added to LUA_LIB. +# +# If libraries are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_READLINE: Search for readline headers and libraries. Requires the +# AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the +# Autoconf Archive. +# +# If a readline compatible library is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is performed. +# +# LICENSE +# +# Copyright (c) 2015 Reuben Thomas +# Copyright (c) 2014 Tim Perkins +# Copyright (c) Rubicon Rowe +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. +# +# Changelog +# * Supported Lua 5.4 (Rubicon Rowe) + +#serial 42 + +dnl ========================================================================= +dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION], +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_PROG_LUA], +[ + dnl Check for required tools. + AC_REQUIRE([AC_PROG_GREP]) + AC_REQUIRE([AC_PROG_SED]) + + dnl Make LUA a precious variable. + AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1]) + + dnl Find a Lua interpreter. + m4_define_default([_AX_LUA_INTERPRETER_LIST], + [lua lua5.4 lua54 lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50]) + + m4_if([$1], [], + [ dnl No version check is needed. Find any Lua interpreter. + AS_IF([test "x$LUA" = 'x'], + [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])]) + ax_display_LUA='lua' + + AS_IF([test "x$LUA" != 'x:'], + [ dnl At least check if this is a Lua interpreter. + AC_MSG_CHECKING([if $LUA is a Lua interpreter]) + _AX_LUA_CHK_IS_INTRP([$LUA], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([not a Lua interpreter]) + ]) + ]) + ], + [ dnl A version check is needed. + AS_IF([test "x$LUA" != 'x'], + [ dnl Check if this is a Lua interpreter. + AC_MSG_CHECKING([if $LUA is a Lua interpreter]) + _AX_LUA_CHK_IS_INTRP([$LUA], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([not a Lua interpreter]) + ]) + dnl Check the version. + m4_if([$2], [], + [_ax_check_text="whether $LUA version >= $1"], + [_ax_check_text="whether $LUA version >= $1, < $2"]) + AC_MSG_CHECKING([$_ax_check_text]) + _AX_LUA_CHK_VER([$LUA], [$1], [$2], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([version is out of range for specified LUA])]) + ax_display_LUA=$LUA + ], + [ dnl Try each interpreter until we find one that satisfies VERSION. + m4_if([$2], [], + [_ax_check_text="for a Lua interpreter with version >= $1"], + [_ax_check_text="for a Lua interpreter with version >= $1, < $2"]) + AC_CACHE_CHECK([$_ax_check_text], + [ax_cv_pathless_LUA], + [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do + test "x$ax_cv_pathless_LUA" = 'xnone' && break + _AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue]) + _AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break]) + done + ]) + dnl Set $LUA to the absolute path of $ax_cv_pathless_LUA. + AS_IF([test "x$ax_cv_pathless_LUA" = 'xnone'], + [LUA=':'], + [AC_PATH_PROG([LUA], [$ax_cv_pathless_LUA])]) + ax_display_LUA=$ax_cv_pathless_LUA + ]) + ]) + + AS_IF([test "x$LUA" = 'x:'], + [ dnl Run any user-specified action, or abort. + m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])]) + ], + [ dnl Query Lua for its version number. + AC_CACHE_CHECK([for $ax_display_LUA version], + [ax_cv_lua_version], + [ dnl Get the interpreter version in X.Y format. This should work for + dnl interpreters version 5.0 and beyond. + ax_cv_lua_version=[`$LUA -e ' + -- return a version number in X.Y format + local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)") + print(ver)'`] + ]) + AS_IF([test "x$ax_cv_lua_version" = 'x'], + [AC_MSG_ERROR([invalid Lua version number])]) + AC_SUBST([LUA_VERSION], [$ax_cv_lua_version]) + AC_SUBST([LUA_SHORT_VERSION], [`echo "$LUA_VERSION" | $SED 's|\.||'`]) + + dnl The following check is not supported: + dnl At times (like when building shared libraries) you may want to know + dnl which OS platform Lua thinks this is. + AC_CACHE_CHECK([for $ax_display_LUA platform], + [ax_cv_lua_platform], + [ax_cv_lua_platform=[`$LUA -e 'print("unknown")'`]]) + AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform]) + + dnl Use the values of $prefix and $exec_prefix for the corresponding + dnl values of LUA_PREFIX and LUA_EXEC_PREFIX. These are made distinct + dnl variables so they can be overridden if need be. However, the general + dnl consensus is that you shouldn't need this ability. + AC_SUBST([LUA_PREFIX], ['${prefix}']) + AC_SUBST([LUA_EXEC_PREFIX], ['${exec_prefix}']) + + dnl Lua provides no way to query the script directory, and instead + dnl provides LUA_PATH. However, we should be able to make a safe educated + dnl guess. If the built-in search path contains a directory which is + dnl prefixed by $prefix, then we can store scripts there. The first + dnl matching path will be used. + AC_CACHE_CHECK([for $ax_display_LUA script directory], + [ax_cv_lua_luadir], + [ AS_IF([test "x$prefix" = 'xNONE'], + [ax_lua_prefix=$ac_default_prefix], + [ax_lua_prefix=$prefix]) + + dnl Initialize to the default path. + ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION" + + dnl Try to find a path with the prefix. + _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [script]) + AS_IF([test "x$ax_lua_prefixed_path" != 'x'], + [ dnl Fix the prefix. + _ax_strip_prefix=`echo "$ax_lua_prefix" | $SED 's|.|.|g'` + ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \ + $SED "s|^$_ax_strip_prefix|$LUA_PREFIX|"` + ]) + ]) + AC_SUBST([luadir], [$ax_cv_lua_luadir]) + AC_SUBST([pkgluadir], [\${luadir}/$PACKAGE]) + + dnl Lua provides no way to query the module directory, and instead + dnl provides LUA_PATH. However, we should be able to make a safe educated + dnl guess. If the built-in search path contains a directory which is + dnl prefixed by $exec_prefix, then we can store modules there. The first + dnl matching path will be used. + AC_CACHE_CHECK([for $ax_display_LUA module directory], + [ax_cv_lua_luaexecdir], + [ AS_IF([test "x$exec_prefix" = 'xNONE'], + [ax_lua_exec_prefix=$ax_lua_prefix], + [ax_lua_exec_prefix=$exec_prefix]) + + dnl Initialize to the default path. + ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION" + + dnl Try to find a path with the prefix. + _AX_LUA_FND_PRFX_PTH([$LUA], + [$ax_lua_exec_prefix], [module]) + AS_IF([test "x$ax_lua_prefixed_path" != 'x'], + [ dnl Fix the prefix. + _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | $SED 's|.|.|g'` + ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \ + $SED "s|^$_ax_strip_prefix|$LUA_EXEC_PREFIX|"` + ]) + ]) + AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir]) + AC_SUBST([pkgluaexecdir], [\${luaexecdir}/$PACKAGE]) + + dnl Run any user specified action. + $3 + ]) +]) + +dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA. +AC_DEFUN([AX_WITH_LUA], +[ + AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA instead]]) + AX_PROG_LUA +]) + + +dnl ========================================================================= +dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_CHK_IS_INTRP], +[ + dnl A minimal Lua factorial to prove this is an interpreter. This should work + dnl for Lua interpreters version 5.0 and beyond. + _ax_lua_factorial=[`$1 2>/dev/null -e ' + -- a simple factorial + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + print("fact(5) is " .. fact(5))'`] + AS_IF([test "$_ax_lua_factorial" = 'fact(5) is 120'], + [$2], [$3]) +]) + + +dnl ========================================================================= +dnl _AX_LUA_CHK_VER(PROG, MINIMUM-VERSION, [TOO-BIG-VERSION], +dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_CHK_VER], +[ + dnl Check that the Lua version is within the bounds. Only the major and minor + dnl version numbers are considered. This should work for Lua interpreters + dnl version 5.0 and beyond. + _ax_lua_good_version=[`$1 -e ' + -- a script to compare versions + function verstr2num(verstr) + local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") + if majorver and minorver then + return tonumber(majorver) * 100 + tonumber(minorver) + end + end + local minver = verstr2num("$2") + local _, _, trimver = string.find(_VERSION, "^Lua (.*)") + local ver = verstr2num(trimver) + local maxver = verstr2num("$3") or 1e9 + if minver <= ver and ver < maxver then + print("yes") + else + print("no") + end'`] + AS_IF([test "x$_ax_lua_good_version" = "xyes"], + [$4], [$5]) +]) + + +dnl ========================================================================= +dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, SCRIPT-OR-MODULE-DIR) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_FND_PRFX_PTH], +[ + dnl Get the script or module directory by querying the Lua interpreter, + dnl filtering on the given prefix, and selecting the shallowest path. If no + dnl path is found matching the prefix, the result will be an empty string. + dnl The third argument determines the type of search, it can be 'script' or + dnl 'module'. Supplying 'script' will perform the search with package.path + dnl and LUA_PATH, and supplying 'module' will search with package.cpath and + dnl LUA_CPATH. This is done for compatibility with Lua 5.0. + + ax_lua_prefixed_path=[`$1 -e ' + -- get the path based on search type + local searchtype = "$3" + local paths = "" + if searchtype == "script" then + paths = (package and package.path) or LUA_PATH + elseif searchtype == "module" then + paths = (package and package.cpath) or LUA_CPATH + end + -- search for the prefix + local prefix = "'$2'" + local minpath = "" + local mindepth = 1e9 + string.gsub(paths, "(@<:@^;@:>@+)", + function (path) + path = string.gsub(path, "%?.*$", "") + path = string.gsub(path, "/@<:@^/@:>@*$", "") + if string.find(path, prefix) then + local depth = string.len(string.gsub(path, "@<:@^/@:>@", "")) + if depth < mindepth then + minpath = path + mindepth = depth + end + end + end) + print(minpath)'`] +]) + + +dnl ========================================================================= +dnl AX_LUA_HEADERS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_HEADERS], +[ + dnl Check for LUA_VERSION. + AC_MSG_CHECKING([if LUA_VERSION is defined]) + AS_IF([test "x$LUA_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION]) + ]) + + dnl Make LUA_INCLUDE a precious variable. + AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1]) + + dnl Some default directories to search. + LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'` + m4_define_default([_AX_LUA_INCLUDE_LIST], + [ /usr/include/lua$LUA_VERSION \ + /usr/include/lua-$LUA_VERSION \ + /usr/include/lua/$LUA_VERSION \ + /usr/include/lua$LUA_SHORT_VERSION \ + /usr/local/include/lua$LUA_VERSION \ + /usr/local/include/lua-$LUA_VERSION \ + /usr/local/include/lua/$LUA_VERSION \ + /usr/local/include/lua$LUA_SHORT_VERSION \ + ]) + + dnl Try to find the headers. + _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" + AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) + CPPFLAGS=$_ax_lua_saved_cppflags + + dnl Try some other directories if LUA_INCLUDE was not set. + AS_IF([test "x$LUA_INCLUDE" = 'x' && + test "x$ac_cv_header_lua_h" != 'xyes'], + [ dnl Try some common include paths. + for _ax_include_path in _AX_LUA_INCLUDE_LIST; do + test ! -d "$_ax_include_path" && continue + + AC_MSG_CHECKING([for Lua headers in]) + AC_MSG_RESULT([$_ax_include_path]) + + AS_UNSET([ac_cv_header_lua_h]) + AS_UNSET([ac_cv_header_lualib_h]) + AS_UNSET([ac_cv_header_lauxlib_h]) + AS_UNSET([ac_cv_header_luaconf_h]) + + _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -I$_ax_include_path" + AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) + CPPFLAGS=$_ax_lua_saved_cppflags + + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], + [ LUA_INCLUDE="-I$_ax_include_path" + break + ]) + done + ]) + + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], + [ dnl Make a program to print LUA_VERSION defined in the header. + dnl TODO It would be really nice if we could do this without compiling a + dnl program, then it would work when cross compiling. But I'm not sure how + dnl to do this reliably. For now, assume versions match when cross compiling. + + AS_IF([test "x$cross_compiling" != 'xyes'], + [ AC_CACHE_CHECK([for Lua header version], + [ax_cv_lua_header_version], + [ _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" + AC_COMPUTE_INT(ax_cv_lua_header_version_major,[LUA_VERSION_NUM/100],[AC_INCLUDES_DEFAULT +#include +],[ax_cv_lua_header_version_major=unknown]) + AC_COMPUTE_INT(ax_cv_lua_header_version_minor,[LUA_VERSION_NUM%100],[AC_INCLUDES_DEFAULT +#include +],[ax_cv_lua_header_version_minor=unknown]) + AS_IF([test "x$ax_cv_lua_header_version_major" = xunknown || test "x$ax_cv_lua_header_version_minor" = xunknown],[ + ax_cv_lua_header_version=unknown + ],[ + ax_cv_lua_header_version="$ax_cv_lua_header_version_major.$ax_cv_lua_header_version_minor" + ]) + CPPFLAGS=$_ax_lua_saved_cppflags + ]) + + dnl Compare this to the previously found LUA_VERSION. + AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION]) + AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"], + [ AC_MSG_RESULT([yes]) + ax_header_version_match='yes' + ], + [ AC_MSG_RESULT([no]) + ax_header_version_match='no' + ]) + ], + [ AC_MSG_WARN([cross compiling so assuming header version number matches]) + ax_header_version_match='yes' + ]) + ]) + + dnl Was LUA_INCLUDE specified? + AS_IF([test "x$ax_header_version_match" != 'xyes' && + test "x$LUA_INCLUDE" != 'x'], + [AC_MSG_ERROR([cannot find headers for specified LUA_INCLUDE])]) + + dnl Test the final result and run user code. + AS_IF([test "x$ax_header_version_match" = 'xyes'], [$1], + [m4_default([$2], [AC_MSG_ERROR([cannot find Lua includes])])]) +]) + +dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS. +AC_DEFUN([AX_LUA_HEADERS_VERSION], +[ + AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS instead]]) +]) + + +dnl ========================================================================= +dnl AX_LUA_LIBS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_LIBS], +[ + dnl TODO Should this macro also check various -L flags? + + dnl Check for LUA_VERSION. + AC_MSG_CHECKING([if LUA_VERSION is defined]) + AS_IF([test "x$LUA_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua libs without knowing LUA_VERSION]) + ]) + + dnl Make LUA_LIB a precious variable. + AC_ARG_VAR([LUA_LIB], [The Lua library, e.g. -llua5.1]) + + AS_IF([test "x$LUA_LIB" != 'x'], + [ dnl Check that LUA_LIBS works. + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([lua_load], [], + [_ax_found_lua_libs='yes'], + [_ax_found_lua_libs='no']) + LIBS=$_ax_lua_saved_libs + + dnl Check the result. + AS_IF([test "x$_ax_found_lua_libs" != 'xyes'], + [AC_MSG_ERROR([cannot find libs for specified LUA_LIB])]) + ], + [ dnl First search for extra libs. + _ax_lua_extra_libs='' + + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([exp], [m]) + AC_SEARCH_LIBS([dlopen], [dl]) + LIBS=$_ax_lua_saved_libs + + AS_IF([test "x$ac_cv_search_exp" != 'xno' && + test "x$ac_cv_search_exp" != 'xnone required'], + [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp"]) + + AS_IF([test "x$ac_cv_search_dlopen" != 'xno' && + test "x$ac_cv_search_dlopen" != 'xnone required'], + [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen"]) + + dnl Try to find the Lua libs. + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([lua_load], + [ lua$LUA_VERSION \ + lua$LUA_SHORT_VERSION \ + lua-$LUA_VERSION \ + lua-$LUA_SHORT_VERSION \ + lua \ + ], + [_ax_found_lua_libs='yes'], + [_ax_found_lua_libs='no'], + [$_ax_lua_extra_libs]) + LIBS=$_ax_lua_saved_libs + + AS_IF([test "x$ac_cv_search_lua_load" != 'xno' && + test "x$ac_cv_search_lua_load" != 'xnone required'], + [LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs"]) + ]) + + dnl Test the result and run user code. + AS_IF([test "x$_ax_found_lua_libs" = 'xyes'], [$1], + [m4_default([$2], [AC_MSG_ERROR([cannot find Lua libs])])]) +]) + + +dnl ========================================================================= +dnl AX_LUA_READLINE([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_READLINE], +[ + AX_LIB_READLINE + AS_IF([test "x$ac_cv_header_readline_readline_h" != 'x' && + test "x$ac_cv_header_readline_history_h" != 'x'], + [ LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS" + $1 + ], + [$2]) +])