Rework embedded scripts to be loaded on-demand and by name

This will be required in order to migrate modules into the executable for binary releases
This commit is contained in:
Jason Perkins 2014-09-26 09:25:14 -04:00
parent ee305d4ff1
commit 90ae7aaa70
13 changed files with 285 additions and 324 deletions

View File

@ -29,6 +29,24 @@
-- Register supporting actions and options.
--
newaction {
trigger = "embed",
description = "Embed scripts in scripts.c; required before release builds",
execute = function ()
include (path.join(corePath, "scripts/embed.lua"))
end
}
newaction {
trigger = "release",
description = "Prepare a new release (incomplete)",
execute = function ()
include (path.join(corePath, "scripts/release.lua"))
end
}
newaction {
trigger = "test",
description = "Run the automated test suite",
@ -44,6 +62,13 @@
}
newoption {
trigger = "to",
value = "path",
description = "Set the output location for the generated files"
}
--
-- Define the project. Put the release configuration first so it will be the
@ -134,48 +159,3 @@
os.rmdir("bin")
os.rmdir("build")
end
--
-- Use the --to=path option to control where the project files get generated. I use
-- this to create project files for each supported toolset, each in their own folder,
-- in preparation for deployment.
--
newoption {
trigger = "to",
value = "path",
description = "Set the output location for the generated files"
}
--
-- Use the embed action to convert all of the Lua scripts into C strings, which
-- can then be built into the executable. Always embed the scripts before creating
-- a release build.
--
dofile("scripts/embed.lua")
newaction {
trigger = "embed",
description = "Embed scripts in scripts.c; required before release builds",
execute = doembed
}
--
-- Use the release action to prepare source and binary packages for a new release.
-- This action isn't complete yet; a release still requires some manual work.
--
dofile("scripts/release.lua")
newaction {
trigger = "release",
description = "Prepare a new release (incomplete)",
execute = dorelease
}

View File

@ -5,10 +5,8 @@
-- issues in Mac OS X Universal builds.
--
-- remember where I live, so I can find the files I need
local basedir = path.getdirectory(os.getcwd())
local function stripfile(fname)
local function loadScript(fname)
local f = io.open(fname)
local s = assert(f:read("*a"))
f:close()
@ -46,9 +44,7 @@
end
local function appendfile(result, fname, contents)
table.insert(result, "\t/* " .. fname .. " */\n")
local function appendScript(result, contents)
-- break up large strings to fit in Visual Studio's string length limit
local max = 4096
local start = 1
@ -64,62 +60,105 @@
end
local s = contents:sub(start, finish)
table.insert(result, "\t\"" .. s .. iif(finish < len, "\"\n", "\",\n"))
table.insert(result, "\t\"" .. s .. iif(finish < len, '"', '",'))
start = finish + 1
end
table.insert(result, "\n")
table.insert(result, "")
end
function doembed()
-- Find and run the manifest file. Checks the normal search paths to
-- allow for manifest and _premake_main customizations, then falls
-- back to the canonical version at ../src if not found.
-- Prepare the file header
local dir = os.pathsearch("_manifest.lua", _OPTIONS["scripts"], os.getenv("PREMAKE_PATH"))
if not dir then
dir = path.join(basedir, "src")
end
local result = {}
table.insert(result, "/* Premake's Lua scripts, as static data buffers for release mode builds */")
table.insert(result, "/* DO NOT EDIT - this file is autogenerated - see BUILD.txt */")
table.insert(result, "/* To regenerate this file, run: premake5 embed */")
table.insert(result, "")
table.insert(result, '#include "premake.h"')
table.insert(result, "")
scripts = dofile(path.join(dir, "_manifest.lua"))
-- Main script always goes first
table.insert(scripts, 1, "_premake_main.lua")
-- Find all of the _manifest.lua files within the project
-- Convert all scripts to single in-memory string
local result = {}
table.insert(result, "/* Premake's Lua scripts, as static data buffers for release mode builds */\n")
table.insert(result, "/* DO NOT EDIT - this file is autogenerated - see BUILD.txt */\n")
table.insert(result, "/* To regenerate this file, run: premake5 embed */ \n\n")
table.insert(result, "const char* builtin_scripts[] = {\n")
local mask = path.join(_MAIN_SCRIPT_DIR, "**/_manifest.lua")
local manifests = os.matchfiles(mask)
for i, fn in ipairs(scripts) do
local s = stripfile(path.join(dir, fn))
appendfile(result, fn, s)
end
table.insert(result, "\t0\n};\n");
result = table.concat(result)
-- Generate an index of the script file names. Script names are stored
-- relative to the directory containing the manifest, i.e. the main
-- Xcode script, which is at $/modules/xcode/xcode.lua is stored as
-- "xcode/xcode.lua". Core scripts currently get special treatment and
-- are stored as "base/os.lua" instead of "core/base/os.lua"; I would
-- like to treat core like just another module eventually.
-- Compare it to the current contents of scripts.c; only write out
-- a new scripts.c if there have been changes
table.insert(result, "const char* builtin_scripts_index[] = {")
local oldVersion
local scriptsFile = path.join(basedir, "src/host/scripts.c")
for mi = 1, #manifests do
local manifestName = manifests[mi]
local manifestDir = path.getdirectory(manifestName)
local baseDir = path.getdirectory(manifestDir)
local file = io.open(scriptsFile, "r")
if file then
oldVersion = file:read("*a")
file:close()
end
local files = dofile(manifests[mi])
for fi = 1, #files do
local filename = path.join(manifestDir, files[fi])
filename = path.getrelative(baseDir, filename)
if oldVersion ~= result then
print("Writing scripts.c")
file = io.open(scriptsFile, "w+b")
file:write(result)
file:close()
-- core files fix-up for backward compatibility
if filename:startswith("src/") then
filename = filename:sub(5)
end
table.insert(result, '\t"' .. filename .. '",')
end
end
table.insert(result, "\tNULL")
table.insert(result, "};")
table.insert(result, "")
-- Embed the actual script contents
table.insert(result, "const char* builtin_scripts[] = {")
for mi = 1, #manifests do
local manifestName = manifests[mi]
local manifestDir = path.getdirectory(manifestName)
local files = dofile(manifests[mi])
for fi = 1, #files do
local filename = path.join(manifestDir, files[fi])
local scr = loadScript(filename)
appendScript(result, scr)
end
end
table.insert(result, "\tNULL")
table.insert(result, "};")
table.insert(result, "")
-- Write it all out. Check against the current contents of scripts.c first,
-- and only overwrite it if there are actual changes.
result = table.concat(result, "\n")
local scriptsFile = path.getabsolute(path.join(_SCRIPT_DIR, "../src/host/scripts.c"))
local oldVersion
local file = io.open(scriptsFile, "r")
if file then
oldVersion = file:read("*a")
file:close()
end
if oldVersion ~= result then
print("Writing scripts.c")
file = io.open(scriptsFile, "w+b")
file:write(result)
file:close()
end

