Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option to pass already created LuaState #224

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions lupa/_lupa.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ A fast Python wrapper around Lua and LuaJIT2.

from __future__ import absolute_import

import inspect
import traceback

cimport cython

from libc.string cimport strlen, strchr
Expand All @@ -21,6 +24,9 @@ from cpython.method cimport (
PyMethod_Check, PyMethod_GET_SELF, PyMethod_GET_FUNCTION)
from cpython.bytes cimport PyBytes_FromFormat

cdef extern from "Python.h":
void *PyLong_AsVoidPtr(object)

#from libc.stdint cimport uintptr_t
cdef extern from *:
"""
Expand Down Expand Up @@ -232,6 +238,7 @@ cdef class LuaRuntime:
>>> lua_func(py_add1, 2)
3
"""
cdef bint _lua_allocated
cdef lua_State *_state
cdef FastRLock _lock
cdef dict _pyrefs_in_lua
Expand All @@ -243,11 +250,20 @@ cdef class LuaRuntime:
cdef object _attribute_setter
cdef bint _unpack_returned_tuples

def __cinit__(self, encoding='UTF-8', source_encoding=None,
def __cinit__(self, state=None, encoding='UTF-8', source_encoding=None,
attribute_filter=None, attribute_handlers=None,
bint register_eval=True, bint unpack_returned_tuples=False,
bint register_builtins=True, overflow_handler=None):
cdef lua_State* L = lua.luaL_newstate()

cdef lua_State *L

if state is None:
self._lua_allocated = True
L = lua.luaL_newstate()
else:
self._lua_allocated = False
L = <lua_State *> PyLong_AsVoidPtr(state)

if L is NULL:
raise LuaError("Failed to initialise Lua runtime")
self._state = L
Expand Down Expand Up @@ -276,14 +292,16 @@ cdef class LuaRuntime:
raise ValueError("attribute_filter and attribute_handlers are mutually exclusive")
self._attribute_getter, self._attribute_setter = getter, setter

lua.luaL_openlibs(L)
if self._lua_allocated:
lua.luaL_openlibs(L)

self.init_python_lib(register_eval, register_builtins)
lua.lua_atpanic(L, <lua.lua_CFunction>1)

self.set_overflow_handler(overflow_handler)

def __dealloc__(self):
if self._state is not NULL:
if self._state is not NULL and self._lua_allocated:
lua.lua_close(self._state)
self._state = NULL

Expand Down Expand Up @@ -523,10 +541,26 @@ cdef class LuaRuntime:
lua.lua_setmetatable(L, -2) # lib tbl
lua.lua_setfield(L, lua.LUA_REGISTRYINDEX, PYREFST) # lib

def safe_eval(code):
try:
return eval(code, globals())
except Exception as e:
traceback.print_exc()
raise

def safe_import(name):
try:
g = globals()
g[name] = __import__(name, globals=g)
except Exception as e:
traceback.print_exc()
raise

# register global names in the module
self.register_py_object(b'Py_None', b'none', None)
if register_eval:
self.register_py_object(b'eval', b'eval', eval)
self.register_py_object(b'eval', b'eval', safe_eval)
self.register_py_object(b'import', b'import', safe_import)
if register_builtins:
self.register_py_object(b'builtins', b'builtins', builtins)

Expand Down
88 changes: 88 additions & 0 deletions lupa/embedded.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import sys
from pathlib import Path


cdef extern from "Python.h":
object PyLong_FromVoidPtr(void *p)


runtime = None


def _lua_eval_intern(code, *args):
# In this scope LuaRuntime returns tuple of (<module_name>, <dll_path>, <return_val>)
return runtime.eval(code, *args)[2]


cdef public int initialize_lua_runtime(void* L):
global runtime

# Convert pointer to Python object to make possible pass it into LuaRuntime constructor
state = PyLong_FromVoidPtr(L)

print(f"Initialize LuaRuntime at proxy module with lua_State *L = {hex(state)}")

from lupa import LuaRuntime

# TODO: Make possible to configure others LuaRuntime options
runtime = LuaRuntime(state=state, encoding="latin-1")


cdef void setup_system_path():
# Add all lua interpreter 'require' paths to Python import paths
paths: list[Path] = [Path(it) for it in _lua_eval_intern("package.path").split(";")]
for path in set(it.parent for it in paths if it.parent.is_dir()):
str_path = str(path)
print(f"Append system path: '{str_path}'")
sys.path.append(str_path)


virtualenv_env_variable = "LUA_PYTHON_VIRTUAL_ENV"


cdef void initialize_virtualenv():
virtualenv_path = os.environ.get(virtualenv_env_variable)
if virtualenv_path is None:
print(f"Environment variable '{virtualenv_env_variable}' not set, try to use system Python")
return

this_file = os.path.join(virtualenv_path, "Scripts", "activate_this.py")
if not os.path.isfile(this_file):
print(f"virtualenv at '{virtualenv_path}' seems corrupted, activation file '{this_file}' not found")
return

print(f"Activate virtualenv at {virtualenv_env_variable}='{virtualenv_path}'")
exec(open(this_file).read(), {'__file__': this_file})


cdef public int embedded_initialize(void *L):
initialize_virtualenv()
initialize_lua_runtime(L)
setup_system_path()
return 1


cdef extern from *:
"""
PyMODINIT_FUNC PyInit_embedded(void);

// use void* to make possible not to link proxy module with lua libraries
#define LUA_ENTRY_POINT(x) __declspec(dllexport) int luaopen_ ## x (void *L)

LUA_ENTRY_POINT(libpylua) {
PyImport_AppendInittab("embedded", PyInit_embedded);
Py_Initialize();
PyImport_ImportModule("embedded");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'd better add some checks here, e.g.

PyObject *mod = PyImport_ImportModule("embedded");
if (mod==NULL)
{
    PyErr_Print();
}
else
{
     Py_DECREF(mod);
}

return embedded_initialize(L);
}
"""

# Export proxy DLL name from this .pyd file
# This name may be used by external Python script installer, e.g.
#
# from lupa import embedded
# dylib_ext = get_dylib_ext_by_os()
# dest_path = os.path.join(dest_dir, f"{embedded.lua_dylib_name}.{dylib_ext}")
# shutil.copyfile(embedded.__file__, dest_path)
lua_dylib_name = "libpylua"
102 changes: 60 additions & 42 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ def try_int(s):
return s


def get_option(name):
for i, arg in enumerate(sys.argv[1:-1], 1):
if arg == name:
sys.argv.pop(i)
return sys.argv.pop(i)
return ""


def has_option(name):
if name in sys.argv[1:]:
sys.argv.remove(name)
return True
envvar_name = 'LUPA_' + name.lstrip('-').upper().replace('-', '_')
return os.environ.get(envvar_name) == 'true'


def cmd_output(command):
"""
Returns the exit code and output of the program, as a triplet of the form
Expand Down Expand Up @@ -130,27 +146,32 @@ def lua_libs(package='luajit'):
return libs_out.split()


option_lua_lib = get_option('--lua-lib')
option_lua_includes = get_option('--lua-includes')


def get_lua_build_from_arguments():
lua_lib = get_option('--lua-lib')
lua_includes = get_option('--lua-includes')

if not lua_lib or not lua_includes:
if not option_lua_lib or not option_lua_includes:
return []

print('Using Lua library: %s' % lua_lib)
print('Using Lua include directory: %s' % lua_includes)
print('Using Lua library: %s' % option_lua_lib)
print('Using Lua include directory: %s' % option_lua_includes)

root, ext = os.path.splitext(lua_lib)
root, ext = os.path.splitext(option_lua_lib)
libname = os.path.basename(root)
if os.name == 'nt' and ext == '.lib':
return [
dict(extra_objects=[lua_lib],
include_dirs=[lua_includes],
libfile=lua_lib)
dict(extra_objects=[option_lua_lib],
include_dirs=[option_lua_includes],
libfile=option_lua_lib,
libversion=libname)
]
else:
return [
dict(extra_objects=[lua_lib],
include_dirs=[lua_includes])
dict(extra_objects=[option_lua_lib],
include_dirs=[option_lua_includes],
libversion=libname)
]


Expand Down Expand Up @@ -309,22 +330,6 @@ def use_bundled_lua(path, macros):
}


def get_option(name):
for i, arg in enumerate(sys.argv[1:-1], 1):
if arg == name:
sys.argv.pop(i)
return sys.argv.pop(i)
return ""


def has_option(name):
if name in sys.argv[1:]:
sys.argv.remove(name)
return True
envvar_name = 'LUPA_' + name.lstrip('-').upper().replace('-', '_')
return os.environ.get(envvar_name) == 'true'


c_defines = [
('CYTHON_CLINE_IN_TRACEBACK', 0),
]
Expand Down Expand Up @@ -361,6 +366,29 @@ def has_option(name):
configs = no_lua_error()


try:
import Cython.Compiler.Version
import Cython.Compiler.Errors as CythonErrors
from Cython.Build import cythonize
print(f"building with Cython {Cython.Compiler.Version.version}")
CythonErrors.LEVEL = 0
except ImportError:
cythonize = None
print("ERROR: Can't import cython ... Cython not installed")


def do_cythonize(modules: list[Extension], use_cython: bool = True) -> list[Extension]:
if not use_cython:
print("building without Cython")
return modules

if cythonize is None:
print("WARNING: trying to build with Cython, but it is not installed")
return modules

return cythonize(modules)


# check if Cython is installed, and use it if requested or necessary
def prepare_extensions(use_cython=True):
ext_modules = []
Expand Down Expand Up @@ -388,21 +416,7 @@ def prepare_extensions(use_cython=True):
print("generated sources not available, need Cython to build")
use_cython = True

cythonize = None
if use_cython:
try:
import Cython.Compiler.Version
import Cython.Compiler.Errors as CythonErrors
from Cython.Build import cythonize
print("building with Cython " + Cython.Compiler.Version.version)
CythonErrors.LEVEL = 0
except ImportError:
print("WARNING: trying to build with Cython, but it is not installed")
else:
print("building without Cython")

if cythonize is not None:
ext_modules = cythonize(ext_modules)
ext_modules = do_cythonize(ext_modules, use_cython)

return ext_modules, ext_libraries

Expand Down Expand Up @@ -437,6 +451,10 @@ def write_file(filename, content):
if dll_files:
extra_setup_args['package_data'] = {'lupa': dll_files}

# Add proxy embedded module to make portal from lua into Python
embedded_pyx_path = os.path.join("lupa", "embedded.pyx")
ext_modules += do_cythonize([Extension("lupa.embedded", sources=[embedded_pyx_path])])

# call distutils

setup(
Expand Down