Add "MINETEST_MOD_PATH" environment variable (#11515)

This adds an environment variable MINETEST_MOD_PATH.
When it exists, Minetest will look there for mods in addition to ~/.minetest/mods/.
This commit is contained in:
emixa-d 2021-10-06 22:19:41 +00:00 committed by GitHub
parent 53e126ac49
commit 9fab5d594c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 74 additions and 6 deletions

@ -682,10 +682,8 @@ function pkgmgr.preparemodlist(data)
local game_mods = {} local game_mods = {}
--read global mods --read global mods
local modpath = core.get_modpath() local modpaths = core.get_modpaths()
for _, modpath in ipairs(modpaths) do
if modpath ~= nil and
modpath ~= "" then
get_mods(modpath, global_mods) get_mods(modpath, global_mods)
end end

@ -219,7 +219,13 @@ Package - content which is downloadable from the content db, may or may not be i
* returns path to global user data, * returns path to global user data,
the directory that contains user-provided mods, worlds, games, and texture packs. the directory that contains user-provided mods, worlds, games, and texture packs.
* core.get_modpath() (possible in async calls) * core.get_modpath() (possible in async calls)
* returns path to global modpath * returns path to global modpath, where mods can be installed
* core.get_modpaths() (possible in async calls)
* returns list of paths to global modpaths, where mods have been installed
The difference with "core.get_modpath" is that no mods should be installed in these
directories by Minetest -- they might be read-only.
* core.get_clientmodpath() (possible in async calls) * core.get_clientmodpath() (possible in async calls)
* returns path to global client-side modpath * returns path to global client-side modpath
* core.get_gamepath() (possible in async calls) * core.get_gamepath() (possible in async calls)

@ -119,6 +119,9 @@ Display an interactive terminal over ncurses during execution.
.TP .TP
.B MINETEST_SUBGAME_PATH .B MINETEST_SUBGAME_PATH
Colon delimited list of directories to search for games. Colon delimited list of directories to search for games.
.TP
.B MINETEST_MOD_PATH
Colon delimited list of directories to search for mods.
.SH BUGS .SH BUGS
Please report all bugs at https://github.com/minetest/minetest/issues. Please report all bugs at https://github.com/minetest/minetest/issues.

@ -113,6 +113,10 @@ SubgameSpec findSubgame(const std::string &id)
if (user != share || user_game) if (user != share || user_game)
mods_paths.insert(user + DIR_DELIM + "mods"); mods_paths.insert(user + DIR_DELIM + "mods");
for (const std::string &mod_path : getEnvModPaths()) {
mods_paths.insert(mod_path);
}
// Get meta // Get meta
std::string conf_path = game_path + DIR_DELIM + "game.conf"; std::string conf_path = game_path + DIR_DELIM + "game.conf";
Settings conf; Settings conf;
@ -384,3 +388,13 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name,
if (new_game_settings) if (new_game_settings)
delete game_settings; delete game_settings;
} }
std::vector<std::string> getEnvModPaths()
{
const char *c_mod_path = getenv("MINETEST_MOD_PATH");
std::vector<std::string> paths;
Strfnd search_paths(c_mod_path ? c_mod_path : "");
while (!search_paths.at_end())
paths.push_back(search_paths.next(PATH_DELIM));
return paths;
}

@ -58,6 +58,8 @@ SubgameSpec findWorldSubgame(const std::string &world_path);
std::set<std::string> getAvailableGameIds(); std::set<std::string> getAvailableGameIds();
std::vector<SubgameSpec> getAvailableGames(); std::vector<SubgameSpec> getAvailableGames();
// Get the list of paths to mods in the environment variable $MINETEST_MOD_PATH
std::vector<std::string> getEnvModPaths();
bool getWorldExists(const std::string &world_path); bool getWorldExists(const std::string &world_path);
//! Try to get the displayed name of a world //! Try to get the displayed name of a world

