diff options
author | V1K1NGbg <victor@ilchev.com> | 2024-08-12 17:23:42 +0300 |
---|---|---|
committer | V1K1NGbg <victor@ilchev.com> | 2024-08-12 17:23:42 +0300 |
commit | a121c775f9a08e76ed9cb7f2313668f38ac9c67b (patch) | |
tree | 11b4c001138f8d110c5afc80cd7387b78faaa5c9 | |
parent | e62cb8540e458c65146513d0e02af22c83a3bfea (diff) | |
download | luarocks-a121c775f9a08e76ed9cb7f2313668f38ac9c67b.tar.gz luarocks-a121c775f9a08e76ed9cb7f2313668f38ac9c67b.tar.bz2 luarocks-a121c775f9a08e76ed9cb7f2313668f38ac9c67b.zip |
test
-rw-r--r-- | src/luarocks/manif/writer.tl | 499 | ||||
-rw-r--r-- | src/luarocks/repos.tl | 695 | ||||
-rw-r--r-- | src/luarocks/test.tl | 24 | ||||
-rw-r--r-- | src/luarocks/test/busted.tl | 2 | ||||
-rw-r--r-- | src/luarocks/test/command.tl | 15 |
5 files changed, 1220 insertions, 15 deletions
diff --git a/src/luarocks/manif/writer.tl b/src/luarocks/manif/writer.tl new file mode 100644 index 00000000..625fba79 --- /dev/null +++ b/src/luarocks/manif/writer.tl | |||
@@ -0,0 +1,499 @@ | |||
1 | |||
2 | local record writer | ||
3 | end | ||
4 | |||
5 | local cfg = require("luarocks.core.cfg") | ||
6 | local search = require("luarocks.search") | ||
7 | local repos = require("luarocks.repos") | ||
8 | local deps = require("luarocks.deps") | ||
9 | local vers = require("luarocks.core.vers") | ||
10 | local fs = require("luarocks.fs") | ||
11 | local util = require("luarocks.util") | ||
12 | local dir = require("luarocks.dir") | ||
13 | local fetch = require("luarocks.fetch") | ||
14 | local path = require("luarocks.path") | ||
15 | local persist = require("luarocks.persist") | ||
16 | local manif = require("luarocks.manif") | ||
17 | local queries = require("luarocks.queries") | ||
18 | |||
19 | --- Update storage table to account for items provided by a package. | ||
20 | -- @param storage table: a table storing items in the following format: | ||
21 | -- keys are item names and values are arrays of packages providing each item, | ||
22 | -- where a package is specified as string `name/version`. | ||
23 | -- @param items table: a table mapping item names to paths. | ||
24 | -- @param name string: package name. | ||
25 | -- @param version string: package version. | ||
26 | local function store_package_items(storage, name, version, items) | ||
27 | assert(type(storage) == "table") | ||
28 | assert(type(items) == "table") | ||
29 | assert(type(name) == "string" and not name:match("/")) | ||
30 | assert(type(version) == "string") | ||
31 | |||
32 | local package_identifier = name.."/"..version | ||
33 | |||
34 | for item_name, path in pairs(items) do -- luacheck: ignore 431 | ||
35 | if not storage[item_name] then | ||
36 | storage[item_name] = {} | ||
37 | end | ||
38 | |||
39 | table.insert(storage[item_name], package_identifier) | ||
40 | end | ||
41 | end | ||
42 | |||
43 | --- Update storage table removing items provided by a package. | ||
44 | -- @param storage table: a table storing items in the following format: | ||
45 | -- keys are item names and values are arrays of packages providing each item, | ||
46 | -- where a package is specified as string `name/version`. | ||
47 | -- @param items table: a table mapping item names to paths. | ||
48 | -- @param name string: package name. | ||
49 | -- @param version string: package version. | ||
50 | local function remove_package_items(storage, name, version, items) | ||
51 | assert(type(storage) == "table") | ||
52 | assert(type(items) == "table") | ||
53 | assert(type(name) == "string" and not name:match("/")) | ||
54 | assert(type(version) == "string") | ||
55 | |||
56 | local package_identifier = name.."/"..version | ||
57 | |||
58 | for item_name, path in pairs(items) do -- luacheck: ignore 431 | ||
59 | local key = item_name | ||
60 | local all_identifiers = storage[key] | ||
61 | if not all_identifiers then | ||
62 | key = key .. ".init" | ||
63 | all_identifiers = storage[key] | ||
64 | end | ||
65 | |||
66 | if all_identifiers then | ||
67 | for i, identifier in ipairs(all_identifiers) do | ||
68 | if identifier == package_identifier then | ||
69 | table.remove(all_identifiers, i) | ||
70 | break | ||
71 | end | ||
72 | end | ||
73 | |||
74 | if #all_identifiers == 0 then | ||
75 | storage[key] = nil | ||
76 | end | ||
77 | else | ||
78 | util.warning("Cannot find entry for " .. item_name .. " in manifest -- corrupted manifest?") | ||
79 | end | ||
80 | end | ||
81 | end | ||
82 | |||
83 | --- Process the dependencies of a manifest table to determine its dependency | ||
84 | -- chains for loading modules. The manifest dependencies information is filled | ||
85 | -- and any dependency inconsistencies or missing dependencies are reported to | ||
86 | -- standard error. | ||
87 | -- @param manifest table: a manifest table. | ||
88 | -- @param deps_mode string: Dependency mode: "one" for the current default tree, | ||
89 | -- "all" for all trees, "order" for all trees with priority >= the current default, | ||
90 | -- "none" for no trees. | ||
91 | local function update_dependencies(manifest, deps_mode) | ||
92 | assert(type(manifest) == "table") | ||
93 | assert(type(deps_mode) == "string") | ||
94 | |||
95 | if not manifest.dependencies then manifest.dependencies = {} end | ||
96 | local mdeps = manifest.dependencies | ||
97 | |||
98 | for pkg, versions in pairs(manifest.repository) do | ||
99 | for version, repositories in pairs(versions) do | ||
100 | for _, repo in ipairs(repositories) do | ||
101 | if repo.arch == "installed" then | ||
102 | local rd = {} | ||
103 | repo.dependencies = rd | ||
104 | deps.scan_deps(rd, mdeps, pkg, version, deps_mode) | ||
105 | rd[pkg] = nil | ||
106 | end | ||
107 | end | ||
108 | end | ||
109 | end | ||
110 | end | ||
111 | |||
112 | |||
113 | |||
114 | --- Sort function for ordering rock identifiers in a manifest's | ||
115 | -- modules table. Rocks are ordered alphabetically by name, and then | ||
116 | -- by version which greater first. | ||
117 | -- @param a string: Version to compare. | ||
118 | -- @param b string: Version to compare. | ||
119 | -- @return boolean: The comparison result, according to the | ||
120 | -- rule outlined above. | ||
121 | local function sort_pkgs(a, b) | ||
122 | assert(type(a) == "string") | ||
123 | assert(type(b) == "string") | ||
124 | |||
125 | local na, va = a:match("(.*)/(.*)$") | ||
126 | local nb, vb = b:match("(.*)/(.*)$") | ||
127 | |||
128 | return (na == nb) and vers.compare_versions(va, vb) or na < nb | ||
129 | end | ||
130 | |||
131 | --- Sort items of a package matching table by version number (higher versions first). | ||
132 | -- @param tbl table: the package matching table: keys should be strings | ||
133 | -- and values arrays of strings with packages names in "name/version" format. | ||
134 | local function sort_package_matching_table(tbl) | ||
135 | assert(type(tbl) == "table") | ||
136 | |||
137 | if next(tbl) then | ||
138 | for item, pkgs in pairs(tbl) do | ||
139 | if #pkgs > 1 then | ||
140 | table.sort(pkgs, sort_pkgs) | ||
141 | -- Remove duplicates from the sorted array. | ||
142 | local prev = nil | ||
143 | local i = 1 | ||
144 | while pkgs[i] do | ||
145 | local curr = pkgs[i] | ||
146 | if curr == prev then | ||
147 | table.remove(pkgs, i) | ||
148 | else | ||
149 | prev = curr | ||
150 | i = i + 1 | ||
151 | end | ||
152 | end | ||
153 | end | ||
154 | end | ||
155 | end | ||
156 | end | ||
157 | |||
158 | --- Filter manifest table by Lua version, removing rockspecs whose Lua version | ||
159 | -- does not match. | ||
160 | -- @param manifest table: a manifest table. | ||
161 | -- @param lua_version string or nil: filter by Lua version | ||
162 | -- @param repodir string: directory of repository being scanned | ||
163 | -- @param cache table: temporary rockspec cache table | ||
164 | local function filter_by_lua_version(manifest, lua_version, repodir, cache) | ||
165 | assert(type(manifest) == "table") | ||
166 | assert(type(repodir) == "string") | ||
167 | assert((not cache) or type(cache) == "table") | ||
168 | |||
169 | cache = cache or {} | ||
170 | lua_version = vers.parse_version(lua_version) | ||
171 | for pkg, versions in pairs(manifest.repository) do | ||
172 | local to_remove = {} | ||
173 | for version, repositories in pairs(versions) do | ||
174 | for _, repo in ipairs(repositories) do | ||
175 | if repo.arch == "rockspec" then | ||
176 | local pathname = dir.path(repodir, pkg.."-"..version..".rockspec") | ||
177 | local rockspec, err = cache[pathname] | ||
178 | if not rockspec then | ||
179 | rockspec, err = fetch.load_local_rockspec(pathname, true) | ||
180 | end | ||
181 | if rockspec then | ||
182 | cache[pathname] = rockspec | ||
183 | for _, dep in ipairs(rockspec.dependencies) do | ||
184 | if dep.name == "lua" then | ||
185 | if not vers.match_constraints(lua_version, dep.constraints) then | ||
186 | table.insert(to_remove, version) | ||
187 | end | ||
188 | break | ||
189 | end | ||
190 | end | ||
191 | else | ||
192 | util.printerr("Error loading rockspec for "..pkg.." "..version..": "..err) | ||
193 | end | ||
194 | end | ||
195 | end | ||
196 | end | ||
197 | if next(to_remove) then | ||
198 | for _, incompat in ipairs(to_remove) do | ||
199 | versions[incompat] = nil | ||
200 | end | ||
201 | if not next(versions) then | ||
202 | manifest.repository[pkg] = nil | ||
203 | end | ||
204 | end | ||
205 | end | ||
206 | end | ||
207 | |||
208 | --- Store search results in a manifest table. | ||
209 | -- @param results table: The search results as returned by search.disk_search. | ||
210 | -- @param manifest table: A manifest table (must contain repository, modules, commands tables). | ||
211 | -- It will be altered to include the search results. | ||
212 | -- @return boolean or (nil, string): true in case of success, or nil followed by an error message. | ||
213 | local function store_results(results, manifest) | ||
214 | assert(type(results) == "table") | ||
215 | assert(type(manifest) == "table") | ||
216 | |||
217 | for name, versions in pairs(results) do | ||
218 | local pkgtable = manifest.repository[name] or {} | ||
219 | for version, entries in pairs(versions) do | ||
220 | local versiontable = {} | ||
221 | for _, entry in ipairs(entries) do | ||
222 | local entrytable = {} | ||
223 | entrytable.arch = entry.arch | ||
224 | if entry.arch == "installed" then | ||
225 | local rock_manifest, err = manif.load_rock_manifest(name, version) | ||
226 | if not rock_manifest then return nil, err end | ||
227 | |||
228 | entrytable.modules = repos.package_modules(name, version) | ||
229 | store_package_items(manifest.modules, name, version, entrytable.modules) | ||
230 | entrytable.commands = repos.package_commands(name, version) | ||
231 | store_package_items(manifest.commands, name, version, entrytable.commands) | ||
232 | end | ||
233 | table.insert(versiontable, entrytable) | ||
234 | end | ||
235 | pkgtable[version] = versiontable | ||
236 | end | ||
237 | manifest.repository[name] = pkgtable | ||
238 | end | ||
239 | sort_package_matching_table(manifest.modules) | ||
240 | sort_package_matching_table(manifest.commands) | ||
241 | return true | ||
242 | end | ||
243 | |||
244 | --- Commit a table to disk in given local path. | ||
245 | -- @param where string: The directory where the table should be saved. | ||
246 | -- @param name string: The filename. | ||
247 | -- @param tbl table: The table to be saved. | ||
248 | -- @return boolean or (nil, string): true if successful, or nil and a | ||
249 | -- message in case of errors. | ||
250 | local function save_table(where, name, tbl) | ||
251 | assert(type(where) == "string") | ||
252 | assert(type(name) == "string" and not name:match("/")) | ||
253 | assert(type(tbl) == "table") | ||
254 | |||
255 | local filename = dir.path(where, name) | ||
256 | local ok, err = persist.save_from_table(filename..".tmp", tbl) | ||
257 | if ok then | ||
258 | ok, err = fs.replace_file(filename, filename..".tmp") | ||
259 | end | ||
260 | return ok, err | ||
261 | end | ||
262 | |||
263 | function writer.make_rock_manifest(name, version) | ||
264 | local install_dir = path.install_dir(name, version) | ||
265 | local tree = {} | ||
266 | for _, file in ipairs(fs.find(install_dir)) do | ||
267 | local full_path = dir.path(install_dir, file) | ||
268 | local walk = tree | ||
269 | local last | ||
270 | local last_name | ||
271 | for filename in file:gmatch("[^\\/]+") do | ||
272 | local next = walk[filename] | ||
273 | if not next then | ||
274 | next = {} | ||
275 | walk[filename] = next | ||
276 | end | ||
277 | last = walk | ||
278 | last_name = filename | ||
279 | walk = next | ||
280 | end | ||
281 | if fs.is_file(full_path) then | ||
282 | local sum, err = fs.get_md5(full_path) | ||
283 | if not sum then | ||
284 | return nil, "Failed producing checksum: "..tostring(err) | ||
285 | end | ||
286 | last[last_name] = sum | ||
287 | end | ||
288 | end | ||
289 | local rock_manifest = { rock_manifest=tree } | ||
290 | manif.rock_manifest_cache[name.."/"..version] = rock_manifest | ||
291 | save_table(install_dir, "rock_manifest", rock_manifest ) | ||
292 | return true | ||
293 | end | ||
294 | |||
295 | -- Writes a 'rock_namespace' file in a locally installed rock directory. | ||
296 | -- @param name string: the rock name, without a namespace | ||
297 | -- @param version string: the rock version | ||
298 | -- @param namespace string?: the namespace | ||
299 | -- @return true if successful (or unnecessary, if there is no namespace), | ||
300 | -- or nil and an error message. | ||
301 | function writer.make_namespace_file(name, version, namespace) | ||
302 | assert(type(name) == "string" and not name:match("/")) | ||
303 | assert(type(version) == "string") | ||
304 | assert(type(namespace) == "string" or not namespace) | ||
305 | if not namespace then | ||
306 | return true | ||
307 | end | ||
308 | local fd, err = io.open(path.rock_namespace_file(name, version), "w") | ||
309 | if not fd then | ||
310 | return nil, err | ||
311 | end | ||
312 | local ok, err = fd:write(namespace) | ||
313 | if not ok then | ||
314 | return nil, err | ||
315 | end | ||
316 | fd:close() | ||
317 | return true | ||
318 | end | ||
319 | |||
320 | --- Scan a LuaRocks repository and output a manifest file. | ||
321 | -- A file called 'manifest' will be written in the root of the given | ||
322 | -- repository directory. | ||
323 | -- @param repo A local repository directory. | ||
324 | -- @param deps_mode string: Dependency mode: "one" for the current default tree, | ||
325 | -- "all" for all trees, "order" for all trees with priority >= the current default, | ||
326 | -- "none" for the default dependency mode from the configuration. | ||
327 | -- @param remote boolean: 'true' if making a manifest for a rocks server. | ||
328 | -- @return boolean or (nil, string): True if manifest was generated, | ||
329 | -- or nil and an error message. | ||
330 | function writer.make_manifest(repo, deps_mode, remote) | ||
331 | assert(type(repo) == "string") | ||
332 | assert(type(deps_mode) == "string") | ||
333 | |||
334 | if deps_mode == "none" then deps_mode = cfg.deps_mode end | ||
335 | |||
336 | if not fs.is_dir(repo) then | ||
337 | return nil, "Cannot access repository at "..repo | ||
338 | end | ||
339 | |||
340 | local query = queries.all("any") | ||
341 | local results = search.disk_search(repo, query) | ||
342 | local manifest = { repository = {}, modules = {}, commands = {} } | ||
343 | |||
344 | manif.cache_manifest(repo, nil, manifest) | ||
345 | |||
346 | local ok, err = store_results(results, manifest) | ||
347 | if not ok then return nil, err end | ||
348 | |||
349 | if remote then | ||
350 | local cache = {} | ||
351 | for luaver in util.lua_versions() do | ||
352 | local vmanifest = { repository = {}, modules = {}, commands = {} } | ||
353 | local ok, err = store_results(results, vmanifest) | ||
354 | filter_by_lua_version(vmanifest, luaver, repo, cache) | ||
355 | if not cfg.no_manifest then | ||
356 | save_table(repo, "manifest-"..luaver, vmanifest) | ||
357 | end | ||
358 | end | ||
359 | else | ||
360 | update_dependencies(manifest, deps_mode) | ||
361 | end | ||
362 | |||
363 | if cfg.no_manifest then | ||
364 | -- We want to have cache updated; but exit before save_table is called | ||
365 | return true | ||
366 | end | ||
367 | return save_table(repo, "manifest", manifest) | ||
368 | end | ||
369 | |||
370 | --- Update manifest file for a local repository | ||
371 | -- adding information about a version of a package installed in that repository. | ||
372 | -- @param name string: Name of a package from the repository. | ||
373 | -- @param version string: Version of a package from the repository. | ||
374 | -- @param repo string or nil: Pathname of a local repository. If not given, | ||
375 | -- the default local repository is used. | ||
376 | -- @param deps_mode string: Dependency mode: "one" for the current default tree, | ||
377 | -- "all" for all trees, "order" for all trees with priority >= the current default, | ||
378 | -- "none" for using the default dependency mode from the configuration. | ||
379 | -- @return boolean or (nil, string): True if manifest was updated successfully, | ||
380 | -- or nil and an error message. | ||
381 | function writer.add_to_manifest(name, version, repo, deps_mode) | ||
382 | assert(type(name) == "string" and not name:match("/")) | ||
383 | assert(type(version) == "string") | ||
384 | local rocks_dir = path.rocks_dir(repo or cfg.root_dir) | ||
385 | assert(type(deps_mode) == "string") | ||
386 | |||
387 | if deps_mode == "none" then deps_mode = cfg.deps_mode end | ||
388 | |||
389 | local manifest, err = manif.load_manifest(rocks_dir) | ||
390 | if not manifest then | ||
391 | util.printerr("No existing manifest. Attempting to rebuild...") | ||
392 | -- Manifest built by `writer.make_manifest` should already | ||
393 | -- include information about given name and version, | ||
394 | -- no need to update it. | ||
395 | return writer.make_manifest(rocks_dir, deps_mode) | ||
396 | end | ||
397 | |||
398 | local results = {[name] = {[version] = {{arch = "installed", repo = rocks_dir}}}} | ||
399 | |||
400 | local ok, err = store_results(results, manifest) | ||
401 | if not ok then return nil, err end | ||
402 | |||
403 | update_dependencies(manifest, deps_mode) | ||
404 | |||
405 | if cfg.no_manifest then | ||
406 | return true | ||
407 | end | ||
408 | return save_table(rocks_dir, "manifest", manifest) | ||
409 | end | ||
410 | |||
411 | --- Update manifest file for a local repository | ||
412 | -- removing information about a version of a package. | ||
413 | -- @param name string: Name of a package removed from the repository. | ||
414 | -- @param version string: Version of a package removed from the repository. | ||
415 | -- @param repo string or nil: Pathname of a local repository. If not given, | ||
416 | -- the default local repository is used. | ||
417 | -- @param deps_mode string: Dependency mode: "one" for the current default tree, | ||
418 | -- "all" for all trees, "order" for all trees with priority >= the current default, | ||
419 | -- "none" for using the default dependency mode from the configuration. | ||
420 | -- @return boolean or (nil, string): True if manifest was updated successfully, | ||
421 | -- or nil and an error message. | ||
422 | function writer.remove_from_manifest(name, version, repo, deps_mode) | ||
423 | assert(type(name) == "string" and not name:match("/")) | ||
424 | assert(type(version) == "string") | ||
425 | local rocks_dir = path.rocks_dir(repo or cfg.root_dir) | ||
426 | assert(type(deps_mode) == "string") | ||
427 | |||
428 | if deps_mode == "none" then deps_mode = cfg.deps_mode end | ||
429 | |||
430 | local manifest, err = manif.load_manifest(rocks_dir) | ||
431 | if not manifest then | ||
432 | util.printerr("No existing manifest. Attempting to rebuild...") | ||
433 | -- Manifest built by `writer.make_manifest` should already | ||
434 | -- include up-to-date information, no need to update it. | ||
435 | return writer.make_manifest(rocks_dir, deps_mode) | ||
436 | end | ||
437 | |||
438 | local package_entry = manifest.repository[name] | ||
439 | if package_entry == nil or package_entry[version] == nil then | ||
440 | -- entry is already missing from repository, no need to do anything | ||
441 | return true | ||
442 | end | ||
443 | |||
444 | local version_entry = package_entry[version][1] | ||
445 | if not version_entry then | ||
446 | -- manifest looks corrupted, rebuild | ||
447 | return writer.make_manifest(rocks_dir, deps_mode) | ||
448 | end | ||
449 | |||
450 | remove_package_items(manifest.modules, name, version, version_entry.modules) | ||
451 | remove_package_items(manifest.commands, name, version, version_entry.commands) | ||
452 | |||
453 | package_entry[version] = nil | ||
454 | manifest.dependencies[name][version] = nil | ||
455 | |||
456 | if not next(package_entry) then | ||
457 | -- No more versions of this package. | ||
458 | manifest.repository[name] = nil | ||
459 | manifest.dependencies[name] = nil | ||
460 | end | ||
461 | |||
462 | update_dependencies(manifest, deps_mode) | ||
463 | |||
464 | if cfg.no_manifest then | ||
465 | return true | ||
466 | end | ||
467 | return save_table(rocks_dir, "manifest", manifest) | ||
468 | end | ||
469 | |||
470 | --- Report missing dependencies for all rocks installed in a repository. | ||
471 | -- @param repo string or nil: Pathname of a local repository. If not given, | ||
472 | -- the default local repository is used. | ||
473 | -- @param deps_mode string: Dependency mode: "one" for the current default tree, | ||
474 | -- "all" for all trees, "order" for all trees with priority >= the current default, | ||
475 | -- "none" for using the default dependency mode from the configuration. | ||
476 | function writer.check_dependencies(repo, deps_mode) | ||
477 | local rocks_dir = path.rocks_dir(repo or cfg.root_dir) | ||
478 | assert(type(deps_mode) == "string") | ||
479 | if deps_mode == "none" then deps_mode = cfg.deps_mode end | ||
480 | |||
481 | local manifest = manif.load_manifest(rocks_dir) | ||
482 | if not manifest then | ||
483 | return | ||
484 | end | ||
485 | |||
486 | for name, versions in util.sortedpairs(manifest.repository) do | ||
487 | for version, version_entries in util.sortedpairs(versions, vers.compare_versions) do | ||
488 | for _, entry in ipairs(version_entries) do | ||
489 | if entry.arch == "installed" then | ||
490 | if manifest.dependencies[name] and manifest.dependencies[name][version] then | ||
491 | deps.report_missing_dependencies(name, version, manifest.dependencies[name][version], deps_mode, util.get_rocks_provided()) | ||
492 | end | ||
493 | end | ||
494 | end | ||
495 | end | ||
496 | end | ||
497 | end | ||
498 | |||
499 | return writer | ||
diff --git a/src/luarocks/repos.tl b/src/luarocks/repos.tl new file mode 100644 index 00000000..14f1658c --- /dev/null +++ b/src/luarocks/repos.tl | |||
@@ -0,0 +1,695 @@ | |||
1 | |||
2 | --- Functions for managing the repository on disk. | ||
3 | local record repos | ||
4 | end | ||
5 | |||
6 | local fs = require("luarocks.fs") | ||
7 | local path = require("luarocks.path") | ||
8 | local cfg = require("luarocks.core.cfg") | ||
9 | local util = require("luarocks.util") | ||
10 | local dir = require("luarocks.dir") | ||
11 | local manif = require("luarocks.manif") | ||
12 | local vers = require("luarocks.core.vers") | ||
13 | |||
14 | local unpack = unpack or table.unpack -- luacheck: ignore 211 | ||
15 | |||
16 | --- Get type and name of an item (a module or a command) provided by a file. | ||
17 | -- @param deploy_type string: rock manifest subtree the file comes from ("bin", "lua", or "lib"). | ||
18 | -- @param file_path string: path to the file relatively to deploy_type subdirectory. | ||
19 | -- @return (string, string): item type ("module" or "command") and name. | ||
20 | local function get_provided_item(deploy_type: string, file_path: string): string, string | ||
21 | local item_type = deploy_type == "bin" and "command" or "module" | ||
22 | local item_name = item_type == "command" and file_path or path.path_to_module(file_path) | ||
23 | return item_type, item_name | ||
24 | end | ||
25 | |||
26 | -- Tree of files installed by a package are stored | ||
27 | -- in its rock manifest. Some of these files have to | ||
28 | -- be deployed to locations where Lua can load them as | ||
29 | -- modules or where they can be used as commands. | ||
30 | -- These files are characterised by pair | ||
31 | -- (deploy_type, file_path), where deploy_type is the first | ||
32 | -- component of the file path and file_path is the rest of the | ||
33 | -- path. Only files with deploy_type in {"lua", "lib", "bin"} | ||
34 | -- are deployed somewhere. | ||
35 | -- Each deployed file provides an "item". An item is | ||
36 | -- characterised by pair (item_type, item_name). | ||
37 | -- item_type is "command" for files with deploy_type | ||
38 | -- "bin" and "module" for deploy_type in {"lua", "lib"}. | ||
39 | -- item_name is same as file_path for commands | ||
40 | -- and is produced using path.path_to_module(file_path) | ||
41 | -- for modules. | ||
42 | |||
43 | --- Get all installed versions of a package. | ||
44 | -- @param name string: a package name. | ||
45 | -- @return table or nil: An array of strings listing installed | ||
46 | -- versions of a package, or nil if none is available. | ||
47 | local function get_installed_versions(name: string): {string} | ||
48 | assert(not name:match("/")) | ||
49 | |||
50 | local dirs = fs.list_dir(path.versions_dir(name)) | ||
51 | return (dirs and #dirs > 0) and dirs or nil | ||
52 | end | ||
53 | |||
54 | --- Check if a package exists in a local repository. | ||
55 | -- Version numbers are compared as exact string comparison. | ||
56 | -- @param name string: name of package | ||
57 | -- @param version string: package version in string format | ||
58 | -- @return boolean: true if a package is installed, | ||
59 | -- false otherwise. | ||
60 | function repos.is_installed(name: string, version: string): boolean | ||
61 | assert(not name:match("/")) | ||
62 | |||
63 | return fs.is_dir(path.install_dir(name, version)) | ||
64 | end | ||
65 | |||
66 | function repos.recurse_rock_manifest_entry(entry, action): boolean, string | ||
67 | assert(type(action) == "function") | ||
68 | |||
69 | if entry == nil then | ||
70 | return true | ||
71 | end | ||
72 | |||
73 | local function do_recurse_rock_manifest_entry(tree, parent_path) | ||
74 | |||
75 | for file, sub in pairs(tree) do | ||
76 | local sub_path = (parent_path and (parent_path .. "/") or "") .. file | ||
77 | local ok, err -- luacheck: ignore 231 | ||
78 | |||
79 | if type(sub) == "table" then | ||
80 | ok, err = do_recurse_rock_manifest_entry(sub, sub_path) | ||
81 | else | ||
82 | ok, err = action(sub_path) | ||
83 | end | ||
84 | |||
85 | if err then return nil, err end | ||
86 | end | ||
87 | return true | ||
88 | end | ||
89 | return do_recurse_rock_manifest_entry(entry) | ||
90 | end | ||
91 | |||
92 | local function store_package_data(result, rock_manifest, deploy_type) | ||
93 | if rock_manifest[deploy_type] then | ||
94 | repos.recurse_rock_manifest_entry(rock_manifest[deploy_type], function(file_path) | ||
95 | local _, item_name = get_provided_item(deploy_type, file_path) | ||
96 | result[item_name] = file_path | ||
97 | return true | ||
98 | end) | ||
99 | end | ||
100 | end | ||
101 | |||
102 | --- Obtain a table of modules within an installed package. | ||
103 | -- @param name string: The package name; for example "luasocket" | ||
104 | -- @param version string: The exact version number including revision; | ||
105 | -- for example "2.0.1-1". | ||
106 | -- @return table: A table of modules where keys are module names | ||
107 | -- and values are file paths of files providing modules | ||
108 | -- relative to "lib" or "lua" rock manifest subtree. | ||
109 | -- If no modules are found or if package name or version | ||
110 | -- are invalid, an empty table is returned. | ||
111 | function repos.package_modules(name, version) | ||
112 | assert(type(name) == "string" and not name:match("/")) | ||
113 | assert(type(version) == "string") | ||
114 | |||
115 | local result = {} | ||
116 | local rock_manifest = manif.load_rock_manifest(name, version) | ||
117 | if not rock_manifest then return result end | ||
118 | store_package_data(result, rock_manifest, "lib") | ||
119 | store_package_data(result, rock_manifest, "lua") | ||
120 | return result | ||
121 | end | ||
122 | |||
123 | --- Obtain a table of command-line scripts within an installed package. | ||
124 | -- @param name string: The package name; for example "luasocket" | ||
125 | -- @param version string: The exact version number including revision; | ||
126 | -- for example "2.0.1-1". | ||
127 | -- @return table: A table of commands where keys and values are command names | ||
128 | -- as strings - file paths of files providing commands | ||
129 | -- relative to "bin" rock manifest subtree. | ||
130 | -- If no commands are found or if package name or version | ||
131 | -- are invalid, an empty table is returned. | ||
132 | function repos.package_commands(name, version) | ||
133 | assert(type(name) == "string" and not name:match("/")) | ||
134 | assert(type(version) == "string") | ||
135 | |||
136 | local result = {} | ||
137 | local rock_manifest = manif.load_rock_manifest(name, version) | ||
138 | if not rock_manifest then return result end | ||
139 | store_package_data(result, rock_manifest, "bin") | ||
140 | return result | ||
141 | end | ||
142 | |||
143 | |||
144 | --- Check if a rock contains binary executables. | ||
145 | -- @param name string: name of an installed rock | ||
146 | -- @param version string: version of an installed rock | ||
147 | -- @return boolean: returns true if rock contains platform-specific | ||
148 | -- binary executables, or false if it is a pure-Lua rock. | ||
149 | function repos.has_binaries(name, version) | ||
150 | assert(type(name) == "string" and not name:match("/")) | ||
151 | assert(type(version) == "string") | ||
152 | |||
153 | local rock_manifest = manif.load_rock_manifest(name, version) | ||
154 | if rock_manifest and rock_manifest.bin then | ||
155 | for bin_name, md5 in pairs(rock_manifest.bin) do | ||
156 | -- TODO verify that it is the same file. If it isn't, find the actual command. | ||
157 | if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, bin_name)) then | ||
158 | return true | ||
159 | end | ||
160 | end | ||
161 | end | ||
162 | return false | ||
163 | end | ||
164 | |||
165 | function repos.run_hook(rockspec, hook_name) | ||
166 | assert(rockspec:type() == "rockspec") | ||
167 | assert(type(hook_name) == "string") | ||
168 | |||
169 | local hooks = rockspec.hooks | ||
170 | if not hooks then | ||
171 | return true | ||
172 | end | ||
173 | |||
174 | if cfg.hooks_enabled == false then | ||
175 | return nil, "This rockspec contains hooks, which are blocked by the 'hooks_enabled' setting in your LuaRocks configuration." | ||
176 | end | ||
177 | |||
178 | if not hooks.substituted_variables then | ||
179 | util.variable_substitutions(hooks, rockspec.variables) | ||
180 | hooks.substituted_variables = true | ||
181 | end | ||
182 | local hook = hooks[hook_name] | ||
183 | if hook then | ||
184 | util.printout(hook) | ||
185 | if not fs.execute(hook) then | ||
186 | return nil, "Failed running "..hook_name.." hook." | ||
187 | end | ||
188 | end | ||
189 | return true | ||
190 | end | ||
191 | |||
192 | function repos.should_wrap_bin_scripts(rockspec) | ||
193 | assert(rockspec:type() == "rockspec") | ||
194 | |||
195 | if cfg.wrap_bin_scripts ~= nil then | ||
196 | return cfg.wrap_bin_scripts | ||
197 | end | ||
198 | if rockspec.deploy and rockspec.deploy.wrap_bin_scripts == false then | ||
199 | return false | ||
200 | end | ||
201 | return true | ||
202 | end | ||
203 | |||
204 | local function find_suffixed(file, suffix) | ||
205 | local filenames = {file} | ||
206 | if suffix and suffix ~= "" then | ||
207 | table.insert(filenames, 1, file .. suffix) | ||
208 | end | ||
209 | |||
210 | for _, filename in ipairs(filenames) do | ||
211 | if fs.exists(filename) then | ||
212 | return filename | ||
213 | end | ||
214 | end | ||
215 | |||
216 | return nil, table.concat(filenames, ", ") .. " not found" | ||
217 | end | ||
218 | |||
219 | local function check_suffix(filename, suffix) | ||
220 | local suffixed_filename, err = find_suffixed(filename, suffix) | ||
221 | if not suffixed_filename then | ||
222 | return "" | ||
223 | end | ||
224 | return suffixed_filename:sub(#filename + 1) | ||
225 | end | ||
226 | |||
227 | -- Files can be deployed using versioned and non-versioned names. | ||
228 | -- Several items with same type and name can exist if they are | ||
229 | -- provided by different packages or versions. In any case | ||
230 | -- item from the newest version of lexicographically smallest package | ||
231 | -- is deployed using non-versioned name and others use versioned names. | ||
232 | |||
233 | local function get_deploy_paths(name, version, deploy_type, file_path, repo) | ||
234 | assert(type(name) == "string") | ||
235 | assert(type(version) == "string") | ||
236 | assert(type(deploy_type) == "string") | ||
237 | assert(type(file_path) == "string") | ||
238 | |||
239 | repo = repo or cfg.root_dir | ||
240 | local deploy_dir = path["deploy_" .. deploy_type .. "_dir"](repo) | ||
241 | local non_versioned = dir.path(deploy_dir, file_path) | ||
242 | local versioned = path.versioned_name(non_versioned, deploy_dir, name, version) | ||
243 | return { nv = non_versioned, v = versioned } | ||
244 | end | ||
245 | |||
246 | local function check_spot_if_available(name, version, deploy_type, file_path) | ||
247 | local item_type, item_name = get_provided_item(deploy_type, file_path) | ||
248 | local cur_name, cur_version = manif.get_current_provider(item_type, item_name) | ||
249 | |||
250 | -- older versions of LuaRocks (< 3) registered "foo.init" files as "foo" | ||
251 | -- (which caused problems, so that behavior was changed). But look for that | ||
252 | -- in the manifest anyway for backward compatibility. | ||
253 | if not cur_name and deploy_type == "lua" and item_name:match("%.init$") then | ||
254 | cur_name, cur_version = manif.get_current_provider(item_type, (item_name:gsub("%.init$", ""))) | ||
255 | end | ||
256 | |||
257 | if (not cur_name) | ||
258 | or (name < cur_name) | ||
259 | or (name == cur_name and (version == cur_version | ||
260 | or vers.compare_versions(version, cur_version))) then | ||
261 | return "nv", cur_name, cur_version, item_name | ||
262 | else | ||
263 | -- Existing version has priority, deploy new version using versioned name. | ||
264 | return "v", cur_name, cur_version, item_name | ||
265 | end | ||
266 | end | ||
267 | |||
268 | local function backup_existing(should_backup, target) | ||
269 | if not should_backup then | ||
270 | fs.delete(target) | ||
271 | return | ||
272 | end | ||
273 | if fs.exists(target) then | ||
274 | local backup = target | ||
275 | repeat | ||
276 | backup = backup.."~" | ||
277 | until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem. | ||
278 | |||
279 | util.warning(target.." is not tracked by this installation of LuaRocks. Moving it to "..backup) | ||
280 | local move_ok, move_err = os.rename(target, backup) | ||
281 | if not move_ok then | ||
282 | return nil, move_err | ||
283 | end | ||
284 | return backup | ||
285 | end | ||
286 | end | ||
287 | |||
288 | local function prepare_op_install() | ||
289 | local mkdirs = {} | ||
290 | local rmdirs = {} | ||
291 | |||
292 | local function memoize_mkdir(d) | ||
293 | if mkdirs[d] then | ||
294 | return true | ||
295 | end | ||
296 | local ok, err = fs.make_dir(d) | ||
297 | if not ok then | ||
298 | return nil, err | ||
299 | end | ||
300 | mkdirs[d] = true | ||
301 | return true | ||
302 | end | ||
303 | |||
304 | local function op_install(op) | ||
305 | local ok, err = memoize_mkdir(dir.dir_name(op.dst)) | ||
306 | if not ok then | ||
307 | return nil, err | ||
308 | end | ||
309 | |||
310 | local backup, err = backup_existing(op.backup, op.dst) | ||
311 | if err then | ||
312 | return nil, err | ||
313 | end | ||
314 | if backup then | ||
315 | op.backup_file = backup | ||
316 | end | ||
317 | |||
318 | ok, err = op.fn(op.src, op.dst, op.backup) | ||
319 | if not ok then | ||
320 | return nil, err | ||
321 | end | ||
322 | |||
323 | rmdirs[dir.dir_name(op.src)] = true | ||
324 | return true | ||
325 | end | ||
326 | |||
327 | local function done_op_install() | ||
328 | for d, _ in pairs(rmdirs) do | ||
329 | fs.remove_dir_tree_if_empty(d) | ||
330 | end | ||
331 | end | ||
332 | |||
333 | return op_install, done_op_install | ||
334 | end | ||
335 | |||
336 | local function rollback_install(op) | ||
337 | fs.delete(op.dst) | ||
338 | if op.backup_file then | ||
339 | os.rename(op.backup_file, op.dst) | ||
340 | end | ||
341 | fs.remove_dir_tree_if_empty(dir.dir_name(op.dst)) | ||
342 | return true | ||
343 | end | ||
344 | |||
345 | local function op_rename(op) | ||
346 | if op.suffix then | ||
347 | local suffix = check_suffix(op.src, op.suffix) | ||
348 | op.src = op.src .. suffix | ||
349 | op.dst = op.dst .. suffix | ||
350 | end | ||
351 | |||
352 | if fs.exists(op.src) then | ||
353 | fs.make_dir(dir.dir_name(op.dst)) | ||
354 | fs.delete(op.dst) | ||
355 | local ok, err = os.rename(op.src, op.dst) | ||
356 | fs.remove_dir_tree_if_empty(dir.dir_name(op.src)) | ||
357 | return ok, err | ||
358 | else | ||
359 | return true | ||
360 | end | ||
361 | end | ||
362 | |||
363 | local function rollback_rename(op) | ||
364 | return op_rename({ src = op.dst, dst = op.src }) | ||
365 | end | ||
366 | |||
367 | local function prepare_op_delete() | ||
368 | local deletes = {} | ||
369 | local rmdirs = {} | ||
370 | |||
371 | local function done_op_delete() | ||
372 | for _, f in ipairs(deletes) do | ||
373 | os.remove(f) | ||
374 | end | ||
375 | |||
376 | for d, _ in pairs(rmdirs) do | ||
377 | fs.remove_dir_tree_if_empty(d) | ||
378 | end | ||
379 | end | ||
380 | |||
381 | local function op_delete(op) | ||
382 | if op.suffix then | ||
383 | local suffix = check_suffix(op.name, op.suffix) | ||
384 | op.name = op.name .. suffix | ||
385 | end | ||
386 | |||
387 | table.insert(deletes, op.name) | ||
388 | |||
389 | rmdirs[dir.dir_name(op.name)] = true | ||
390 | end | ||
391 | |||
392 | return op_delete, done_op_delete | ||
393 | end | ||
394 | |||
395 | local function rollback_ops(ops, op_fn, n) | ||
396 | for i = 1, n do | ||
397 | op_fn(ops[i]) | ||
398 | end | ||
399 | end | ||
400 | |||
401 | --- Double check that all files referenced in `rock_manifest` are installed in `repo`. | ||
402 | function repos.check_everything_is_installed(name, version, rock_manifest, repo, accept_versioned) | ||
403 | local missing = {} | ||
404 | local suffix = cfg.wrapper_suffix or "" | ||
405 | for _, category in ipairs({"bin", "lua", "lib"}) do | ||
406 | if rock_manifest[category] then | ||
407 | repos.recurse_rock_manifest_entry(rock_manifest[category], function(file_path) | ||
408 | local paths = get_deploy_paths(name, version, category, file_path, repo) | ||
409 | if category == "bin" then | ||
410 | if (fs.exists(paths.nv) or fs.exists(paths.nv .. suffix)) | ||
411 | or (accept_versioned and (fs.exists(paths.v) or fs.exists(paths.v .. suffix))) then | ||
412 | return | ||
413 | end | ||
414 | else | ||
415 | if fs.exists(paths.nv) or (accept_versioned and fs.exists(paths.v)) then | ||
416 | return | ||
417 | end | ||
418 | end | ||
419 | table.insert(missing, paths.nv) | ||
420 | end) | ||
421 | end | ||
422 | end | ||
423 | if #missing > 0 then | ||
424 | return nil, "failed deploying files. " .. | ||
425 | "The following files were not installed:\n" .. | ||
426 | table.concat(missing, "\n") | ||
427 | end | ||
428 | return true | ||
429 | end | ||
430 | |||
431 | --- Deploy a package from the rocks subdirectory. | ||
432 | -- @param name string: name of package | ||
433 | -- @param version string: exact package version in string format | ||
434 | -- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped. | ||
435 | -- @param deps_mode: string: Which trees to check dependencies for: | ||
436 | -- "one" for the current default tree, "all" for all trees, | ||
437 | -- "order" for all trees with priority >= the current default, "none" for no trees. | ||
438 | function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode) | ||
439 | assert(type(name) == "string" and not name:match("/")) | ||
440 | assert(type(version) == "string") | ||
441 | assert(type(wrap_bin_scripts) == "boolean") | ||
442 | |||
443 | local rock_manifest, load_err = manif.load_rock_manifest(name, version) | ||
444 | if not rock_manifest then return nil, load_err end | ||
445 | |||
446 | local repo = cfg.root_dir | ||
447 | local renames = {} | ||
448 | local installs = {} | ||
449 | |||
450 | local function install_binary(source, target) | ||
451 | if wrap_bin_scripts and fs.is_lua(source) then | ||
452 | return fs.wrap_script(source, target, deps_mode, name, version) | ||
453 | else | ||
454 | return fs.copy_binary(source, target) | ||
455 | end | ||
456 | end | ||
457 | |||
458 | local function move_lua(source, target) | ||
459 | return fs.move(source, target, "read") | ||
460 | end | ||
461 | |||
462 | local function move_lib(source, target) | ||
463 | return fs.move(source, target, "exec") | ||
464 | end | ||
465 | |||
466 | if rock_manifest.bin then | ||
467 | local source_dir = path.bin_dir(name, version) | ||
468 | repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path) | ||
469 | local source = dir.path(source_dir, file_path) | ||
470 | local paths = get_deploy_paths(name, version, "bin", file_path, repo) | ||
471 | local mode, cur_name, cur_version = check_spot_if_available(name, version, "bin", file_path) | ||
472 | |||
473 | if mode == "nv" and cur_name then | ||
474 | local cur_paths = get_deploy_paths(cur_name, cur_version, "bin", file_path, repo) | ||
475 | table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v, suffix = cfg.wrapper_suffix }) | ||
476 | end | ||
477 | local target = mode == "nv" and paths.nv or paths.v | ||
478 | local backup = name ~= cur_name or version ~= cur_version | ||
479 | if wrap_bin_scripts and fs.is_lua(source) then | ||
480 | target = target .. (cfg.wrapper_suffix or "") | ||
481 | end | ||
482 | table.insert(installs, { fn = install_binary, src = source, dst = target, backup = backup }) | ||
483 | end) | ||
484 | end | ||
485 | |||
486 | if rock_manifest.lua then | ||
487 | local source_dir = path.lua_dir(name, version) | ||
488 | repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path) | ||
489 | local source = dir.path(source_dir, file_path) | ||
490 | local paths = get_deploy_paths(name, version, "lua", file_path, repo) | ||
491 | local mode, cur_name, cur_version = check_spot_if_available(name, version, "lua", file_path) | ||
492 | |||
493 | if mode == "nv" and cur_name then | ||
494 | local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path, repo) | ||
495 | table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) | ||
496 | cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path:gsub("%.lua$", "." .. cfg.lib_extension), repo) | ||
497 | table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) | ||
498 | end | ||
499 | local target = mode == "nv" and paths.nv or paths.v | ||
500 | local backup = name ~= cur_name or version ~= cur_version | ||
501 | table.insert(installs, { fn = move_lua, src = source, dst = target, backup = backup }) | ||
502 | end) | ||
503 | end | ||
504 | |||
505 | if rock_manifest.lib then | ||
506 | local source_dir = path.lib_dir(name, version) | ||
507 | repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path) | ||
508 | local source = dir.path(source_dir, file_path) | ||
509 | local paths = get_deploy_paths(name, version, "lib", file_path, repo) | ||
510 | local mode, cur_name, cur_version = check_spot_if_available(name, version, "lib", file_path) | ||
511 | |||
512 | if mode == "nv" and cur_name then | ||
513 | local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo) | ||
514 | table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) | ||
515 | cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path, repo) | ||
516 | table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) | ||
517 | end | ||
518 | local target = mode == "nv" and paths.nv or paths.v | ||
519 | local backup = name ~= cur_name or version ~= cur_version | ||
520 | table.insert(installs, { fn = move_lib, src = source, dst = target, backup = backup }) | ||
521 | end) | ||
522 | end | ||
523 | |||
524 | for i, op in ipairs(renames) do | ||
525 | local ok, err = op_rename(op) | ||
526 | if not ok then | ||
527 | rollback_ops(renames, rollback_rename, i - 1) | ||
528 | return nil, err | ||
529 | end | ||
530 | end | ||
531 | local op_install, done_op_install = prepare_op_install() | ||
532 | for i, op in ipairs(installs) do | ||
533 | local ok, err = op_install(op) | ||
534 | if not ok then | ||
535 | rollback_ops(installs, rollback_install, i - 1) | ||
536 | rollback_ops(renames, rollback_rename, #renames) | ||
537 | return nil, err | ||
538 | end | ||
539 | end | ||
540 | done_op_install() | ||
541 | |||
542 | local ok, err = repos.check_everything_is_installed(name, version, rock_manifest, repo, true) | ||
543 | if not ok then | ||
544 | return nil, err | ||
545 | end | ||
546 | |||
547 | local writer = require("luarocks.manif.writer") | ||
548 | return writer.add_to_manifest(name, version, nil, deps_mode) | ||
549 | end | ||
550 | |||
551 | local function add_to_double_checks(double_checks, name, version) | ||
552 | double_checks[name] = double_checks[name] or {} | ||
553 | double_checks[name][version] = true | ||
554 | end | ||
555 | |||
556 | local function double_check_all(double_checks, repo) | ||
557 | local errs = {} | ||
558 | for next_name, versions in pairs(double_checks) do | ||
559 | for next_version in pairs(versions) do | ||
560 | local rock_manifest, load_err = manif.load_rock_manifest(next_name, next_version) | ||
561 | local ok, err = repos.check_everything_is_installed(next_name, next_version, rock_manifest, repo, true) | ||
562 | if not ok then | ||
563 | table.insert(errs, err) | ||
564 | end | ||
565 | end | ||
566 | end | ||
567 | if next(errs) then | ||
568 | return nil, table.concat(errs, "\n") | ||
569 | end | ||
570 | return true | ||
571 | end | ||
572 | |||
573 | --- Delete a package from the local repository. | ||
574 | -- @param name string: name of package | ||
575 | -- @param version string: exact package version in string format | ||
576 | -- @param deps_mode: string: Which trees to check dependencies for: | ||
577 | -- "one" for the current default tree, "all" for all trees, | ||
578 | -- "order" for all trees with priority >= the current default, "none" for no trees. | ||
579 | -- @param quick boolean: do not try to fix the versioned name | ||
580 | -- of another version that provides the same module that | ||
581 | -- was deleted. This is used during 'purge', as every module | ||
582 | -- will be eventually deleted. | ||
583 | function repos.delete_version(name, version, deps_mode, quick) | ||
584 | assert(type(name) == "string" and not name:match("/")) | ||
585 | assert(type(version) == "string") | ||
586 | assert(type(deps_mode) == "string") | ||
587 | |||
588 | local rock_manifest, load_err = manif.load_rock_manifest(name, version) | ||
589 | if not rock_manifest then | ||
590 | if not quick then | ||
591 | local writer = require("luarocks.manif.writer") | ||
592 | writer.remove_from_manifest(name, version, nil, deps_mode) | ||
593 | return nil, "rock_manifest file not found for "..name.." "..version.." - removed entry from the manifest" | ||
594 | end | ||
595 | return nil, load_err | ||
596 | end | ||
597 | |||
598 | local repo = cfg.root_dir | ||
599 | local renames = {} | ||
600 | local deletes = {} | ||
601 | |||
602 | local double_checks = {} | ||
603 | |||
604 | if rock_manifest.bin then | ||
605 | repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path) | ||
606 | local paths = get_deploy_paths(name, version, "bin", file_path, repo) | ||
607 | local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "bin", file_path) | ||
608 | if mode == "v" then | ||
609 | table.insert(deletes, { name = paths.v, suffix = cfg.wrapper_suffix }) | ||
610 | else | ||
611 | table.insert(deletes, { name = paths.nv, suffix = cfg.wrapper_suffix }) | ||
612 | |||
613 | local next_name, next_version = manif.get_next_provider("command", item_name) | ||
614 | if next_name then | ||
615 | add_to_double_checks(double_checks, next_name, next_version) | ||
616 | local next_paths = get_deploy_paths(next_name, next_version, "bin", file_path, repo) | ||
617 | table.insert(renames, { src = next_paths.v, dst = next_paths.nv, suffix = cfg.wrapper_suffix }) | ||
618 | end | ||
619 | end | ||
620 | end) | ||
621 | end | ||
622 | |||
623 | if rock_manifest.lua then | ||
624 | repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path) | ||
625 | local paths = get_deploy_paths(name, version, "lua", file_path, repo) | ||
626 | local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lua", file_path) | ||
627 | if mode == "v" then | ||
628 | table.insert(deletes, { name = paths.v }) | ||
629 | else | ||
630 | table.insert(deletes, { name = paths.nv }) | ||
631 | |||
632 | local next_name, next_version = manif.get_next_provider("module", item_name) | ||
633 | if next_name then | ||
634 | add_to_double_checks(double_checks, next_name, next_version) | ||
635 | local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path, repo) | ||
636 | table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv }) | ||
637 | local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path:gsub("%.[^.]+$", ".lua"), repo) | ||
638 | table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv }) | ||
639 | end | ||
640 | end | ||
641 | end) | ||
642 | end | ||
643 | |||
644 | if rock_manifest.lib then | ||
645 | repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path) | ||
646 | local paths = get_deploy_paths(name, version, "lib", file_path, repo) | ||
647 | local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lib", file_path) | ||
648 | if mode == "v" then | ||
649 | table.insert(deletes, { name = paths.v }) | ||
650 | else | ||
651 | table.insert(deletes, { name = paths.nv }) | ||
652 | |||
653 | local next_name, next_version = manif.get_next_provider("module", item_name) | ||
654 | if next_name then | ||
655 | add_to_double_checks(double_checks, next_name, next_version) | ||
656 | local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo) | ||
657 | table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv }) | ||
658 | local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path, repo) | ||
659 | table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv }) | ||
660 | end | ||
661 | end | ||
662 | end) | ||
663 | end | ||
664 | |||
665 | local op_delete, done_op_delete = prepare_op_delete() | ||
666 | for _, op in ipairs(deletes) do | ||
667 | op_delete(op) | ||
668 | end | ||
669 | done_op_delete() | ||
670 | |||
671 | if not quick then | ||
672 | for _, op in ipairs(renames) do | ||
673 | op_rename(op) | ||
674 | end | ||
675 | |||
676 | local ok, err = double_check_all(double_checks, repo) | ||
677 | if not ok then | ||
678 | return nil, err | ||
679 | end | ||
680 | end | ||
681 | |||
682 | fs.delete(path.install_dir(name, version)) | ||
683 | if not get_installed_versions(name) then | ||
684 | fs.delete(dir.path(cfg.rocks_dir, name)) | ||
685 | end | ||
686 | |||
687 | if quick then | ||
688 | return true | ||
689 | end | ||
690 | |||
691 | local writer = require("luarocks.manif.writer") | ||
692 | return writer.remove_from_manifest(name, version, nil, deps_mode) | ||
693 | end | ||
694 | |||
695 | return repos | ||
diff --git a/src/luarocks/test.tl b/src/luarocks/test.tl index 56bc8c74..b1a97495 100644 --- a/src/luarocks/test.tl +++ b/src/luarocks/test.tl | |||
@@ -17,7 +17,12 @@ local test_types = { | |||
17 | local test_modules: {string | Test} = {} | 17 | local test_modules: {string | Test} = {} |
18 | 18 | ||
19 | for _, test_type in ipairs(test_types) do | 19 | for _, test_type in ipairs(test_types) do |
20 | local mod: Test = require("luarocks.test." .. test_type) | 20 | local mod: Test |
21 | if test_type == "command" then | ||
22 | mod = require("luarocks.test.command") | ||
23 | elseif test_type == "busted" then | ||
24 | mod = require("luarocks.test.busted") | ||
25 | end | ||
21 | table.insert(test_modules, mod) | 26 | table.insert(test_modules, mod) |
22 | test_modules[test_type] = mod --! | 27 | test_modules[test_type] = mod --! |
23 | test_modules[mod] = test_type | 28 | test_modules[mod] = test_type |
@@ -65,7 +70,7 @@ function test.run_test_suite(rockspec_arg: string | Rockspec, test_type: string, | |||
65 | "test_dependencies", | 70 | "test_dependencies", |
66 | } | 71 | } |
67 | for _, dep_kind in ipairs(all_deps) do | 72 | for _, dep_kind in ipairs(all_deps) do |
68 | if rockspec[dep_kind] and next(rockspec[dep_kind]) then | 73 | if rockspec[dep_kind] and next(rockspec[dep_kind]) then --! rockspec as {atring: any} |
69 | local _, err, errcode = deps.fulfill_dependencies(rockspec, dep_kind, "all") | 74 | local _, err, errcode = deps.fulfill_dependencies(rockspec, dep_kind, "all") |
70 | if err then | 75 | if err then |
71 | return nil, err, errcode | 76 | return nil, err, errcode |
@@ -73,10 +78,17 @@ function test.run_test_suite(rockspec_arg: string | Rockspec, test_type: string, | |||
73 | end | 78 | end |
74 | end | 79 | end |
75 | 80 | ||
76 | local mod_name = "luarocks.test." .. test_type | 81 | local pok, test_mod: boolean --! Test type |
77 | local pok, test_mod = pcall(require, mod_name) | 82 | if test_type == "command" then |
78 | if not pok then | 83 | pok, test_mod = pcall(require, "luarocks.test.command") |
79 | return nil, "failed loading test execution module " .. mod_name | 84 | if not pok then |
85 | return nil, "failed loading test execution module luarocks.test.command" | ||
86 | end | ||
87 | elseif test_type == "busted" then | ||
88 | pok, test_mod = pcall(require, "luarocks.test.busted") | ||
89 | if not pok then | ||
90 | return nil, "failed loading test execution module luarocks.test.busted" | ||
91 | end | ||
80 | end | 92 | end |
81 | 93 | ||
82 | if prepare then | 94 | if prepare then |
diff --git a/src/luarocks/test/busted.tl b/src/luarocks/test/busted.tl index e2663ffa..0870d178 100644 --- a/src/luarocks/test/busted.tl +++ b/src/luarocks/test/busted.tl | |||
@@ -15,7 +15,7 @@ function busted.detect_type(): boolean | |||
15 | return false | 15 | return false |
16 | end | 16 | end |
17 | 17 | ||
18 | function busted.run_tests(test, args: {string}): boolean, string | 18 | function busted.run_tests(test, args: {string}): boolean, string --! Test type |
19 | if not test then | 19 | if not test then |
20 | test = {} | 20 | test = {} |
21 | end | 21 | end |
diff --git a/src/luarocks/test/command.tl b/src/luarocks/test/command.tl index bed6744e..d7781580 100644 --- a/src/luarocks/test/command.tl +++ b/src/luarocks/test/command.tl | |||
@@ -1,19 +1,18 @@ | |||
1 | 1 | ||
2 | local command = {} | 2 | local record command |
3 | end | ||
3 | 4 | ||
4 | local fs = require("luarocks.fs") | 5 | local fs = require("luarocks.fs") |
5 | local cfg = require("luarocks.core.cfg") | 6 | local cfg = require("luarocks.core.cfg") |
6 | 7 | ||
7 | local unpack = table.unpack or unpack | 8 | function command.detect_type(): boolean |
8 | |||
9 | function command.detect_type() | ||
10 | if fs.exists("test.lua") then | 9 | if fs.exists("test.lua") then |
11 | return true | 10 | return true |
12 | end | 11 | end |
13 | return false | 12 | return false |
14 | end | 13 | end |
15 | 14 | ||
16 | function command.run_tests(test, args) | 15 | function command.run_tests(test, args): boolean, string --! Test type |
17 | if not test then | 16 | if not test then |
18 | test = { | 17 | test = { |
19 | script = "test.lua" | 18 | script = "test.lua" |
@@ -24,7 +23,7 @@ function command.run_tests(test, args) | |||
24 | test.script = "test.lua" | 23 | test.script = "test.lua" |
25 | end | 24 | end |
26 | 25 | ||
27 | local ok | 26 | local ok: boolean |
28 | 27 | ||
29 | if test.script then | 28 | if test.script then |
30 | if type(test.script) ~= "string" then | 29 | if type(test.script) ~= "string" then |
@@ -34,12 +33,12 @@ function command.run_tests(test, args) | |||
34 | return nil, "Test script " .. test.script .. " does not exist" | 33 | return nil, "Test script " .. test.script .. " does not exist" |
35 | end | 34 | end |
36 | local lua = fs.Q(cfg.variables["LUA"]) -- get lua interpreter configured | 35 | local lua = fs.Q(cfg.variables["LUA"]) -- get lua interpreter configured |
37 | ok = fs.execute(lua, test.script, unpack(args)) | 36 | ok = fs.execute(lua, test.script, table.unpack(args)) |
38 | elseif test.command then | 37 | elseif test.command then |
39 | if type(test.command) ~= "string" then | 38 | if type(test.command) ~= "string" then |
40 | return nil, "Malformed rockspec: 'command' expects a string" | 39 | return nil, "Malformed rockspec: 'command' expects a string" |
41 | end | 40 | end |
42 | ok = fs.execute(test.command, unpack(args)) | 41 | ok = fs.execute(test.command, table.unpack(args)) |
43 | end | 42 | end |
44 | 43 | ||
45 | if ok then | 44 | if ok then |