View File

@ -15,11 +15,11 @@
-- Find and load all of the test file manifests
--
local mask = path.join(_MAIN_SCRIPT_DIR, "**/tests/_manifest.lua")
local mask = path.join(_MAIN_SCRIPT_DIR, "**/tests/_tests.lua")
local manifests = os.matchfiles(mask)
-- Hmm, "**" should probably also match against "."?
local top = path.join(_MAIN_SCRIPT_DIR, "tests/_manifest.lua")
local top = path.join(_MAIN_SCRIPT_DIR, "tests/_tests.lua")
if os.isfile(top) then
table.insert(manifests, 1, top)
end

View File

@ -8,6 +8,14 @@
local versionhelp = "premake5 (Premake Build Script Generator) %s"
-- Load the collection of core scripts, required for everything else to work
local manifest = dofile("_manifest.lua")
for i = 1, #manifest do
dofile(manifest[i])
end
--
-- Script-side program entry point.
--

View File

@ -5,22 +5,6 @@
--
---
-- A replacement for Lua's built-in dofile() function that knows how to
-- search for script files. Note that I've also modified luaL_loadfile()
-- in src/host/lua_auxlib.c to set the _SCRIPT variable and adjust the
-- working directory.
---
local builtin_dofile = dofile
function dofile(fname)
fname = os.locate(fname) or fname
return builtin_dofile(fname)
end
--
-- Find and execute a Lua source file present on the filesystem, but
-- continue without error if the file is not present. This is used to

View File

@ -207,43 +207,6 @@
---
-- Locate a file on one of Premake's search paths: 1) the --script command
-- line argument, 2) the PREMAKE_PATH environment variable, 3) relative to
-- the Premake executable.
--
-- @param ...
-- A list of file names for which to search. The first one discovered
-- is the one that will be returned.
-- @return
-- The path to file if found, or nil if not.
---
function os.locate(...)
local function find(fname)
-- is this a direct path to a file?
if os.isfile(fname) then
return fname
end
-- find it on my paths
local dir = os.pathsearch(fname, _OPTIONS["scripts"], os.getenv("PREMAKE_PATH"), path.getdirectory(_PREMAKE_COMMAND))
if dir then
return path.join(dir, fname)
end
end
for i = 1, select("#",...) do
local fname = select(i, ...)
local result = find(fname)
if result then
return result
end
end
end
---
-- Perform a wildcard search for files or directories.
--

