diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 154533dd..f896ed6e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,19 @@ Changes ======= +2.1.0 (2023-04-08) +------------------ + +**The Python build dependency has been completely removed.** You now only need +Lua, Make, and a C++ compiler. The script for creating a virtual environment is +still written in Python, but that's a utility, not required for installation. + +Other Changes +~~~~~~~~~~~~~ + +The virtual environment script has been removed. Use `lenv `_ +instead. + 2.0.1 (2023-04-06) ------------------ diff --git a/Makefile b/Makefile index 654fde53..3b8a42b6 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ $(TEST_EXECUTABLE): $(DOCTEST_HEADER) $(TEST_CPP_OBJECT_FILES) $(LIB_OBJECT_FILE $(CONSTS_DIR)/%_const.cpp: $(UNICORN_INCDIR)/unicorn/%.h | $(CONSTS_DIR) - python3 tools/generate_constants.py $< $@ + $(SET_SEARCH_PATHS); $(LUA) tools/generate_constants.lua $< $@ # We're deliberately omitting CXXFLAGS as provided by LuaRocks because it includes diff --git a/README.rst b/README.rst index 24a651a5..f6a628d9 100644 --- a/README.rst +++ b/README.rst @@ -199,30 +199,14 @@ directory of this repository, and run Development ----------- -Configuration -~~~~~~~~~~~~~ - Using a virtual environment for Lua is strongly recommended. You'll want to avoid using your OS's real Lua, and using virtual environments allows you to test with -multiple versions of Lua. - -To create a separate execution environment, you can use the ``lua_venv.py`` -script. - -.. code-block:: sh - - python3 tools/lua_venv.py --luarocks 5.3 ~/my-virtualenvs/5.3/ - -This will download Lua 5.3 and install it in a directory named ``~/my-virtualenvs/5.3``. -Use ``~/my-virtualenvs/5.3/luarocks/bin/luarocks`` as your LuaRocks executable. +multiple versions of Lua. You can use `lenv `_ +for this. If you're running MacOS and encounter a linker error with LuaJIT, check out `this ticket `_. -Using Your OS's Lua -^^^^^^^^^^^^^^^^^^^ - -Use your global installation of LuaRocks for operations. Building and Testing ~~~~~~~~~~~~~~~~~~~~ @@ -236,7 +220,7 @@ Building and Testing luarocks test Examples -~~~~~~~~ +-------- See the ``examples`` directory for examples of how you can use this library. diff --git a/include/unicornlua/unicornlua.h b/include/unicornlua/unicornlua.h index f5b94db0..7355fab0 100644 --- a/include/unicornlua/unicornlua.h +++ b/include/unicornlua/unicornlua.h @@ -25,12 +25,12 @@ /** * The minor version number of this Lua library (second part, x.1.x). */ -#define UNICORNLUA_VERSION_MINOR 0 +#define UNICORNLUA_VERSION_MINOR 1 /** * The patch version number of this Lua library (third part, x.x.1). */ -#define UNICORNLUA_VERSION_PATCH 1 +#define UNICORNLUA_VERSION_PATCH 0 /** * Create a 24-bit number from a release's major, minor, and patch numbers. diff --git a/tools/generate_constants.lua b/tools/generate_constants.lua new file mode 100644 index 00000000..b3fa5b67 --- /dev/null +++ b/tools/generate_constants.lua @@ -0,0 +1,205 @@ +pl_file = require "pl.file" +pl_lexer = require "pl.lexer" +pl_path = require "pl.path" +pl_stringx = require "pl.stringx" +pl_tablex = require "pl.tablex" +pl_template = require "pl.template" +pl_utils = require "pl.utils" + +pl_stringx.import() + + +OUTPUT_CPP_TEMPLATE = [[ +/** Autogenerated from installed Unicorn header files. DO NOT EDIT. + * + * Source: $(header_file) + * + * @file $(slug)_const.cpp + */ + +#include +! if slug ~= "unicorn" then +#include +! end + +#include "unicornlua/lua.h" +#include "unicornlua/utils.h" + +static const struct NamedIntConst kConstants[] { +! for name, text in pairs(constants) do + {"$(name)", $(name)}, +! end + {nullptr, 0} +}; + +extern "C" UNICORN_EXPORT int luaopen_unicorn_$(slug)_const(lua_State *L) { + lua_createtable(L, 0, $(pl_tablex.size(constants))); + load_int_constants(L, kConstants); + return 1; +} +]] + + +function main() + local source_header = arg[1] + local output_file = arg[2] + + if #arg < 1 or #arg > 2 then + pl_utils.quit( + 1, + "USAGE: %s header_file [output_file]\nIf `output_file` isn't given" + .. " or is \"-\", stdout is used.\n", + arg[-1] + ) + end + + -- Read in the entire file so we can tack on a trailing newline at the end + -- of the text. + -- https://github.com/lunarmodules/Penlight/issues/450 + local source_text = pl_file.read(source_header) .. "\n" + + local constants = extract_constants(source_text) + local source_basename = pl_path.basename(source_header) + local stem = pl_path.splitext(source_basename) + + local text, render_error = pl_template.substitute( + OUTPUT_CPP_TEMPLATE, + { + _chunk_name = "cpp_template", + _escape = "!", + _parent = _G, + constants = constants, + header_file = source_header, + slug = stem, + } + ) + + if render_error ~= nil then + pl_utils.quit(1, "%s\n", render_error) + end + + if output_file == nil or output_file == "-" then + print(text) + else + pl_file.write(output_file, text) + end +end + + +function extract_constants(source) + local tokenizer = pl_lexer.cpp(source) + local constants = {} + local ttype, value = tokenizer() + + while ttype ~= nil do + local extracted + + if ttype == "prepro" then + extracted = maybe_extract_preprocessor(value) + elseif ttype == "keyword" and value == "enum" then + -- Enum declaration to follow + extracted = maybe_extract_enum(tokenizer) + end + + if extracted ~= nil then + for name, text in pairs(extracted) do + -- If a definition for the macro already exists, ignore the new + -- one. It most likely is due to a #if ... #elif ... block that + -- we're not interpreting. + if constants[name] == nil then + constants[name] = text + end + end + end + + ttype, value = tokenizer() + extracted = nil + end + + return constants +end + + +function maybe_extract_preprocessor(text) + local parts = text:split() + -- We know the first part is "#define". After that come the identifier and + -- whatever the expansion of the macro is, if applicable. + local directive = parts[1] + local macro_name = parts[2] + local macro_text = parts[3] + + if directive == "#define" + and macro_name:startswith("UC_") + and macro_name:lfind("(") == nil -- Ignore function macros + and macro_text ~= nil + and macro_text ~= "" + then + -- FIXME (dargueta): Ensure that `macro_text` can be evaluated as an integer + return {[macro_name] = macro_text} + end + return {} +end + + +function maybe_extract_enum(tokenizer) + -- The tokenizer is positioned immediately after the `enum` keyword. The + -- next token in the stream will either be the name of the enum, or `{` + -- if this is of the form `typedef enum { ... } XYZ`. + local ttype, text + + local start_lineno = pl_lexer.lineno(tokenizer) + -- `tok` is either the name of the enum or `{`. + repeat + ttype, text = tokenizer() + if ttype == nil then + local current_line = pl_lexer.lineno(tokenizer) + pl_utils.quit( + 1, + "Unexpected EOF on line %d, expected `{` on or near line %d", + current_line, + start_lineno + ) + end + until ttype == "{" + + local constants = {} + + -- The general structure we're expecting is + -- IDENTIFIER [expression] ("," | "}") + -- For this application we can probably get away with completely ignoring + -- `expression` entirely, i.e. consuming the identifier and then discarding + -- tokens until we reach a comma. This'll misbehave if, for example, there's + -- a macro call as the value, but this is unlikely. + while ttype ~= "}" do + local current_lineno = pl_lexer.lineno(tokenizer) + + ttype, text = tokenizer() + if ttype == "}" then + return constants + elseif ttype ~= "iden" then + pl_utils.quit( + 1, + "Expected identifier on line %s", + tostring(current_lineno) + ) + end + + constants[text] = text + + -- Skip everything until we hit a comma that ends the current item + -- definition, or "}" which indicates the end of the enum. + while ttype ~= "," and ttype ~= "}" and ttype ~= nil do + ttype, text = tokenizer() + end + if ttype == nil then + pl_lexer.quit( + 1, + "Unexpected EOF while processing enum value starting line %d", + current_lineno + ) + end + end + return constants +end + +main() diff --git a/tools/generate_constants.py b/tools/generate_constants.py deleted file mode 100644 index d9a1e0ee..00000000 --- a/tools/generate_constants.py +++ /dev/null @@ -1,205 +0,0 @@ -"""Generate constants files from the Unicorn headers. - -This is heavily borrowed from the script in the Unicorn source repo, except modified to -work with an installed library rather than from the source code. The headers must be -present, but that's all that's required. -""" - -import ast -import collections -import datetime -import logging -import os -import re -import sys - - -GENERATED_FILE_TEMPLATE = """\ -/** Autogenerated from installed Unicorn header files. DO NOT EDIT. - * - * Source: {header_file} - * Generated: {now:%Y-%m-%d %I:%M %p} - * - * @file {slug}_const.cpp - */ - -#include - -#include "unicornlua/lua.h" -#include "unicornlua/utils.h" - -static const struct NamedIntConst kConstants[] {{ -{values}, - {{nullptr, 0}} -}}; - -extern "C" UNICORN_EXPORT int luaopen_unicorn_{slug}_const(lua_State *L) {{ - lua_createtable(L, 0, {n_values}); - load_int_constants(L, kConstants); - return 1; -}} -""" - - -def clean_line(line): - """Strip whitespace off a line and separate out the comment, if any.""" - if "//" in line: - line, _sep, comment = line.partition("//") - if "/*" in line: - line, _sep, comment = line.partition("/*") - comment, _sep, _trash = comment.partition("*/") - else: - comment = "" - - return line.strip(), comment.strip() - - -def parse_header_file(header_file): - """Parse a single header file to get all defined constants out of it.""" - resolved_values = collections.OrderedDict() - raw_matches = {} - - with open(header_file, "r") as fd: - all_file_lines = collections.OrderedDict( - [ - (lineno, line.strip()) - for lineno, line in enumerate(fd, start=1) - if not line.isspace() - ] - ) - - line_iterator = iter(all_file_lines.items()) - - for lineno, line in line_iterator: - line, _comment = clean_line(line) - - # First check to see if this is a #define statement - match = re.match(r"^#define\s+UC_(?P\w+)\s+(?P.*)$", line) - if match: - name = "UC_" + match.group("id") - raw_value = match.group("value") - try: - resolved_values[name] = ast.literal_eval(raw_value) - except (NameError, SyntaxError, ValueError): - raw_matches[name] = raw_value - continue - - # Not a #define; see if it's an enum. - if "enum uc_" not in line.lower(): - continue - - # This is the beginning of an enum. Subsequent lines until the closing `}` are - # part of it. We need to keep track because enums without an explicitly defined - # value are incremented by one from the previous enum value. - next_enum_value = 0 - enum_start_line = lineno - while True: - lineno, line = next(line_iterator, (None, None)) - if line is None: - # Hit EOF before we hit the end of the enum. That's odd. - logging.warning( - "Hit EOF before end of enum beginning on line %d.", enum_start_line - ) - break - elif "}" in line: - # Hit the end of the enum. - break - - line, _comment = clean_line(line) - - # Sometimes we have multiple enum definitions on one line. We need to handle - # these one at a time. Splitting the line by commas should be enough to - # separate out multiple expressions. - for expression in line.strip(",").split(","): - expression = expression.strip() - if not expression: - continue - - # See if this enum value is being assigned rather than implicit. - match = re.match(r"^UC_(?P\w+)\s*=\s*(?P.+)$", expression) - if match: - # Enum value is assigned. Whatever's on the right-hand side, any - # names it references must already be defined. - name = "UC_" + match.group("id") - raw_value = match.group("expr") - try: - processed_value = eval(raw_value, resolved_values) - except NameError as nerr: - logging.error( - "Failed to resolve %r on line %d: %s", name, lineno, nerr - ) - continue - resolved_values[name] = processed_value - next_enum_value = processed_value + 1 - else: - # Not an explicit assignment. Expect this expression to be just a - # single identifier. - match = re.match(r"^UC_(\w+)$", expression) - if match: - name = match.group(1) - resolved_values["UC_" + name] = next_enum_value - next_enum_value += 1 - else: - raise SyntaxError( - "Couldn't match any expression type to: %r" % expression - ) - - for name, raw_value in raw_matches.items(): - # Convert any remaining values that are still unresolved. This usually only - # applies to #define macros that reference other constants. - if name not in resolved_values: - resolved_values[name] = eval(raw_value, resolved_values) - - return resolved_values - - -def generate_constants_for_file(header_file, output_file): - """Generate a constants file from a Unicorn header. - - Arguments: - header_file (str): - The path to the source file header where the constants are defined. Ideally - this is an absolute path. - output_file (str): - The path to the Lua file to create that'll contain these defined constants. - """ - header_file = os.path.abspath(header_file) - logging.info("Processing file: %s", header_file) - - all_value_pairs = parse_header_file(header_file) - if not all_value_pairs: - logging.error("No constants found in header, refusing to write to output file.") - return - - with open(output_file, "w") as out_fd: - out_fd.write( - GENERATED_FILE_TEMPLATE.format( - header_file=header_file, - now=datetime.datetime.now(), - n_values=len(all_value_pairs), - slug=os.path.splitext(os.path.basename(header_file))[0].lower(), - values=",\n".join( - ' {"%s", %s}' % kv for kv in sorted(all_value_pairs.items()) - ), - ) - ) - - logging.info("Finished. Found %d constant(s) in file.", len(all_value_pairs)) - - -def main(): - logging.basicConfig(level=logging.INFO, format="[%(levelname)-5s] %(message)s") - if len(sys.argv) != 3: - logging.error( - "Script takes two arguments, the path to a header file and the path to the" - " C++ file to generate." - ) - return sys.exit(1) - - logging.info("Generating `%s`...", sys.argv[2]) - generate_constants_for_file(os.path.abspath(sys.argv[1]), sys.argv[2]) - return sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/tools/lua_settings.ini b/tools/lua_settings.ini deleted file mode 100644 index b625d79c..00000000 --- a/tools/lua_settings.ini +++ /dev/null @@ -1,35 +0,0 @@ -[supported_versions] -5.1 = 5.1 -5.2 = 5.2 -5.3 = 5.3 -5.4 = 5.4 -luajit2.0 = 2.0 - - -; The specific version to install when the user specifies a short version on the -; command line. -[specific_versions] -5.1 = 5.1.5 -5.2 = 5.2.3 -5.3 = 5.3.6 -5.4 = 5.4.4 -luajit2.0 = 2.0.5 - - -; A mapping of values of Python's `sys.platform` to the corresponding Makefile -; target when building Lua. -[platform_targets] -linux = linux -darwin = macosx -win32 = mingw -cygwin = posix - - -[lua] -; The default major version of Lua to install if not specified by the user. -default_version = 5.4 - - -[luarocks] -; The default version of LuaRocks to install if not specified by the user. -default_version = 3.9.2 diff --git a/tools/lua_venv.py b/tools/lua_venv.py deleted file mode 100755 index 66384e17..00000000 --- a/tools/lua_venv.py +++ /dev/null @@ -1,490 +0,0 @@ -#!/usr/bin/env python3 - -"""Script for downloading and installing Lua and LuaRocks.""" - -import argparse -import configparser -import json -import logging -import os -import re -import shutil -import subprocess -import zipfile - -import sys -import tempfile -from urllib import request - - -LOG = logging.getLogger(__name__) -HERE = os.path.dirname(__file__) - -CONFIG = configparser.ConfigParser(interpolation=None) -CONFIG.read(os.path.join(HERE, "lua_settings.ini")) - -SUPPORTED_LUA_VERSIONS = tuple(CONFIG["supported_versions"].keys()) -SPECIFIC_VERSIONS = CONFIG["specific_versions"] -LUAROCKS_VERSION = CONFIG["luarocks"]["default_version"] - - -class ErrorExit(RuntimeError): - """An exception to throw when a fatal error occurs.""" - - -def download_lua(args, download_dir): - """Download the Lua tarball to the specified directory.""" - full_version = CONFIG["specific_versions"][args.lua_version] - - if args.lua_version.startswith("luajit"): - LOG.info("Downloading LuaJIT %s into `%s`...", full_version, download_dir) - response = request.urlopen( - "https://luajit.org/download/LuaJIT-%s.tar.gz" % full_version - ) - else: - # Normal Lua - LOG.info("Downloading Lua %s into `%s`...", full_version, download_dir) - response = request.urlopen( - "https://www.lua.org/ftp/lua-%s.tar.gz" % full_version - ) - - if response.status != 200: - raise ErrorExit( - "Lua download failed: HTTP %d: %s" % (response.status, response.reason) - ) - - output_file = os.path.join(download_dir, "lua.tar.gz") - with open(output_file, "wb") as fd: - shutil.copyfileobj(response, fd) - return output_file - - -def configure_lua(args, lua_platform, extract_dir): - """Customize Lua before building it. - - Before building normal Lua we need to change where it looks for installed libraries. - This way we can determine the directory to install the built Unicorn binding with - one command, without worrying if we're using a virtual environment or not. - - Arguments: - args: The parsed command line arguments. - lua_platform: The Lua makefile target we're compiling this for. - extract_dir: - The path to the directory where the Lua tarball was extracted. - """ - # We don't need to modify any LuaJIT files, as we can do it all in the Make command. - if args.lua_version.startswith("luajit"): - return - - with open(os.path.join(extract_dir, "src", "luaconf.h"), "r+") as fd: - luaconf_contents = fd.read() - luaconf_contents = re.sub( - r"#define\s+LUA_ROOT[^\\n]*\\n", - r'#define LUA_ROOT "%s"\n' % args.install_to, - luaconf_contents, - ) - fd.seek(0) - fd.truncate(0) - fd.write(luaconf_contents) - - -def compile_lua(args, lua_platform, _tarball_path, extract_dir): - """Compile Lua. - - Arguments: - args: The parsed command line arguments. - lua_platform: - The string that tells Lua what platform to compile for, e.g. "linux", - "posix", "generic", etc. See the Lua documentation for what values are - supported. - extract_dir: - The path to the directory where the Lua tarball was extracted to. This must - contain the main Makefile. - - Returns: - A dict containing information about the Lua installation, such as where the - binary will be installed at, where the headers are, where the library search - directory is, and so on. - """ - install_to = os.path.abspath(os.path.normpath(args.install_to)) - - if args.lua_version.startswith("luajit"): - run_args = ["amalg", "PREFIX=" + install_to] - else: - run_args = [lua_platform, "local"] - - result = subprocess.run( - ["make", "-C", extract_dir] + run_args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Compilation failed.") - raise ErrorExit(result.stdout) - - if args.lua_version.startswith("luajit"): - short_version = args.lua_version[6:] - return { - "lua_root": install_to, - "lua_exe": os.path.join(install_to, "bin", "luajit"), - "lua_include": os.path.join( - install_to, "include", "luajit-" + short_version - ), - "lua_lib": os.path.join(install_to, "lib", "libluajit-5.1.a"), - "is_luajit": True, - "lua_short_version": "5.1", - } - - # else: Regular Lua - return { - "lua_root": install_to, - "lua_exe": os.path.join(install_to, "bin", "lua"), - "lua_include": os.path.join(install_to, "include"), - "lua_lib": os.path.join(install_to, "lib", "liblua.a"), - "is_luajit": False, - "lua_short_version": args.lua_version.rpartition(".")[0], - } - - -def install_lua(lua_version, lua_platform, install_to, extract_dir): - """Install Lua which has already been compiled. - - Arguments: - lua_version: - The version of Lua to install, as passed in on the command line. This is not - the "specific" version. - lua_platform: - The Lua build target we selected based on the operating system. This is used - to determine if we're running on Windows so we can change the installation - arguments. - install_to: - The path to the directory where Lua is to be installed. May be a relative - path. See the Lua documentation for the exact directory structure created - here. - extract_dir: - A path to the directory where the Lua tarball was extracted to. The Makefile - must be in here. - """ - install_to = os.path.abspath(os.path.normpath(install_to)) - - if lua_version.startswith("luajit"): - run_args = ["PREFIX=" + install_to] - elif lua_platform == "mingw": - # mingw handles the paths for us and there's no way to install something to - # *not* the standard system directories. - LOG.warning( - "This system uses MinGW, so we have no control over the installation" - " directory. Ignoring %r.", - install_to, - ) - run_args = [] - else: - run_args = ["INSTALL_TOP=" + install_to] - - result = subprocess.run( - ["make", "-C", extract_dir, "install"] + run_args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Installation failed.") - raise ErrorExit(result.stdout) - - -######################################################################################## -# LuaRocks stuff - - -def download_luarocks_linux(download_dir): - response = request.urlopen( - "https://luarocks.org/releases/luarocks-%s.tar.gz" % LUAROCKS_VERSION - ) - if response.status != 200: - raise ErrorExit( - "LuaRocks download failed: HTTP %d: %s" % (response.status, response.reason) - ) - - output_file = os.path.join(download_dir, "luarocks.tar.gz") - with open(output_file, "wb") as fd: - shutil.copyfileobj(response, fd) - return output_file - - -def download_luarocks_windows(download_dir): - if sys.maxsize > 2**32: - bits = 64 - else: - bits = 32 - - response = request.urlopen( - "http://luarocks.github.io/luarocks/releases/luarocks-%s-windows-%d.zip" - % (LUAROCKS_VERSION, bits) - ) - if response.status != 200: - raise ErrorExit( - "LuaRocks download failed: HTTP %d: %s" % (response.status, response.reason) - ) - - output_file = os.path.join(download_dir, "luarocks.zip") - with open(output_file, "wb") as fd: - shutil.copyfileobj(response, fd) - return output_file - - -def download_luarocks(download_dir): - if sys.platform == "win32": - return download_luarocks_windows(download_dir) - return download_luarocks_linux(download_dir) - - -def install_luarocks_linux(lua_path_info, install_to, extract_dir): - LOG.info("Configuring LuaRocks") - result = subprocess.run( - [ - os.path.join(extract_dir, "configure"), - "--prefix=" + install_to, - "--with-lua=" + lua_path_info["lua_root"], - "--with-lua-include=" + lua_path_info["lua_include"], - "--force-config", - ], - cwd=extract_dir, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Failed to configure LuaRocks.") - raise ErrorExit(result.stdout) - - LOG.info("Bootstrapping LuaRocks installation") - result = subprocess.run( - ["make", "-C", extract_dir, "bootstrap"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Failed to install LuaRocks.") - raise ErrorExit(result.stdout) - - -def install_luarocks_windows(lua_path_info, install_to, extract_dir): - LOG.info("Configuring LuaRocks") - zip_file_path = os.path.join(extract_dir, "luarocks.zip") - luarocks_path = os.path.join(install_to, "luarocks.exe") - with zipfile.ZipFile(zip_file_path, "r") as archive: - with archive.open("luarocks.exe", "rb") as source_fd: - with open(luarocks_path, "wb") as out_fd: - shutil.copyfileobj(source_fd, out_fd, 2**24) - - result = subprocess.run( - [ - luarocks_path, - "config", - "--lua-version", - lua_path_info["lua_short_version"], - "lua_dir", - lua_path_info["lua_root"], - "--with-lua-include=" + lua_path_info["lua_include"], - "--force-config", - ], - cwd=extract_dir, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Failed to configure LuaRocks.") - raise ErrorExit(result.stdout) - - LOG.info("Bootstrapping LuaRocks installation") - result = subprocess.run( - ["make", "-C", extract_dir, "bootstrap"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Failed to install LuaRocks.") - raise ErrorExit(result.stdout) - - -def install_luarocks(lua_path_info, install_to, extract_dir): - if sys.platform == "win32": - return install_luarocks_windows(lua_path_info, install_to, extract_dir) - return install_luarocks_linux(lua_path_info, install_to, extract_dir) - - -def get_luarocks_paths(luarocks_exe): - """Get the paths where LuaRocks installs C libraries and Lua files. - - Arguments: - luarocks_exe: The path to the LuaRocks executable. May be relative. - - Returns: - A dictionary with two keys: `LUAROCKS_CPATH` and `LUAROCKS_LPATH`. These point - to the directories where LuaRocks installs C libraries and libraries written in - pure Lua, respectively. - """ - luarocks_exe = os.path.abspath(luarocks_exe) - result = subprocess.run( - [luarocks_exe, "path", "--lr-path"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Failed to pull LuaRocks library path.") - raise ErrorExit(result.stderr) - - lpath = result.stdout.strip() - - result = subprocess.run( - [luarocks_exe, "path", "--lr-cpath"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ) - if result.returncode != 0: - LOG.error("Failed to pull LuaRocks C library path.") - raise ErrorExit(result.stderr) - - return {"luarocks_lpath": lpath, "luarocks_cpath": result.stdout.strip()} - - -######################################################################################## -# Main stuff - - -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument( - "-v", - "--verbose", - dest="log_level", - action="store_const", - const=logging.DEBUG, - default=logging.INFO, - ) - parser.add_argument( - "-o", - "--config-out", - help="Write file locations and other information to this file for use by the" - " `configure` script. If not given, results are written to STDOUT.", - metavar="PATH", - ) - parser.add_argument( - "-l", - "--luarocks", - action="store_true", - help="Install LuaRocks as well. It'll be configured to work with this specific" - " Lua installation and won't interfere with other installations of LuaRocks.", - ) - parser.add_argument( - "lua_version", - metavar="VERSION", - choices=SUPPORTED_LUA_VERSIONS, - help="The version of Lua to download and install. Valid choices are: " - + ", ".join(repr(v) for v in sorted(CONFIG.options("supported_versions"))), - ) - parser.add_argument( - "install_to", - metavar="INSTALL_PATH", - help="The directory to install Lua into. It'll be created if it doesn't already" - " exist.", - ) - return parser.parse_args() - - -def main(): - args = parse_args() - - logging.basicConfig(format="[%(levelname)-8s] %(message)s", level=args.log_level) - lua_platform = CONFIG["platform_targets"][sys.platform] - if not lua_platform: - LOG.warning("OS platform potentially unsupported: %s", sys.platform) - lua_platform = "generic" - - LOG.debug("Platform is %r, using Lua target %r.", sys.platform, lua_platform) - with tempfile.TemporaryDirectory() as download_dir: - LOG.info("Downloading Lua %s ...", args.lua_version) - tarball_path = download_lua(args, download_dir) - - if args.lua_version.startswith("luajit"): - extract_dir = os.path.join( - download_dir, "LuaJIT-" + SPECIFIC_VERSIONS[args.lua_version] - ) - else: - extract_dir = os.path.join( - download_dir, "lua-" + SPECIFIC_VERSIONS[args.lua_version] - ) - - LOG.info("Extracting `%s` into `%s` ...", tarball_path, extract_dir) - shutil.unpack_archive(tarball_path, download_dir, "gztar") - - LOG.info("Configuring compilation options ...") - configure_lua(args, lua_platform, extract_dir) - - LOG.info("Compiling ...") - path_info = compile_lua(args, lua_platform, tarball_path, extract_dir) - - install_to = os.path.abspath(os.path.normpath(args.install_to)) - LOG.info("Installing to `%s` ...", install_to) - # Ensure the installation location exists before we try installing there. - os.makedirs(install_to, exist_ok=True) - install_lua(args.lua_version, lua_platform, install_to, extract_dir) - - configuration_variables = path_info.copy() - configuration_variables["lua_short_version"] = args.lua_version - configuration_variables["virtualenv_dir"] = install_to - configuration_variables["lua_full_version"] = SPECIFIC_VERSIONS[ - args.lua_version - ] - - if args.luarocks: - with tempfile.TemporaryDirectory() as download_dir: - LOG.info("Downloading LuaRocks %s ...", LUAROCKS_VERSION) - tarball_path = download_luarocks(download_dir) - - extract_dir = os.path.join(download_dir, "luarocks-" + LUAROCKS_VERSION) - - LOG.info("Extracting `%s` into `%s` ...", tarball_path, extract_dir) - shutil.unpack_archive(tarball_path, download_dir, "gztar") - - luarocks_install_to = os.path.join(install_to, "luarocks") - LOG.info("Installing LuaRocks into `%s` ...", luarocks_install_to) - os.makedirs(luarocks_install_to, exist_ok=True) - install_luarocks(path_info, luarocks_install_to, extract_dir) - - LOG.info("Pulling LuaRocks path information ...") - luarocks_paths = get_luarocks_paths( - os.path.join(luarocks_install_to, "bin", "luarocks") - ) - - configuration_variables.update(luarocks_paths) - configuration_variables["luarocks_exe"] = os.path.join( - luarocks_install_to, "bin", "luarocks" - ) - configuration_variables["luarocks_version"] = LUAROCKS_VERSION - else: - LOG.info("Not installing LuaRocks.") - - if args.config_out: - LOG.info("Writing configuration variables to `%s` ...", args.config_out) - with open(args.config_out, "w") as fd: - json.dump(configuration_variables, fd, indent=2) - else: - print(json.dumps(configuration_variables, indent=2)) - - -if __name__ == "__main__": - try: - main() - except ErrorExit as error: - LOG.error(str(error)) - sys.exit(1) - except KeyboardInterrupt: - LOG.warning("Killed by the user.") - sys.exit(0) diff --git a/unicorn-2.0.1-1.rockspec b/unicorn-2.1.0-1.rockspec similarity index 96% rename from unicorn-2.0.1-1.rockspec rename to unicorn-2.1.0-1.rockspec index c2ce37cc..c59eb763 100644 --- a/unicorn-2.0.1-1.rockspec +++ b/unicorn-2.1.0-1.rockspec @@ -1,10 +1,10 @@ rockspec_format = "3.0" package = "unicorn" -version = "2.0.1-1" +version = "2.1.0-1" source = { url = "git+ssh://git@github.com/dargueta/unicorn-lua.git", - tag = "v2.0.1" + tag = "v2.1.0" } description = { @@ -65,6 +65,11 @@ test = { -- "PTHREAD_LIBDIR=$(PTHREAD_LIBDIR)", }, } + +build_dependencies = { + "penlight >=1.8.1, <2.0", +} + build = { type = "make", variables = {