@ -502,6 +502,21 @@ int ModApiMainMenu::l_get_modpath(lua_State *L)
return 1; return 1;
} }
/******************************************************************************/
int ModApiMainMenu::l_get_modpaths(lua_State *L)
{
int index = 1;
lua_newtable(L);
ModApiMainMenu::l_get_modpath(L);
lua_rawseti(L, -2, index);
for (const std::string &component : getEnvModPaths()) {
index++;
lua_pushstring(L, component.c_str());
lua_rawseti(L, -2, index);
}
return 1;
}
/******************************************************************************/ /******************************************************************************/
int ModApiMainMenu::l_get_clientmodpath(lua_State *L) int ModApiMainMenu::l_get_clientmodpath(lua_State *L)
{ {
@ -856,6 +871,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_mapgen_names); API_FCT(get_mapgen_names);
API_FCT(get_user_path); API_FCT(get_user_path);
API_FCT(get_modpath); API_FCT(get_modpath);
API_FCT(get_modpaths);
API_FCT(get_clientmodpath); API_FCT(get_clientmodpath);
API_FCT(get_gamepath); API_FCT(get_gamepath);
API_FCT(get_texturepath); API_FCT(get_texturepath);
@ -889,6 +905,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
API_FCT(get_mapgen_names); API_FCT(get_mapgen_names);
API_FCT(get_user_path); API_FCT(get_user_path);
API_FCT(get_modpath); API_FCT(get_modpath);
API_FCT(get_modpaths);
API_FCT(get_clientmodpath); API_FCT(get_clientmodpath);
API_FCT(get_gamepath); API_FCT(get_gamepath);
API_FCT(get_texturepath); API_FCT(get_texturepath);

@ -112,6 +112,8 @@ private:
static int l_get_modpath(lua_State *L); static int l_get_modpath(lua_State *L);
static int l_get_modpaths(lua_State *L);
static int l_get_clientmodpath(lua_State *L); static int l_get_clientmodpath(lua_State *L);
static int l_get_gamepath(lua_State *L); static int l_get_gamepath(lua_State *L);

@ -44,6 +44,7 @@ set (UNITTEST_CLIENT_SRCS
set (TEST_WORLDDIR ${CMAKE_CURRENT_SOURCE_DIR}/test_world) set (TEST_WORLDDIR ${CMAKE_CURRENT_SOURCE_DIR}/test_world)
set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/devtest) set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/devtest)
set (TEST_MOD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/test_mod)
configure_file( configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in"

@ -4,3 +4,4 @@
#define TEST_WORLDDIR "@TEST_WORLDDIR@" #define TEST_WORLDDIR "@TEST_WORLDDIR@"
#define TEST_SUBGAME_PATH "@TEST_SUBGAME_PATH@" #define TEST_SUBGAME_PATH "@TEST_SUBGAME_PATH@"
#define TEST_MOD_PATH "@TEST_MOD_PATH@"

@ -0,0 +1 @@
-- deliberately empty

@ -0,0 +1,2 @@
name = test_mod
description = A mod doing nothing, to test if MINETEST_MOD_PATH is recognised

@ -48,14 +48,20 @@ static TestServerModManager g_test_instance;
void TestServerModManager::runTests(IGameDef *gamedef) void TestServerModManager::runTests(IGameDef *gamedef)
{ {
const char *saved_env_mt_subgame_path = getenv("MINETEST_SUBGAME_PATH"); const char *saved_env_mt_subgame_path = getenv("MINETEST_SUBGAME_PATH");
const char *saved_env_mt_mod_path = getenv("MINETEST_MOD_PATH");
#ifdef WIN32 #ifdef WIN32
{ {
std::string subgame_path("MINETEST_SUBGAME_PATH="); std::string subgame_path("MINETEST_SUBGAME_PATH=");
subgame_path.append(TEST_SUBGAME_PATH); subgame_path.append(TEST_SUBGAME_PATH);
_putenv(subgame_path.c_str()); _putenv(subgame_path.c_str());
std::string mod_path("MINETEST_MOD_PATH=");
mod_path.append(TEST_MOD_PATH);
_putenv(mod_path.c_str());
} }
#else #else
setenv("MINETEST_SUBGAME_PATH", TEST_SUBGAME_PATH, 1); setenv("MINETEST_SUBGAME_PATH", TEST_SUBGAME_PATH, 1);
setenv("MINETEST_MOD_PATH", TEST_MOD_PATH, 1);
#endif #endif
TEST(testCreation); TEST(testCreation);
@ -75,12 +81,21 @@ void TestServerModManager::runTests(IGameDef *gamedef)
if (saved_env_mt_subgame_path) if (saved_env_mt_subgame_path)
subgame_path.append(saved_env_mt_subgame_path); subgame_path.append(saved_env_mt_subgame_path);
_putenv(subgame_path.c_str()); _putenv(subgame_path.c_str());
std::string mod_path("MINETEST_MOD_PATH=");
if (saved_env_mt_mod_path)
mod_path.append(saved_env_mt_mod_path);
_putenv(mod_path.c_str());
} }
#else #else
if (saved_env_mt_subgame_path) if (saved_env_mt_subgame_path)
setenv("MINETEST_SUBGAME_PATH", saved_env_mt_subgame_path, 1); setenv("MINETEST_SUBGAME_PATH", saved_env_mt_subgame_path, 1);
else else
unsetenv("MINETEST_SUBGAME_PATH"); unsetenv("MINETEST_SUBGAME_PATH");
if (saved_env_mt_mod_path)
setenv("MINETEST_MOD_PATH", saved_env_mt_mod_path, 1);
else
unsetenv("MINETEST_MOD_PATH");
#endif #endif
} }
@ -89,6 +104,7 @@ void TestServerModManager::testCreation()
std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt"; std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt";
Settings world_config; Settings world_config;
world_config.set("gameid", "devtest"); world_config.set("gameid", "devtest");
world_config.set("load_mod_test_mod", "true");
UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true); UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true);
ServerModManager sm(TEST_WORLDDIR); ServerModManager sm(TEST_WORLDDIR);
} }
@ -119,16 +135,21 @@ void TestServerModManager::testGetMods()
UASSERTEQ(bool, mods.empty(), false); UASSERTEQ(bool, mods.empty(), false);
// Ensure we found basenodes mod (part of devtest) // Ensure we found basenodes mod (part of devtest)
// and test_mod (for testing MINETEST_MOD_PATH).
bool default_found = false; bool default_found = false;
bool test_mod_found = false;
for (const auto &m : mods) { for (const auto &m : mods) {
if (m.name == "basenodes") if (m.name == "basenodes")
default_found = true; default_found = true;
if (m.name == "test_mod")
test_mod_found = true;
// Verify if paths are not empty // Verify if paths are not empty
UASSERTEQ(bool, m.path.empty(), false); UASSERTEQ(bool, m.path.empty(), false);
} }
UASSERTEQ(bool, default_found, true); UASSERTEQ(bool, default_found, true);
UASSERTEQ(bool, test_mod_found, true);
} }
void TestServerModManager::testGetModspec() void TestServerModManager::testGetModspec()