View File

@ -7,19 +7,72 @@
#include "premake.h"
static int chunk_wrapper(lua_State* L);
/* Pull in Lua's aux lib implementation, but rename luaL_loadfile() so I
* can replace it with my own implementation. */
#define luaL_loadfile original_luaL_loadfile
#include "lua-5.1.4/src/lauxlib.c"
#undef luaL_loadfile
/**
* Execute a chunk of code previous loaded by my customized version of
* luaL_loadfile(), below. Sets the _SCRIPT global variable to the
* absolute path of the loaded chunk, and makes its enclosing directory
* current so that relative path references to other files or scripts
* can be used.
* Extend the default implementation of luaL_loadfile() to call my chunk
* wrapper, above, before executing any scripts loaded from a file.
*/
LUALIB_API int luaL_loadfile (lua_State* L, const char* filename)
{
int z;
/* try to locate the script on the filesystem */
if (do_isfile(filename)) {
lua_pushstring(L, filename);
}
else {
lua_pushcfunction(L, os_pathsearch);
lua_pushstring(L, filename);
lua_pushstring(L, scripts_path);
lua_pushstring(L, getenv("PREMAKE_PATH"));
lua_call(L, 3, 1);
if (!lua_isnil(L, -1)) {
lua_pushstring(L, "/");
lua_pushstring(L, filename);
lua_concat(L, 3);
}
}
if (!lua_isnil(L, -1)) {
int i = lua_gettop(L);
z = original_luaL_loadfile(L, lua_tostring(L, -1));
lua_remove(L, i);
}
else {
lua_pop(L, 1);
z = premake_load_embedded_script(L, filename);
}
if (z == OKAY) {
lua_pushstring(L, filename);
lua_pushcclosure(L, chunk_wrapper, 2);
}
return z;
}
/**
* Execute a chunk of code previously loaded by my customized version of
* luaL_loadfile(), below. Sets the _SCRIPT global variable to the absolute
* path of the loaded chunk, and makes its enclosing directory current so
* that relative path references to other files or scripts can be used.
*/
static int chunk_wrapper(lua_State* L)
@ -33,10 +86,11 @@ static int chunk_wrapper(lua_State* L)
args = lua_gettop(L);
/* Remember the current _SCRIPT and working directory so I can
* restore them after the script chunk has been run. */
* restore them after this new chunk has been run. */
do_getcwd(cwd, PATH_MAX);
lua_getglobal(L, "_SCRIPT");
lua_getglobal(L, "_SCRIPT_DIR");
/* Set the new _SCRIPT variable... */
@ -70,24 +124,8 @@ static int chunk_wrapper(lua_State* L)
do_chdir(cwd);
lua_pushvalue(L, args + 1);
lua_setglobal(L, "_SCRIPT");
lua_pushvalue(L, args + 2);
lua_setglobal(L, "_SCRIPT_DIR");
return lua_gettop(L) - args - 1;
}
/**
* Extend the default implementation of luaL_loadfile() to call my chunk
* wrapper, above, before executing any scripts loaded from a file.
*/
LUALIB_API int luaL_loadfile (lua_State* L, const char* filename)
{
int z = original_luaL_loadfile(L, filename);
if (z == 0) {
lua_pushstring(L, filename);
lua_pushcclosure(L, chunk_wrapper, 2);
}
return z;
return lua_gettop(L) - args - 2;
}

42
src/host/os_locate.c Normal file
View File

@ -0,0 +1,42 @@
/**
* \file os_locate.c
* \brief Locates a file, given a set of search paths.
* \author Copyright (c) 2014 Jason Perkins and the Premake project
*/
#include <stdlib.h>
#include "premake.h"
int os_locate(lua_State* L)
{
int i, top;
const char* premake_path = getenv("PREMAKE_PATH");
top = lua_gettop(L);
for (i = 1; i <= top; ++i) {
/* direct path to file? */
if (do_isfile(lua_tostring(L, i))) {
lua_pushvalue(L, i);
return 1;
}
/* search for it */
lua_pushcfunction(L, os_pathsearch);
lua_pushvalue(L, i);
lua_getglobal(L, "_MAIN_SCRIPT_DIR");
lua_pushstring(L, scripts_path);
lua_pushstring(L, premake_path);
lua_call(L, 4, 1);
if (!lua_isnil(L, -1)) {
lua_pushcfunction(L, path_join);
lua_pushvalue(L, -2);
lua_pushvalue(L, i);
lua_call(L, 2, 1);
return 1;
}
}
return 0;
}

