aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorV1K1NGbg <victor@ilchev.com>2024-08-06 19:51:19 +0300
committerV1K1NGbg <victor@ilchev.com>2024-08-06 19:51:19 +0300
commit4f21d49f314707caa906df4bf44b86d0851700b3 (patch)
tree5781f88e793d9e97b374bb01ae4cf455f8fbd328
parent294f30af4f9af2cf1f7bbd2c787b132dcc954aa4 (diff)
downloadluarocks-4f21d49f314707caa906df4bf44b86d0851700b3.tar.gz
luarocks-4f21d49f314707caa906df4bf44b86d0851700b3.tar.bz2
luarocks-4f21d49f314707caa906df4bf44b86d0851700b3.zip
sanity check
-rw-r--r--src/luarocks/deps-original.lua833
-rw-r--r--src/luarocks/deps.lua428
2 files changed, 204 insertions, 1057 deletions
diff --git a/src/luarocks/deps-original.lua b/src/luarocks/deps-original.lua
deleted file mode 100644
index 1a9eefde..00000000
--- a/src/luarocks/deps-original.lua
+++ /dev/null
@@ -1,833 +0,0 @@
1
2--- High-level dependency related functions.
3local deps = {}
4
5local cfg = require("luarocks.core.cfg")
6local manif = require("luarocks.manif")
7local path = require("luarocks.path")
8local dir = require("luarocks.dir")
9local fun = require("luarocks.fun")
10local util = require("luarocks.util")
11local vers = require("luarocks.core.vers")
12local queries = require("luarocks.queries")
13local deplocks = require("luarocks.deplocks")
14
15--- Generate a function that matches dep queries against the manifest,
16-- taking into account rocks_provided, the list of versions to skip,
17-- and the lockfile.
18-- @param deps_mode "one", "none", "all" or "order"
19-- @param rocks_provided a one-level table mapping names to versions,
20-- listing rocks to consider provided by the VM
21-- @param rocks_provided table: A table of auto-provided dependencies.
22-- by this Lua implementation for the given dependency.
23-- @param depskey key to use when matching the lockfile ("dependencies",
24-- "build_dependencies", etc.)
25-- @param skip_set a two-level table mapping names to versions to
26-- boolean, listing rocks that should not be matched
27-- @return function(dep): {string}, {string:string}, string, boolean
28-- * array of matching versions
29-- * map of versions to locations
30-- * version matched via lockfile if any
31-- * true if rock matched via rocks_provided
32local function prepare_get_versions(deps_mode, rocks_provided, depskey, skip_set)
33 assert(type(deps_mode) == "string")
34 assert(type(rocks_provided) == "table")
35 assert(type(depskey) == "string")
36 assert(type(skip_set) == "table" or skip_set == nil)
37
38 return function(dep)
39 local versions, locations
40 local provided = rocks_provided[dep.name]
41 if provided then
42 -- Provided rocks have higher priority than manifest's rocks.
43 versions, locations = { provided }, {}
44 else
45 if deps_mode == "none" then
46 deps_mode = "one"
47 end
48 versions, locations = manif.get_versions(dep, deps_mode)
49 end
50
51 if skip_set and skip_set[dep.name] then
52 for i = #versions, 1, -1 do
53 local v = versions[i]
54 if skip_set[dep.name][v] then
55 table.remove(versions, i)
56 end
57 end
58 end
59
60 local lockversion = deplocks.get(depskey, dep.name)
61
62 return versions, locations, lockversion, provided ~= nil
63 end
64end
65
66--- Attempt to match a dependency to an installed rock.
67-- @param get_versions a getter function obtained via prepare_get_versions
68-- @return (string, string, table) or (nil, nil, table):
69-- 1. latest installed version of the rock matching the dependency
70-- 2. location where the installed version is installed
71-- 3. the 'dep' query table
72-- 4. true if provided via VM
73-- or
74-- 1. nil
75-- 2. nil
76-- 3. either 'dep' or an alternative query to be used
77-- 4. false
78local function match_dep(dep, get_versions)
79 assert(type(dep) == "table")
80 assert(type(get_versions) == "function")
81
82 local versions, locations, lockversion, provided = get_versions(dep)
83
84 local latest_version
85 local latest_vstring
86 for _, vstring in ipairs(versions) do
87 local version = vers.parse_version(vstring)
88 if vers.match_constraints(version, dep.constraints) then
89 if not latest_version or version > latest_version then
90 latest_version = version
91 latest_vstring = vstring
92 end
93 end
94 end
95
96 if lockversion and not locations[lockversion] then
97 local latest_matching_msg = ""
98 if latest_vstring and latest_vstring ~= lockversion then
99 latest_matching_msg = " (latest matching is " .. latest_vstring .. ")"
100 end
101 util.printout("Forcing " .. dep.name .. " to pinned version " .. lockversion .. latest_matching_msg)
102 return nil, nil, queries.new(dep.name, dep.namespace, lockversion)
103 end
104
105 return latest_vstring, locations[latest_vstring], dep, provided
106end
107
108local function match_all_deps(dependencies, get_versions)
109 assert(type(dependencies) == "table")
110 assert(type(get_versions) == "function")
111
112 local matched, missing, no_upgrade = {}, {}, {}
113
114 for _, dep in ipairs(dependencies) do
115 local found, _, provided
116 found, _, dep, provided = match_dep(dep, get_versions)
117 if found then
118 if not provided then
119 matched[dep] = {name = dep.name, version = found}
120 end
121 else
122 if dep.constraints[1] and dep.constraints[1].no_upgrade then
123 no_upgrade[dep.name] = dep
124 else
125 missing[dep.name] = dep
126 end
127 end
128 end
129 return matched, missing, no_upgrade
130end
131
132--- Attempt to match dependencies of a rockspec to installed rocks.
133-- @param dependencies table: The table of dependencies.
134-- @param rocks_provided table: The table of auto-provided dependencies.
135-- @param skip_set table or nil: Program versions to not use as valid matches.
136-- Table where keys are program names and values are tables where keys
137-- are program versions and values are 'true'.
138-- @param deps_mode string: Which trees to check dependencies for
139-- @return table, table, table: A table where keys are dependencies parsed
140-- in table format and values are tables containing fields 'name' and
141-- version' representing matches; a table of missing dependencies
142-- parsed as tables; and a table of "no-upgrade" missing dependencies
143-- (to be used in plugin modules so that a plugin does not force upgrade of
144-- its parent application).
145function deps.match_deps(dependencies, rocks_provided, deps_mode, skip_set)
146 assert(type(dependencies) == "table")
147 assert(type(rocks_provided) == "table")
148 assert(type(skip_set) == "table" or skip_set == nil)
149 assert(type(deps_mode) == "string")
150
151 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies", skip_set)
152 return match_all_deps(dependencies, get_versions)
153end
154
155local function rock_status(dep, get_versions)
156 assert(dep:type() == "query")
157 assert(type(get_versions) == "function")
158
159 local installed, _, _, provided = match_dep(dep, get_versions)
160 local installation_type = provided and "provided by VM" or "installed"
161 return installed and installed.." "..installation_type..": success" or "not installed"
162end
163
164--- Check depenendencies of a package and report any missing ones.
165-- @param name string: package name.
166-- @param version string: package version.
167-- @param dependencies table: array of dependencies.
168-- @param deps_mode string: Which trees to check dependencies for
169-- @param rocks_provided table: A table of auto-dependencies provided
170-- by this Lua implementation for the given dependency.
171-- "one" for the current default tree, "all" for all trees,
172-- "order" for all trees with priority >= the current default, "none" for no trees.
173function deps.report_missing_dependencies(name, version, dependencies, deps_mode, rocks_provided)
174 assert(type(name) == "string")
175 assert(type(version) == "string")
176 assert(type(dependencies) == "table")
177 assert(type(deps_mode) == "string")
178 assert(type(rocks_provided) == "table")
179
180 if deps_mode == "none" then
181 return
182 end
183
184 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies")
185
186 local first_missing_dep = true
187
188 for _, dep in ipairs(dependencies) do
189 local found, _
190 found, _, dep = match_dep(dep, get_versions)
191 if not found then
192 if first_missing_dep then
193 util.printout(("Missing dependencies for %s %s:"):format(name, version))
194 first_missing_dep = false
195 end
196
197 util.printout((" %s (%s)"):format(tostring(dep), rock_status(dep, get_versions)))
198 end
199 end
200end
201
202function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey)
203 assert(dep:type() == "query")
204 assert(type(deps_mode) == "string" or deps_mode == nil)
205 assert(type(rocks_provided) == "table" or rocks_provided == nil)
206 assert(type(verify) == "boolean" or verify == nil)
207 assert(type(depskey) == "string")
208
209 deps_mode = deps_mode or "all"
210 rocks_provided = rocks_provided or {}
211
212 local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey)
213
214 local found, where
215 found, where, dep = match_dep(dep, get_versions)
216 if found then
217 local tree_manifests = manif.load_rocks_tree_manifests(deps_mode)
218 manif.scan_dependencies(dep.name, found, tree_manifests, deplocks.proxy(depskey))
219 return true, found, where
220 end
221
222 local search = require("luarocks.search")
223 local install = require("luarocks.cmd.install")
224
225 local url, search_err = search.find_suitable_rock(dep)
226 if not url then
227 return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err
228 end
229 util.printout("Installing "..url)
230 local install_args = {
231 rock = url,
232 deps_mode = deps_mode,
233 namespace = dep.namespace,
234 verify = verify,
235 }
236 local ok, install_err, errcode = install.command(install_args)
237 if not ok then
238 return nil, "Failed installing dependency: "..url.." - "..install_err, errcode
239 end
240
241 found, where = match_dep(dep, get_versions)
242 if not found then
243 return nil, "Repository inconsistency detected (previously unfinished/corrupted installation?)"
244 end
245 return true, found, where
246end
247
248local function check_supported_platforms(rockspec)
249 if rockspec.supported_platforms and next(rockspec.supported_platforms) then
250 local all_negative = true
251 local supported = false
252 for _, plat in pairs(rockspec.supported_platforms) do
253 local neg
254 neg, plat = plat:match("^(!?)(.*)")
255 if neg == "!" then
256 if cfg.is_platform(plat) then
257 return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
258 end
259 else
260 all_negative = false
261 if cfg.is_platform(plat) then
262 supported = true
263 break
264 end
265 end
266 end
267 if supported == false and not all_negative then
268 local plats = cfg.print_platforms()
269 return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms."
270 end
271 end
272
273 return true
274end
275
276--- Check dependencies of a rock and attempt to install any missing ones.
277-- Packages are installed using the LuaRocks "install" command.
278-- Aborts the program if a dependency could not be fulfilled.
279-- @param rockspec table: A rockspec in table format.
280-- @param depskey string: Rockspec key to fetch to get dependency table
281-- ("dependencies", "build_dependencies", etc.).
282-- @param deps_mode string
283-- @param verify boolean
284-- @param deplock_dir string: dirname of the deplock file
285-- @return boolean or (nil, string, [string]): True if no errors occurred, or
286-- nil and an error message if any test failed, followed by an optional
287-- error code.
288function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock_dir)
289 assert(type(rockspec) == "table")
290 assert(type(depskey) == "string")
291 assert(type(deps_mode) == "string")
292 assert(type(verify) == "boolean" or verify == nil)
293 assert(type(deplock_dir) == "string" or deplock_dir == nil)
294
295 local name = rockspec.name
296 local version = rockspec.version
297 local rocks_provided = rockspec.rocks_provided
298
299 local ok, filename, err = deplocks.load(name, deplock_dir or ".")
300 if filename then
301 util.printout("Using dependencies pinned in lockfile: " .. filename)
302
303 local get_versions = prepare_get_versions("none", rocks_provided, depskey)
304 for dnsname, dversion in deplocks.each(depskey) do
305 local dname, dnamespace = util.split_namespace(dnsname)
306 local dep = queries.new(dname, dnamespace, dversion)
307
308 util.printout(("%s %s is pinned to %s (%s)"):format(
309 name, version, tostring(dep), rock_status(dep, get_versions)))
310
311 local ok, err = deps.fulfill_dependency(dep, "none", rocks_provided, verify, depskey)
312 if not ok then
313 return nil, err
314 end
315 end
316 util.printout()
317 return true
318 elseif err then
319 util.warning(err)
320 end
321
322 ok, err = check_supported_platforms(rockspec)
323 if not ok then
324 return nil, err
325 end
326
327 deps.report_missing_dependencies(name, version, rockspec[depskey], deps_mode, rocks_provided)
328
329 util.printout()
330
331 local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey)
332 for _, dep in ipairs(rockspec[depskey]) do
333
334 util.printout(("%s %s depends on %s (%s)"):format(
335 name, version, tostring(dep), rock_status(dep, get_versions)))
336
337 local ok, found_or_err, _, no_upgrade = deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey)
338 if ok then
339 deplocks.add(depskey, dep.name, found_or_err)
340 else
341 if no_upgrade then
342 util.printerr("This version of "..name.." is designed for use with")
343 util.printerr(tostring(dep)..", but is configured to avoid upgrading it")
344 util.printerr("automatically. Please upgrade "..dep.name.." with")
345 util.printerr(" luarocks install "..dep.name)
346 util.printerr("or look for a suitable version of "..name.." with")
347 util.printerr(" luarocks search "..name)
348 end
349 return nil, found_or_err
350 end
351 end
352
353 return true
354end
355
356--- If filename matches a pattern, return the capture.
357-- For example, given "libfoo.so" and "lib?.so" is a pattern,
358-- returns "foo" (which can then be used to build names
359-- based on other patterns.
360-- @param file string: a filename
361-- @param pattern string: a pattern, where ? is to be matched by the filename.
362-- @return string The pattern, if found, or nil.
363local function deconstruct_pattern(file, pattern)
364 local depattern = "^"..(pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")).."$"
365 return (file:match(depattern))
366end
367
368--- Construct all possible patterns for a name and add to the files array.
369-- Run through the patterns array replacing all occurrences of "?"
370-- with the given file name and store them in the files array.
371-- @param file string A raw name (e.g. "foo")
372-- @param array of string An array of patterns with "?" as the wildcard
373-- (e.g. {"?.so", "lib?.so"})
374-- @param files The array of constructed names
375local function add_all_patterns(file, patterns, files)
376 for _, pattern in ipairs(patterns) do
377 table.insert(files, {#files + 1, (pattern:gsub("?", file))})
378 end
379end
380
381local function get_external_deps_dirs(mode)
382 local patterns = cfg.external_deps_patterns
383 local subdirs = cfg.external_deps_subdirs
384 if mode == "install" then
385 patterns = cfg.runtime_external_deps_patterns
386 subdirs = cfg.runtime_external_deps_subdirs
387 end
388 local dirs = {
389 BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
390 INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
391 LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }
392 }
393 if mode == "install" then
394 dirs.INCDIR = nil
395 end
396 return dirs
397end
398
399local function resolve_prefix(prefix, dirs)
400 if type(prefix) == "string" then
401 return prefix
402 elseif type(prefix) == "table" then
403 if prefix.bin then
404 dirs.BINDIR.subdir = prefix.bin
405 end
406 if prefix.include then
407 if dirs.INCDIR then
408 dirs.INCDIR.subdir = prefix.include
409 end
410 end
411 if prefix.lib then
412 dirs.LIBDIR.subdir = prefix.lib
413 end
414 return prefix.prefix
415 end
416end
417
418local function add_patterns_for_file(files, file, patterns)
419 -- If it doesn't look like it contains a filename extension
420 if not (file:match("%.[a-z]+$") or file:match("%.[a-z]+%.")) then
421 add_all_patterns(file, patterns, files)
422 else
423 for _, pattern in ipairs(patterns) do
424 local matched = deconstruct_pattern(file, pattern)
425 if matched then
426 add_all_patterns(matched, patterns, files)
427 end
428 end
429 table.insert(files, {#files + 1, file})
430 end
431end
432
433local function check_external_dependency_at(prefix, name, ext_files, vars, dirs, err_files, cache)
434 local fs = require("luarocks.fs")
435 cache = cache or {}
436
437 for dirname, dirdata in util.sortedpairs(dirs) do
438 local paths
439 local path_var_value = vars[name.."_"..dirname]
440 if path_var_value then
441 paths = { path_var_value }
442 elseif type(dirdata.subdir) == "table" then
443 paths = {}
444 for i,v in ipairs(dirdata.subdir) do
445 paths[i] = dir.path(prefix, v)
446 end
447 else
448 paths = { dir.path(prefix, dirdata.subdir) }
449 end
450 local file_or_files = ext_files[dirdata.testfile]
451 if file_or_files then
452 local files = {}
453 if type(file_or_files) == "string" then
454 add_patterns_for_file(files, file_or_files, dirdata.pattern)
455 elseif type(file_or_files) == "table" then
456 for _, f in ipairs(file_or_files) do
457 add_patterns_for_file(files, f, dirdata.pattern)
458 end
459 end
460
461 local found = false
462 table.sort(files, function(a, b)
463 if (not a[2]:match("%*")) and b[2]:match("%*") then
464 return true
465 elseif a[2]:match("%*") and (not b[2]:match("%*")) then
466 return false
467 else
468 return a[1] < b[1]
469 end
470 end)
471 for _, fa in ipairs(files) do
472
473 local f = fa[2]
474 -- small convenience hack
475 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
476 f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
477 end
478
479 local pattern
480 if f:match("%*") then
481 pattern = "^" .. f:gsub("([-.+])", "%%%1"):gsub("%*", ".*") .. "$"
482 f = "matching "..f
483 end
484
485 for _, d in ipairs(paths) do
486 if pattern then
487 if not cache[d] then
488 cache[d] = fs.list_dir(d)
489 end
490 local match = string.match
491 for _, entry in ipairs(cache[d]) do
492 if match(entry, pattern) then
493 found = true
494 break
495 end
496 end
497 else
498 found = fs.is_file(dir.path(d, f))
499 end
500 if found then
501 dirdata.dir = d
502 dirdata.file = f
503 break
504 else
505 table.insert(err_files[dirdata.testfile], f.." in "..d)
506 end
507 end
508 if found then
509 break
510 end
511 end
512 if not found then
513 return nil, dirname, dirdata.testfile
514 end
515 else
516 -- When we have a set of subdir suffixes, look for one that exists.
517 -- For these reason, we now put "lib" ahead of "" on Windows in our
518 -- default set.
519 dirdata.dir = paths[1]
520 for _, p in ipairs(paths) do
521 if fs.exists(p) then
522 dirdata.dir = p
523 break
524 end
525 end
526 end
527 end
528
529 for dirname, dirdata in pairs(dirs) do
530 vars[name.."_"..dirname] = dirdata.dir
531 vars[name.."_"..dirname.."_FILE"] = dirdata.file
532 end
533 vars[name.."_DIR"] = prefix
534 return true
535end
536
537local function check_external_dependency(name, ext_files, vars, mode, cache)
538 local ok
539 local err_dirname
540 local err_testfile
541 local err_files = {program = {}, header = {}, library = {}}
542
543 local dirs = get_external_deps_dirs(mode)
544
545 local prefixes
546 if vars[name .. "_DIR"] then
547 prefixes = { vars[name .. "_DIR"] }
548 elseif vars.DEPS_DIR then
549 prefixes = { vars.DEPS_DIR }
550 else
551 prefixes = cfg.external_deps_dirs
552 end
553
554 for _, prefix in ipairs(prefixes) do
555 prefix = resolve_prefix(prefix, dirs)
556 if cfg.is_platform("mingw32") and name == "LUA" then
557 dirs.LIBDIR.pattern = fun.filter(util.deep_copy(dirs.LIBDIR.pattern), function(s)
558 return not s:match("%.a$")
559 end)
560 elseif cfg.is_platform("windows") and name == "LUA" then
561 dirs.LIBDIR.pattern = fun.filter(util.deep_copy(dirs.LIBDIR.pattern), function(s)
562 return not s:match("%.dll$")
563 end)
564 end
565 ok, err_dirname, err_testfile = check_external_dependency_at(prefix, name, ext_files, vars, dirs, err_files, cache)
566 if ok then
567 return true
568 end
569 end
570
571 return nil, err_dirname, err_testfile, err_files
572end
573
574function deps.autodetect_external_dependencies(build)
575 -- only applies to the 'builtin' build type
576 if not build or not build.modules then
577 return nil
578 end
579
580 local extdeps = {}
581 local any = false
582 for _, data in pairs(build.modules) do
583 if type(data) == "table" and data.libraries then
584 local libraries = data.libraries
585 if type(libraries) == "string" then
586 libraries = { libraries }
587 end
588 local incdirs = {}
589 local libdirs = {}
590 for _, lib in ipairs(libraries) do
591 local upper = lib:upper():gsub("%+", "P"):gsub("[^%w]", "_")
592 any = true
593 extdeps[upper] = { library = lib }
594 table.insert(incdirs, "$(" .. upper .. "_INCDIR)")
595 table.insert(libdirs, "$(" .. upper .. "_LIBDIR)")
596 end
597 if not data.incdirs then
598 data.incdirs = incdirs
599 end
600 if not data.libdirs then
601 data.libdirs = libdirs
602 end
603 end
604 end
605 return any and extdeps or nil
606end
607
608--- Set up path-related variables for external dependencies.
609-- For each key in the external_dependencies table in the
610-- rockspec file, four variables are created: <key>_DIR, <key>_BINDIR,
611-- <key>_INCDIR and <key>_LIBDIR. These are not overwritten
612-- if already set (e.g. by the LuaRocks config file or through the
613-- command-line). Values in the external_dependencies table
614-- are tables that may contain a "header" or a "library" field,
615-- with filenames to be tested for existence.
616-- @param rockspec table: The rockspec table.
617-- @param mode string: if "build" is given, checks all files;
618-- if "install" is given, do not scan for headers.
619-- @return boolean or (nil, string): True if no errors occurred, or
620-- nil and an error message if any test failed.
621function deps.check_external_deps(rockspec, mode)
622 assert(rockspec:type() == "rockspec")
623
624 if not rockspec.external_dependencies then
625 rockspec.external_dependencies = deps.autodetect_external_dependencies(rockspec.build)
626 end
627 if not rockspec.external_dependencies then
628 return true
629 end
630
631 for name, ext_files in util.sortedpairs(rockspec.external_dependencies) do
632 local ok, err_dirname, err_testfile, err_files = check_external_dependency(name, ext_files, rockspec.variables, mode)
633 if not ok then
634 local lines = {"Could not find "..err_testfile.." file for "..name}
635
636 local err_paths = {}
637 for _, err_file in ipairs(err_files[err_testfile]) do
638 if not err_paths[err_file] then
639 err_paths[err_file] = true
640 table.insert(lines, " No file "..err_file)
641 end
642 end
643
644 table.insert(lines, "You may have to install "..name.." in your system and/or pass "..name.."_DIR or "..name.."_"..err_dirname.." to the luarocks command.")
645 table.insert(lines, "Example: luarocks install "..rockspec.name.." "..name.."_DIR=/usr/local")
646
647 return nil, table.concat(lines, "\n"), "dependency"
648 end
649 end
650 return true
651end
652
653--- Recursively add satisfied dependencies of a package to a table,
654-- to build a transitive closure of all dependent packages.
655-- Additionally ensures that `dependencies` table of the manifest is up-to-date.
656-- @param results table: The results table being built, maps package names to versions.
657-- @param mdeps table: The manifest dependencies table.
658-- @param name string: Package name.
659-- @param version string: Package version.
660function deps.scan_deps(results, mdeps, name, version, deps_mode)
661 assert(type(results) == "table")
662 assert(type(mdeps) == "table")
663 assert(type(name) == "string" and not name:match("/"))
664 assert(type(version) == "string")
665
666 local fetch = require("luarocks.fetch")
667
668 if results[name] then
669 return
670 end
671 if not mdeps[name] then mdeps[name] = {} end
672 local mdn = mdeps[name]
673 local dependencies = mdn[version]
674 local rocks_provided
675 if not dependencies then
676 local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version), false)
677 if not rockspec then
678 return
679 end
680 dependencies = rockspec.dependencies
681 rocks_provided = rockspec.rocks_provided
682 mdn[version] = dependencies
683 else
684 rocks_provided = util.get_rocks_provided()
685 end
686
687 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies")
688
689 local matched = match_all_deps(dependencies, get_versions)
690 results[name] = version
691 for _, match in pairs(matched) do
692 deps.scan_deps(results, mdeps, match.name, match.version, deps_mode)
693 end
694end
695
696local function lua_h_exists(d, luaver)
697 local major, minor = luaver:match("(%d+)%.(%d+)")
698 local luanum = ("%s%02d"):format(major, tonumber(minor))
699
700 local lua_h = dir.path(d, "lua.h")
701 local fd = io.open(lua_h)
702 if fd then
703 local data = fd:read("*a")
704 fd:close()
705 if data:match("LUA_VERSION_NUM%s*" .. tostring(luanum)) then
706 return d
707 end
708 return nil, "Lua header lua.h found at " .. d .. " does not match Lua version " .. luaver .. ". You can use `luarocks config variables.LUA_INCDIR <path>` to set the correct location.", "dependency", 2
709 end
710
711 return nil, "Failed finding Lua header lua.h (searched at " .. d .. "). You may need to install Lua development headers. You can use `luarocks config variables.LUA_INCDIR <path>` to set the correct location.", "dependency", 1
712end
713
714local function find_lua_incdir(prefix, luaver, luajitver)
715 luajitver = luajitver and luajitver:gsub("%-.*", "")
716 local shortv = luaver:gsub("%.", "")
717 local incdirs = {
718 prefix .. "/include/lua/" .. luaver,
719 prefix .. "/include/lua" .. luaver,
720 prefix .. "/include/lua-" .. luaver,
721 prefix .. "/include/lua" .. shortv,
722 prefix .. "/include",
723 prefix,
724 luajitver and (prefix .. "/include/luajit-" .. (luajitver:match("^(%d+%.%d+)") or "")),
725 }
726 local errprio = 0
727 local mainerr
728 for _, d in ipairs(incdirs) do
729 local ok, err, _, prio = lua_h_exists(d, luaver)
730 if ok then
731 return d
732 end
733 if prio > errprio then
734 mainerr = err
735 errprio = prio
736 end
737 end
738
739 -- not found, will fallback to a default
740 return nil, mainerr
741end
742
743function deps.check_lua_incdir(vars)
744 if vars.LUA_INCDIR_OK == true
745 then return true
746 end
747
748 local ljv = util.get_luajit_version()
749
750 if vars.LUA_INCDIR then
751 local ok, err = lua_h_exists(vars.LUA_INCDIR, cfg.lua_version)
752 if ok then
753 vars.LUA_INCDIR_OK = true
754 end
755 return ok, err
756 end
757
758 if vars.LUA_DIR then
759 local d, err = find_lua_incdir(vars.LUA_DIR, cfg.lua_version, ljv)
760 if d then
761 vars.LUA_INCDIR = d
762 vars.LUA_INCDIR_OK = true
763 return true
764 end
765 return nil, err
766 end
767
768 return nil, "Failed finding Lua headers; neither LUA_DIR or LUA_INCDIR are set. You may need to install them or configure LUA_INCDIR.", "dependency"
769end
770
771function deps.check_lua_libdir(vars)
772 if vars.LUA_LIBDIR_OK == true
773 then return true
774 end
775
776 local fs = require("luarocks.fs")
777 local ljv = util.get_luajit_version()
778
779 if vars.LUA_LIBDIR and vars.LUALIB and fs.exists(dir.path(vars.LUA_LIBDIR, vars.LUALIB)) then
780 vars.LUA_LIBDIR_OK = true
781 return true
782 end
783
784 local shortv = cfg.lua_version:gsub("%.", "")
785 local libnames = {
786 "lua" .. cfg.lua_version,
787 "lua" .. shortv,
788 "lua-" .. cfg.lua_version,
789 "lua-" .. shortv,
790 "lua",
791 }
792 if ljv then
793 table.insert(libnames, 1, "luajit-" .. cfg.lua_version)
794 table.insert(libnames, 2, "luajit")
795 end
796 local cache = {}
797 local save_LUA_INCDIR = vars.LUA_INCDIR
798 local ok, _, _, errfiles = check_external_dependency("LUA", { library = libnames }, vars, "build", cache)
799 vars.LUA_INCDIR = save_LUA_INCDIR
800 local err
801 if ok then
802 local filename = dir.path(vars.LUA_LIBDIR, vars.LUA_LIBDIR_FILE)
803 local fd = io.open(filename, "r")
804 if fd then
805 if not vars.LUA_LIBDIR_FILE:match((cfg.lua_version:gsub("%.", "%%.?"))) then
806 -- if filename isn't versioned, check file contents
807 local txt = fd:read("*a")
808 ok = txt:match("Lua " .. cfg.lua_version, 1, true)
809 or txt:match("lua" .. (cfg.lua_version:gsub("%.", "")), 1, true)
810 if not ok then
811 err = "Lua library at " .. filename .. " does not match Lua version " .. cfg.lua_version .. ". You can use `luarocks config variables.LUA_LIBDIR <path>` to set the correct location."
812 end
813 end
814
815 fd:close()
816 end
817 end
818
819 if ok then
820 vars.LUALIB = vars.LUA_LIBDIR_FILE
821 vars.LUA_LIBDIR_OK = true
822 return true
823 else
824 err = err or "Failed finding the Lua library. You can use `luarocks config variables.LUA_LIBDIR <path>` to set the correct location."
825 return nil, err, "dependency", errfiles
826 end
827end
828
829function deps.get_deps_mode(args)
830 return args.deps_mode or cfg.deps_mode
831end
832
833return deps
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua
index 9b4eb697..1a9eefde 100644
--- a/src/luarocks/deps.lua
+++ b/src/luarocks/deps.lua
@@ -1,8 +1,7 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2 1
2--- High-level dependency related functions.
3local deps = {} 3local deps = {}
4 4
5
6local cfg = require("luarocks.core.cfg") 5local cfg = require("luarocks.core.cfg")
7local manif = require("luarocks.manif") 6local manif = require("luarocks.manif")
8local path = require("luarocks.path") 7local path = require("luarocks.path")
@@ -13,56 +12,34 @@ local vers = require("luarocks.core.vers")
13local queries = require("luarocks.queries") 12local queries = require("luarocks.queries")
14local deplocks = require("luarocks.deplocks") 13local deplocks = require("luarocks.deplocks")
15 14
16 15--- Generate a function that matches dep queries against the manifest,
17 16-- taking into account rocks_provided, the list of versions to skip,
18 17-- and the lockfile.
19 18-- @param deps_mode "one", "none", "all" or "order"
20 19-- @param rocks_provided a one-level table mapping names to versions,
21 20-- listing rocks to consider provided by the VM
22 21-- @param rocks_provided table: A table of auto-provided dependencies.
23 22-- by this Lua implementation for the given dependency.
24 23-- @param depskey key to use when matching the lockfile ("dependencies",
25 24-- "build_dependencies", etc.)
26 25-- @param skip_set a two-level table mapping names to versions to
27 26-- boolean, listing rocks that should not be matched
28 27-- @return function(dep): {string}, {string:string}, string, boolean
29 28-- * array of matching versions
30 29-- * map of versions to locations
31 30-- * version matched via lockfile if any
32 31-- * true if rock matched via rocks_provided
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59local function prepare_get_versions(deps_mode, rocks_provided, depskey, skip_set) 32local function prepare_get_versions(deps_mode, rocks_provided, depskey, skip_set)
33 assert(type(deps_mode) == "string")
34 assert(type(rocks_provided) == "table")
35 assert(type(depskey) == "string")
36 assert(type(skip_set) == "table" or skip_set == nil)
60 37
61 return function(dep) 38 return function(dep)
62 local versions, locations 39 local versions, locations
63 local provided = rocks_provided[dep.name] 40 local provided = rocks_provided[dep.name]
64 if provided then 41 if provided then
65 42 -- Provided rocks have higher priority than manifest's rocks.
66 versions, locations = { provided }, {} 43 versions, locations = { provided }, {}
67 else 44 else
68 if deps_mode == "none" then 45 if deps_mode == "none" then
@@ -86,20 +63,21 @@ local function prepare_get_versions(deps_mode, rocks_provided, depskey, skip_set
86 end 63 end
87end 64end
88 65
89 66--- Attempt to match a dependency to an installed rock.
90 67-- @param get_versions a getter function obtained via prepare_get_versions
91 68-- @return (string, string, table) or (nil, nil, table):
92 69-- 1. latest installed version of the rock matching the dependency
93 70-- 2. location where the installed version is installed
94 71-- 3. the 'dep' query table
95 72-- 4. true if provided via VM
96 73-- or
97 74-- 1. nil
98 75-- 2. nil
99 76-- 3. either 'dep' or an alternative query to be used
100 77-- 4. false
101local function match_dep(dep, 78local function match_dep(dep, get_versions)
102 get_versions) 79 assert(type(dep) == "table")
80 assert(type(get_versions) == "function")
103 81
104 local versions, locations, lockversion, provided = get_versions(dep) 82 local versions, locations, lockversion, provided = get_versions(dep)
105 83
@@ -127,8 +105,9 @@ local function match_dep(dep,
127 return latest_vstring, locations[latest_vstring], dep, provided 105 return latest_vstring, locations[latest_vstring], dep, provided
128end 106end
129 107
130local function match_all_deps(dependencies, 108local function match_all_deps(dependencies, get_versions)
131 get_versions) 109 assert(type(dependencies) == "table")
110 assert(type(get_versions) == "function")
132 111
133 local matched, missing, no_upgrade = {}, {}, {} 112 local matched, missing, no_upgrade = {}, {}, {}
134 113
@@ -137,7 +116,7 @@ local function match_all_deps(dependencies,
137 found, _, dep, provided = match_dep(dep, get_versions) 116 found, _, dep, provided = match_dep(dep, get_versions)
138 if found then 117 if found then
139 if not provided then 118 if not provided then
140 matched[dep] = { name = dep.name, version = found } 119 matched[dep] = {name = dep.name, version = found}
141 end 120 end
142 else 121 else
143 if dep.constraints[1] and dep.constraints[1].no_upgrade then 122 if dep.constraints[1] and dep.constraints[1].no_upgrade then
@@ -150,41 +129,53 @@ local function match_all_deps(dependencies,
150 return matched, missing, no_upgrade 129 return matched, missing, no_upgrade
151end 130end
152 131
153 132--- Attempt to match dependencies of a rockspec to installed rocks.
154 133-- @param dependencies table: The table of dependencies.
155 134-- @param rocks_provided table: The table of auto-provided dependencies.
156 135-- @param skip_set table or nil: Program versions to not use as valid matches.
157 136-- Table where keys are program names and values are tables where keys
158 137-- are program versions and values are 'true'.
159 138-- @param deps_mode string: Which trees to check dependencies for
160 139-- @return table, table, table: A table where keys are dependencies parsed
161 140-- in table format and values are tables containing fields 'name' and
162 141-- version' representing matches; a table of missing dependencies
163 142-- parsed as tables; and a table of "no-upgrade" missing dependencies
164 143-- (to be used in plugin modules so that a plugin does not force upgrade of
165 144-- its parent application).
166function deps.match_deps(dependencies, rocks_provided, deps_mode, skip_set) 145function deps.match_deps(dependencies, rocks_provided, deps_mode, skip_set)
146 assert(type(dependencies) == "table")
147 assert(type(rocks_provided) == "table")
148 assert(type(skip_set) == "table" or skip_set == nil)
149 assert(type(deps_mode) == "string")
167 150
168 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies", skip_set) 151 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies", skip_set)
169 return match_all_deps(dependencies, get_versions) 152 return match_all_deps(dependencies, get_versions)
170end 153end
171 154
172local function rock_status(dep, get_versions) 155local function rock_status(dep, get_versions)
156 assert(dep:type() == "query")
157 assert(type(get_versions) == "function")
158
173 local installed, _, _, provided = match_dep(dep, get_versions) 159 local installed, _, _, provided = match_dep(dep, get_versions)
174 local installation_type = provided and "provided by VM" or "installed" 160 local installation_type = provided and "provided by VM" or "installed"
175 return installed and installed .. " " .. installation_type .. ": success" or "not installed" 161 return installed and installed.." "..installation_type..": success" or "not installed"
176end 162end
177 163
178 164--- Check depenendencies of a package and report any missing ones.
179 165-- @param name string: package name.
180 166-- @param version string: package version.
181 167-- @param dependencies table: array of dependencies.
182 168-- @param deps_mode string: Which trees to check dependencies for
183 169-- @param rocks_provided table: A table of auto-dependencies provided
184 170-- by this Lua implementation for the given dependency.
185 171-- "one" for the current default tree, "all" for all trees,
186 172-- "order" for all trees with priority >= the current default, "none" for no trees.
187function deps.report_missing_dependencies(name, version, dependencies, deps_mode, rocks_provided) 173function deps.report_missing_dependencies(name, version, dependencies, deps_mode, rocks_provided)
174 assert(type(name) == "string")
175 assert(type(version) == "string")
176 assert(type(dependencies) == "table")
177 assert(type(deps_mode) == "string")
178 assert(type(rocks_provided) == "table")
188 179
189 if deps_mode == "none" then 180 if deps_mode == "none" then
190 return 181 return
@@ -209,6 +200,11 @@ function deps.report_missing_dependencies(name, version, dependencies, deps_mode
209end 200end
210 201
211function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey) 202function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey)
203 assert(dep:type() == "query")
204 assert(type(deps_mode) == "string" or deps_mode == nil)
205 assert(type(rocks_provided) == "table" or rocks_provided == nil)
206 assert(type(verify) == "boolean" or verify == nil)
207 assert(type(depskey) == "string")
212 208
213 deps_mode = deps_mode or "all" 209 deps_mode = deps_mode or "all"
214 rocks_provided = rocks_provided or {} 210 rocks_provided = rocks_provided or {}
@@ -228,9 +224,9 @@ function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey
228 224
229 local url, search_err = search.find_suitable_rock(dep) 225 local url, search_err = search.find_suitable_rock(dep)
230 if not url then 226 if not url then
231 return nil, "Could not satisfy dependency " .. tostring(dep) .. ": " .. search_err 227 return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err
232 end 228 end
233 util.printout("Installing " .. url) 229 util.printout("Installing "..url)
234 local install_args = { 230 local install_args = {
235 rock = url, 231 rock = url,
236 deps_mode = deps_mode, 232 deps_mode = deps_mode,
@@ -239,7 +235,7 @@ function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey
239 } 235 }
240 local ok, install_err, errcode = install.command(install_args) 236 local ok, install_err, errcode = install.command(install_args)
241 if not ok then 237 if not ok then
242 return nil, "Failed installing dependency: " .. url .. " - " .. install_err, errcode 238 return nil, "Failed installing dependency: "..url.." - "..install_err, errcode
243 end 239 end
244 240
245 found, where = match_dep(dep, get_versions) 241 found, where = match_dep(dep, get_versions)
@@ -250,15 +246,15 @@ function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey
250end 246end
251 247
252local function check_supported_platforms(rockspec) 248local function check_supported_platforms(rockspec)
253 if rockspec.supported_platforms and next(rockspec.supported_platforms) ~= nil then 249 if rockspec.supported_platforms and next(rockspec.supported_platforms) then
254 local all_negative = true 250 local all_negative = true
255 local supported = false 251 local supported = false
256 for _, plat in ipairs(rockspec.supported_platforms) do 252 for _, plat in pairs(rockspec.supported_platforms) do
257 local neg 253 local neg
258 neg, plat = plat:match("^(!?)(.*)") 254 neg, plat = plat:match("^(!?)(.*)")
259 if neg == "!" then 255 if neg == "!" then
260 if cfg.is_platform(plat) then 256 if cfg.is_platform(plat) then
261 return nil, "This rockspec for " .. rockspec.package .. " does not support " .. plat .. " platforms." 257 return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
262 end 258 end
263 else 259 else
264 all_negative = false 260 all_negative = false
@@ -270,26 +266,32 @@ local function check_supported_platforms(rockspec)
270 end 266 end
271 if supported == false and not all_negative then 267 if supported == false and not all_negative then
272 local plats = cfg.print_platforms() 268 local plats = cfg.print_platforms()
273 return nil, "This rockspec for " .. rockspec.package .. " does not support " .. plats .. " platforms." 269 return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms."
274 end 270 end
275 end 271 end
276 272
277 return true 273 return true
278end 274end
279 275
280 276--- Check dependencies of a rock and attempt to install any missing ones.
281 277-- Packages are installed using the LuaRocks "install" command.
282 278-- Aborts the program if a dependency could not be fulfilled.
283 279-- @param rockspec table: A rockspec in table format.
284 280-- @param depskey string: Rockspec key to fetch to get dependency table
285 281-- ("dependencies", "build_dependencies", etc.).
286 282-- @param deps_mode string
287 283-- @param verify boolean
288 284-- @param deplock_dir string: dirname of the deplock file
289 285-- @return boolean or (nil, string, [string]): True if no errors occurred, or
290 286-- nil and an error message if any test failed, followed by an optional
291 287-- error code.
292function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock_dir) 288function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock_dir)
289 assert(type(rockspec) == "table")
290 assert(type(depskey) == "string")
291 assert(type(deps_mode) == "string")
292 assert(type(verify) == "boolean" or verify == nil)
293 assert(type(deplock_dir) == "string" or deplock_dir == nil)
294
293 local name = rockspec.name 295 local name = rockspec.name
294 local version = rockspec.version 296 local version = rockspec.version
295 local rocks_provided = rockspec.rocks_provided 297 local rocks_provided = rockspec.rocks_provided
@@ -299,23 +301,16 @@ function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock
299 util.printout("Using dependencies pinned in lockfile: " .. filename) 301 util.printout("Using dependencies pinned in lockfile: " .. filename)
300 302
301 local get_versions = prepare_get_versions("none", rocks_provided, depskey) 303 local get_versions = prepare_get_versions("none", rocks_provided, depskey)
302 local dnsnamestr, dversionstr
303 for dnsname, dversion in deplocks.each(depskey) do 304 for dnsname, dversion in deplocks.each(depskey) do
304 if type(dnsname) == "string" then 305 local dname, dnamespace = util.split_namespace(dnsname)
305 dnsnamestr = dnsname 306 local dep = queries.new(dname, dnamespace, dversion)
306 end
307 if type(dversion) == "string" then
308 dversionstr = dversion
309 end
310 local dname, dnamespace = util.split_namespace(dnsnamestr)
311 local dep = queries.new(dname, dnamespace, dversionstr)
312 307
313 util.printout(("%s %s is pinned to %s (%s)"):format( 308 util.printout(("%s %s is pinned to %s (%s)"):format(
314 name, version, tostring(dep), rock_status(dep, get_versions))) 309 name, version, tostring(dep), rock_status(dep, get_versions)))
315 310
316 local okfullfill, errfullfill = deps.fulfill_dependency(dep, "none", rocks_provided, verify, depskey) 311 local ok, err = deps.fulfill_dependency(dep, "none", rocks_provided, verify, depskey)
317 if not okfullfill then 312 if not ok then
318 return nil, errfullfill 313 return nil, err
319 end 314 end
320 end 315 end
321 util.printout() 316 util.printout()
@@ -329,28 +324,28 @@ function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock
329 return nil, err 324 return nil, err
330 end 325 end
331 326
332 deps.report_missing_dependencies(name, version, (rockspec)[depskey], deps_mode, rocks_provided) 327 deps.report_missing_dependencies(name, version, rockspec[depskey], deps_mode, rocks_provided)
333 328
334 util.printout() 329 util.printout()
335 330
336 local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey) 331 local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey)
337 for _, dep in ipairs((rockspec)[depskey]) do 332 for _, dep in ipairs(rockspec[depskey]) do
338 333
339 util.printout(("%s %s depends on %s (%s)"):format( 334 util.printout(("%s %s depends on %s (%s)"):format(
340 name, version, tostring(dep), rock_status(dep, get_versions))) 335 name, version, tostring(dep), rock_status(dep, get_versions)))
341 336
342 local okfulfill, found_or_err, _ = deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey) 337 local ok, found_or_err, _, no_upgrade = deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey)
343 if okfulfill then 338 if ok then
344 deplocks.add(depskey, dep.name, found_or_err) 339 deplocks.add(depskey, dep.name, found_or_err)
345 else 340 else
346 341 if no_upgrade then
347 342 util.printerr("This version of "..name.." is designed for use with")
348 343 util.printerr(tostring(dep)..", but is configured to avoid upgrading it")
349 344 util.printerr("automatically. Please upgrade "..dep.name.." with")
350 345 util.printerr(" luarocks install "..dep.name)
351 346 util.printerr("or look for a suitable version of "..name.." with")
352 347 util.printerr(" luarocks search "..name)
353 348 end
354 return nil, found_or_err 349 return nil, found_or_err
355 end 350 end
356 end 351 end
@@ -358,28 +353,28 @@ function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock
358 return true 353 return true
359end 354end
360 355
361 356--- If filename matches a pattern, return the capture.
362 357-- For example, given "libfoo.so" and "lib?.so" is a pattern,
363 358-- returns "foo" (which can then be used to build names
364 359-- based on other patterns.
365 360-- @param file string: a filename
366 361-- @param pattern string: a pattern, where ? is to be matched by the filename.
367 362-- @return string The pattern, if found, or nil.
368local function deconstruct_pattern(file, pattern) 363local function deconstruct_pattern(file, pattern)
369 local depattern = "^" .. (pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")) .. "$" 364 local depattern = "^"..(pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")).."$"
370 return (file:match(depattern)) 365 return (file:match(depattern))
371end 366end
372 367
373 368--- Construct all possible patterns for a name and add to the files array.
374 369-- Run through the patterns array replacing all occurrences of "?"
375 370-- with the given file name and store them in the files array.
376 371-- @param file string A raw name (e.g. "foo")
377 372-- @param array of string An array of patterns with "?" as the wildcard
378 373-- (e.g. {"?.so", "lib?.so"})
379 374-- @param files The array of constructed names
380local function add_all_patterns(file, patterns, files) 375local function add_all_patterns(file, patterns, files)
381 for _, pattern in ipairs(patterns) do 376 for _, pattern in ipairs(patterns) do
382 table.insert(files, { #files + 1, (pattern:gsub("?", file)) }) 377 table.insert(files, {#files + 1, (pattern:gsub("?", file))})
383 end 378 end
384end 379end
385 380
@@ -393,7 +388,7 @@ local function get_external_deps_dirs(mode)
393 local dirs = { 388 local dirs = {
394 BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin }, 389 BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
395 INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include }, 390 INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
396 LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }, 391 LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }
397 } 392 }
398 if mode == "install" then 393 if mode == "install" then
399 dirs.INCDIR = nil 394 dirs.INCDIR = nil
@@ -421,7 +416,7 @@ local function resolve_prefix(prefix, dirs)
421end 416end
422 417
423local function add_patterns_for_file(files, file, patterns) 418local function add_patterns_for_file(files, file, patterns)
424 419 -- If it doesn't look like it contains a filename extension
425 if not (file:match("%.[a-z]+$") or file:match("%.[a-z]+%.")) then 420 if not (file:match("%.[a-z]+$") or file:match("%.[a-z]+%.")) then
426 add_all_patterns(file, patterns, files) 421 add_all_patterns(file, patterns, files)
427 else 422 else
@@ -431,35 +426,26 @@ local function add_patterns_for_file(files, file, patterns)
431 add_all_patterns(matched, patterns, files) 426 add_all_patterns(matched, patterns, files)
432 end 427 end
433 end 428 end
434 table.insert(files, { #files + 1, file }) 429 table.insert(files, {#files + 1, file})
435 end 430 end
436end 431end
437 432
438local function check_external_dependency_at( 433local function check_external_dependency_at(prefix, name, ext_files, vars, dirs, err_files, cache)
439 prefix,
440 name,
441 ext_files,
442 vars,
443 dirs,
444 err_files,
445 cache)
446
447 local fs = require("luarocks.fs") 434 local fs = require("luarocks.fs")
448 cache = cache or {} 435 cache = cache or {}
449 436
450 for dirname, dirdata in util.sortedpairs(dirs) do 437 for dirname, dirdata in util.sortedpairs(dirs) do
451 local paths 438 local paths
452 local path_var_value = vars[name .. "_" .. dirname] 439 local path_var_value = vars[name.."_"..dirname]
453 local dirdatastr = dirdata.subdir
454 if path_var_value then 440 if path_var_value then
455 paths = { path_var_value } 441 paths = { path_var_value }
456 elseif type(dirdatastr) == "table" then 442 elseif type(dirdata.subdir) == "table" then
457 paths = {} 443 paths = {}
458 for i, v in ipairs(dirdatastr) do 444 for i,v in ipairs(dirdata.subdir) do
459 paths[i] = dir.path(prefix, v) 445 paths[i] = dir.path(prefix, v)
460 end 446 end
461 else 447 else
462 paths = { dir.path(prefix, dirdatastr) } 448 paths = { dir.path(prefix, dirdata.subdir) }
463 end 449 end
464 local file_or_files = ext_files[dirdata.testfile] 450 local file_or_files = ext_files[dirdata.testfile]
465 if file_or_files then 451 if file_or_files then
@@ -485,15 +471,15 @@ local function check_external_dependency_at(
485 for _, fa in ipairs(files) do 471 for _, fa in ipairs(files) do
486 472
487 local f = fa[2] 473 local f = fa[2]
488 474 -- small convenience hack
489 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then 475 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
490 f = f:gsub("%.[^.]+$", "." .. cfg.external_lib_extension) 476 f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
491 end 477 end
492 478
493 local pattern 479 local pattern
494 if f:match("%*") then 480 if f:match("%*") then
495 pattern = "^" .. f:gsub("([-.+])", "%%%1"):gsub("%*", ".*") .. "$" 481 pattern = "^" .. f:gsub("([-.+])", "%%%1"):gsub("%*", ".*") .. "$"
496 f = "matching " .. f 482 f = "matching "..f
497 end 483 end
498 484
499 for _, d in ipairs(paths) do 485 for _, d in ipairs(paths) do
@@ -516,7 +502,7 @@ local function check_external_dependency_at(
516 dirdata.file = f 502 dirdata.file = f
517 break 503 break
518 else 504 else
519 table.insert(err_files[dirdata.testfile], f .. " in " .. d) 505 table.insert(err_files[dirdata.testfile], f.." in "..d)
520 end 506 end
521 end 507 end
522 if found then 508 if found then
@@ -527,9 +513,9 @@ local function check_external_dependency_at(
527 return nil, dirname, dirdata.testfile 513 return nil, dirname, dirdata.testfile
528 end 514 end
529 else 515 else
530 516 -- When we have a set of subdir suffixes, look for one that exists.
531 517 -- For these reason, we now put "lib" ahead of "" on Windows in our
532 518 -- default set.
533 dirdata.dir = paths[1] 519 dirdata.dir = paths[1]
534 for _, p in ipairs(paths) do 520 for _, p in ipairs(paths) do
535 if fs.exists(p) then 521 if fs.exists(p) then
@@ -541,23 +527,18 @@ local function check_external_dependency_at(
541 end 527 end
542 528
543 for dirname, dirdata in pairs(dirs) do 529 for dirname, dirdata in pairs(dirs) do
544 vars[name .. "_" .. dirname] = dirdata.dir 530 vars[name.."_"..dirname] = dirdata.dir
545 vars[name .. "_" .. dirname .. "_FILE"] = dirdata.file 531 vars[name.."_"..dirname.."_FILE"] = dirdata.file
546 end 532 end
547 vars[name .. "_DIR"] = prefix 533 vars[name.."_DIR"] = prefix
548 return true 534 return true
549end 535end
550 536
551local function check_external_dependency( 537local function check_external_dependency(name, ext_files, vars, mode, cache)
552 name,
553 ext_files,
554 vars,
555 mode,
556 cache)
557 local ok 538 local ok
558 local err_dirname 539 local err_dirname
559 local err_testfile 540 local err_testfile
560 local err_files = { program = {}, header = {}, library = {} } 541 local err_files = {program = {}, header = {}, library = {}}
561 542
562 local dirs = get_external_deps_dirs(mode) 543 local dirs = get_external_deps_dirs(mode)
563 544
@@ -591,7 +572,7 @@ local function check_external_dependency(
591end 572end
592 573
593function deps.autodetect_external_dependencies(build) 574function deps.autodetect_external_dependencies(build)
594 575 -- only applies to the 'builtin' build type
595 if not build or not build.modules then 576 if not build or not build.modules then
596 return nil 577 return nil
597 end 578 end
@@ -600,12 +581,9 @@ function deps.autodetect_external_dependencies(build)
600 local any = false 581 local any = false
601 for _, data in pairs(build.modules) do 582 for _, data in pairs(build.modules) do
602 if type(data) == "table" and data.libraries then 583 if type(data) == "table" and data.libraries then
603 local libraries 584 local libraries = data.libraries
604 local librariesstr = data.libraries 585 if type(libraries) == "string" then
605 if type(librariesstr) == "string" then 586 libraries = { libraries }
606 libraries = { librariesstr }
607 else
608 libraries = librariesstr
609 end 587 end
610 local incdirs = {} 588 local incdirs = {}
611 local libdirs = {} 589 local libdirs = {}
@@ -627,20 +605,21 @@ function deps.autodetect_external_dependencies(build)
627 return any and extdeps or nil 605 return any and extdeps or nil
628end 606end
629 607
630 608--- Set up path-related variables for external dependencies.
631 609-- For each key in the external_dependencies table in the
632 610-- rockspec file, four variables are created: <key>_DIR, <key>_BINDIR,
633 611-- <key>_INCDIR and <key>_LIBDIR. These are not overwritten
634 612-- if already set (e.g. by the LuaRocks config file or through the
635 613-- command-line). Values in the external_dependencies table
636 614-- are tables that may contain a "header" or a "library" field,
637 615-- with filenames to be tested for existence.
638 616-- @param rockspec table: The rockspec table.
639 617-- @param mode string: if "build" is given, checks all files;
640 618-- if "install" is given, do not scan for headers.
641 619-- @return boolean or (nil, string): True if no errors occurred, or
642 620-- nil and an error message if any test failed.
643function deps.check_external_deps(rockspec, mode) 621function deps.check_external_deps(rockspec, mode)
622 assert(rockspec:type() == "rockspec")
644 623
645 if not rockspec.external_dependencies then 624 if not rockspec.external_dependencies then
646 rockspec.external_dependencies = deps.autodetect_external_dependencies(rockspec.build) 625 rockspec.external_dependencies = deps.autodetect_external_dependencies(rockspec.build)
@@ -649,21 +628,21 @@ function deps.check_external_deps(rockspec, mode)
649 return true 628 return true
650 end 629 end
651 630
652 for name, ext_files in util.sortedpairs(rockspec.external_dependencies.queries) do 631 for name, ext_files in util.sortedpairs(rockspec.external_dependencies) do
653 local ok, err_dirname, err_testfile, err_files = check_external_dependency(name, ext_files, rockspec.variables, mode) 632 local ok, err_dirname, err_testfile, err_files = check_external_dependency(name, ext_files, rockspec.variables, mode)
654 if not ok then 633 if not ok then
655 local lines = { "Could not find " .. err_testfile .. " file for " .. name } 634 local lines = {"Could not find "..err_testfile.." file for "..name}
656 635
657 local err_paths = {} 636 local err_paths = {}
658 for _, err_file in ipairs(err_files[err_testfile]) do 637 for _, err_file in ipairs(err_files[err_testfile]) do
659 if not err_paths[err_file] then 638 if not err_paths[err_file] then
660 err_paths[err_file] = true 639 err_paths[err_file] = true
661 table.insert(lines, " No file " .. err_file) 640 table.insert(lines, " No file "..err_file)
662 end 641 end
663 end 642 end
664 643
665 table.insert(lines, "You may have to install " .. name .. " in your system and/or pass " .. name .. "_DIR or " .. name .. "_" .. err_dirname .. " to the luarocks command.") 644 table.insert(lines, "You may have to install "..name.." in your system and/or pass "..name.."_DIR or "..name.."_"..err_dirname.." to the luarocks command.")
666 table.insert(lines, "Example: luarocks install " .. rockspec.name .. " " .. name .. "_DIR=/usr/local") 645 table.insert(lines, "Example: luarocks install "..rockspec.name.." "..name.."_DIR=/usr/local")
667 646
668 return nil, table.concat(lines, "\n"), "dependency" 647 return nil, table.concat(lines, "\n"), "dependency"
669 end 648 end
@@ -671,17 +650,18 @@ function deps.check_external_deps(rockspec, mode)
671 return true 650 return true
672end 651end
673 652
674 653--- Recursively add satisfied dependencies of a package to a table,
675 654-- to build a transitive closure of all dependent packages.
676 655-- Additionally ensures that `dependencies` table of the manifest is up-to-date.
677 656-- @param results table: The results table being built, maps package names to versions.
678 657-- @param mdeps table: The manifest dependencies table.
679 658-- @param name string: Package name.
680 659-- @param version string: Package version.
681function deps.scan_deps(results, mdeps, name, version, deps_mode) 660function deps.scan_deps(results, mdeps, name, version, deps_mode)
682 assert(type(results) == "table") 661 assert(type(results) == "table")
683 assert(type(mdeps) == "table") 662 assert(type(mdeps) == "table")
684 assert(not name:match("/")) 663 assert(type(name) == "string" and not name:match("/"))
664 assert(type(version) == "string")
685 665
686 local fetch = require("luarocks.fetch") 666 local fetch = require("luarocks.fetch")
687 667
@@ -693,7 +673,7 @@ function deps.scan_deps(results, mdeps, name, version, deps_mode)
693 local dependencies = mdn[version] 673 local dependencies = mdn[version]
694 local rocks_provided 674 local rocks_provided
695 if not dependencies then 675 if not dependencies then
696 local rockspec = fetch.load_local_rockspec(path.rockspec_file(name, version), false) 676 local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version), false)
697 if not rockspec then 677 if not rockspec then
698 return 678 return
699 end 679 end
@@ -706,7 +686,7 @@ function deps.scan_deps(results, mdeps, name, version, deps_mode)
706 686
707 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies") 687 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies")
708 688
709 local matched = match_all_deps(dependencies.queries, get_versions) 689 local matched = match_all_deps(dependencies, get_versions)
710 results[name] = version 690 results[name] = version
711 for _, match in pairs(matched) do 691 for _, match in pairs(matched) do
712 deps.scan_deps(results, mdeps, match.name, match.version, deps_mode) 692 deps.scan_deps(results, mdeps, match.name, match.version, deps_mode)
@@ -756,13 +736,13 @@ local function find_lua_incdir(prefix, luaver, luajitver)
756 end 736 end
757 end 737 end
758 738
759 739 -- not found, will fallback to a default
760 return nil, mainerr 740 return nil, mainerr
761end 741end
762 742
763function deps.check_lua_incdir(vars) 743function deps.check_lua_incdir(vars)
764 if vars.LUA_INCDIR_OK == true then 744 if vars.LUA_INCDIR_OK == true
765 return true 745 then return true
766 end 746 end
767 747
768 local ljv = util.get_luajit_version() 748 local ljv = util.get_luajit_version()
@@ -789,8 +769,8 @@ function deps.check_lua_incdir(vars)
789end 769end
790 770
791function deps.check_lua_libdir(vars) 771function deps.check_lua_libdir(vars)
792 if vars.LUA_LIBDIR_OK == true then 772 if vars.LUA_LIBDIR_OK == true
793 return true 773 then return true
794 end 774 end
795 775
796 local fs = require("luarocks.fs") 776 local fs = require("luarocks.fs")
@@ -823,10 +803,10 @@ function deps.check_lua_libdir(vars)
823 local fd = io.open(filename, "r") 803 local fd = io.open(filename, "r")
824 if fd then 804 if fd then
825 if not vars.LUA_LIBDIR_FILE:match((cfg.lua_version:gsub("%.", "%%.?"))) then 805 if not vars.LUA_LIBDIR_FILE:match((cfg.lua_version:gsub("%.", "%%.?"))) then
826 806 -- if filename isn't versioned, check file contents
827 local txt = fd:read("*a") 807 local txt = fd:read("*a")
828 ok = txt:find("Lua " .. cfg.lua_version, 1, true) or 808 ok = txt:match("Lua " .. cfg.lua_version, 1, true)
829 txt:find("lua" .. (cfg.lua_version:gsub("%.", "")), 1, true) 809 or txt:match("lua" .. (cfg.lua_version:gsub("%.", "")), 1, true)
830 if not ok then 810 if not ok then
831 err = "Lua library at " .. filename .. " does not match Lua version " .. cfg.lua_version .. ". You can use `luarocks config variables.LUA_LIBDIR <path>` to set the correct location." 811 err = "Lua library at " .. filename .. " does not match Lua version " .. cfg.lua_version .. ". You can use `luarocks config variables.LUA_LIBDIR <path>` to set the correct location."
832 end 812 end