diff --git a/src/lua/init.lua b/src/lua/init.lua index 9fd56f483fac969e5609d2578b3eddd19256066c..ff3e74c3c366dea1d355c4ea4576aca8d496f2ad 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -44,6 +44,33 @@ tarantool_exit(int); local fio = require("fio") +local soext = (jit.os == "OSX" and "dylib" or "so") + +local ROCKS_LIB_PATH = '.rocks/lib/tarantool' +local ROCKS_LUA_PATH = '.rocks/share/tarantool' +local LIB_TEMPLATES = { '?.'..soext } +local LUA_TEMPLATES = { '?.lua', '?/init.lua' } +local ROCKS_LIB_TEMPLATES = { ROCKS_LIB_PATH .. '/?.'..soext } +local ROCKS_LUA_TEMPLATES = { ROCKS_LUA_PATH .. '/?.lua', ROCKS_LUA_PATH .. '/?/init.lua' } + +local package_searchroot + +local function searchroot() + return package_searchroot or fio.cwd() +end + +local function setsearchroot(path) + if not path then + -- Here we need to get this function caller's sourcedir. + path = debug.sourcedir(3) + elseif path == box.NULL then + path = nil + else + assert(type(path) == 'string', 'Search root must be a string') + end + package_searchroot = path and fio.abspath(path) +end + dostring = function(s, ...) local chunk, message = loadstring(s) if chunk == nil then @@ -70,8 +97,6 @@ local function pid() return tonumber(ffi.C.getpid()) end -local soext = (jit.os == "OSX" and "dylib" or "so") - local function mksymname(name) local mark = string.find(name, "-") if mark then name = string.sub(name, mark + 1) end @@ -86,81 +111,72 @@ local function load_lua(file) return loadfile(file) end -local function search_cwd_lib(name) - local path = "./?."..soext - return package.searchpath(name, path) -end +local function traverse_path(path) + path = fio.abspath(path) + local paths = { path } -local function search_cwd_lua(name) - local path = "./?.lua;./?/init.lua" - return package.searchpath(name, path) -end - -local function traverse_rocks(name, pathes_search) - local cwd = fio.cwd() - local index = string.len(cwd) + 1 - local strerr = "" - while index ~= nil do - cwd = string.sub(cwd, 1, index - 1) - for i, path in ipairs(pathes_search) do - local file, err = package.searchpath(name, cwd .. path) - if err == nil then - return file - end - strerr = strerr .. err - end - index = string.find(cwd, "/[^/]*$") + while path ~= '/' do + path = fio.dirname(path) + table.insert(paths, path) end - return nil, strerr -end -local function search_rocks_lua(name) - local pathes_search = { - "/.rocks/share/tarantool/?.lua;", - "/.rocks/share/tarantool/?/init.lua;", - } - return traverse_rocks(name, pathes_search) + return paths end -local function search_rocks_lib(name) - local pathes_search = { - "/.rocks/lib/tarantool/?."..soext - } - return traverse_rocks(name, pathes_search) -end +-- Generate a search function, which performs searching through +-- templates setup in options. +-- +-- @param path_fn function which returns a base path for the +-- resulting template +-- @param templates table with lua search templates +-- @param need_traverse bool flag which tells search function to +-- build multiple paths by expanding base path up to the +-- root ('/') +-- @return a searcher function which builds a path template and +-- calls package.searchpath +local function gen_search_func(path_fn, templates, need_traverse) + assert(type(path_fn) == 'function', 'path_fn must be a function') + assert(type(templates) == 'table', 'templates must be a table') -local function cwd_loader_func(lib) - local search_cwd = lib and search_cwd_lib or search_cwd_lua - local load_func = lib and load_lib or load_lua return function(name) - if not name then - return "empty name of module" - end - local file, err = search_cwd(name) - if not file then - return err - end - local loaded, err = load_func(file, name) - if err == nil then - return loaded - else - return err + local path = path_fn() or '.' + local paths = need_traverse and traverse_path(path) or { path } + + local searchpaths = {} + + for _, path in ipairs(paths) do + for _, template in pairs(templates) do + table.insert(searchpaths, fio.pathjoin(path, template)) + end end + + local searchpath = table.concat(searchpaths, ';') + + return package.searchpath(name, searchpath) end end -local function rocks_loader_func(lib) - local search_rocks = lib and search_rocks_lib or search_rocks_lua - local load_func = lib and load_lib or load_lua - return function (name) +-- Compose a loader function from options. +-- +-- @param search_fn function will be used to search a file from +-- path template +-- @param load_fn function will be used to load a file, found by +-- search function +-- @return function a loader, which first search for the file and +-- then loads it +local function gen_loader_func(search_fn, load_fn) + assert(type(search_fn) == 'function', 'search_fn must be defined') + assert(type(load_fn) == 'function', 'load_fn must be defined') + + return function(name) if not name then return "empty name of module" end - local file, err = search_rocks(name) + local file, err = search_fn(name) if not file then return err end - local loaded, err = load_func(file, name) + local loaded, err = load_fn(file, name) if err == nil then return loaded else @@ -169,22 +185,25 @@ local function rocks_loader_func(lib) end end -local function search_path_func(cpath) - return function(name) - return package.searchpath(name, cpath and package.cpath or package.path) - end -end +local search_lua = gen_search_func(searchroot, LUA_TEMPLATES) +local search_lib = gen_search_func(searchroot, LIB_TEMPLATES) +local search_rocks_lua = gen_search_func(searchroot, ROCKS_LUA_TEMPLATES, true) +local search_rocks_lib = gen_search_func(searchroot, ROCKS_LIB_TEMPLATES, true) + +local search_funcs = { + search_lua, + search_lib, + search_rocks_lua, + search_rocks_lib, + function(name) return package.searchpath(name, package.path) end, + function(name) return package.searchpath(name, package.cpath) end, +} local function search(name) if not name then return "empty name of module" end - local searchers = { - search_cwd_lua, search_cwd_lib, - search_rocks_lua, search_rocks_lib, - search_path_func(false), search_path_func(true) - } - for _, searcher in ipairs(searchers) do + for _, searcher in ipairs(search_funcs) do local file = searcher(name) if file ~= nil then return file @@ -194,15 +213,17 @@ local function search(name) end -- loader_preload 1 -table.insert(package.loaders, 2, cwd_loader_func(false)) -table.insert(package.loaders, 3, cwd_loader_func(true)) -table.insert(package.loaders, 4, rocks_loader_func(false)) -table.insert(package.loaders, 5, rocks_loader_func(true)) +table.insert(package.loaders, 2, gen_loader_func(search_lua, load_lua)) +table.insert(package.loaders, 3, gen_loader_func(search_lib, load_lib)) +table.insert(package.loaders, 4, gen_loader_func(search_rocks_lua, load_lua)) +table.insert(package.loaders, 5, gen_loader_func(search_rocks_lib, load_lib)) -- package.path 6 -- package.cpath 7 -- croot 8 package.search = search +package.searchroot = searchroot +package.setsearchroot = setsearchroot return { uptime = uptime; diff --git a/test/app/loaders.result b/test/app/loaders.result index 1f4c9820502501d469fa46846d7f4147f091d3e4..969529599ee9cb5015bdeada73ccdff749ea34ac 100644 --- a/test/app/loaders.result +++ b/test/app/loaders.result @@ -78,7 +78,7 @@ test_run:cmd("setopt delimiter ';'"); --- - true ... -function create_dirs(name) +function create_rocks_dirs(name) fio.mkdir(name) fio.mkdir(name .. "/.rocks") fio.mkdir(name .. "/.rocks/share") @@ -92,13 +92,13 @@ test_run:cmd("setopt delimiter ''"); --- - true ... -create_dirs(work_dir) +create_rocks_dirs(work_dir) --- ... -create_dirs(pr1_dir) +create_rocks_dirs(pr1_dir) --- ... -create_dirs(pr2_dir) +create_rocks_dirs(pr2_dir) --- ... soext = (jit.os == "OSX" and "dylib" or "so") @@ -202,6 +202,183 @@ fio.chdir(orig_cwd) --- - true ... +-- +-- Check searchroot .rocks loader +-- +-- Rocks loader does not look into subdirectories +package.loaded.loaders = nil +--- +... +package.loaded.loaders1 = nil +--- +... +package.loaded.loaderslib = nil +--- +... +f = rocks_loader("loaders") +--- +... +type(f) -- error +--- +- string +... +f = rocks_loader_dyn("loaderslib") +--- +... +type(f) -- error +--- +- string +... +f = rocks_loader("loaders1") +--- +... +type(f) -- error +--- +- string +... +-- Rocks loader traverses paths upwards +package.setsearchroot(pr2_dir) +--- +... +f = rocks_loader("loaders") +--- +... +type(f) -- function +--- +- function +... +f() +--- +- success +... +f = rocks_loader_dyn("loaderslib") +--- +... +type(f) -- function +--- +- function +... +f() +--- +- success +... +f = rocks_loader("loaders1") +--- +... +type(f) -- error +--- +- string +... +-- +-- Check searchroot loader +-- +package.loaded.loaders = nil +--- +... +package.loaded.loaders1 = nil +--- +... +package.loaded.loaderslib = nil +--- +... +lua_loader = package.loaders[2] +--- +... +lib_loader = package.loaders[3] +--- +... +fio.symlink(loaders_path, fio.pathjoin(work_dir, "loaders.lua")) +--- +- true +... +fio.symlink(loaderslib_path, fio.pathjoin(pr1_dir, "loaderslib."..soext)) +--- +- true +... +-- Reset searchroot +package.setsearchroot(box.NULL) +--- +... +-- Libs are not accessible from cwd +f = lua_loader("loaders") +--- +... +type(f) -- error +--- +- string +... +f = lib_loader("loaderslib") +--- +... +type(f) -- error +--- +- string +... +package.loaded.loaders = nil +--- +... +package.loaded.loaders1 = nil +--- +... +package.loaded.loaderslib = nil +--- +... +-- "loaders" module is located in work_dir +package.setsearchroot(work_dir) +--- +... +f = lua_loader("loaders") +--- +... +type(f) -- function +--- +- function +... +f() +--- +- success +... +f = lib_loader("loaderslib") +--- +... +type(f) -- error +--- +- string +... +package.loaded.loaders = nil +--- +... +package.loaded.loaders1 = nil +--- +... +package.loaded.loaderslib = nil +--- +... +-- "loaderslib" module is located in pr1_dir +package.setsearchroot(pr1_dir) +--- +... +f = lua_loader("loaders") +--- +... +type(f) -- error +--- +- string +... +f = lib_loader("loaderslib") +--- +... +type(f) -- function +--- +- function +... +f() +--- +- success +... +package.setsearchroot(box.NULL) +--- +... fio.rmtree(tmp_dir) --- - true diff --git a/test/app/loaders.test.lua b/test/app/loaders.test.lua index b98ad93337fb7b69c381a156b5fcdc83b894cba7..f7a903bbc24bb751d19d9a52dea39f60d0fd431b 100644 --- a/test/app/loaders.test.lua +++ b/test/app/loaders.test.lua @@ -30,7 +30,7 @@ lua_dir = ".rocks/share/tarantool" lib_dir = ".rocks/lib/tarantool" test_run:cmd("setopt delimiter ';'"); -function create_dirs(name) +function create_rocks_dirs(name) fio.mkdir(name) fio.mkdir(name .. "/.rocks") fio.mkdir(name .. "/.rocks/share") @@ -40,9 +40,9 @@ function create_dirs(name) end; test_run:cmd("setopt delimiter ''"); -create_dirs(work_dir) -create_dirs(pr1_dir) -create_dirs(pr2_dir) +create_rocks_dirs(work_dir) +create_rocks_dirs(pr1_dir) +create_rocks_dirs(pr2_dir) soext = (jit.os == "OSX" and "dylib" or "so") loaders_path = fio.pathjoin(source_dir, "loaders.lua") @@ -78,4 +78,90 @@ f = rocks_loader_dyn("loaderslib") type(f) -- error fio.chdir(orig_cwd) + +-- +-- Check searchroot .rocks loader +-- + +-- Rocks loader does not look into subdirectories + +package.loaded.loaders = nil +package.loaded.loaders1 = nil +package.loaded.loaderslib = nil + +f = rocks_loader("loaders") +type(f) -- error +f = rocks_loader_dyn("loaderslib") +type(f) -- error +f = rocks_loader("loaders1") +type(f) -- error + +-- Rocks loader traverses paths upwards + +package.setsearchroot(pr2_dir) + +f = rocks_loader("loaders") +type(f) -- function +f() +f = rocks_loader_dyn("loaderslib") +type(f) -- function +f() +f = rocks_loader("loaders1") +type(f) -- error + +-- +-- Check searchroot loader +-- + +package.loaded.loaders = nil +package.loaded.loaders1 = nil +package.loaded.loaderslib = nil + +lua_loader = package.loaders[2] +lib_loader = package.loaders[3] + +fio.symlink(loaders_path, fio.pathjoin(work_dir, "loaders.lua")) +fio.symlink(loaderslib_path, fio.pathjoin(pr1_dir, "loaderslib."..soext)) + +-- Reset searchroot + +package.setsearchroot(box.NULL) + +-- Libs are not accessible from cwd + +f = lua_loader("loaders") +type(f) -- error +f = lib_loader("loaderslib") +type(f) -- error + +package.loaded.loaders = nil +package.loaded.loaders1 = nil +package.loaded.loaderslib = nil + +-- "loaders" module is located in work_dir + +package.setsearchroot(work_dir) + +f = lua_loader("loaders") +type(f) -- function +f() +f = lib_loader("loaderslib") +type(f) -- error + +package.loaded.loaders = nil +package.loaded.loaders1 = nil +package.loaded.loaderslib = nil + +-- "loaderslib" module is located in pr1_dir + +package.setsearchroot(pr1_dir) + +f = lua_loader("loaders") +type(f) -- error +f = lib_loader("loaderslib") +type(f) -- function +f() + +package.setsearchroot(box.NULL) + fio.rmtree(tmp_dir)