aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorV1K1NGbg <victor@ilchev.com>2024-08-04 20:32:46 +0300
committerV1K1NGbg <victor@ilchev.com>2024-08-05 20:51:31 +0300
commita6cf146b49aa70e13bf00c2c06e2dd805fb209f4 (patch)
treeab18c1a29c83b53ee2c3f74b4b6d302443305e6e /src
parent0bc3de49cad9697e101535c1b83c86a55b3731b4 (diff)
downloadluarocks-a6cf146b49aa70e13bf00c2c06e2dd805fb209f4.tar.gz
luarocks-a6cf146b49aa70e13bf00c2c06e2dd805fb209f4.tar.bz2
luarocks-a6cf146b49aa70e13bf00c2c06e2dd805fb209f4.zip
almost finished rockspecs and started fetch
Diffstat (limited to 'src')
-rw-r--r--src/luarocks/core/cfg.d.tl123
-rw-r--r--src/luarocks/fetch.tl610
-rw-r--r--src/luarocks/fs.d.tl3
-rw-r--r--src/luarocks/path.tl10
-rw-r--r--src/luarocks/rockspecs.tl186
5 files changed, 908 insertions, 24 deletions
diff --git a/src/luarocks/core/cfg.d.tl b/src/luarocks/core/cfg.d.tl
index 26aec8d9..722f1020 100644
--- a/src/luarocks/core/cfg.d.tl
+++ b/src/luarocks/core/cfg.d.tl
@@ -8,6 +8,7 @@ local record cfg
8 rocks_subdir: string 8 rocks_subdir: string
9 lua_modules_path: string 9 lua_modules_path: string
10 lib_modules_path: string 10 lib_modules_path: string
11 aggressive_cache: boolean
11 rocks_trees: {string| Tree} 12 rocks_trees: {string| Tree}
12 lua_version: string 13 lua_version: string
13 deps_mode: string 14 deps_mode: string
@@ -15,6 +16,7 @@ local record cfg
15 deploy_lua_dir: string 16 deploy_lua_dir: string
16 deploy_lib_dir: string 17 deploy_lib_dir: string
17 lib_extension: string 18 lib_extension: string
19 local_cache: string
18 record Tree 20 record Tree
19 root: string 21 root: string
20 rocks_dir: string 22 rocks_dir: string
@@ -29,30 +31,116 @@ local record cfg
29 issues_url: string 31 issues_url: string
30 maintainer: string 32 maintainer: string
31 license: string 33 license: string
34 labels: any --!
35 end
36
37 record Variables
38 CVS: string
39 LUA: string
40 GPG: string
41 CURL: string
42 PREFIX: string
43 LUADIR: string
44 LIBDIR: string
45 CONFDIR: string
46 BINDIR: string
47 DOCDIR: string
48 CURLNOCERTFLAG: string
49 end
50
51 record Source
52 url: string
53 module: string
54 pathname: string
55 tag: string
56 md5: string
57 file: string
58 dir: string
59 branch: string
60 cvs_tag: string
61 cvs_module: string
62 protocol: string --! not in the rockspec definition but used
63 dir_set: boolean
64 platforms: any --!
32 end 65 end
33 66
34 record Test 67 record Test
35 type: string 68 type: string
36 platforms: {{string: any}} 69 platforms: any --! {{string: any}}?
70 end
71
72 record Install
73 lua: any
74 lib: any
75 conf: any
76 bin: any
77 end
78
79 record Build
80 type: string
81 modules: string
82 copy_directories: string
83 platforms: any --!
84 install: Install
85 end
86
87 record Dependencie --!1 --? Query??
88
89 end
90
91 record Dependencies
92 {Dependencie} --!1
93 platforms: any --!
94 end
95
96 record BuildDependencies
97 {Dependencie} --!1
98 platforms: any --!
99 end
100
101 record SupportedPlatforms
102 end
103
104 record ExternalDependencies
105 {Dependencie} --!1
106 platforms: any --!
107 end
108
109 record TestDependencies
110 {Dependencie} --!1
111 platforms: any --!
112 end
113
114 record Hooks
115 post_install: string
116 platforms: any --!
37 end 117 end
38 118
39 record Rockspec --? luarocks-dev-1.rockspec 119 record Deploy
120 wrap_bin_scripts: boolean
121 end
122
123 record Rockspec
40 rockspec_format: string 124 rockspec_format: string
41 name: string --? package 125 name: string --! not in the rockspec definition but used
126 package: string
42 version: string 127 version: string
43 record source 128 local_abs_filename: string
44 url: string 129 rocks_provided: {string : string}
45 module: string 130 source: Source
46 pathname: string
47 tag: string
48 end
49 description: Description 131 description: Description
50 test_dependencies: {string} 132 build: Build
133 dependencies: Dependencies
134 build_dependencies: BuildDependencies
135 test_dependencies: TestDependencies
136 supported_platforms: SupportedPlatforms
137 external_dependencies: ExternalDependencies
138 variables: Variables
139 hooks: Hooks
51 test: Test 140 test: Test
141 deploy: Deploy
142 type: function(): string
52 format_is_at_least: function(Rockspec, string): boolean 143 format_is_at_least: function(Rockspec, string): boolean
53 record variables
54 CVS: string
55 end
56 end 144 end
57 145
58 record cache 146 record cache
@@ -61,12 +149,7 @@ local record cfg
61 rocks_provided: {string: string} --? right type? infered from src/luarocks/util 149 rocks_provided: {string: string} --? right type? infered from src/luarocks/util
62 end 150 end
63 151
64 record variables 152 variables: Variables
65 LUA: string
66 GPG: string
67 CURL: string
68 CURLNOCERTFLAG: string
69 end
70 rocks_provided: {Rockspec} 153 rocks_provided: {Rockspec}
71 -- persist 154 -- persist
72 home: string 155 home: string
@@ -92,6 +175,8 @@ local record cfg
92 -- loader 175 -- loader
93 init: function(): boolean, string, string 176 init: function(): boolean, string, string
94 init_package_paths: function 177 init_package_paths: function
178 -- rockspecs
179 each_platform: function(?string): (function():string)
95end 180end
96 181
97return cfg \ No newline at end of file 182return cfg \ No newline at end of file
diff --git a/src/luarocks/fetch.tl b/src/luarocks/fetch.tl
new file mode 100644
index 00000000..798fc3b7
--- /dev/null
+++ b/src/luarocks/fetch.tl
@@ -0,0 +1,610 @@
1
2--- Functions related to fetching and loading local and remote files.
3local fetch = {}
4
5local fs = require("luarocks.fs")
6local dir = require("luarocks.dir")
7local rockspecs = require("luarocks.rockspecs")
8local signing = require("luarocks.signing")
9local persist = require("luarocks.persist")
10local util = require("luarocks.util")
11local cfg = require("luarocks.core.cfg")
12
13
14--- Fetch a local or remote file, using a local cache directory.
15-- Make a remote or local URL/pathname local, fetching the file if necessary.
16-- Other "fetch" and "load" functions use this function to obtain files.
17-- If a local pathname is given, it is returned as a result.
18-- @param url string: a local pathname or a remote URL.
19-- @param mirroring string: mirroring mode.
20-- If set to "no_mirror", then rocks_servers mirror configuration is not used.
21-- @return (string, nil, nil, boolean) or (nil, string, [string]):
22-- in case of success:
23-- * the absolute local pathname for the fetched file
24-- * nil
25-- * nil
26-- * `true` if the file was fetched from cache
27-- in case of failure:
28-- * nil
29-- * an error message
30-- * an optional error code.
31function fetch.fetch_caching(url: string, mirroring: string): string, string, string, boolean
32 local repo_url, filename = url:match("^(.*)/([^/]+)$")
33 local name = repo_url:gsub("[/:]","_")
34 local cache_dir = dir.path(cfg.local_cache, name)
35 local ok = fs.make_dir(cache_dir)
36
37 local cachefile = dir.path(cache_dir, filename)
38 local checkfile = cachefile .. ".check"
39
40 if (fs.file_age(checkfile) < 10 or
41 cfg.aggressive_cache and (not name:match("^manifest"))) and fs.exists(cachefile)
42 then
43 return cachefile, nil, nil, true
44 end
45
46 local lock, errlock
47 if ok then
48 lock, errlock = fs.lock_access(cache_dir)
49 end
50
51 if not (ok and lock) then
52 cfg.local_cache = fs.make_temp_dir("local_cache")
53 if not cfg.local_cache then
54 return nil, "Failed creating temporary local_cache directory"
55 end
56 cache_dir = dir.path(cfg.local_cache, name)
57 ok = fs.make_dir(cache_dir)
58 if not ok then
59 return nil, "Failed creating temporary cache directory "..cache_dir
60 end
61 lock = fs.lock_access(cache_dir)
62 end
63
64 local file, err, errcode, from_cache = fetch.fetch_url(url, cachefile, true, mirroring)
65 if not file then
66 fs.unlock_access(lock)
67 return nil, err or "Failed downloading "..url, errcode
68 end
69
70 local fd, err = io.open(checkfile, "wb")
71 if err then
72 fs.unlock_access(lock)
73 return nil, err
74 end
75 fd:write("!")
76 fd:close()
77
78 fs.unlock_access(lock)
79 return file, nil, nil, from_cache
80end
81
82local function ensure_trailing_slash(url)
83 return (url:gsub("/*$", "/"))
84end
85
86local function is_url_relative_to_rocks_servers(url, servers)
87 for _, item in ipairs(servers) do
88 if type(item) == "table" then
89 for i, s in ipairs(item) do
90 local base = ensure_trailing_slash(s)
91 if string.find(url, base, 1, true) == 1 then
92 return i, url:sub(#base + 1), item
93 end
94 end
95 end
96 end
97end
98
99local function download_with_mirrors(url, filename, cache, servers)
100 local idx, rest, mirrors = is_url_relative_to_rocks_servers(url, servers)
101
102 if not idx then
103 -- URL is not from a rock server
104 return fs.download(url, filename, cache)
105 end
106
107 -- URL is from a rock server: try to download it falling back to mirrors.
108 local err = "\n"
109 for i = idx, #mirrors do
110 local try_url = ensure_trailing_slash(mirrors[i]) .. rest
111 if i > idx then
112 util.warning("Failed downloading. Attempting mirror at " .. try_url)
113 end
114 local ok, name, from_cache = fs.download(try_url, filename, cache)
115 if ok then
116 return ok, name, from_cache
117 else
118 err = err .. name .. "\n"
119 end
120 end
121
122 return nil, err, "network"
123end
124
125--- Fetch a local or remote file.
126-- Make a remote or local URL/pathname local, fetching the file if necessary.
127-- Other "fetch" and "load" functions use this function to obtain files.
128-- If a local pathname is given, it is returned as a result.
129-- @param url string: a local pathname or a remote URL.
130-- @param filename string or nil: this function attempts to detect the
131-- resulting local filename of the remote file as the basename of the URL;
132-- if that is not correct (due to a redirection, for example), the local
133-- filename can be given explicitly as this second argument.
134-- @param cache boolean: compare remote timestamps via HTTP HEAD prior to
135-- re-downloading the file.
136-- @param mirroring string: mirroring mode.
137-- If set to "no_mirror", then rocks_servers mirror configuration is not used.
138-- @return (string, nil, nil, boolean) or (nil, string, [string]):
139-- in case of success:
140-- * the absolute local pathname for the fetched file
141-- * nil
142-- * nil
143-- * `true` if the file was fetched from cache
144-- in case of failure:
145-- * nil
146-- * an error message
147-- * an optional error code.
148function fetch.fetch_url(url, filename, cache, mirroring)
149 assert(type(url) == "string")
150 assert(type(filename) == "string" or not filename)
151
152 local protocol, pathname = dir.split_url(url)
153 if protocol == "file" then
154 local fullname = fs.absolute_name(pathname)
155 if not fs.exists(fullname) then
156 local hint = (not pathname:match("^/"))
157 and (" - note that given path in rockspec is not absolute: " .. url)
158 or ""
159 return nil, "Local file not found: " .. fullname .. hint
160 end
161 filename = filename or dir.base_name(pathname)
162 local dstname = fs.absolute_name(dir.path(".", filename))
163 local ok, err
164 if fullname == dstname then
165 ok = true
166 else
167 ok, err = fs.copy(fullname, dstname)
168 end
169 if ok then
170 return dstname
171 else
172 return nil, "Failed copying local file " .. fullname .. " to " .. dstname .. ": " .. err
173 end
174 elseif dir.is_basic_protocol(protocol) then
175 local ok, name, from_cache
176 if mirroring ~= "no_mirror" then
177 ok, name, from_cache = download_with_mirrors(url, filename, cache, cfg.rocks_servers)
178 else
179 ok, name, from_cache = fs.download(url, filename, cache)
180 end
181 if not ok then
182 return nil, "Failed downloading "..url..(name and " - "..name or ""), from_cache
183 end
184 return name, nil, nil, from_cache
185 else
186 return nil, "Unsupported protocol "..protocol
187 end
188end
189
190--- For remote URLs, create a temporary directory and download URL inside it.
191-- This temporary directory will be deleted on program termination.
192-- For local URLs, just return the local pathname and its directory.
193-- @param url string: URL to be downloaded
194-- @param tmpname string: name pattern to use for avoiding conflicts
195-- when creating temporary directory.
196-- @param filename string or nil: local filename of URL to be downloaded,
197-- in case it can't be inferred from the URL.
198-- @return (string, string) or (nil, string, [string]): absolute local pathname of
199-- the fetched file and temporary directory name; or nil and an error message
200-- followed by an optional error code
201function fetch.fetch_url_at_temp_dir(url, tmpname, filename, cache)
202 assert(type(url) == "string")
203 assert(type(tmpname) == "string")
204 assert(type(filename) == "string" or not filename)
205 filename = filename or dir.base_name(url)
206
207 local protocol, pathname = dir.split_url(url)
208 if protocol == "file" then
209 if fs.exists(pathname) then
210 return pathname, dir.dir_name(fs.absolute_name(pathname))
211 else
212 return nil, "File not found: "..pathname
213 end
214 else
215 local temp_dir, err = fs.make_temp_dir(tmpname)
216 if not temp_dir then
217 return nil, "Failed creating temporary directory "..tmpname..": "..err
218 end
219 util.schedule_function(fs.delete, temp_dir)
220 local ok, err = fs.change_dir(temp_dir)
221 if not ok then return nil, err end
222
223 local file, err, errcode
224
225 if cache then
226 local cachefile
227 cachefile, err, errcode = fetch.fetch_caching(url)
228
229 if cachefile then
230 file = dir.path(temp_dir, filename)
231 fs.copy(cachefile, file)
232 end
233 end
234
235 if not file then
236 file, err, errcode = fetch.fetch_url(url, filename, cache)
237 end
238
239 fs.pop_dir()
240 if not file then
241 return nil, "Error fetching file: "..err, errcode
242 end
243
244 return file, temp_dir
245 end
246end
247
248-- Determine base directory of a fetched URL by extracting its
249-- archive and looking for a directory in the root.
250-- @param file string: absolute local pathname of the fetched file
251-- @param temp_dir string: temporary directory in which URL was fetched.
252-- @param src_url string: URL to use when inferring base directory.
253-- @param src_dir string or nil: expected base directory (inferred
254-- from src_url if not given).
255-- @return (string, string) or (string, nil) or (nil, string):
256-- The inferred base directory and the one actually found (which may
257-- be nil if not found), or nil followed by an error message.
258-- The inferred dir is returned first to avoid confusion with errors,
259-- because it is never nil.
260function fetch.find_base_dir(file, temp_dir, src_url, src_dir)
261 local ok, err = fs.change_dir(temp_dir)
262 if not ok then return nil, err end
263 fs.unpack_archive(file)
264
265 if not src_dir then
266 local rockspec = {
267 source = {
268 file = file,
269 dir = src_dir,
270 url = src_url,
271 }
272 }
273 ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
274 if ok then
275 src_dir = rockspec.source.dir
276 end
277 end
278
279 local inferred_dir = src_dir or dir.deduce_base_dir(src_url)
280 local found_dir = nil
281 if fs.exists(inferred_dir) then
282 found_dir = inferred_dir
283 else
284 util.printerr("Directory "..inferred_dir.." not found")
285 local files = fs.list_dir()
286 if files then
287 table.sort(files)
288 for i,filename in ipairs(files) do
289 if fs.is_dir(filename) then
290 util.printerr("Found "..filename)
291 found_dir = filename
292 break
293 end
294 end
295 end
296 end
297 fs.pop_dir()
298 return inferred_dir, found_dir
299end
300
301local function fetch_and_verify_signature_for(url, filename, tmpdir)
302 local sig_url = signing.signature_url(url)
303 local sig_file, err, errcode = fetch.fetch_url_at_temp_dir(sig_url, tmpdir)
304 if not sig_file then
305 return nil, "Could not fetch signature file for verification: " .. err, errcode
306 end
307
308 local ok, err = signing.verify_signature(filename, sig_file)
309 if not ok then
310 return nil, "Failed signature verification: " .. err
311 end
312
313 return fs.absolute_name(sig_file)
314end
315
316--- Obtain a rock and unpack it.
317-- If a directory is not given, a temporary directory will be created,
318-- which will be deleted on program termination.
319-- @param rock_file string: URL or filename of the rock.
320-- @param dest string or nil: if given, directory will be used as
321-- a permanent destination.
322-- @param verify boolean: if true, download and verify signature for rockspec
323-- @return string or (nil, string, [string]): the directory containing the contents
324-- of the unpacked rock.
325function fetch.fetch_and_unpack_rock(url, dest, verify)
326 assert(type(url) == "string")
327 assert(type(dest) == "string" or not dest)
328
329 local name = dir.base_name(url):match("(.*)%.[^.]*%.rock")
330 local tmpname = "luarocks-rock-" .. name
331
332 local rock_file, err, errcode = fetch.fetch_url_at_temp_dir(url, tmpname, nil, true)
333 if not rock_file then
334 return nil, "Could not fetch rock file: " .. err, errcode
335 end
336
337 local sig_file
338 if verify then
339 sig_file, err = fetch_and_verify_signature_for(url, rock_file, tmpname)
340 if err then
341 return nil, err
342 end
343 end
344
345 rock_file = fs.absolute_name(rock_file)
346
347 local unpack_dir
348 if dest then
349 unpack_dir = dest
350 local ok, err = fs.make_dir(unpack_dir)
351 if not ok then
352 return nil, "Failed unpacking rock file: " .. err
353 end
354 else
355 unpack_dir, err = fs.make_temp_dir(name)
356 if not unpack_dir then
357 return nil, "Failed creating temporary dir: " .. err
358 end
359 end
360 if not dest then
361 util.schedule_function(fs.delete, unpack_dir)
362 end
363 local ok, err = fs.change_dir(unpack_dir)
364 if not ok then return nil, err end
365 ok, err = fs.unzip(rock_file)
366 if not ok then
367 return nil, "Failed unpacking rock file: " .. rock_file .. ": " .. err
368 end
369 if sig_file then
370 ok, err = fs.copy(sig_file, ".")
371 if not ok then
372 return nil, "Failed copying signature file"
373 end
374 end
375 fs.pop_dir()
376 return unpack_dir
377end
378
379--- Back-end function that actually loads the local rockspec.
380-- Performs some validation and postprocessing of the rockspec contents.
381-- @param rel_filename string: The local filename of the rockspec file.
382-- @param quick boolean: if true, skips some steps when loading
383-- rockspec.
384-- @return table or (nil, string): A table representing the rockspec
385-- or nil followed by an error message.
386function fetch.load_local_rockspec(rel_filename, quick)
387 assert(type(rel_filename) == "string")
388 local abs_filename = fs.absolute_name(rel_filename)
389
390 local basename = dir.base_name(abs_filename)
391 if basename ~= "rockspec" then
392 if not basename:match("(.*)%-[^-]*%-[0-9]*") then
393 return nil, "Expected filename in format 'name-version-revision.rockspec'."
394 end
395 end
396
397 local tbl, err = persist.load_into_table(abs_filename)
398 if not tbl then
399 return nil, "Could not load rockspec file "..abs_filename.." ("..err..")"
400 end
401
402 local rockspec, err = rockspecs.from_persisted_table(abs_filename, tbl, err, quick)
403 if not rockspec then
404 return nil, abs_filename .. ": " .. err
405 end
406
407 local name_version = rockspec.package:lower() .. "-" .. rockspec.version
408 if basename ~= "rockspec" and basename ~= name_version .. ".rockspec" then
409 return nil, "Inconsistency between rockspec filename ("..basename..") and its contents ("..name_version..".rockspec)."
410 end
411
412 return rockspec
413end
414
415--- Load a local or remote rockspec into a table.
416-- This is the entry point for the LuaRocks tools.
417-- Only the LuaRocks runtime loader should use
418-- load_local_rockspec directly.
419-- @param filename string: Local or remote filename of a rockspec.
420-- @param location string or nil: Where to download. If not given,
421-- a temporary dir is created.
422-- @param verify boolean: if true, download and verify signature for rockspec
423-- @return table or (nil, string, [string]): A table representing the rockspec
424-- or nil followed by an error message and optional error code.
425function fetch.load_rockspec(url, location, verify)
426 assert(type(url) == "string")
427
428 local name
429 local basename = dir.base_name(url)
430 if basename == "rockspec" then
431 name = "rockspec"
432 else
433 name = basename:match("(.*)%.rockspec")
434 if not name then
435 return nil, "Filename '"..url.."' does not look like a rockspec."
436 end
437 end
438
439 local tmpname = "luarocks-rockspec-"..name
440 local filename, err, errcode
441 if location then
442 local ok, err = fs.change_dir(location)
443 if not ok then return nil, err end
444 filename, err = fetch.fetch_url(url)
445 fs.pop_dir()
446 else
447 filename, err, errcode = fetch.fetch_url_at_temp_dir(url, tmpname, nil, true)
448 end
449 if not filename then
450 return nil, err, errcode
451 end
452
453 if verify then
454 local _, err = fetch_and_verify_signature_for(url, filename, tmpname)
455 if err then
456 return nil, err
457 end
458 end
459
460 return fetch.load_local_rockspec(filename)
461end
462
463--- Download sources for building a rock using the basic URL downloader.
464-- @param rockspec table: The rockspec table
465-- @param extract boolean: Whether to extract the sources from
466-- the fetched source tarball or not.
467-- @param dest_dir string or nil: If set, will extract to the given directory;
468-- if not given, will extract to a temporary directory.
469-- @return (string, string) or (nil, string, [string]): The absolute pathname of
470-- the fetched source tarball and the temporary directory created to
471-- store it; or nil and an error message and optional error code.
472function fetch.get_sources(rockspec, extract, dest_dir)
473 assert(rockspec:type() == "rockspec")
474 assert(type(extract) == "boolean")
475 assert(type(dest_dir) == "string" or not dest_dir)
476
477 local url = rockspec.source.url
478 local name = rockspec.name.."-"..rockspec.version
479 local filename = rockspec.source.file
480 local source_file, store_dir
481 local ok, err, errcode
482 if dest_dir then
483 ok, err = fs.change_dir(dest_dir)
484 if not ok then return nil, err, "dest_dir" end
485 source_file, err, errcode = fetch.fetch_url(url, filename)
486 fs.pop_dir()
487 store_dir = dest_dir
488 else
489 source_file, store_dir, errcode = fetch.fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
490 end
491 if not source_file then
492 return nil, err or store_dir, errcode
493 end
494 if rockspec.source.md5 then
495 if not fs.check_md5(source_file, rockspec.source.md5) then
496 return nil, "MD5 check for "..filename.." has failed.", "md5"
497 end
498 end
499 if extract then
500 local ok, err = fs.change_dir(store_dir)
501 if not ok then return nil, err end
502 ok, err = fs.unpack_archive(rockspec.source.file)
503 if not ok then return nil, err end
504 ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
505 if not ok then return nil, err end
506 fs.pop_dir()
507 end
508 return source_file, store_dir
509end
510
511function fetch.find_rockspec_source_dir(rockspec, store_dir)
512 local ok, err = fs.change_dir(store_dir)
513 if not ok then return nil, err end
514
515 local file_count, dir_count, found_dir = 0, 0, 0
516
517 if rockspec.source.dir and fs.exists(rockspec.source.dir) then
518 ok, err = true, nil
519 elseif rockspec.source.file and rockspec.source.dir then
520 ok, err = nil, "Directory "..rockspec.source.dir.." not found inside archive "..rockspec.source.file
521 elseif not rockspec.source.dir_set then -- and rockspec:format_is_at_least("3.0") then
522
523 local name = dir.base_name(rockspec.source.file or rockspec.source.url or "")
524
525 if name:match("%.lua$") or name:match("%.c$") then
526 if fs.is_file(name) then
527 rockspec.source.dir = "."
528 ok, err = true, nil
529 end
530 end
531
532 if not rockspec.source.dir then
533 for file in fs.dir() do
534 file_count = file_count + 1
535 if fs.is_dir(file) then
536 dir_count = dir_count + 1
537 found_dir = file
538 end
539 end
540
541 if dir_count == 1 then
542 rockspec.source.dir = found_dir
543 ok, err = true, nil
544 else
545 ok, err = nil, "Could not determine source directory from rock contents (" .. tostring(file_count).." file(s), "..tostring(dir_count).." dir(s))"
546 end
547 end
548 else
549 ok, err = nil, "Could not determine source directory, please set source.dir in rockspec."
550 end
551
552 fs.pop_dir()
553
554 assert(rockspec.source.dir or not ok)
555 return ok, err
556end
557
558--- Download sources for building a rock, calling the appropriate protocol method.
559-- @param rockspec table: The rockspec table
560-- @param extract boolean: When downloading compressed formats, whether to extract
561-- the sources from the fetched archive or not.
562-- @param dest_dir string or nil: If set, will extract to the given directory.
563-- if not given, will extract to a temporary directory.
564-- @return (string, string) or (nil, string): The absolute pathname of
565-- the fetched source tarball and the temporary directory created to
566-- store it; or nil and an error message.
567function fetch.fetch_sources(rockspec, extract, dest_dir)
568 assert(rockspec:type() == "rockspec")
569 assert(type(extract) == "boolean")
570 assert(type(dest_dir) == "string" or not dest_dir)
571
572 -- auto-convert git://github.com URLs to use git+https
573 -- see https://github.blog/2021-09-01-improving-git-protocol-security-github/
574 if rockspec.source.url:match("^git://github%.com/")
575 or rockspec.source.url:match("^git://www%.github%.com/") then
576 rockspec.source.url = rockspec.source.url:gsub("^git://", "git+https://")
577 rockspec.source.protocol = "git+https"
578 end
579
580 local protocol = rockspec.source.protocol
581 local ok, err, proto
582 if dir.is_basic_protocol(protocol) then
583 proto = fetch
584 else
585 ok, proto = pcall(require, "luarocks.fetch."..protocol:gsub("[+-]", "_"))
586 if not ok then
587 return nil, "Unknown protocol "..protocol
588 end
589 end
590
591 if cfg.only_sources_from
592 and rockspec.source.pathname
593 and #rockspec.source.pathname > 0 then
594 if #cfg.only_sources_from == 0 then
595 return nil, "Can't download "..rockspec.source.url.." -- download from remote servers disabled"
596 elseif rockspec.source.pathname:find(cfg.only_sources_from, 1, true) ~= 1 then
597 return nil, "Can't download "..rockspec.source.url.." -- only downloading from "..cfg.only_sources_from
598 end
599 end
600
601 local source_file, store_dir = proto.get_sources(rockspec, extract, dest_dir)
602 if not source_file then return nil, store_dir end
603
604 ok, err = fetch.find_rockspec_source_dir(rockspec, store_dir)
605 if not ok then return nil, err, "source.dir", source_file, store_dir end
606
607 return source_file, store_dir
608end
609
610return fetch
diff --git a/src/luarocks/fs.d.tl b/src/luarocks/fs.d.tl
index bde6895a..3ea6d9ca 100644
--- a/src/luarocks/fs.d.tl
+++ b/src/luarocks/fs.d.tl
@@ -35,6 +35,9 @@ local record fs
35 -- zip 35 -- zip
36 find: function(string): {string} 36 find: function(string): {string}
37 filter_file: function(function, string, string): boolean, string 37 filter_file: function(function, string, string): boolean, string
38 -- fetch
39 file_age: function(string): number
40 exists: function(string): boolean
38end 41end
39 42
40return fs 43return fs
diff --git a/src/luarocks/path.tl b/src/luarocks/path.tl
index 5682a2f7..f350f80c 100644
--- a/src/luarocks/path.tl
+++ b/src/luarocks/path.tl
@@ -116,7 +116,7 @@ end
116-- @param tree string or nil: If given, specifies the local tree to use. 116-- @param tree string or nil: If given, specifies the local tree to use.
117-- @return string: The resulting path -- does not guarantee that 117-- @return string: The resulting path -- does not guarantee that
118-- the package (and by extension, the path) exists. 118-- the package (and by extension, the path) exists.
119function path.lib_dir(name: string, version: string, tree: string | Tree): string 119function path.lib_dir(name: string, version: string, tree?: string | Tree): string
120 assert(not name:match("/")) 120 assert(not name:match("/"))
121 return dir.path(path.rocks_dir(tree), name, version, "lib") 121 return dir.path(path.rocks_dir(tree), name, version, "lib")
122end 122end
@@ -127,7 +127,7 @@ end
127-- @param tree string or nil: If given, specifies the local tree to use. 127-- @param tree string or nil: If given, specifies the local tree to use.
128-- @return string: The resulting path -- does not guarantee that 128-- @return string: The resulting path -- does not guarantee that
129-- the package (and by extension, the path) exists. 129-- the package (and by extension, the path) exists.
130function path.lua_dir(name: string, version: string, tree: string | Tree): string 130function path.lua_dir(name: string, version: string, tree?: string | Tree): string
131 assert(not name:match("/")) 131 assert(not name:match("/"))
132 return dir.path(path.rocks_dir(tree), name, version, "lua") 132 return dir.path(path.rocks_dir(tree), name, version, "lua")
133end 133end
@@ -138,7 +138,7 @@ end
138-- @param tree string or nil: If given, specifies the local tree to use. 138-- @param tree string or nil: If given, specifies the local tree to use.
139-- @return string: The resulting path -- does not guarantee that 139-- @return string: The resulting path -- does not guarantee that
140-- the package (and by extension, the path) exists. 140-- the package (and by extension, the path) exists.
141function path.doc_dir(name: string, version: string, tree: string | Tree): string 141function path.doc_dir(name: string, version: string, tree?: string | Tree): string
142 assert(not name:match("/")) 142 assert(not name:match("/"))
143 return dir.path(path.rocks_dir(tree), name, version, "doc") 143 return dir.path(path.rocks_dir(tree), name, version, "doc")
144end 144end
@@ -149,7 +149,7 @@ end
149-- @param tree string or nil: If given, specifies the local tree to use. 149-- @param tree string or nil: If given, specifies the local tree to use.
150-- @return string: The resulting path -- does not guarantee that 150-- @return string: The resulting path -- does not guarantee that
151-- the package (and by extension, the path) exists. 151-- the package (and by extension, the path) exists.
152function path.conf_dir(name: string, version: string, tree: string | Tree): string 152function path.conf_dir(name: string, version: string, tree?: string | Tree): string
153 assert(not name:match("/")) 153 assert(not name:match("/"))
154 return dir.path(path.rocks_dir(tree), name, version, "conf") 154 return dir.path(path.rocks_dir(tree), name, version, "conf")
155end 155end
@@ -161,7 +161,7 @@ end
161-- @param tree string or nil: If given, specifies the local tree to use. 161-- @param tree string or nil: If given, specifies the local tree to use.
162-- @return string: The resulting path -- does not guarantee that 162-- @return string: The resulting path -- does not guarantee that
163-- the package (and by extension, the path) exists. 163-- the package (and by extension, the path) exists.
164function path.bin_dir(name: string, version: string, tree: string | Tree): string 164function path.bin_dir(name: string, version: string, tree?: string | Tree): string
165 assert(not name:match("/")) 165 assert(not name:match("/"))
166 return dir.path(path.rocks_dir(tree), name, version, "bin") 166 return dir.path(path.rocks_dir(tree), name, version, "bin")
167end 167end
diff --git a/src/luarocks/rockspecs.tl b/src/luarocks/rockspecs.tl
new file mode 100644
index 00000000..7d7835ae
--- /dev/null
+++ b/src/luarocks/rockspecs.tl
@@ -0,0 +1,186 @@
1local record rockspecs
2end
3
4local cfg = require("luarocks.core.cfg")
5local dir = require("luarocks.dir")
6local path = require("luarocks.path")
7local queries = require("luarocks.queries")
8local type_rockspec = require("luarocks.type.rockspec")
9local util = require("luarocks.util")
10local vers = require("luarocks.core.vers")
11
12local type Rockspec = cfg.Rockspec
13local type Variables = cfg.Variables
14local type Dependencie = cfg.Dependencie
15
16local vendored_build_type_set: {string: boolean} = {
17 ["builtin"] = true,
18 ["cmake"] = true,
19 ["command"] = true,
20 ["make"] = true,
21 ["module"] = true, -- compatibility alias
22 ["none"] = true,
23}
24
25local rockspec_mt: metatable<Rockspec> = {}
26
27rockspec_mt.__index = cfg.Rockspec
28
29function cfg.Rockspec.type(): string
30 return "rockspec"
31end
32
33--- Perform platform-specific overrides on a table.
34-- Overrides values of table with the contents of the appropriate
35-- subset of its "platforms" field. The "platforms" field should
36-- be a table containing subtables keyed with strings representing
37-- platform names. Names that match the contents of the global
38-- detected platforms setting are used. For example, if
39-- platform "unix" is detected, then the fields of
40-- tbl.platforms.unix will overwrite those of tbl with the same
41-- names. For table values, the operation is performed recursively
42-- (tbl.platforms.foo.x.y.z overrides tbl.x.y.z; other contents of
43-- tbl.x are preserved).
44-- @param tbl table or nil: Table which may contain a "platforms" field;
45-- if it doesn't (or if nil is passed), this function does nothing.
46local function platform_overrides(tbl?: {any: any})
47
48 if not tbl then return end
49
50 local tblp = tbl.platforms
51
52 if tblp is {any: any} then
53 for platform in cfg.each_platform() do
54 local platform_tbl = tblp[platform]
55 if platform_tbl is {any: any} then
56 util.deep_merge(tbl, platform_tbl)
57 end
58 end
59 end
60 tbl.platforms = nil
61end
62
63local function convert_dependencies(rockspec: {string: any}, key: string): boolean, string
64 local deps: {Dependencie} = rockspec[key] as {Dependencie}
65 if deps then
66 for i = 1, #deps do
67 local parsed, err = queries.from_dep_string(deps[i])
68 if not parsed then
69 return nil, "Parse error processing dependency '"..deps[i].."': "..tostring(err)
70 end
71 deps[i] = parsed
72 end
73 else
74 deps = {}
75 end
76 return true
77end
78
79--- Set up path-related variables for a given rock.
80-- Create a "variables" table in the rockspec table, containing
81-- adjusted variables according to the configuration file.
82-- @param rockspec table: The rockspec table.
83local function configure_paths(rockspec: Rockspec)
84 local vars: {string: string} = {}
85 for k,v in pairs(cfg.variables as {string: string}) do
86 vars[k] = v
87 end
88 local name, version = rockspec.name, rockspec.version
89 vars.PREFIX = path.install_dir(name, version)
90 vars.LUADIR = path.lua_dir(name, version)
91 vars.LIBDIR = path.lib_dir(name, version)
92 vars.CONFDIR = path.conf_dir(name, version)
93 vars.BINDIR = path.bin_dir(name, version)
94 vars.DOCDIR = path.doc_dir(name, version)
95 rockspec.variables = vars as Variables
96end
97
98function rockspecs.from_persisted_table(filename: string, rockspec: Rockspec, globals?: {string: any}, quick?: boolean): Rockspec, string
99
100 if rockspec.rockspec_format then
101 if vers.compare_versions(rockspec.rockspec_format, type_rockspec.rockspec_format) then
102 return nil, "Rockspec format "..rockspec.rockspec_format.." is not supported, please upgrade LuaRocks."
103 end
104 end
105
106 if not quick then
107 local ok, err = type_rockspec.check(rockspec as {any: any}, globals or {})
108 if not ok then
109 return nil, err
110 end
111 end
112
113 --- Check if rockspec format version satisfies version requirement.
114 -- @param rockspec table: The rockspec table.
115 -- @param version string: required version.
116 -- @return boolean: true if rockspec format matches version or is newer, false otherwise.
117 do
118 local parsed_format = vers.parse_version(rockspec.rockspec_format or "1.0")
119 rockspec.format_is_at_least = function(self: Rockspec, version: string): boolean
120 return parsed_format >= vers.parse_version(version)
121 end
122 end
123
124 platform_overrides(rockspec.build as {any : any}) --!
125 platform_overrides(rockspec.dependencies as {any : any})
126 platform_overrides(rockspec.build_dependencies as {any : any})
127 platform_overrides(rockspec.test_dependencies as {any : any})
128 platform_overrides(rockspec.external_dependencies as {any : any})
129 platform_overrides(rockspec.source as {any : any})
130 platform_overrides(rockspec.hooks as {any : any})
131 platform_overrides(rockspec.test as {any : any})
132
133 rockspec.name = rockspec.package:lower()
134
135 local protocol, pathname = dir.split_url(rockspec.source.url)
136 if dir.is_basic_protocol(protocol) then
137 rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url)
138 end
139 rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
140
141 -- Temporary compatibility
142 if rockspec.source.cvs_module then rockspec.source.module = rockspec.source.cvs_module end
143 if rockspec.source.cvs_tag then rockspec.source.tag = rockspec.source.cvs_tag end
144
145 rockspec.local_abs_filename = filename
146 rockspec.source.dir_set = rockspec.source.dir ~= nil
147 rockspec.source.dir = rockspec.source.dir or rockspec.source.module
148
149 rockspec.rocks_provided = util.get_rocks_provided(rockspec)
150
151 for _, key in ipairs({"dependencies", "build_dependencies", "test_dependencies"}) do
152 local ok, err = convert_dependencies(rockspec, key)
153 if not ok then
154 return nil, err
155 end
156 end
157
158 if rockspec.build
159 and rockspec.build.type
160 and not vendored_build_type_set[rockspec.build.type] then
161 local build_pkg_name = "luarocks-build-" .. rockspec.build.type
162 if not rockspec.build_dependencies then
163 rockspec.build_dependencies = {}
164 end
165
166 local found = false
167 for _, dep in ipairs(rockspec.build_dependencies) do
168 if dep.name == build_pkg_name then
169 found = true
170 break
171 end
172 end
173
174 if not found then
175 table.insert(rockspec.build_dependencies, queries.from_dep_string(build_pkg_name))
176 end
177 end
178
179 if not quick then
180 configure_paths(rockspec)
181 end
182
183 return setmetatable(rockspec, rockspec_mt)
184end
185
186return rockspecs