View File

@ -17,21 +17,14 @@
#define VERSION "HEAD"
#define COPYRIGHT "Copyright (C) 2002-2014 Jason Perkins and the Premake Project"
#define PROJECT_URL "https://bitbucket.org/premake/premake-dev/wiki"
#define ERROR_MESSAGE "%s\n"
#define ERROR_MESSAGE "Error: %s\n"
static int process_arguments(lua_State* L, int argc, const char** argv);
static int load_builtin_scripts(lua_State* L);
int premake_locate(lua_State* L, const char* argv0);
/* A search path for script files */
static const char* scripts_path = NULL;
/* precompiled bytecode buffer; in bytecode.c */
extern const char* builtin_scripts[];
const char* scripts_path = NULL;
/* Built-in functions */
@ -67,6 +60,7 @@ static const luaL_Reg os_functions[] = {
{ "getversion", os_getversion },
{ "isfile", os_isfile },
{ "islink", os_islink },
{ "locate", os_locate },
{ "matchdone", os_matchdone },
{ "matchisfile", os_matchisfile },
{ "matchname", os_matchname },
@ -127,8 +121,6 @@ int premake_init(lua_State* L)
int premake_execute(lua_State* L, int argc, const char** argv)
{
int z;
/* push the absolute path to the Premake executable */
lua_pushcfunction(L, path_getabsolute);
premake_locate(L, argv[0]);
@ -136,15 +128,31 @@ int premake_execute(lua_State* L, int argc, const char** argv)
lua_setglobal(L, "_PREMAKE_COMMAND");
/* Parse the command line arguments */
z = process_arguments(L, argc, argv);
if (process_arguments(L, argc, argv) != OKAY) {
return !OKAY;
}
/* Run the built-in Premake scripts */
if (z == OKAY) z = load_builtin_scripts(L);
/* load the main script */
if (luaL_dofile(L, "_premake_main.lua") != OKAY) {
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
return z;
/* and call the main entry point */
lua_getglobal(L, "_premake_main");
if (lua_pcall(L, 0, 1, 0) != OKAY) {
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
else {
return (int)lua_tonumber(L, -1);
}
return OKAY;
}
/**
* Locate the Premake executable, and push its full path to the Lua stack.
* Based on:
@ -264,112 +272,34 @@ int process_arguments(lua_State* L, int argc, const char** argv)
#if !defined(NDEBUG)
/**
* When running in debug mode, the scripts are loaded from the disk. The path to
* the scripts must be provided via either the /scripts command line option or
* the PREMAKE_PATH environment variable.
* Load a script that was previously embedded into the executable. If
* successful, a function containing the new script chunk is pushed to
* the stack, just like luaL_loadfile would do had the chunk been loaded
* from a file.
*/
int load_builtin_scripts(lua_State* L)
{
const char* filename;
/* call os.pathsearch() to locate _premake_main.lua */
lua_pushcfunction(L, os_pathsearch);
lua_pushstring(L, "_premake_main.lua");
lua_pushstring(L, scripts_path);
lua_pushstring(L, getenv("PREMAKE_PATH"));
lua_call(L, 3, 1);
if (lua_isnil(L, -1))
{
printf(ERROR_MESSAGE,
"Unable to find _premake_main.lua; use /scripts option when in debug mode!\n"
"Please refer to the documentation (or build in release mode instead)."
);
return !OKAY;
}
/* set the _SCRIPTS variable for the manifest, so it can locate everything */
scripts_path = lua_tostring(L, -1);
filename = lua_pushfstring(L, "%s/_manifest.lua", scripts_path);
lua_pushvalue(L, -1);
lua_setglobal(L, "_SCRIPT");
/* load the manifest, which includes all the required scripts */
if (luaL_dofile(L, filename))
{
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
lua_pushnil(L);
while (lua_next(L, -2))
{
filename = lua_pushfstring(L, "%s/%s", scripts_path, lua_tostring(L, -1));
if (luaL_dofile(L, filename)) {
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
lua_pop(L, 2);
}
lua_pop(L, 1);
/* run the bootstrapping script */
filename = lua_pushfstring(L, "%s/_premake_main.lua", scripts_path);
if (luaL_dofile(L, filename))
{
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
/* in debug mode, show full traceback on all errors */
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
/* hand off control to the scripts */
lua_getglobal(L, "_premake_main");
if (lua_pcall(L, 0, 1, -2) != OKAY)
{
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
else
{
return (int)lua_tonumber(L, -1);
}
}
#endif
#if defined(NDEBUG)
/**
* When running in release mode, the scripts are loaded from a static data
* buffer, where they were stored by a preprocess. To update these embedded
* scripts, run `premake5 embed` then rebuild.
*/
int load_builtin_scripts(lua_State* L)
int premake_load_embedded_script(lua_State* L, const char* filename)
{
/* locate a record matching the filename */
int i;
for (i = 0; builtin_scripts[i]; ++i)
{
if (luaL_dostring(L, builtin_scripts[i]) != OKAY)
{
for (i = 0; builtin_scripts_index[i] != NULL; ++i) {
if (strcmp(builtin_scripts_index[i], filename) == 0) {
break;
}
}
/* load its script */
if (builtin_scripts_index[i] != NULL) {
const char* chunk = builtin_scripts[i];
if (luaL_loadbuffer(L, chunk, strlen(chunk), filename) == OKAY) {
return OKAY;
}
else {
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
}
/* hand off control to the scripts */
lua_getglobal(L, "_premake_main");
if (lua_pcall(L, 0, 1, 0) != OKAY)
{
printf(ERROR_MESSAGE, lua_tostring(L, -1));
return !OKAY;
}
else
{
return (int)lua_tonumber(L, -1);
}
return !OKAY;
}
#endif

View File

@ -60,6 +60,10 @@
#define OKAY (0)
/* If a /scripts argument is present, its value */
extern const char* scripts_path;
/* Bootstrapping helper functions */
int do_chdir(const char* path);
unsigned long do_hash(const char* str, int seed);
@ -91,6 +95,7 @@ int os_is64bit(lua_State* L);
int os_isdir(lua_State* L);
int os_isfile(lua_State* L);
int os_islink(lua_State* L);
int os_locate(lua_State* L);
int os_matchdone(lua_State* L);
int os_matchisfile(lua_State* L);
int os_matchname(lua_State* L);
@ -110,3 +115,9 @@ int string_startswith(lua_State* L);
int premake_init(lua_State* L);
int premake_locate(lua_State* L, const char* argv0);
int premake_execute(lua_State* L, int argc, const char** argv);
int premake_find_exe(lua_State* L, const char* argv0);
int premake_load_embedded_script(lua_State* L, const char* filename);
extern const char* builtin_scripts_index[];
extern const char* builtin_scripts[];

View File

@ -1,6 +1,5 @@
return {
-- Base API tests
"test_dofile.lua",
"test_string.lua",
"base/test_configset.lua",
"base/test_context.lua",

View File

@ -42,7 +42,7 @@
--
function suite.isfile_ReturnsTrue_OnExistingFile()
test.istrue(os.isfile("_manifest.lua"))
test.istrue(os.isfile("_tests.lua"))
end
function suite.isfile_ReturnsFalse_OnNonexistantFile()
@ -120,13 +120,13 @@
end
function suite.pathsearch_ReturnsPath_OnFound()
test.isequal(_TESTS_DIR, os.pathsearch("_manifest.lua", _TESTS_DIR))
test.isequal(_TESTS_DIR, os.pathsearch("_tests.lua", _TESTS_DIR))
end
function suite.pathsearch_FindsFile_OnComplexPath()
test.isequal(_TESTS_DIR, os.pathsearch("_manifest.lua", "aaa;" .. _TESTS_DIR .. ";bbb"))
test.isequal(_TESTS_DIR, os.pathsearch("_tests.lua", "aaa;" .. _TESTS_DIR .. ";bbb"))
end
function suite.pathsearch_NilPathsAllowed()
test.isequal(_TESTS_DIR, os.pathsearch("_manifest.lua", nil, _TESTS_DIR, nil))
test.isequal(_TESTS_DIR, os.pathsearch("_tests.lua", nil, _TESTS_DIR, nil))
end

View File

@ -1,33 +0,0 @@
--
-- tests/test_dofile.lua
-- Automated test suite for the extended dofile() functions.
-- Copyright (c) 2008, 2014 Jason Perkins and the Premake project
--
local suite = test.declare("do_file")
local os_getenv
function suite.setup()
os_getenv = os.getenv
end
function suite.teardown()
os.getenv = os_getenv
end
function suite.searchesPath()
os.getenv = function() return _TESTS_DIR .. "/folder" end
result = dofile("ok.lua")
test.isequal("ok", result)
end
function suite.searchesScriptsOption()
_OPTIONS["scripts"] = _TESTS_DIR .. "/folder"
result = dofile("ok.lua")
test.isequal("ok", result)
end