From ce5ed124744644d414121c88c94cc67f3437362c Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Thu, 22 Aug 2024 17:48:58 -0300 Subject: Teal: convert luarocks.loader --- src/luarocks/loader.tl | 248 +++++++++++++++++++++++++++++++------------------ 1 file changed, 156 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/luarocks/loader.tl b/src/luarocks/loader.tl index 772fdfcb..527f1255 100644 --- a/src/luarocks/loader.tl +++ b/src/luarocks/loader.tl @@ -5,20 +5,20 @@ -- used to load previous modules, so that the loader chooses versions -- that are declared to be compatible with the ones loaded earlier. --- luacheck: globals luarocks - local loaders = package.loaders or package.searchers local require, ipairs, table, type, next, tostring, error = require, ipairs, table, type, next, tostring, error -local unpack = unpack or table.unpack -local loader = {} +local record loader + context: {string: string} + luarocks_loader: function(string): LoaderFn | string, any +end local is_clean = not package.loaded["luarocks.core.cfg"] -- This loader module depends only on core modules. local cfg = require("luarocks.core.cfg") -local cfg_ok, err = cfg.init() +local cfg_ok, _err = cfg.init() if cfg_ok then cfg.init_package_paths() end @@ -26,12 +26,35 @@ end local path = require("luarocks.core.path") local manif = require("luarocks.core.manif") local vers = require("luarocks.core.vers") -local require = nil -- luacheck: ignore 411 + +local type Version = require("luarocks.core.types.version").Version +local type TreeManifest = require("luarocks.core.types.manifest").Tree_manifest +local type Tree = require("luarocks.core.types.tree").Tree +local type FilterFn = function(string, string, string, Tree, integer): string +local type LoaderFn = function() + +local record Provider + name: string + version: Version + module_name: string + tree: TreeManifest +end + -------------------------------------------------------------------------------- +-- Backwards compatibility +-------------------------------------------------------------------------------- + +local record LuaRocksGlobal + loader: loader +end + +global luarocks: LuaRocksGlobal -- Workaround for wrappers produced by older versions of LuaRocks local temporary_global = false -local status, luarocks_value = pcall(function() return luarocks end) +local status, luarocks_value = pcall(function(): LuaRocksGlobal + return luarocks +end) if status and luarocks_value then -- The site_config.lua file generated by old versions uses module(), -- so it produces a global `luarocks` table. Since we have the table, @@ -52,16 +75,18 @@ else end end +-------------------------------------------------------------------------------- +-- Context management +-------------------------------------------------------------------------------- + loader.context = {} --- Process the dependencies of a package to determine its dependency -- chain for loading modules. --- @param name string: The name of an installed rock. --- @param version string: The version of the rock, in string format -function loader.add_context(name, version) - -- assert(type(name) == "string") - -- assert(type(version) == "string") - +-- +-- @param name The name of an installed rock. +-- @param version The version of the rock, in string format +function loader.add_context(name: string, version: string) if temporary_global then -- The first thing a wrapper does is to call add_context. -- From here on, it's safe to clean the global environment. @@ -71,17 +96,19 @@ function loader.add_context(name, version) local tree_manifests = manif.load_rocks_tree_manifests() if not tree_manifests then - return nil + return end - return manif.scan_dependencies(name, version, tree_manifests, loader.context) + manif.scan_dependencies(name, version, tree_manifests, loader.context) end --- Internal sorting function. --- @param a table: A provider table. --- @param b table: Another provider table. --- @return boolean: True if the version of a is greater than that of b. -local function sort_versions(a,b) +-- +-- @param a A provider table. +-- @param b Another provider table. +-- +-- @return true if the version of a is greater than that of b. +local function sort_versions(a: Provider, b: Provider): boolean return a.version > b.version end @@ -92,67 +119,94 @@ end -- the version 2.0.2 needs to be loaded and it is not the current -- version, the module requested for the other loaders will be -- "socket.core_2_0_2". --- @param module The module name requested by the user, such as "socket.core" --- @param name The rock name, such as "luasocket" --- @param version The rock version, such as "2.0.2-1" --- @param module_name The actual module name, such as "socket.core" or "socket.core_2_0_2". --- @return table or (nil, string): The module table as returned by some other loader, --- or nil followed by an error message if no other loader managed to load the module. -local function call_other_loaders(module, name, version, module_name) +-- +-- @param module The module name requested by the user, e.g. "socket.core" +-- @param name The rock name, such as "luasocket" +-- @param version The rock version, such as "2.0.2-1" +-- @param module_name Actual module name, such as "socket.core_2_0_2" +-- +-- @return The loader function returned or an error message. +-- @return Additional loader data, if returned by the loader. +local function call_other_loaders(module: string, name: string, version: string, module_name: string): LoaderFn | string, any for _, a_loader in ipairs(loaders) do if a_loader ~= loader.luarocks_loader then - local results = { a_loader(module_name) } - if type(results[1]) == "function" then - return unpack(results) + local results: {any} = { a_loader(module_name) } + local f = results[1] + if f is LoaderFn then + if #results == 2 then + return f, results[2] + else + return f + end end end end - return "Failed loading module "..module.." in LuaRocks rock "..name.." "..version + return "Failed loading module " .. module .. " in LuaRocks rock " .. name .. " " .. version end -local function add_providers(providers, entries, tree, module, filter_file_name) +--- Find entries which provide the wanted module in the tree, +-- and store them in the array of providers for later sorting. +-- +-- @param providers The array of providers being accumulated into +-- @param entries The packages which provide the module +-- @param tree TreeManifest where filenames can be found. +-- @param module The module name being looked up +-- @param filter_name A filtering function to adjust the filename. +-- +-- @return If the current LuaRocks loader context already resolved this +-- dependency based on other dependencies, return the name of the module +-- for immediate use. +-- @return Version of the module for immediate use, if matched. +-- @return File name of the module for immediate use, if matched. +local function add_providers(providers: {Provider}, entries: {string}, tree: TreeManifest, module: string, filter_name: FilterFn): string, string, string for i, entry in ipairs(entries) do local name, version = entry:match("^([^/]*)/(.*)$") + local file_name = tree.manifest.repository[name][version][1].modules[module] if type(file_name) ~= "string" then - error("Invalid data in manifest file for module "..tostring(module).." (invalid data for "..tostring(name).." "..tostring(version)..")") + error("Invalid data in manifest file for module " .. tostring(module) .. " (invalid data for " .. tostring(name) .. " " .. tostring(version) .. ")") end - file_name = filter_file_name(file_name, name, version, tree.tree, i) + + file_name = filter_name(file_name, name, version, tree.tree, i) + if loader.context[name] == version then return name, version, file_name end - version = vers.parse_version(version) - table.insert(providers, {name = name, version = version, module_name = file_name, tree = tree}) + + table.insert(providers, { + name = name, + version = vers.parse_version(version), + module_name = file_name, + tree = tree + }) end end ---- Search for a module in the rocks trees --- @param module string: module name (eg. "socket.core") --- @param filter_file_name function(string, string, string, string, number): --- a function that takes the module file name (eg "socket/core.so"), the rock name --- (eg "luasocket"), the version (eg "2.0.2-1"), the path of the rocks tree +--- Search for a module in the rocks trees. +-- +-- @param module module name (eg. "socket.core") +-- @param filter_name a function that takes the module file name +-- (eg "socket/core.so"), the rock name (eg "luasocket"), +-- the version (eg "2.0.2-1"), the path of the rocks tree -- (eg "/usr/local"), and the numeric index of the matching entry, so the -- filter function can know if the matching module was the first entry or not. --- @return string, string, string, (string or table): --- * name of the rock containing the module (eg. "luasocket") --- * version of the rock (eg. "2.0.2-1") --- * return value of filter_file_name --- * tree of the module (string or table in `tree_manifests` format) -local function select_module(module, filter_file_name) - --assert(type(module) == "string") - --assert(type(filter_module_name) == "function") +-- +-- @return name of the rock containing the module (eg. "luasocket") +-- @return version of the rock (eg. "2.0.2-1") +-- @return return value of filter_name +local function select_module(module: string, filter_name: FilterFn): string, string, string local tree_manifests = manif.load_rocks_tree_manifests() if not tree_manifests then return nil end - local providers = {} - local initmodule + local providers: {Provider} = {} + local initmodule: string for _, tree in ipairs(tree_manifests) do local entries = tree.manifest.modules[module] if entries then - local n, v, f = add_providers(providers, entries, tree, module, filter_file_name) + local n, v, f = add_providers(providers, entries, tree, module, filter_name) if n then return n, v, f end @@ -160,7 +214,7 @@ local function select_module(module, filter_file_name) initmodule = initmodule or module .. ".init" entries = tree.manifest.modules[initmodule] if entries then - local n, v, f = add_providers(providers, entries, tree, initmodule, filter_file_name) + local n, v, f = add_providers(providers, entries, tree, initmodule, filter_name) if n then return n, v, f end @@ -171,46 +225,53 @@ local function select_module(module, filter_file_name) if next(providers) then table.sort(providers, sort_versions) local first = providers[1] - return first.name, first.version.string, first.module_name, first.tree + return first.name, first.version.string, first.module_name end end ---- Search for a module --- @param module string: module name (eg. "socket.core") --- @return string, string, string, (string or table): --- * name of the rock containing the module (eg. "luasocket") --- * version of the rock (eg. "2.0.2-1") --- * name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned). --- * tree of the module (string or table in `tree_manifests` format) -local function pick_module(module) - return - select_module(module, function(file_name, name, version, tree, i) - if i > 1 then - file_name = path.versioned_name(file_name, "", name, version) - end - return path.path_to_module(file_name) - end) +--- Filter operation for adjusting the versioned names when multiple packages provide +-- the same file. +-- +-- @param file_name The original filename +-- @param name The rock name +-- @param version The rock version +-- @param _tree (unused) +-- @param i The priority index, to determine whether to version the name. +-- +-- @return A filename, which may be plain or versioned. +-- (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned). +local function filter_module_name(file_name: string, name: string, version: string, _tree: Tree, i: integer): string + if i > 1 then + file_name = path.versioned_name(file_name, "", name, version) + end + return path.path_to_module(file_name) +end + +--- Search for a module. +-- +-- @param module name of the module (eg. "socket.core") +-- +-- @return name of the rock containing the module (eg. "luasocket") +-- @return version of the rock (eg. "2.0.2-1") +-- @return name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned). +-- @return tree of the module (string or table in `tree_manifests` format) +local function pick_module(module: string): string, string, string, string | TreeManifest + return select_module(module, filter_module_name) end --- Return the pathname of the file that would be loaded for a module. --- @param module string: module name (eg. "socket.core") --- @param where string: places to look for the module. If `where` contains +-- +-- @param module module name (eg. "socket.core") +-- @param where places to look for the module. If `where` contains -- "l", it will search using the LuaRocks loader; if it contains "p", -- it will look in the filesystem using package.path and package.cpath. -- You can use both at the same time. --- @return If successful, it will return four values. --- * If found using the LuaRocks loader, it will return: --- * filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"), --- * rock name --- * rock version --- * "l" to indicate the match comes from the loader. --- * If found scanning package.path and package.cpath, it will return: --- * filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"), --- * "path" or "cpath" --- * nil --- * "p" to indicate the match comes from scanning package.path and cpath. --- If unsuccessful, nothing is returned. -function loader.which(module, where) +-- +-- @return If found, filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"), +-- @return If found via the loader, the rock name; otherwise, "path" or "cpath" +-- @return If found via the loader, the rock version; otherwise, nil +-- @return If found via the loader, "l"; if found via package.path or package.cpath, "p". +function loader.which(module: string, where?: string): string, string, string, string where = where or "l" if where:match("l") then local rock_name, rock_version, file_name = select_module(module, path.which_i) @@ -224,8 +285,8 @@ function loader.which(module, where) end if where:match("p") then local modpath = module:gsub("%.", "/") - for _, v in ipairs({"path", "cpath"}) do - for p in package[v]:gmatch("([^;]+)") do + for _, v in ipairs({package.path, package.cpath}) do + for p in v:gmatch("([^;]+)") do local file_name = p:gsub("%?", modpath) -- luacheck: ignore 421 local fd = io.open(file_name) if fd then @@ -238,18 +299,21 @@ function loader.which(module, where) end --- Package loader for LuaRocks support. --- A module is searched in installed rocks that match the +-- See require() +-- in the Lua reference manual for details on the require() mechanism. +-- The LuaRocks loader works by searching in installed rocks that match the -- current LuaRocks context. If module is not part of the -- context, or if a context has not yet been set, the module -- in the package with the highest version is used. --- @param module string: The module name, like in plain require(). --- @return table: The module table (typically), like in plain --- require(). See require() --- in the Lua reference manual for details. -function loader.luarocks_loader(module) +-- +-- @param module The module name, like in plain require(). +-- +-- @return A function which can load the module found, +-- or a string with an error message. +function loader.luarocks_loader(module: string): LoaderFn | string, any local name, version, module_name = pick_module(module) if not name then - return "No LuaRocks module found for "..module + return "No LuaRocks module found for " .. module else loader.add_context(name, version) return call_other_loaders(module, name, version, module_name) -- cgit v1.2.3-55-g6feb