/** * \file premake.c * \brief Program entry point. * \author Copyright (c) 2002-2015 Jason Perkins and the Premake project */ #include #include #include #include "premake.h" #if PLATFORM_MACOSX #include #endif #define VERSION "5.0.0-dev" #define COPYRIGHT "Copyright (C) 2002-2015 Jason Perkins and the Premake Project" #define PROJECT_URL "https://github.com/premake/premake-core/wiki" #define ERROR_MESSAGE "Error: %s\n" static void build_premake_path(lua_State* L); static int process_arguments(lua_State* L, int argc, const char** argv); static int run_premake_main(lua_State* L, const char* script); /* A search path for script files */ const char* scripts_path = NULL; /* Built-in functions */ static const luaL_Reg criteria_functions[] = { { "_compile", criteria_compile }, { "_delete", criteria_delete }, { "matches", criteria_matches }, { NULL, NULL } }; static const luaL_Reg debug_functions[] = { { "prompt", debug_prompt }, { NULL, NULL } }; static const luaL_Reg path_functions[] = { { "getabsolute", path_getabsolute }, { "getrelative", path_getrelative }, { "isabsolute", path_isabsolute }, { "join", path_join }, { "normalize", path_normalize }, { "translate", path_translate }, { NULL, NULL } }; static const luaL_Reg os_functions[] = { { "chdir", os_chdir }, { "chmod", os_chmod }, { "copyfile", os_copyfile }, { "_is64bit", os_is64bit }, { "isdir", os_isdir }, { "getcwd", os_getcwd }, { "getversion", os_getversion }, { "isfile", os_isfile }, { "islink", os_islink }, { "locate", os_locate }, { "matchdone", os_matchdone }, { "matchisfile", os_matchisfile }, { "matchname", os_matchname }, { "matchnext", os_matchnext }, { "matchstart", os_matchstart }, { "mkdir", os_mkdir }, { "pathsearch", os_pathsearch }, { "realpath", os_realpath }, { "rmdir", os_rmdir }, { "stat", os_stat }, { "uuid", os_uuid }, { "writefile_ifnotequal", os_writefile_ifnotequal }, { NULL, NULL } }; static const luaL_Reg string_functions[] = { { "endswith", string_endswith }, { "hash", string_hash }, { "sha1", string_sha1 }, { "startswith", string_startswith }, { NULL, NULL } }; #ifdef PREMAKE_CURL static const luaL_Reg http_functions[] = { { "get", http_get }, { "download", http_download }, { NULL, NULL } }; #endif /** * Initialize the Premake Lua environment. */ int premake_init(lua_State* L) { const char* value; luaL_register(L, "criteria", criteria_functions); luaL_register(L, "debug", debug_functions); luaL_register(L, "path", path_functions); luaL_register(L, "os", os_functions); luaL_register(L, "string", string_functions); #ifdef PREMAKE_CURL luaL_register(L, "http", http_functions); #endif /* push the application metadata */ lua_pushstring(L, LUA_COPYRIGHT); lua_setglobal(L, "_COPYRIGHT"); lua_pushstring(L, VERSION); lua_setglobal(L, "_PREMAKE_VERSION"); lua_pushstring(L, COPYRIGHT); lua_setglobal(L, "_PREMAKE_COPYRIGHT"); lua_pushstring(L, PROJECT_URL); lua_setglobal(L, "_PREMAKE_URL"); /* set the OS platform variable */ lua_pushstring(L, PLATFORM_STRING); lua_setglobal(L, "_OS"); /* find the user's home directory */ value = getenv("HOME"); if (!value) value = getenv("USERPROFILE"); lua_pushstring(L, value); lua_setglobal(L, "_USER_HOME_DIR"); /* publish the initial working directory */ os_getcwd(L); lua_setglobal(L, "_WORKING_DIR"); /* start the premake namespace */ lua_newtable(L); lua_setglobal(L, "premake"); return OKAY; } int premake_execute(lua_State* L, int argc, const char** argv, const char* script) { int iErrFunc; /* push the absolute path to the Premake executable */ lua_pushcfunction(L, path_getabsolute); premake_locate_executable(L, argv[0]); lua_call(L, 1, 1); lua_setglobal(L, "_PREMAKE_COMMAND"); /* Parse the command line arguments */ if (process_arguments(L, argc, argv) != OKAY) { return !OKAY; } /* Use --scripts and PREMAKE_PATH to populate premake.path */ build_premake_path(L); /* Find and run the main Premake bootstrapping script */ if (run_premake_main(L, script) != OKAY) { printf(ERROR_MESSAGE, lua_tostring(L, -1)); return !OKAY; } /* in debug mode, show full traceback on all errors */ #if defined(NDEBUG) iErrFunc = 0; #else lua_getglobal(L, "debug"); lua_getfield(L, -1, "traceback"); iErrFunc = -2; #endif /* and call the main entry point */ lua_getglobal(L, "_premake_main"); if (lua_pcall(L, 0, 1, iErrFunc) != OKAY) { printf(ERROR_MESSAGE, lua_tostring(L, -1)); return !OKAY; } else { return (int)lua_tonumber(L, -1); } } /** * Locate the Premake executable, and push its full path to the Lua stack. * Based on: * http://sourceforge.net/tracker/index.php?func=detail&aid=3351583&group_id=71616&atid=531880 * http://stackoverflow.com/questions/933850/how-to-find-the-location-of-the-executable-in-c * http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe */ int premake_locate_executable(lua_State* L, const char* argv0) { char buffer[PATH_MAX]; const char* path = NULL; #if PLATFORM_WINDOWS DWORD len = GetModuleFileName(NULL, buffer, PATH_MAX); if (len > 0) path = buffer; #endif #if PLATFORM_MACOSX CFURLRef bundleURL = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); CFStringRef pathRef = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle); if (CFStringGetCString(pathRef, buffer, PATH_MAX - 1, kCFStringEncodingUTF8)) path = buffer; #endif #if PLATFORM_LINUX int len = readlink("/proc/self/exe", buffer, PATH_MAX); if (len > 0) path = buffer; #endif #if PLATFORM_BSD int len = readlink("/proc/curproc/file", buffer, PATH_MAX); if (len < 0) len = readlink("/proc/curproc/exe", buffer, PATH_MAX); if (len > 0) path = buffer; #endif #if PLATFORM_SOLARIS int len = readlink("/proc/self/path/a.out", buffer, PATH_MAX); if (len > 0) path = buffer; #endif /* As a fallback, search the PATH with argv[0] */ if (!path) { lua_pushcfunction(L, os_pathsearch); lua_pushstring(L, argv0); lua_pushstring(L, getenv("PATH")); if (lua_pcall(L, 2, 1, 0) == OKAY && !lua_isnil(L, -1)) { lua_pushstring(L, "/"); lua_pushstring(L, argv0); lua_concat(L, 3); path = lua_tostring(L, -1); } } /* If all else fails, use argv[0] as-is and hope for the best */ if (!path) { /* make it absolute, if needed */ os_getcwd(L); lua_pushstring(L, "/"); lua_pushstring(L, argv0); if (!path_isabsolute(L)) { lua_concat(L, 3); } else { lua_pop(L, 1); } path = lua_tostring(L, -1); } lua_pushstring(L, path); return 1; } /** * Checks one or more of the standard script search locations to locate the * specified file. If found, returns the discovered path to the script on * the top of the Lua stack. */ int premake_test_file(lua_State* L, const char* filename, int searchMask) { int i; if (searchMask & TEST_LOCAL) { if (do_isfile(filename)) { lua_pushcfunction(L, path_getabsolute); lua_pushstring(L, filename); lua_call(L, 1, 1); return OKAY; } } if (scripts_path && (searchMask & TEST_SCRIPTS)) { if (do_locate(L, filename, scripts_path)) return OKAY; } if (searchMask & TEST_PATH) { const char* path = getenv("PREMAKE_PATH"); if (path && do_locate(L, filename, path)) return OKAY; } #if !defined(PREMAKE_NO_BUILTIN_SCRIPTS) if ((searchMask & TEST_EMBEDDED) != 0) { /* Try to locate a record matching the filename */ for (i = 0; builtin_scripts_index[i] != NULL; ++i) { if (strcmp(builtin_scripts_index[i], filename) == 0) { lua_pushstring(L, "$/"); lua_pushstring(L, filename); lua_concat(L, 2); return OKAY; } } } #endif return !OKAY; } static const char* set_scripts_path(const char* relativePath) { char* path = (char*)malloc(PATH_MAX); do_getabsolute(path, relativePath, NULL); scripts_path = path; return scripts_path; } /** * Set the premake.path variable, pulling from the --scripts argument * and PREMAKE_PATH environment variable if present. */ static void build_premake_path(lua_State* L) { int top; const char* value; lua_getglobal(L, "premake"); top = lua_gettop(L); /* Start by searching the current working directory */ lua_pushstring(L, "."); /* The --scripts argument goes next, if present */ if (scripts_path) { lua_pushstring(L, ";"); lua_pushstring(L, scripts_path); } /* Then the PREMAKE_PATH environment variable */ value = getenv("PREMAKE_PATH"); if (value) { lua_pushstring(L, ";"); lua_pushstring(L, value); } /* Then in ~/.premake */ lua_pushstring(L, ";"); lua_getglobal(L, "_USER_HOME_DIR"); lua_pushstring(L, "/.premake"); /* In the user's Application Support folder */ #if defined(PLATFORM_MACOSX) lua_pushstring(L, ";"); lua_getglobal(L, "_USER_HOME_DIR"); lua_pushstring(L, "/Library/Application Support/Premake"); #endif /* In the /usr tree */ lua_pushstring(L, ";/usr/local/share/premake;/usr/share/premake"); /* Put it all together */ lua_concat(L, lua_gettop(L) - top); /* Match Lua's package.path; use semicolon separators */ #if !defined(PLATFORM_WINDOWS) lua_getglobal(L, "string"); lua_getfield(L, -1, "gsub"); lua_pushvalue(L, -3); lua_pushstring(L, ":"); lua_pushstring(L, ";"); lua_call(L, 3, 1); /* remove the string global table */ lua_remove(L, -2); /* remove the previously concatonated result */ lua_remove(L, -2); #endif /* Store it in premake.path */ lua_setfield(L, -2, "path"); /* Remove the premake namespace table */ lua_pop(L, 1); } /** * Copy all command line arguments into the script-side _ARGV global, and * check for the presence of a /scripts= argument to help locate * the manifest if needed. * \returns OKAY if successful. */ static int process_arguments(lua_State* L, int argc, const char** argv) { int i; /* Copy all arguments in the _ARGV global */ lua_newtable(L); for (i = 1; i < argc; ++i) { lua_pushstring(L, argv[i]); lua_rawseti(L, -2, luaL_getn(L, -2) + 1); /* The /scripts option gets picked up here; used later to find the * manifest and scripts later if necessary */ if (strncmp(argv[i], "/scripts=", 9) == 0) { argv[i] = set_scripts_path(argv[i] + 9); } else if (strncmp(argv[i], "--scripts=", 10) == 0) { argv[i] = set_scripts_path(argv[i] + 10); } } lua_setglobal(L, "_ARGV"); return OKAY; } /** * Find and run the main Premake bootstrapping script. The loading of the * bootstrap and the other core scripts use a limited set of search paths * to avoid mismatches between the native host code and the scripts * themselves. */ static int run_premake_main(lua_State* L, const char* script) { /* Release builds want to load the embedded scripts, with --scripts * argument allowed as an override. Debug builds will look at the * local file system first, then fall back to embedded. */ #if defined(NDEBUG) int z = premake_test_file(L, script, TEST_SCRIPTS | TEST_EMBEDDED); #else int z = premake_test_file(L, script, TEST_LOCAL | TEST_SCRIPTS | TEST_PATH | TEST_EMBEDDED); #endif /* If no embedded script can be found, release builds will then * try to fall back to the local file system, just in case */ #if defined(NDEBUG) if (z != OKAY) { z = premake_test_file(L, script, TEST_LOCAL | TEST_PATH); } #endif if (z == OKAY) { z = luaL_dofile(L, lua_tostring(L, -1)); } return z; } /** * Locate a file in the embedded script index. If found, returns the * contents of the file's script. */ const char* premake_find_embedded_script(const char* filename) { #if !defined(PREMAKE_NO_BUILTIN_SCRIPTS) int i; for (i = 0; builtin_scripts_index[i] != NULL; ++i) { if (strcmp(builtin_scripts_index[i], filename) == 0) { return builtin_scripts[i]; } } #endif return NULL; } /** * 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 premake_load_embedded_script(lua_State* L, const char* filename) { #if !defined(NDEBUG) static int warned = 0; #endif const char* chunk = premake_find_embedded_script(filename); if (chunk == NULL) { return !OKAY; } /* Debug builds probably want to be loading scripts from the disk */ #if !defined(NDEBUG) if (!warned) { warned = 1; printf("** warning: using embedded script '%s'; use /scripts argument to load from files\n", filename); } #endif /* "Fully qualify" the filename by turning it into the form $/filename */ lua_pushstring(L, "$/"); lua_pushstring(L, filename); lua_concat(L, 2); /* Load the chunk */ return luaL_loadbuffer(L, chunk, strlen(chunk), filename); }