Skip to content
Snippets Groups Projects
Commit 009d3d9b authored by Alexander Turenko's avatar Alexander Turenko Committed by Igor Munkin
Browse files

lua: search modules next to the main script

It eliminates a need to call `package.setsearchroot()` in the main
script to find modules in the application's directory.

Such layout is typical for cartridge based applications. It it also
used, when different versions of modules are in use in different
applications and installing the modules into the system is not an
option.

Regarding the implementation. There are two possible ways to add more
searching logic: add more entries into `package.path` and
`package.cpath` or add/wrap a loader. I'm going the second way, because
we already have a logic around .rocks and override modules implemented
as loaders. It is easier to follow.

However, I would reimplement all those loaders as paths generators for
`package.path` and `package.cpath` in a future. See also #3136.

Follows up #7774
Fixes #8182

@TarantoolBot document
Title: Default module search paths now include the main script directory

Let's assume that there is an application in the directory
/path/to/myapp with modules inside:

```
+ /path/to/myapp/
  +- .rocks/share/tarantool/
     +- foo.lua
  +- init.lua
  +- myapp/
     +- bar.lua
```

Now the following command automatically adds the path to the main script
directory to search paths for modules:

```sh
$ tarantool /path/to/app/init.lua
```

So `require('foo')` and `require('myapp.bar')` works without any
additional work.
parent 479fec64
No related branches found
No related tags found
No related merge requests found
## feature/lua
* Default module search paths now include the main script directory (gh-8182).
......@@ -250,6 +250,62 @@ table.insert(package.loaders, 5, gen_loader_func(search_rocks_lib, load_lib))
-- package.cpath 7
-- croot 8
-- Search for modules next to the main script.
--
-- If the script is not provided (script == nil) or provided as
-- stdin (script == '-'), there is no script directory, so nothing
-- to do here.
local script = minifio.script()
if script ~= nil and script ~= '-' then
-- It is important to obtain the directory at initialization,
-- before any cwd change may occur. The script path may be
-- passed as relative to the current directory.
local script_dir = minifio.dirname(minifio.abspath(script))
local function script_dir_fn()
return script_dir
end
-- Search non-recursively, only next to the script.
local search_app_lua = gen_search_func(script_dir_fn, LUA_TEMPLATES)
local search_app_lib = gen_search_func(script_dir_fn, LIB_TEMPLATES)
local search_app_rocks_lua = gen_search_func(script_dir_fn,
ROCKS_LUA_TEMPLATES)
local search_app_rocks_lib = gen_search_func(script_dir_fn,
ROCKS_LIB_TEMPLATES)
-- Mix the script directory loaders into corresponding
-- searchroot based loaders. It allows to avoid changing
-- ordinals of the loaders and also makes the override
-- loader search here.
--
-- We can just add more paths to package.path/package.cpath,
-- but:
--
-- * Search for override modules is implemented as a loader.
-- * Search inside .rocks in implemented as a loaders.
--
-- And it is simpler to wrap this logic rather than repeat.
-- It is possible (and maybe even desirable) to reimplement
-- all the loaders logic as paths generation, but we should
-- do that for all the logic at once.
package.loaders[2] = chain_loaders({
package.loaders[2],
gen_loader_func(search_app_lua, load_lua),
})
package.loaders[3] = chain_loaders({
package.loaders[3],
gen_loader_func(search_app_lib, load_lib),
})
package.loaders[4] = chain_loaders({
package.loaders[4],
gen_loader_func(search_app_rocks_lua, load_lua),
})
package.loaders[5] = chain_loaders({
package.loaders[5],
gen_loader_func(search_app_rocks_lib, load_lib),
})
end
local function getenv_boolean(varname, default)
local envvar = os.getenv(varname)
......
local fio = require('fio')
local t = require('luatest')
local treegen = require('test.treegen')
local justrun = require('test.justrun')
local g = t.group()
local MODULE_SCRIPT_TEMPLATE = [[
print(require('json').encode({
['script'] = '<script>',
}))
return {whoami = '<module_name>'}
]]
-- Print a result of the require call.
local MAIN_SCRIPT_TEMPLATE = [[
print(require('json').encode({
['script'] = '<script>',
['<module_name>'] = require('<module_name>'),
}))
]]
g.before_all(function(g)
treegen.init(g)
treegen.add_template(g, '^main%.lua$', MAIN_SCRIPT_TEMPLATE)
treegen.add_template(g, '^.*%.lua$', MODULE_SCRIPT_TEMPLATE)
end)
g.after_all(function(g)
treegen.clean(g)
end)
local function expected_output(module_relpath, module_name)
local res = {
{
['script'] = module_relpath,
},
{
['script'] = 'main.lua',
[module_name] = {
whoami = module_name,
},
},
}
return {
exit_code = 0,
stdout = res,
}
end
-- Create an 'application directory' with a main script and a
-- module. Run the main script and ensure that the module is
-- successfully required.
for _, case in ipairs({
-- Application's 'foo' module.
{'foo.lua', 'foo'},
{'foo/init.lua', 'foo'},
{'app/foo.lua', 'app.foo'},
{'app/foo/init.lua', 'app.foo'},
{'.rocks/share/tarantool/foo.lua', 'foo'},
{'.rocks/share/tarantool/foo/init.lua', 'foo'},
{'.rocks/share/tarantool/app/foo.lua', 'app.foo'},
{'.rocks/share/tarantool/app/foo/init.lua', 'app.foo'},
-- Application's socket override module.
--
-- See also override_test.lua.
{'override/socket.lua', 'socket'},
{'.rocks/share/tarantool/override/socket.lua', 'socket'},
}) do
local module_relpath = case[1]
local module_name = case[2]
local module_slug = module_relpath
:gsub('^%.rocks/share/tarantool/', 'rocks/'):gsub('/', '_'):sub(1, -5)
g['test_' .. module_slug] = function(g)
local scripts = {module_relpath, 'main.lua'}
local replacements = {module_name = module_name}
local dir = treegen.prepare_directory(g, scripts, replacements)
local main_script = fio.pathjoin(dir, 'main.lua')
-- The current working directory is in the filesystem
-- root, so the only way to reach the modules is to search
-- for them next to the script.
--
-- If we would run tarantool from inside the application
-- directory, the module would be found just because it is
-- in the current directory.
local res = justrun.tarantool('/', {}, {main_script})
local exp = expected_output(module_relpath, module_name)
t.assert_equals(res, exp)
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment