aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHisham Muhammad <hisham@gobolinux.org>2024-08-20 20:05:50 -0300
committerHisham Muhammad <hisham@gobolinux.org>2024-08-20 20:06:33 -0300
commit703372dcff31de2dd1edcaedbf2aba68f5d572bf (patch)
treebc4f5b9f89be570ff1e95fa900130039aea3120a
parentc400ccaec13d9004a970e41d248c1ccee050aa33 (diff)
downloadluarocks-lua-to-teal-migration-loader.tar.gz
luarocks-lua-to-teal-migration-loader.tar.bz2
luarocks-lua-to-teal-migration-loader.zip
convert luarocks.loaderlua-to-teal-migration-loader
-rw-r--r--src/luarocks/core/cfg.d.tl1
-rw-r--r--src/luarocks/core/path.tl4
-rw-r--r--src/luarocks/loader-original.lua269
-rw-r--r--src/luarocks/loader.lua316
-rw-r--r--src/luarocks/loader.tl333
5 files changed, 795 insertions, 128 deletions
diff --git a/src/luarocks/core/cfg.d.tl b/src/luarocks/core/cfg.d.tl
index 8437a043..03338d9b 100644
--- a/src/luarocks/core/cfg.d.tl
+++ b/src/luarocks/core/cfg.d.tl
@@ -59,6 +59,7 @@ local record cfg
59 end 59 end
60 -- loader 60 -- loader
61 init: function(?{string : string}, ?function(string)): boolean, string, string 61 init: function(?{string : string}, ?function(string)): boolean, string, string
62 init_package_paths: function()
62 -- rockspecs 63 -- rockspecs
63 each_platform: function(?string): (function():string) 64 each_platform: function(?string): (function():string)
64 -- fetch 65 -- fetch
diff --git a/src/luarocks/core/path.tl b/src/luarocks/core/path.tl
index eff15a51..680c3296 100644
--- a/src/luarocks/core/path.tl
+++ b/src/luarocks/core/path.tl
@@ -73,7 +73,7 @@ function path.deploy_lua_dir(tree: string | Tree): string
73 if tree is string then 73 if tree is string then
74 return dir.path(tree, cfg.lua_modules_path) 74 return dir.path(tree, cfg.lua_modules_path)
75 else 75 else
76 return tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path) 76 return tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path)
77 end 77 end
78end 78end
79 79
@@ -95,7 +95,7 @@ local is_src_extension: {string: boolean} = { [".lua"] = true, [".tl"] = true, [
95-- @param i number: the index, 1 if version is the current default, > 1 otherwise. 95-- @param i number: the index, 1 if version is the current default, > 1 otherwise.
96-- This is done this way for use by select_module in luarocks.loader. 96-- This is done this way for use by select_module in luarocks.loader.
97-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so") 97-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
98function path.which_i(file_name: string, name: string, version: string, tree: string, i: number): string 98function path.which_i(file_name: string, name: string, version: string, tree: string | Tree, i: number): string
99 local deploy_dir: string 99 local deploy_dir: string
100 local extension = file_name:match("%.[a-z]+$") 100 local extension = file_name:match("%.[a-z]+$")
101 if is_src_extension[extension] then 101 if is_src_extension[extension] then
diff --git a/src/luarocks/loader-original.lua b/src/luarocks/loader-original.lua
new file mode 100644
index 00000000..772fdfcb
--- /dev/null
+++ b/src/luarocks/loader-original.lua
@@ -0,0 +1,269 @@
1--- A module which installs a Lua package loader that is LuaRocks-aware.
2-- This loader uses dependency information from the LuaRocks tree to load
3-- correct versions of modules. It does this by constructing a "context"
4-- table in the environment, which records which versions of packages were
5-- used to load previous modules, so that the loader chooses versions
6-- that are declared to be compatible with the ones loaded earlier.
7
8-- luacheck: globals luarocks
9
10local loaders = package.loaders or package.searchers
11local require, ipairs, table, type, next, tostring, error =
12 require, ipairs, table, type, next, tostring, error
13local unpack = unpack or table.unpack
14
15local loader = {}
16
17local is_clean = not package.loaded["luarocks.core.cfg"]
18
19-- This loader module depends only on core modules.
20local cfg = require("luarocks.core.cfg")
21local cfg_ok, err = cfg.init()
22if cfg_ok then
23 cfg.init_package_paths()
24end
25
26local path = require("luarocks.core.path")
27local manif = require("luarocks.core.manif")
28local vers = require("luarocks.core.vers")
29local require = nil -- luacheck: ignore 411
30--------------------------------------------------------------------------------
31
32-- Workaround for wrappers produced by older versions of LuaRocks
33local temporary_global = false
34local status, luarocks_value = pcall(function() return luarocks end)
35if status and luarocks_value then
36 -- The site_config.lua file generated by old versions uses module(),
37 -- so it produces a global `luarocks` table. Since we have the table,
38 -- add the `loader` field to make the old wrappers happy.
39 luarocks.loader = loader
40else
41 -- When a new version is installed on top of an old version,
42 -- site_config.lua may be replaced, and then it no longer creates
43 -- a global.
44 -- Detect when being called via -lluarocks.loader; this is
45 -- most likely a wrapper.
46 local info = debug and debug.getinfo(2, "nS")
47 if info and info.what == "C" and not info.name then
48 luarocks = { loader = loader }
49 temporary_global = true
50 -- For the other half of this hack,
51 -- see the next use of `temporary_global` below.
52 end
53end
54
55loader.context = {}
56
57--- Process the dependencies of a package to determine its dependency
58-- chain for loading modules.
59-- @param name string: The name of an installed rock.
60-- @param version string: The version of the rock, in string format
61function loader.add_context(name, version)
62 -- assert(type(name) == "string")
63 -- assert(type(version) == "string")
64
65 if temporary_global then
66 -- The first thing a wrapper does is to call add_context.
67 -- From here on, it's safe to clean the global environment.
68 luarocks = nil
69 temporary_global = false
70 end
71
72 local tree_manifests = manif.load_rocks_tree_manifests()
73 if not tree_manifests then
74 return nil
75 end
76
77 return manif.scan_dependencies(name, version, tree_manifests, loader.context)
78end
79
80--- Internal sorting function.
81-- @param a table: A provider table.
82-- @param b table: Another provider table.
83-- @return boolean: True if the version of a is greater than that of b.
84local function sort_versions(a,b)
85 return a.version > b.version
86end
87
88--- Request module to be loaded through other loaders,
89-- once the proper name of the module has been determined.
90-- For example, in case the module "socket.core" has been requested
91-- to the LuaRocks loader and it determined based on context that
92-- the version 2.0.2 needs to be loaded and it is not the current
93-- version, the module requested for the other loaders will be
94-- "socket.core_2_0_2".
95-- @param module The module name requested by the user, such as "socket.core"
96-- @param name The rock name, such as "luasocket"
97-- @param version The rock version, such as "2.0.2-1"
98-- @param module_name The actual module name, such as "socket.core" or "socket.core_2_0_2".
99-- @return table or (nil, string): The module table as returned by some other loader,
100-- or nil followed by an error message if no other loader managed to load the module.
101local function call_other_loaders(module, name, version, module_name)
102 for _, a_loader in ipairs(loaders) do
103 if a_loader ~= loader.luarocks_loader then
104 local results = { a_loader(module_name) }
105 if type(results[1]) == "function" then
106 return unpack(results)
107 end
108 end
109 end
110 return "Failed loading module "..module.." in LuaRocks rock "..name.." "..version
111end
112
113local function add_providers(providers, entries, tree, module, filter_file_name)
114 for i, entry in ipairs(entries) do
115 local name, version = entry:match("^([^/]*)/(.*)$")
116 local file_name = tree.manifest.repository[name][version][1].modules[module]
117 if type(file_name) ~= "string" then
118 error("Invalid data in manifest file for module "..tostring(module).." (invalid data for "..tostring(name).." "..tostring(version)..")")
119 end
120 file_name = filter_file_name(file_name, name, version, tree.tree, i)
121 if loader.context[name] == version then
122 return name, version, file_name
123 end
124 version = vers.parse_version(version)
125 table.insert(providers, {name = name, version = version, module_name = file_name, tree = tree})
126 end
127end
128
129--- Search for a module in the rocks trees
130-- @param module string: module name (eg. "socket.core")
131-- @param filter_file_name function(string, string, string, string, number):
132-- a function that takes the module file name (eg "socket/core.so"), the rock name
133-- (eg "luasocket"), the version (eg "2.0.2-1"), the path of the rocks tree
134-- (eg "/usr/local"), and the numeric index of the matching entry, so the
135-- filter function can know if the matching module was the first entry or not.
136-- @return string, string, string, (string or table):
137-- * name of the rock containing the module (eg. "luasocket")
138-- * version of the rock (eg. "2.0.2-1")
139-- * return value of filter_file_name
140-- * tree of the module (string or table in `tree_manifests` format)
141local function select_module(module, filter_file_name)
142 --assert(type(module) == "string")
143 --assert(type(filter_module_name) == "function")
144
145 local tree_manifests = manif.load_rocks_tree_manifests()
146 if not tree_manifests then
147 return nil
148 end
149
150 local providers = {}
151 local initmodule
152 for _, tree in ipairs(tree_manifests) do
153 local entries = tree.manifest.modules[module]
154 if entries then
155 local n, v, f = add_providers(providers, entries, tree, module, filter_file_name)
156 if n then
157 return n, v, f
158 end
159 else
160 initmodule = initmodule or module .. ".init"
161 entries = tree.manifest.modules[initmodule]
162 if entries then
163 local n, v, f = add_providers(providers, entries, tree, initmodule, filter_file_name)
164 if n then
165 return n, v, f
166 end
167 end
168 end
169 end
170
171 if next(providers) then
172 table.sort(providers, sort_versions)
173 local first = providers[1]
174 return first.name, first.version.string, first.module_name, first.tree
175 end
176end
177
178--- Search for a module
179-- @param module string: module name (eg. "socket.core")
180-- @return string, string, string, (string or table):
181-- * name of the rock containing the module (eg. "luasocket")
182-- * version of the rock (eg. "2.0.2-1")
183-- * name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned).
184-- * tree of the module (string or table in `tree_manifests` format)
185local function pick_module(module)
186 return
187 select_module(module, function(file_name, name, version, tree, i)
188 if i > 1 then
189 file_name = path.versioned_name(file_name, "", name, version)
190 end
191 return path.path_to_module(file_name)
192 end)
193end
194
195--- Return the pathname of the file that would be loaded for a module.
196-- @param module string: module name (eg. "socket.core")
197-- @param where string: places to look for the module. If `where` contains
198-- "l", it will search using the LuaRocks loader; if it contains "p",
199-- it will look in the filesystem using package.path and package.cpath.
200-- You can use both at the same time.
201-- @return If successful, it will return four values.
202-- * If found using the LuaRocks loader, it will return:
203-- * filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"),
204-- * rock name
205-- * rock version
206-- * "l" to indicate the match comes from the loader.
207-- * If found scanning package.path and package.cpath, it will return:
208-- * filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"),
209-- * "path" or "cpath"
210-- * nil
211-- * "p" to indicate the match comes from scanning package.path and cpath.
212-- If unsuccessful, nothing is returned.
213function loader.which(module, where)
214 where = where or "l"
215 if where:match("l") then
216 local rock_name, rock_version, file_name = select_module(module, path.which_i)
217 if rock_name then
218 local fd = io.open(file_name)
219 if fd then
220 fd:close()
221 return file_name, rock_name, rock_version, "l"
222 end
223 end
224 end
225 if where:match("p") then
226 local modpath = module:gsub("%.", "/")
227 for _, v in ipairs({"path", "cpath"}) do
228 for p in package[v]:gmatch("([^;]+)") do
229 local file_name = p:gsub("%?", modpath) -- luacheck: ignore 421
230 local fd = io.open(file_name)
231 if fd then
232 fd:close()
233 return file_name, v, nil, "p"
234 end
235 end
236 end
237 end
238end
239
240--- Package loader for LuaRocks support.
241-- A module is searched in installed rocks that match the
242-- current LuaRocks context. If module is not part of the
243-- context, or if a context has not yet been set, the module
244-- in the package with the highest version is used.
245-- @param module string: The module name, like in plain require().
246-- @return table: The module table (typically), like in plain
247-- require(). See <a href="http://www.lua.org/manual/5.1/manual.html#pdf-require">require()</a>
248-- in the Lua reference manual for details.
249function loader.luarocks_loader(module)
250 local name, version, module_name = pick_module(module)
251 if not name then
252 return "No LuaRocks module found for "..module
253 else
254 loader.add_context(name, version)
255 return call_other_loaders(module, name, version, module_name)
256 end
257end
258
259table.insert(loaders, 1, loader.luarocks_loader)
260
261if is_clean then
262 for modname, _ in pairs(package.loaded) do
263 if modname:match("^luarocks%.") then
264 package.loaded[modname] = nil
265 end
266 end
267end
268
269return loader
diff --git a/src/luarocks/loader.lua b/src/luarocks/loader.lua
index 772fdfcb..37f196f0 100644
--- a/src/luarocks/loader.lua
+++ b/src/luarocks/loader.lua
@@ -1,24 +1,24 @@
1--- A module which installs a Lua package loader that is LuaRocks-aware. 1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2-- This loader uses dependency information from the LuaRocks tree to load 2
3-- correct versions of modules. It does this by constructing a "context" 3
4-- table in the environment, which records which versions of packages were 4
5-- used to load previous modules, so that the loader chooses versions 5
6-- that are declared to be compatible with the ones loaded earlier.
7 6
8-- luacheck: globals luarocks
9 7
10local loaders = package.loaders or package.searchers 8local loaders = package.loaders or package.searchers
11local require, ipairs, table, type, next, tostring, error = 9local require, ipairs, table, type, next, tostring, error =
12 require, ipairs, table, type, next, tostring, error 10require, ipairs, table, type, next, tostring, error
13local unpack = unpack or table.unpack
14 11
15local loader = {} 12local loader = {}
16 13
14
15
16
17local is_clean = not package.loaded["luarocks.core.cfg"] 17local is_clean = not package.loaded["luarocks.core.cfg"]
18 18
19-- This loader module depends only on core modules. 19
20local cfg = require("luarocks.core.cfg") 20local cfg = require("luarocks.core.cfg")
21local cfg_ok, err = cfg.init() 21local cfg_ok, _err = cfg.init()
22if cfg_ok then 22if cfg_ok then
23 cfg.init_package_paths() 23 cfg.init_package_paths()
24end 24end
@@ -26,121 +26,175 @@ end
26local path = require("luarocks.core.path") 26local path = require("luarocks.core.path")
27local manif = require("luarocks.core.manif") 27local manif = require("luarocks.core.manif")
28local vers = require("luarocks.core.vers") 28local vers = require("luarocks.core.vers")
29local require = nil -- luacheck: ignore 411
30--------------------------------------------------------------------------------
31 29
32-- Workaround for wrappers produced by older versions of LuaRocks 30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
33local temporary_global = false 54local temporary_global = false
34local status, luarocks_value = pcall(function() return luarocks end) 55local status, luarocks_value = pcall(function()
56 return luarocks
57end)
35if status and luarocks_value then 58if status and luarocks_value then
36 -- The site_config.lua file generated by old versions uses module(), 59
37 -- so it produces a global `luarocks` table. Since we have the table, 60
38 -- add the `loader` field to make the old wrappers happy. 61
39 luarocks.loader = loader 62 luarocks.loader = loader
40else 63else
41 -- When a new version is installed on top of an old version, 64
42 -- site_config.lua may be replaced, and then it no longer creates 65
43 -- a global. 66
44 -- Detect when being called via -lluarocks.loader; this is 67
45 -- most likely a wrapper. 68
46 local info = debug and debug.getinfo(2, "nS") 69 local info = debug and debug.getinfo(2, "nS")
47 if info and info.what == "C" and not info.name then 70 if info and info.what == "C" and not info.name then
48 luarocks = { loader = loader } 71 luarocks = { loader = loader }
49 temporary_global = true 72 temporary_global = true
50 -- For the other half of this hack, 73
51 -- see the next use of `temporary_global` below. 74
52 end 75 end
53end 76end
54 77
78
79
80
81
55loader.context = {} 82loader.context = {}
56 83
57--- Process the dependencies of a package to determine its dependency
58-- chain for loading modules.
59-- @param name string: The name of an installed rock.
60-- @param version string: The version of the rock, in string format
61function loader.add_context(name, version)
62 -- assert(type(name) == "string")
63 -- assert(type(version) == "string")
64 84
85
86
87
88
89function loader.add_context(name, version)
65 if temporary_global then 90 if temporary_global then
66 -- The first thing a wrapper does is to call add_context. 91
67 -- From here on, it's safe to clean the global environment. 92
68 luarocks = nil 93 luarocks = nil
69 temporary_global = false 94 temporary_global = false
70 end 95 end
71 96
72 local tree_manifests = manif.load_rocks_tree_manifests() 97 local tree_manifests = manif.load_rocks_tree_manifests()
73 if not tree_manifests then 98 if not tree_manifests then
74 return nil 99 return
75 end 100 end
76 101
77 return manif.scan_dependencies(name, version, tree_manifests, loader.context) 102 manif.scan_dependencies(name, version, tree_manifests, loader.context)
78end 103end
79 104
80--- Internal sorting function. 105
81-- @param a table: A provider table. 106
82-- @param b table: Another provider table. 107
83-- @return boolean: True if the version of a is greater than that of b. 108
84local function sort_versions(a,b) 109
110
111local function sort_versions(a, b)
85 return a.version > b.version 112 return a.version > b.version
86end 113end
87 114
88--- Request module to be loaded through other loaders, 115
89-- once the proper name of the module has been determined. 116
90-- For example, in case the module "socket.core" has been requested 117
91-- to the LuaRocks loader and it determined based on context that 118
92-- the version 2.0.2 needs to be loaded and it is not the current 119
93-- version, the module requested for the other loaders will be 120
94-- "socket.core_2_0_2". 121
95-- @param module The module name requested by the user, such as "socket.core" 122
96-- @param name The rock name, such as "luasocket" 123
97-- @param version The rock version, such as "2.0.2-1" 124
98-- @param module_name The actual module name, such as "socket.core" or "socket.core_2_0_2". 125
99-- @return table or (nil, string): The module table as returned by some other loader, 126
100-- or nil followed by an error message if no other loader managed to load the module. 127
128
129
101local function call_other_loaders(module, name, version, module_name) 130local function call_other_loaders(module, name, version, module_name)
102 for _, a_loader in ipairs(loaders) do 131 for _, a_loader in ipairs(loaders) do
103 if a_loader ~= loader.luarocks_loader then 132 if a_loader ~= loader.luarocks_loader then
104 local results = { a_loader(module_name) } 133 local results = { a_loader(module_name) }
105 if type(results[1]) == "function" then 134 local f = results[1]
106 return unpack(results) 135 if type(f) == "function" then
136 if #results == 2 then
137 return f, results[2]
138 else
139 return f
140 end
107 end 141 end
108 end 142 end
109 end 143 end
110 return "Failed loading module "..module.." in LuaRocks rock "..name.." "..version 144 return "Failed loading module " .. module .. " in LuaRocks rock " .. name .. " " .. version
111end 145end
112 146
113local function add_providers(providers, entries, tree, module, filter_file_name) 147
148
149
150
151
152
153
154
155
156
157
158
159
160
161local function add_providers(providers, entries, tree, module, filter_name)
114 for i, entry in ipairs(entries) do 162 for i, entry in ipairs(entries) do
115 local name, version = entry:match("^([^/]*)/(.*)$") 163 local name, version = entry:match("^([^/]*)/(.*)$")
164
116 local file_name = tree.manifest.repository[name][version][1].modules[module] 165 local file_name = tree.manifest.repository[name][version][1].modules[module]
117 if type(file_name) ~= "string" then 166 if type(file_name) ~= "string" then
118 error("Invalid data in manifest file for module "..tostring(module).." (invalid data for "..tostring(name).." "..tostring(version)..")") 167 error("Invalid data in manifest file for module " .. tostring(module) .. " (invalid data for " .. tostring(name) .. " " .. tostring(version) .. ")")
119 end 168 end
120 file_name = filter_file_name(file_name, name, version, tree.tree, i) 169
170 file_name = filter_name(file_name, name, version, tree.tree, i)
171
121 if loader.context[name] == version then 172 if loader.context[name] == version then
122 return name, version, file_name 173 return name, version, file_name
123 end 174 end
124 version = vers.parse_version(version) 175
125 table.insert(providers, {name = name, version = version, module_name = file_name, tree = tree}) 176 table.insert(providers, {
177 name = name,
178 version = vers.parse_version(version),
179 module_name = file_name,
180 tree = tree,
181 })
126 end 182 end
127end 183end
128 184
129--- Search for a module in the rocks trees 185
130-- @param module string: module name (eg. "socket.core") 186
131-- @param filter_file_name function(string, string, string, string, number): 187
132-- a function that takes the module file name (eg "socket/core.so"), the rock name 188
133-- (eg "luasocket"), the version (eg "2.0.2-1"), the path of the rocks tree 189
134-- (eg "/usr/local"), and the numeric index of the matching entry, so the 190
135-- filter function can know if the matching module was the first entry or not. 191
136-- @return string, string, string, (string or table): 192
137-- * name of the rock containing the module (eg. "luasocket") 193
138-- * version of the rock (eg. "2.0.2-1") 194
139-- * return value of filter_file_name 195
140-- * tree of the module (string or table in `tree_manifests` format) 196
141local function select_module(module, filter_file_name) 197local function select_module(module, filter_name)
142 --assert(type(module) == "string")
143 --assert(type(filter_module_name) == "function")
144 198
145 local tree_manifests = manif.load_rocks_tree_manifests() 199 local tree_manifests = manif.load_rocks_tree_manifests()
146 if not tree_manifests then 200 if not tree_manifests then
@@ -152,7 +206,7 @@ local function select_module(module, filter_file_name)
152 for _, tree in ipairs(tree_manifests) do 206 for _, tree in ipairs(tree_manifests) do
153 local entries = tree.manifest.modules[module] 207 local entries = tree.manifest.modules[module]
154 if entries then 208 if entries then
155 local n, v, f = add_providers(providers, entries, tree, module, filter_file_name) 209 local n, v, f = add_providers(providers, entries, tree, module, filter_name)
156 if n then 210 if n then
157 return n, v, f 211 return n, v, f
158 end 212 end
@@ -160,7 +214,7 @@ local function select_module(module, filter_file_name)
160 initmodule = initmodule or module .. ".init" 214 initmodule = initmodule or module .. ".init"
161 entries = tree.manifest.modules[initmodule] 215 entries = tree.manifest.modules[initmodule]
162 if entries then 216 if entries then
163 local n, v, f = add_providers(providers, entries, tree, initmodule, filter_file_name) 217 local n, v, f = add_providers(providers, entries, tree, initmodule, filter_name)
164 if n then 218 if n then
165 return n, v, f 219 return n, v, f
166 end 220 end
@@ -168,48 +222,55 @@ local function select_module(module, filter_file_name)
168 end 222 end
169 end 223 end
170 224
171 if next(providers) then 225 if next(providers) ~= nil then
172 table.sort(providers, sort_versions) 226 table.sort(providers, sort_versions)
173 local first = providers[1] 227 local first = providers[1]
174 return first.name, first.version.string, first.module_name, first.tree 228 return first.name, first.version.string, first.module_name
175 end 229 end
176end 230end
177 231
178--- Search for a module 232
179-- @param module string: module name (eg. "socket.core") 233
180-- @return string, string, string, (string or table): 234
181-- * name of the rock containing the module (eg. "luasocket") 235
182-- * version of the rock (eg. "2.0.2-1") 236
183-- * name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned). 237
184-- * tree of the module (string or table in `tree_manifests` format) 238
239
240
241
242
243local function filter_module_name(file_name, name, version, _tree, i)
244 if i > 1 then
245 file_name = path.versioned_name(file_name, "", name, version)
246 end
247 return path.path_to_module(file_name)
248end
249
250
251
252
253
254
255
256
257
185local function pick_module(module) 258local function pick_module(module)
186 return 259 return select_module(module, filter_module_name)
187 select_module(module, function(file_name, name, version, tree, i)
188 if i > 1 then
189 file_name = path.versioned_name(file_name, "", name, version)
190 end
191 return path.path_to_module(file_name)
192 end)
193end 260end
194 261
195--- Return the pathname of the file that would be loaded for a module. 262
196-- @param module string: module name (eg. "socket.core") 263
197-- @param where string: places to look for the module. If `where` contains 264
198-- "l", it will search using the LuaRocks loader; if it contains "p", 265
199-- it will look in the filesystem using package.path and package.cpath. 266
200-- You can use both at the same time. 267
201-- @return If successful, it will return four values. 268
202-- * If found using the LuaRocks loader, it will return: 269
203-- * filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"), 270
204-- * rock name 271
205-- * rock version 272
206-- * "l" to indicate the match comes from the loader. 273
207-- * If found scanning package.path and package.cpath, it will return:
208-- * filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"),
209-- * "path" or "cpath"
210-- * nil
211-- * "p" to indicate the match comes from scanning package.path and cpath.
212-- If unsuccessful, nothing is returned.
213function loader.which(module, where) 274function loader.which(module, where)
214 where = where or "l" 275 where = where or "l"
215 if where:match("l") then 276 if where:match("l") then
@@ -224,9 +285,9 @@ function loader.which(module, where)
224 end 285 end
225 if where:match("p") then 286 if where:match("p") then
226 local modpath = module:gsub("%.", "/") 287 local modpath = module:gsub("%.", "/")
227 for _, v in ipairs({"path", "cpath"}) do 288 for _, v in ipairs({ package.path, package.cpath }) do
228 for p in package[v]:gmatch("([^;]+)") do 289 for p in v:gmatch("([^;]+)") do
229 local file_name = p:gsub("%?", modpath) -- luacheck: ignore 421 290 local file_name = p:gsub("%?", modpath)
230 local fd = io.open(file_name) 291 local fd = io.open(file_name)
231 if fd then 292 if fd then
232 fd:close() 293 fd:close()
@@ -237,19 +298,22 @@ function loader.which(module, where)
237 end 298 end
238end 299end
239 300
240--- Package loader for LuaRocks support. 301
241-- A module is searched in installed rocks that match the 302
242-- current LuaRocks context. If module is not part of the 303
243-- context, or if a context has not yet been set, the module 304
244-- in the package with the highest version is used. 305
245-- @param module string: The module name, like in plain require(). 306
246-- @return table: The module table (typically), like in plain 307
247-- require(). See <a href="http://www.lua.org/manual/5.1/manual.html#pdf-require">require()</a> 308
248-- in the Lua reference manual for details. 309
310
311
312
249function loader.luarocks_loader(module) 313function loader.luarocks_loader(module)
250 local name, version, module_name = pick_module(module) 314 local name, version, module_name = pick_module(module)
251 if not name then 315 if not name then
252 return "No LuaRocks module found for "..module 316 return "No LuaRocks module found for " .. module
253 else 317 else
254 loader.add_context(name, version) 318 loader.add_context(name, version)
255 return call_other_loaders(module, name, version, module_name) 319 return call_other_loaders(module, name, version, module_name)
diff --git a/src/luarocks/loader.tl b/src/luarocks/loader.tl
new file mode 100644
index 00000000..0a5e3c30
--- /dev/null
+++ b/src/luarocks/loader.tl
@@ -0,0 +1,333 @@
1--- A module which installs a Lua package loader that is LuaRocks-aware.
2-- This loader uses dependency information from the LuaRocks tree to load
3-- correct versions of modules. It does this by constructing a "context"
4-- table in the environment, which records which versions of packages were
5-- used to load previous modules, so that the loader chooses versions
6-- that are declared to be compatible with the ones loaded earlier.
7
8local loaders = package.loaders or package.searchers
9local require, ipairs, table, type, next, tostring, error =
10 require, ipairs, table, type, next, tostring, error
11
12local record loader
13 context: {string: string}
14 luarocks_loader: function(string): LoaderFn | string, any
15end
16
17local is_clean = not package.loaded["luarocks.core.cfg"]
18
19-- This loader module depends only on core modules.
20local cfg = require("luarocks.core.cfg")
21local cfg_ok, _err = cfg.init()
22if cfg_ok then
23 cfg.init_package_paths()
24end
25
26local path = require("luarocks.core.path")
27local manif = require("luarocks.core.manif")
28local vers = require("luarocks.core.vers")
29
30local type Version = require("luarocks.core.types.version").Version
31local type TreeManifest = require("luarocks.core.types.manifest").Tree_manifest
32local type Tree = require("luarocks.core.types.tree").Tree
33local type FilterFn = function(string, string, string, Tree, integer): string
34local type LoaderFn = function()
35
36local record Provider
37 name: string
38 version: Version
39 module_name: string
40 tree: TreeManifest
41end
42
43--------------------------------------------------------------------------------
44-- Backwards compatibility
45--------------------------------------------------------------------------------
46
47local record LuaRocksGlobal
48 loader: loader
49end
50
51global luarocks: LuaRocksGlobal
52
53-- Workaround for wrappers produced by older versions of LuaRocks
54local temporary_global = false
55local status, luarocks_value = pcall(function(): LuaRocksGlobal
56 return luarocks
57end)
58if status and luarocks_value then
59 -- The site_config.lua file generated by old versions uses module(),
60 -- so it produces a global `luarocks` table. Since we have the table,
61 -- add the `loader` field to make the old wrappers happy.
62 luarocks.loader = loader
63else
64 -- When a new version is installed on top of an old version,
65 -- site_config.lua may be replaced, and then it no longer creates
66 -- a global.
67 -- Detect when being called via -lluarocks.loader; this is
68 -- most likely a wrapper.
69 local info = debug and debug.getinfo(2, "nS")
70 if info and info.what == "C" and not info.name then
71 luarocks = { loader = loader }
72 temporary_global = true
73 -- For the other half of this hack,
74 -- see the next use of `temporary_global` below.
75 end
76end
77
78--------------------------------------------------------------------------------
79-- Context management
80--------------------------------------------------------------------------------
81
82loader.context = {}
83
84--- Process the dependencies of a package to determine its dependency
85-- chain for loading modules.
86--
87-- @param name The name of an installed rock.
88-- @param version The version of the rock, in string format
89function loader.add_context(name: string, version: string)
90 if temporary_global then
91 -- The first thing a wrapper does is to call add_context.
92 -- From here on, it's safe to clean the global environment.
93 luarocks = nil
94 temporary_global = false
95 end
96
97 local tree_manifests = manif.load_rocks_tree_manifests()
98 if not tree_manifests then
99 return
100 end
101
102 manif.scan_dependencies(name, version, tree_manifests, loader.context)
103end
104
105--- Internal sorting function.
106--
107-- @param a A provider table.
108-- @param b Another provider table.
109--
110-- @return true if the version of a is greater than that of b.
111local function sort_versions(a: Provider, b: Provider): boolean
112 return a.version > b.version
113end
114
115--- Request module to be loaded through other loaders,
116-- once the proper name of the module has been determined.
117-- For example, in case the module "socket.core" has been requested
118-- to the LuaRocks loader and it determined based on context that
119-- the version 2.0.2 needs to be loaded and it is not the current
120-- version, the module requested for the other loaders will be
121-- "socket.core_2_0_2".
122--
123-- @param module The module name requested by the user, e.g. "socket.core"
124-- @param name The rock name, such as "luasocket"
125-- @param version The rock version, such as "2.0.2-1"
126-- @param module_name Actual module name, such as "socket.core_2_0_2"
127--
128-- @return The loader function returned or an error message.
129-- @return Additional loader data, if returned by the loader.
130local function call_other_loaders(module: string, name: string, version: string, module_name: string): LoaderFn | string, any
131 for _, a_loader in ipairs(loaders) do
132 if a_loader ~= loader.luarocks_loader then
133 local results: {any} = { a_loader(module_name) }
134 local f = results[1]
135 if f is LoaderFn then
136 if #results == 2 then
137 return f, results[2]
138 else
139 return f
140 end
141 end
142 end
143 end
144 return "Failed loading module " .. module .. " in LuaRocks rock " .. name .. " " .. version
145end
146
147--- Find entries which provide the wanted module in the tree,
148-- and store them in the array of providers for later sorting.
149--
150-- @param providers The array of providers being accumulated into
151-- @param entries The packages which provide the module
152-- @param tree TreeManifest where filenames can be found.
153-- @param module The module name being looked up
154-- @param filter_name A filtering function to adjust the filename.
155--
156-- @return If the current LuaRocks loader context already resolved this
157-- dependency based on other dependencies, return the name of the module
158-- for immediate use.
159-- @return Version of the module for immediate use, if matched.
160-- @return File name of the module for immediate use, if matched.
161local function add_providers(providers: {Provider}, entries: {string}, tree: TreeManifest, module: string, filter_name: FilterFn): string, string, string
162 for i, entry in ipairs(entries) do
163 local name, version = entry:match("^([^/]*)/(.*)$")
164
165 local file_name = tree.manifest.repository[name][version][1].modules[module]
166 if type(file_name) ~= "string" then
167 error("Invalid data in manifest file for module " .. tostring(module) .. " (invalid data for " .. tostring(name) .. " " .. tostring(version) .. ")")
168 end
169
170 file_name = filter_name(file_name, name, version, tree.tree, i)
171
172 if loader.context[name] == version then
173 return name, version, file_name
174 end
175
176 table.insert(providers, {
177 name = name,
178 version = vers.parse_version(version),
179 module_name = file_name,
180 tree = tree
181 })
182 end
183end
184
185--- Search for a module in the rocks trees.
186--
187-- @param module module name (eg. "socket.core")
188-- @param filter_name a function that takes the module file name
189-- (eg "socket/core.so"), the rock name (eg "luasocket"),
190-- the version (eg "2.0.2-1"), the path of the rocks tree
191-- (eg "/usr/local"), and the numeric index of the matching entry, so the
192-- filter function can know if the matching module was the first entry or not.
193--
194-- @return name of the rock containing the module (eg. "luasocket")
195-- @return version of the rock (eg. "2.0.2-1")
196-- @return return value of filter_name
197local function select_module(module: string, filter_name: FilterFn): string, string, string
198
199 local tree_manifests = manif.load_rocks_tree_manifests()
200 if not tree_manifests then
201 return nil
202 end
203
204 local providers: {Provider} = {}
205 local initmodule: string
206 for _, tree in ipairs(tree_manifests) do
207 local entries = tree.manifest.modules[module]
208 if entries then
209 local n, v, f = add_providers(providers, entries, tree, module, filter_name)
210 if n then
211 return n, v, f
212 end
213 else
214 initmodule = initmodule or module .. ".init"
215 entries = tree.manifest.modules[initmodule]
216 if entries then
217 local n, v, f = add_providers(providers, entries, tree, initmodule, filter_name)
218 if n then
219 return n, v, f
220 end
221 end
222 end
223 end
224
225 if next(providers) ~= nil then
226 table.sort(providers, sort_versions)
227 local first = providers[1]
228 return first.name, first.version.string, first.module_name
229 end
230end
231
232--- Filter operation for adjusting the versioned names when multiple packages provide
233-- the same file.
234--
235-- @param file_name The original filename
236-- @param name The rock name
237-- @param version The rock version
238-- @param _tree (unused)
239-- @param i The priority index, to determine whether to version the name.
240--
241-- @return A filename, which may be plain or versioned.
242-- (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned).
243local function filter_module_name(file_name: string, name: string, version: string, _tree: Tree, i: integer): string
244 if i > 1 then
245 file_name = path.versioned_name(file_name, "", name, version)
246 end
247 return path.path_to_module(file_name)
248end
249
250--- Search for a module.
251--
252-- @param module name of the module (eg. "socket.core")
253--
254-- @return name of the rock containing the module (eg. "luasocket")
255-- @return version of the rock (eg. "2.0.2-1")
256-- @return name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned).
257-- @return tree of the module (string or table in `tree_manifests` format)
258local function pick_module(module: string): string, string, string, string | TreeManifest
259 return select_module(module, filter_module_name)
260end
261
262--- Return the pathname of the file that would be loaded for a module.
263--
264-- @param module module name (eg. "socket.core")
265-- @param where places to look for the module. If `where` contains
266-- "l", it will search using the LuaRocks loader; if it contains "p",
267-- it will look in the filesystem using package.path and package.cpath.
268-- You can use both at the same time.
269--
270-- @return If found, filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"),
271-- @return If found via the loader, the rock name; otherwise, "path" or "cpath"
272-- @return If found via the loader, the rock version; otherwise, nil
273-- @return If found via the loader, "l"; if found via package.path or package.cpath, "p".
274function loader.which(module: string, where?: string): string, string, string, string
275 where = where or "l"
276 if where:match("l") then
277 local rock_name, rock_version, file_name = select_module(module, path.which_i)
278 if rock_name then
279 local fd = io.open(file_name)
280 if fd then
281 fd:close()
282 return file_name, rock_name, rock_version, "l"
283 end
284 end
285 end
286 if where:match("p") then
287 local modpath = module:gsub("%.", "/")
288 for _, v in ipairs({package.path, package.cpath}) do
289 for p in v:gmatch("([^;]+)") do
290 local file_name = p:gsub("%?", modpath) -- luacheck: ignore 421
291 local fd = io.open(file_name)
292 if fd then
293 fd:close()
294 return file_name, v, nil, "p"
295 end
296 end
297 end
298 end
299end
300
301--- Package loader for LuaRocks support.
302-- See <a href="http://www.lua.org/manual/5.4/manual.html#pdf-require">require()</a>
303-- in the Lua reference manual for details on the require() mechanism.
304-- The LuaRocks loader works by searching in installed rocks that match the
305-- current LuaRocks context. If module is not part of the
306-- context, or if a context has not yet been set, the module
307-- in the package with the highest version is used.
308--
309-- @param module The module name, like in plain require().
310--
311-- @return A function which can load the module found,
312-- or a string with an error message.
313function loader.luarocks_loader(module: string): LoaderFn | string, any
314 local name, version, module_name = pick_module(module)
315 if not name then
316 return "No LuaRocks module found for " .. module
317 else
318 loader.add_context(name, version)
319 return call_other_loaders(module, name, version, module_name)
320 end
321end
322
323table.insert(loaders, 1, loader.luarocks_loader)
324
325if is_clean then
326 for modname, _ in pairs(package.loaded) do
327 if modname:match("^luarocks%.") then
328 package.loaded[modname] = nil
329 end
330 end
331end
332
333return loader