Commit d2bed913 authored by Garrett Regier's avatar Garrett Regier

Use Lua to implement the plugin loader's logic

This allows us to avoid using Lua's C API and have a
more understandable implementation.

https://bugzilla.gnome.org/show_bug.cgi?id=742410
parent 2be61ccc
*.bak
*.lo
*.la
*.luac
*.o
*.pyc
*.gir
......@@ -51,6 +52,7 @@ Makefile.in
/intltool-update.in
/libtool
/ltmain.sh
/loaders/lua5.1/peas-lua-resources.c
/loaders/python/peas-python-resources.c
/loaders/python3/peas-python3-resources.c
/m4
......
......@@ -323,8 +323,10 @@ else
else
AC_MSG_RESULT([$found_lua51 ($with_lua51)])
LUA51_BIN="$with_lua51"
LUA51_CFLAGS=`$PKG_CONFIG --cflags $with_lua51`
LUA51_LIBS=`$PKG_CONFIG --libs $with_lua51`
AC_SUBST(LUA51_BIN)
AC_SUBST(LUA51_CFLAGS)
AC_SUBST(LUA51_LIBS)
......
......@@ -13,6 +13,9 @@ AM_CPPFLAGS = \
loader_LTLIBRARIES = liblua51loader.la
liblua51loader_la_SOURCES = \
peas-lua-internal.c \
peas-lua-internal.h \
peas-lua-resources.c \
peas-plugin-loader-lua.c \
peas-plugin-loader-lua.h \
peas-lua-utils.c \
......@@ -27,5 +30,23 @@ liblua51loader_la_LIBADD = \
$(PEAS_LIBS) \
$(LUA51_LIBS)
%.luac: %.lua
$(AM_V_GEN) $(LUA51_BIN) $(srcdir)/peas-lua-compile.lua $< $@
all-local: peas-lua-internal.luac
loader_resources_deps = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(srcdir)/peas-lua.gresource.xml)
peas-lua-resources.c: $(srcdir)/peas-lua.gresource.xml $(loader_resources_deps)
$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --internal --target=$@ --sourcedir=$(srcdir) --generate-source $(srcdir)/peas-lua.gresource.xml
EXTRA_DIST = \
peas-lua-compile.lua \
peas-lua.gresource.xml \
$(loader_resources_deps)
CLEANFILES = \
peas-lua-internal.luac \
peas-lua-resources.c
gcov_sources = $(liblua51loader_la_SOURCES)
include $(top_srcdir)/Makefile.gcov
--
-- Copyright (C) 2015 - Garrett Regier
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Library General Public License as published by
-- the Free Software Foundation; either version 2 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 Library General Public License for more details.
--
-- You should have received a copy of the GNU Library General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor,
-- Boston, MA 02110-1301, USA.
local io = require 'io'
local os = require 'os'
local function check(err, format, ...)
if err == nil then
return
end
io.stderr:write(('Error: %s:\n%s\n'):format(format:format(...), err))
os.exit(1)
end
local function main(arg)
for i = 1, #arg, 2 do
local filename = arg[i]
local output = arg[i + 1]
local input_file, err = io.open(filename, 'rb')
check(err, 'Failed to open file "%s"', filename)
-- Error includes the filename
local compiled, err = loadstring(input_file:read('*a'),
'@' .. filename)
check(err, 'Invalid Lua file')
local f, err = io.open(output, 'wb')
check(err, 'Failed to open file "%s"', output)
local success, err = f:write(string.dump(compiled))
check(err, 'Failed to write to "%s"', output)
local success, err = f:close()
check(err, 'Failed to save "%s"', output)
end
end
os.exit(main(arg) or 0)
-- ex:ts=4:et:
/*
* peas-lua-internal.c
* This file is part of libpeas
*
* Copyright (C) 2015 - Garrett Regier
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
* the Free Software Foundation; either version 2 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "peas-lua-internal.h"
#include <gio/gio.h>
#include <lauxlib.h>
#include <lualib.h>
#include "peas-lua-utils.h"
static gpointer hooks_key = NULL;
static gpointer failed_err_key = NULL;
static int
failed_fn (lua_State *L)
{
gchar *msg;
/* The first parameter is the Hooks table instance */
luaL_checktype (L, 1, LUA_TTABLE);
/* The tracebacks have a trailing newline */
msg = g_strchomp (g_strdup (luaL_checkstring (L, 2)));
g_warning ("%s", msg);
/* peas_lua_internal_call() knows to check for this value */
lua_pushlightuserdata (L, &failed_err_key);
g_free (msg);
return lua_error (L);
}
gboolean
peas_lua_internal_setup (lua_State *L)
{
GBytes *internal_lua;
const gchar *code;
gsize code_len;
/* We don't use the byte-compiled Lua source
* because glib-compile-resources cannot output
* depends for generated files.
*
* There are also concerns that the bytecode is
* not stable enough between different Lua versions.
*
* https://bugzilla.gnome.org/show_bug.cgi?id=673101
*/
internal_lua = g_resources_lookup_data ("/org/gnome/libpeas/loaders/"
"lua5.1/internal.lua",
G_RESOURCE_LOOKUP_FLAGS_NONE,
NULL);
g_return_val_if_fail (internal_lua != NULL, FALSE);
code = g_bytes_get_data (internal_lua, &code_len);
/* Filenames are prefixed with '@' */
if (luaL_loadbuffer (L, code, code_len, "@peas-lua-internal.lua") != 0)
{
g_warning ("Failed to load internal Lua code: %s",
lua_tostring (L, -1));
/* Pop error */
lua_pop (L, 1);
g_bytes_unref (internal_lua);
return FALSE;
}
g_bytes_unref (internal_lua);
if (!peas_lua_utils_call (L, 0, 1))
{
g_warning ("Failed to run internal Lua code: %s",
lua_tostring (L, -1));
/* Pop error */
lua_pop (L, 1);
return FALSE;
}
if (!lua_istable (L, -1))
{
g_warning ("Invalid result from internal Lua code: %s",
lua_tostring (L, -1));
/* Pop result */
lua_pop (L, 1);
return FALSE;
}
/* Set Hooks.failed to failed_fn */
lua_pushcfunction (L, failed_fn);
lua_setfield (L, -2, "failed");
/* Set registry[&hooks_key] = hooks */
lua_pushlightuserdata (L, &hooks_key);
lua_pushvalue (L, -2);
lua_rawset (L, LUA_REGISTRYINDEX);
/* Pop hooks */
lua_pop (L, -1);
return TRUE;
}
void
peas_lua_internal_shutdown (lua_State *L)
{
lua_pushlightuserdata (L, &hooks_key);
lua_pushnil (L);
lua_rawset (L, LUA_REGISTRYINDEX);
}
gboolean
peas_lua_internal_call (lua_State *L,
const gchar *name,
guint n_args,
gint return_type)
{
/* Get the Hooks table */
lua_pushlightuserdata (L, &hooks_key);
lua_rawget (L, LUA_REGISTRYINDEX);
/* Get the method */
lua_getfield (L, -1, name);
/* Swap the method and the table */
lua_insert (L, -2);
if (n_args > 0)
{
/* Before: [args..., method, table]
* After: [method, table, args...]
*/
lua_insert (L, -n_args - 2);
lua_insert (L, -n_args - 2);
}
if (!peas_lua_utils_call (L, 1 + n_args, 1))
{
/* Raised by failed_fn() to prevent printing the error */
if (!lua_isuserdata (L, -1) ||
lua_touserdata (L, -1) != &failed_err_key)
{
g_warning ("Failed to run internal Lua hook '%s':\n%s",
name, lua_tostring (L, -1));
}
/* Pop the error */
lua_pop (L, 1);
return FALSE;
}
if (lua_type (L, -1) != return_type)
{
/* Don't warn for a nil result */
if (lua_type (L, -1) != LUA_TNIL)
{
g_warning ("Invalid return value for internal Lua hook '%s': "
"expected %s, got: %s (%s)", name,
lua_typename (L, return_type),
lua_typename (L, lua_type (L, -1)),
lua_tostring (L, -1));
}
/* Pop result */
lua_pop (L, 1);
return FALSE;
}
/* Pop the result if nil */
if (return_type == LUA_TNIL)
lua_pop (L, 1);
return TRUE;
}
/*
* peas-lua-internal.h
* This file is part of libpeas
*
* Copyright (C) 2015 - Garrett Regier
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
* the Free Software Foundation; either version 2 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __PEAS_LUA_INTERNAL_H__
#define __PEAS_LUA_INTERNAL_H__
#include <glib.h>
#include <lua.h>
G_BEGIN_DECLS
gboolean peas_lua_internal_setup (lua_State *L);
void peas_lua_internal_shutdown (lua_State *L);
gboolean peas_lua_internal_call (lua_State *L,
const gchar *name,
guint n_args,
gint return_type);
G_END_DECLS
#endif /* __PEAS_LUA_INTERNAL_H__ */
--
-- Copyright (C) 2015 - Garrett Regier
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Library General Public License as published by
-- the Free Software Foundation; either version 2 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 Library General Public License for more details.
--
-- You should have received a copy of the GNU Library General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor,
-- Boston, MA 02110-1301, USA.
local debug = require 'debug'
local package = require 'package'
local lgi = require 'lgi'
local GObject = lgi.GObject
local Peas = lgi.Peas
local Hooks = {}
Hooks.__index = Hooks
function Hooks.new()
local self = { priv = {} }
setmetatable(self, Hooks)
self.priv.module_cache = {}
self.priv.extension_cache = {}
return self
end
function Hooks:failed(msg)
-- This is implemented by the plugin loader
error('Hooks:failed() was not implemented!')
end
local function add_package_path(package_path)
local paths = (';%s/?.lua;%s/?/init.lua'):format(package_path,
package_path)
if not package.path:find(paths, 1, true) then
package.path = package.path .. paths
end
end
local function format_plugin_exception(err)
-- Format the error even if given a userdata
local formatted = debug.traceback(tostring(err), 2)
if type(formatted) ~= 'string' then
return formatted
end
-- Remove all mentions of this file
local lines = {}
for line in formatted:gmatch('([^\n]+\n?)') do
if line:find('peas-lua-internal.lua', 1, true) then
break
end
table.insert(lines, line)
end
return table.concat(lines, '')
end
function Hooks:load(filename, module_dir, module_name)
local module = self.priv.module_cache[filename]
if module ~= nil then
return module ~= false
end
if package.loaded[module_name] ~= nil then
local msg = ("Error loading plugin '%s': " ..
"module name '%s' has already been used")
self:failed(msg:format(filename, module_name))
end
add_package_path(module_dir)
local success, result = xpcall(function()
return require(module_name)
end, format_plugin_exception)
if not success then
local msg = "Error loading plugin '%s':\n%s"
self:failed(msg:format(module_name, tostring(result)))
end
if type(result) ~= 'table' then
self.priv.module_cache[filename] = false
local msg = "Error loading plugin '%s': expected table, got: %s (%s)"
self:failed(msg:format(module_name, type(result), tostring(result)))
end
self.priv.module_cache[filename] = result
self.priv.extension_cache[filename] = {}
return true
end
function Hooks:find_extension_type(filename, gtype)
local module_gtypes = self.priv.extension_cache[filename]
local extension_type = module_gtypes[gtype]
if extension_type ~= nil then
if extension_type == false then
return nil
end
return extension_type
end
for _, value in pairs(self.priv.module_cache[filename]) do
local value_gtype = value._gtype
if value_gtype ~= nil then
if GObject.type_is_a(value_gtype, gtype) then
module_gtypes[gtype] = value_gtype
return value_gtype
end
end
end
module_gtypes[gtype] = false
return nil
end
local function check_native(native, wrapped, typename)
local msg = ('Invalid wrapper for %s: %s'):format(typename,
tostring(wrapped))
-- Cannot compare userdata directly!
assert(wrapped ~= nil, msg)
assert(tostring(native) == tostring(wrapped._native), msg)
end
function Hooks:setup_extension(exten, info)
local wrapped_exten = GObject.Object(exten, false)
check_native(exten, wrapped_exten, 'extension')
local wrapped_info = Peas.PluginInfo(info, false)
check_native(info, wrapped_info, 'PeasPluginInfo')
wrapped_exten.priv.plugin_info = wrapped_info
end
function Hooks:garbage_collect()
collectgarbage()
end
return Hooks.new()
-- ex:set ts=4 et sw=4 ai:
......@@ -2,7 +2,7 @@
* peas-lua-utils.c
* This file is part of libpeas
*
* Copyright (C) 2014 - Garrett Regier
* Copyright (C) 2014-2015 - Garrett Regier
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
......@@ -120,3 +120,58 @@ error:
g_strfreev (version_str_parts);
return success;
}
static gint
traceback (lua_State *L)
{
/* Always ignore an error that isn't a string */
if (!lua_isstring (L, 1))
return 1;
lua_getglobal (L, "debug");
if (!lua_istable (L, -1))
{
lua_pop (L, 1);
return 1;
}
lua_getfield (L, -1, "traceback");
if (!lua_isfunction (L, -1))
{
lua_pop (L, 2);
return 1;
}
/* Replace debug with traceback */
lua_replace (L, -2);
/* Push the error */
lua_pushvalue (L, 1);
/* Skip this function when generating the traceback */
lua_pushinteger (L, 2);
/* If we fail we have a new error object... */
lua_pcall (L, 2, 1, 0);
return 1;
}
gboolean
peas_lua_utils_call (lua_State *L,
guint n_args,
guint n_results)
{
gboolean success;
/* Push the error function */
lua_pushcfunction (L, traceback);
/* Move traceback to before the arguments */
lua_insert (L, -2 - n_args);
success = lua_pcall (L, n_args, n_results, -2 - n_args) == 0;
/* Remove traceback */
lua_remove (L, -1 - (success ? n_results : 1));
return success;
}
......@@ -2,7 +2,7 @@
* peas-lua-utils.h
* This file is part of libpeas
*
* Copyright (C) 2014 - Garrett Regier
* Copyright (C) 2014-2015 - Garrett Regier
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
......@@ -36,6 +36,10 @@ gboolean peas_lua_utils_check_version (lua_State *L,
guint req_minor,
guint req_micro);
gboolean peas_lua_utils_call (lua_State *L,
guint n_args,
guint n_results);
G_END_DECLS
#endif /* __PEAS_LUA_UTILS_H__ */
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/libpeas/loaders/lua5.1">
<file alias="internal.lua">peas-lua-internal.lua</file>
</gresource>
</gresources>
This diff is collapsed.
......@@ -153,10 +153,8 @@ test_extension_lua_nonexistent (PeasEngine *engine)
{
PeasPluginInfo *info;
testing_util_push_log_hook ("Error failed to load Lua module "
"'extension-lua51-nonexistent'*");
testing_util_push_log_hook ("Error loading plugin "
"'extension-lua51-nonexistent'");
"'extension-lua51-nonexistent'*");
info = peas_engine_get_plugin_info (engine, "extension-lua51-nonexistent");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment