comparison share/lua/5.2/luarocks/deps.lua @ 1132:d137f631bad5

<GreyKnight> (cd luabuild/luarocks-2.0.12; make install)
author HackBot
date Fri, 14 Dec 2012 22:24:27 +0000
parents
children
comparison
equal deleted inserted replaced
1131:ff65dfb63263 1132:d137f631bad5
1
2 --- Dependency handling functions.
3 -- Dependencies are represented in LuaRocks through strings with
4 -- a package name followed by a comma-separated list of constraints.
5 -- Each constraint consists of an operator and a version number.
6 -- In this string format, version numbers are represented as
7 -- naturally as possible, like they are used by upstream projects
8 -- (e.g. "2.0beta3"). Internally, LuaRocks converts them to a purely
9 -- numeric representation, allowing comparison following some
10 -- "common sense" heuristics. The precise specification of the
11 -- comparison criteria is the source code of this module, but the
12 -- test/test_deps.lua file included with LuaRocks provides some
13 -- insights on what these criteria are.
14 module("luarocks.deps", package.seeall)
15
16 local cfg = require("luarocks.cfg")
17 local manif_core = require("luarocks.manif_core")
18 local path = require("luarocks.path")
19 local dir = require("luarocks.dir")
20 local util = require("luarocks.util")
21
22 local operators = {
23 ["=="] = "==",
24 ["~="] = "~=",
25 [">"] = ">",
26 ["<"] = "<",
27 [">="] = ">=",
28 ["<="] = "<=",
29 ["~>"] = "~>",
30 -- plus some convenience translations
31 [""] = "==",
32 ["="] = "==",
33 ["!="] = "~="
34 }
35
36 local deltas = {
37 scm = 1000,
38 cvs = 1000,
39 rc = -1000,
40 pre = -10000,
41 beta = -100000,
42 alpha = -1000000
43 }
44
45 local version_mt = {
46 --- Equality comparison for versions.
47 -- All version numbers must be equal.
48 -- If both versions have revision numbers, they must be equal;
49 -- otherwise the revision number is ignored.
50 -- @param v1 table: version table to compare.
51 -- @param v2 table: version table to compare.
52 -- @return boolean: true if they are considered equivalent.
53 __eq = function(v1, v2)
54 if #v1 ~= #v2 then
55 return false
56 end
57 for i = 1, #v1 do
58 if v1[i] ~= v2[i] then
59 return false
60 end
61 end
62 if v1.revision and v2.revision then
63 return (v1.revision == v2.revision)
64 end
65 return true
66 end,
67 --- Size comparison for versions.
68 -- All version numbers are compared.
69 -- If both versions have revision numbers, they are compared;
70 -- otherwise the revision number is ignored.
71 -- @param v1 table: version table to compare.
72 -- @param v2 table: version table to compare.
73 -- @return boolean: true if v1 is considered lower than v2.
74 __lt = function(v1, v2)
75 for i = 1, math.max(#v1, #v2) do
76 local v1i, v2i = v1[i] or 0, v2[i] or 0
77 if v1i ~= v2i then
78 return (v1i < v2i)
79 end
80 end
81 if v1.revision and v2.revision then
82 return (v1.revision < v2.revision)
83 end
84 return false
85 end
86 }
87
88 local version_cache = {}
89 setmetatable(version_cache, {
90 __mode = "kv"
91 })
92
93 --- Parse a version string, converting to table format.
94 -- A version table contains all components of the version string
95 -- converted to numeric format, stored in the array part of the table.
96 -- If the version contains a revision, it is stored numerically
97 -- in the 'revision' field. The original string representation of
98 -- the string is preserved in the 'string' field.
99 -- Returned version tables use a metatable
100 -- allowing later comparison through relational operators.
101 -- @param vstring string: A version number in string format.
102 -- @return table or nil: A version table or nil
103 -- if the input string contains invalid characters.
104 function parse_version(vstring)
105 if not vstring then return nil end
106 assert(type(vstring) == "string")
107
108 local cached = version_cache[vstring]
109 if cached then
110 return cached
111 end
112
113 local version = {}
114 local i = 1
115
116 local function add_token(number)
117 version[i] = version[i] and version[i] + number/100000 or number
118 i = i + 1
119 end
120
121 -- trim leading and trailing spaces
122 vstring = vstring:match("^%s*(.*)%s*$")
123 version.string = vstring
124 -- store revision separately if any
125 local main, revision = vstring:match("(.*)%-(%d+)$")
126 if revision then
127 vstring = main
128 version.revision = tonumber(revision)
129 end
130 while #vstring > 0 do
131 -- extract a number
132 local token, rest = vstring:match("^(%d+)[%.%-%_]*(.*)")
133 if token then
134 add_token(tonumber(token))
135 else
136 -- extract a word
137 token, rest = vstring:match("^(%a+)[%.%-%_]*(.*)")
138 if not token then
139 util.printerr("Warning: version number '"..vstring.."' could not be parsed.")
140 version[i] = 0
141 break
142 end
143 local last = #version
144 version[i] = deltas[token] or (token:byte() / 1000)
145 end
146 vstring = rest
147 end
148 setmetatable(version, version_mt)
149 version_cache[vstring] = version
150 return version
151 end
152
153 --- Utility function to compare version numbers given as strings.
154 -- @param a string: one version.
155 -- @param b string: another version.
156 -- @return boolean: True if a > b.
157 function compare_versions(a, b)
158 return parse_version(a) > parse_version(b)
159 end
160
161 --- Consumes a constraint from a string, converting it to table format.
162 -- For example, a string ">= 1.0, > 2.0" is converted to a table in the
163 -- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned
164 -- back to the caller.
165 -- @param input string: A list of constraints in string format.
166 -- @return (table, string) or nil: A table representing the same
167 -- constraints and the string with the unused input, or nil if the
168 -- input string is invalid.
169 local function parse_constraint(input)
170 assert(type(input) == "string")
171
172 local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)")
173 op = operators[op]
174 version = parse_version(version)
175 if not op or not version then return nil end
176 return { op = op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest
177 end
178
179 --- Convert a list of constraints from string to table format.
180 -- For example, a string ">= 1.0, < 2.0" is converted to a table in the format
181 -- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}.
182 -- Version tables use a metatable allowing later comparison through
183 -- relational operators.
184 -- @param input string: A list of constraints in string format.
185 -- @return table or nil: A table representing the same constraints,
186 -- or nil if the input string is invalid.
187 function parse_constraints(input)
188 assert(type(input) == "string")
189
190 local constraints, constraint = {}, nil
191 while #input > 0 do
192 constraint, input = parse_constraint(input)
193 if constraint then
194 table.insert(constraints, constraint)
195 else
196 return nil
197 end
198 end
199 return constraints
200 end
201
202 --- Convert a dependency from string to table format.
203 -- For example, a string "foo >= 1.0, < 2.0"
204 -- is converted to a table in the format
205 -- {name = "foo", constraints = {{op = ">=", version={1,0}},
206 -- {op = "<", version={2,0}}}}. Version tables use a metatable
207 -- allowing later comparison through relational operators.
208 -- @param dep string: A dependency in string format
209 -- as entered in rockspec files.
210 -- @return table or nil: A table representing the same dependency relation,
211 -- or nil if the input string is invalid.
212 function parse_dep(dep)
213 assert(type(dep) == "string")
214
215 local name, rest = dep:match("^%s*([a-zA-Z][a-zA-Z0-9%.%-%_]*)%s*(.*)")
216 if not name then return nil end
217 local constraints = parse_constraints(rest)
218 if not constraints then return nil end
219 return { name = name, constraints = constraints }
220 end
221
222 --- Convert a version table to a string.
223 -- @param v table: The version table
224 -- @param internal boolean or nil: Whether to display versions in their
225 -- internal representation format or how they were specified.
226 -- @return string: The dependency information pretty-printed as a string.
227 function show_version(v, internal)
228 assert(type(v) == "table")
229 assert(type(internal) == "boolean" or not internal)
230
231 return (internal
232 and table.concat(v, ":")..(v.revision and tostring(v.revision) or "")
233 or v.string)
234 end
235
236 --- Convert a dependency in table format to a string.
237 -- @param dep table: The dependency in table format
238 -- @param internal boolean or nil: Whether to display versions in their
239 -- internal representation format or how they were specified.
240 -- @return string: The dependency information pretty-printed as a string.
241 function show_dep(dep, internal)
242 assert(type(dep) == "table")
243 assert(type(internal) == "boolean" or not internal)
244
245 local pretty = {}
246 for _, c in ipairs(dep.constraints) do
247 table.insert(pretty, c.op .. " " .. show_version(c.version, internal))
248 end
249 return dep.name.." "..table.concat(pretty, ", ")
250 end
251
252 --- A more lenient check for equivalence between versions.
253 -- This returns true if the requested components of a version
254 -- match and ignore the ones that were not given. For example,
255 -- when requesting "2", then "2", "2.1", "2.3.5-9"... all match.
256 -- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2"
257 -- doesn't.
258 -- @param version string or table: Version to be tested; may be
259 -- in string format or already parsed into a table.
260 -- @param requested string or table: Version requested; may be
261 -- in string format or already parsed into a table.
262 -- @return boolean: True if the tested version matches the requested
263 -- version, false otherwise.
264 local function partial_match(version, requested)
265 assert(type(version) == "string" or type(version) == "table")
266 assert(type(requested) == "string" or type(version) == "table")
267
268 if type(version) ~= "table" then version = parse_version(version) end
269 if type(requested) ~= "table" then requested = parse_version(requested) end
270 if not version or not requested then return false end
271
272 for i, ri in ipairs(requested) do
273 local vi = version[i] or 0
274 if ri ~= vi then return false end
275 end
276 if requested.revision then
277 return requested.revision == version.revision
278 end
279 return true
280 end
281
282 --- Check if a version satisfies a set of constraints.
283 -- @param version table: A version in table format
284 -- @param constraints table: An array of constraints in table format.
285 -- @return boolean: True if version satisfies all constraints,
286 -- false otherwise.
287 function match_constraints(version, constraints)
288 assert(type(version) == "table")
289 assert(type(constraints) == "table")
290 local ok = true
291 setmetatable(version, version_mt)
292 for _, constr in pairs(constraints) do
293 local constr_version = constr.version
294 setmetatable(constr.version, version_mt)
295 if constr.op == "==" then ok = version == constr_version
296 elseif constr.op == "~=" then ok = version ~= constr_version
297 elseif constr.op == ">" then ok = version > constr_version
298 elseif constr.op == "<" then ok = version < constr_version
299 elseif constr.op == ">=" then ok = version >= constr_version
300 elseif constr.op == "<=" then ok = version <= constr_version
301 elseif constr.op == "~>" then ok = partial_match(version, constr_version)
302 end
303 if not ok then break end
304 end
305 return ok
306 end
307
308 --- Attempt to match a dependency to an installed rock.
309 -- @param dep table: A dependency parsed in table format.
310 -- @param blacklist table: Versions that can't be accepted. Table where keys
311 -- are program versions and values are 'true'.
312 -- @return table or nil: A table containing fields 'name' and 'version'
313 -- representing an installed rock which matches the given dependency,
314 -- or nil if it could not be matched.
315 local function match_dep(dep, blacklist, deps_mode)
316 assert(type(dep) == "table")
317
318 local versions
319 if dep.name == "lua" then
320 versions = { cfg.lua_version }
321 else
322 versions = manif_core.get_versions(dep.name, deps_mode)
323 end
324 if not versions then
325 return nil
326 end
327 if blacklist then
328 local i = 1
329 while versions[i] do
330 if blacklist[versions[i]] then
331 table.remove(versions, i)
332 else
333 i = i + 1
334 end
335 end
336 end
337 local candidates = {}
338 for _, vstring in ipairs(versions) do
339 local version = parse_version(vstring)
340 if match_constraints(version, dep.constraints) then
341 table.insert(candidates, version)
342 end
343 end
344 if #candidates == 0 then
345 return nil
346 else
347 table.sort(candidates)
348 return {
349 name = dep.name,
350 version = candidates[#candidates].string
351 }
352 end
353 end
354
355 --- Attempt to match dependencies of a rockspec to installed rocks.
356 -- @param rockspec table: The rockspec loaded as a table.
357 -- @param blacklist table or nil: Program versions to not use as valid matches.
358 -- Table where keys are program names and values are tables where keys
359 -- are program versions and values are 'true'.
360 -- @return table, table: A table where keys are dependencies parsed
361 -- in table format and values are tables containing fields 'name' and
362 -- version' representing matches, and a table of missing dependencies
363 -- parsed as tables.
364 function match_deps(rockspec, blacklist, deps_mode)
365 assert(type(rockspec) == "table")
366 assert(type(blacklist) == "table" or not blacklist)
367 local matched, missing, no_upgrade = {}, {}, {}
368
369 for _, dep in ipairs(rockspec.dependencies) do
370 local found = match_dep(dep, blacklist and blacklist[dep.name] or nil, deps_mode)
371 if found then
372 if dep.name ~= "lua" then
373 matched[dep] = found
374 end
375 else
376 if dep.constraints[1] and dep.constraints[1].no_upgrade then
377 no_upgrade[dep.name] = dep
378 else
379 missing[dep.name] = dep
380 end
381 end
382 end
383 return matched, missing, no_upgrade
384 end
385
386 --- Return a set of values of a table.
387 -- @param tbl table: The input table.
388 -- @return table: The array of keys.
389 local function values_set(tbl)
390 local set = {}
391 for _, v in pairs(tbl) do
392 set[v] = true
393 end
394 return set
395 end
396
397 --- Check dependencies of a rock and attempt to install any missing ones.
398 -- Packages are installed using the LuaRocks "install" command.
399 -- Aborts the program if a dependency could not be fulfilled.
400 -- @param rockspec table: A rockspec in table format.
401 -- @return boolean or (nil, string, [string]): True if no errors occurred, or
402 -- nil and an error message if any test failed, followed by an optional
403 -- error code.
404 function fulfill_dependencies(rockspec, deps_mode)
405
406 local search = require("luarocks.search")
407 local install = require("luarocks.install")
408
409 if rockspec.supported_platforms then
410 if not platforms_set then
411 platforms_set = values_set(cfg.platforms)
412 end
413 local supported = nil
414 for _, plat in pairs(rockspec.supported_platforms) do
415 local neg, plat = plat:match("^(!?)(.*)")
416 if neg == "!" then
417 if platforms_set[plat] then
418 return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
419 end
420 else
421 if platforms_set[plat] then
422 supported = true
423 else
424 if supported == nil then
425 supported = false
426 end
427 end
428 end
429 end
430 if supported == false then
431 local plats = table.concat(cfg.platforms, ", ")
432 return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms."
433 end
434 end
435
436 local matched, missing, no_upgrade = match_deps(rockspec, nil, deps_mode)
437
438 if next(no_upgrade) then
439 util.printerr("Missing dependencies for "..rockspec.name.." "..rockspec.version..":")
440 for _, dep in pairs(no_upgrade) do
441 util.printerr(show_dep(dep))
442 end
443 if next(missing) then
444 for _, dep in pairs(missing) do
445 util.printerr(show_dep(dep))
446 end
447 end
448 util.printerr()
449 for _, dep in pairs(no_upgrade) do
450 util.printerr("This version of "..rockspec.name.." is designed for use with")
451 util.printerr(show_dep(dep)..", but is configured to avoid upgrading it")
452 util.printerr("automatically. Please upgrade "..dep.name.." with")
453 util.printerr(" luarocks install "..dep.name)
454 util.printerr("or choose an older version of "..rockspec.name.." with")
455 util.printerr(" luarocks search "..rockspec.name)
456 end
457 return nil, "Failed matching dependencies."
458 end
459
460 if next(missing) then
461 util.printerr()
462 util.printerr("Missing dependencies for "..rockspec.name..":")
463 for _, dep in pairs(missing) do
464 util.printerr(show_dep(dep))
465 end
466 util.printerr()
467
468 for _, dep in pairs(missing) do
469 -- Double-check in case dependency was filled during recursion.
470 if not match_dep(dep, nil, deps_mode) then
471 local rock = search.find_suitable_rock(dep)
472 if not rock then
473 return nil, "Could not satisfy dependency: "..show_dep(dep)
474 end
475 local ok, err, errcode = install.run(rock)
476 if not ok then
477 return nil, "Failed installing dependency: "..rock.." - "..err, errcode
478 end
479 end
480 end
481 end
482 return true
483 end
484
485 --- If filename matches a pattern, return the capture.
486 -- For example, given "libfoo.so" and "lib?.so" is a pattern,
487 -- returns "foo" (which can then be used to build names
488 -- based on other patterns.
489 -- @param file string: a filename
490 -- @param pattern string: a pattern, where ? is to be matched by the filename.
491 -- @return string The pattern, if found, or nil.
492 local function deconstruct_pattern(file, pattern)
493 local depattern = "^"..(pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")).."$"
494 return (file:match(depattern))
495 end
496
497 --- Construct all possible patterns for a name and add to the files array.
498 -- Run through the patterns array replacing all occurrences of "?"
499 -- with the given file name and store them in the files array.
500 -- @param file string A raw name (e.g. "foo")
501 -- @param array of string An array of patterns with "?" as the wildcard
502 -- (e.g. {"?.so", "lib?.so"})
503 -- @param files The array of constructed names
504 local function add_all_patterns(file, patterns, files)
505 for _, pattern in ipairs(patterns) do
506 table.insert(files, (pattern:gsub("?", file)))
507 end
508 end
509
510 --- Set up path-related variables for external dependencies.
511 -- For each key in the external_dependencies table in the
512 -- rockspec file, four variables are created: <key>_DIR, <key>_BINDIR,
513 -- <key>_INCDIR and <key>_LIBDIR. These are not overwritten
514 -- if already set (e.g. by the LuaRocks config file or through the
515 -- command-line). Values in the external_dependencies table
516 -- are tables that may contain a "header" or a "library" field,
517 -- with filenames to be tested for existence.
518 -- @param rockspec table: The rockspec table.
519 -- @param mode string: if "build" is given, checks all files;
520 -- if "install" is given, do not scan for headers.
521 -- @return boolean or (nil, string): True if no errors occurred, or
522 -- nil and an error message if any test failed.
523 function check_external_deps(rockspec, mode)
524 assert(type(rockspec) == "table")
525
526 local fs = require("luarocks.fs")
527
528 local vars = rockspec.variables
529 local patterns = cfg.external_deps_patterns
530 local subdirs = cfg.external_deps_subdirs
531 if mode == "install" then
532 patterns = cfg.runtime_external_deps_patterns
533 subdirs = cfg.runtime_external_deps_subdirs
534 end
535 if rockspec.external_dependencies then
536 for name, files in pairs(rockspec.external_dependencies) do
537 local ok = true
538 local failed_file = nil
539 local failed_dirname = nil
540 for _, extdir in ipairs(cfg.external_deps_dirs) do
541 ok = true
542 local prefix = vars[name.."_DIR"]
543 local dirs = {
544 BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
545 INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
546 LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }
547 }
548 if mode == "install" then
549 dirs.INCDIR = nil
550 end
551 if not prefix then
552 prefix = extdir
553 end
554 if type(prefix) == "table" then
555 if prefix.bin then
556 dirs.BINDIR.subdir = prefix.bin
557 end
558 if prefix.include then
559 if dirs.INCDIR then
560 dirs.INCDIR.subdir = prefix.include
561 end
562 end
563 if prefix.lib then
564 dirs.LIBDIR.subdir = prefix.lib
565 end
566 prefix = prefix.prefix
567 end
568 for dirname, dirdata in pairs(dirs) do
569 dirdata.dir = vars[name.."_"..dirname] or dir.path(prefix, dirdata.subdir)
570 local file = files[dirdata.testfile]
571 if file then
572 local files = {}
573 if not file:match("%.") then
574 add_all_patterns(file, dirdata.pattern, files)
575 else
576 for _, pattern in ipairs(dirdata.pattern) do
577 local matched = deconstruct_pattern(file, pattern)
578 if matched then
579 add_all_patterns(matched, dirdata.pattern, files)
580 end
581 end
582 table.insert(files, file)
583 end
584 local found = false
585 failed_file = nil
586 for _, f in pairs(files) do
587 -- small convenience hack
588 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
589 f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
590 end
591 if f:match("%*") then
592 local replaced = f:gsub("%.", "%%."):gsub("%*", ".*")
593 for _, entry in ipairs(fs.list_dir(dirdata.dir)) do
594 if entry:match(replaced) then
595 found = true
596 break
597 end
598 end
599 else
600 found = fs.is_file(dir.path(dirdata.dir, f))
601 end
602 if found then
603 break
604 else
605 if failed_file then
606 failed_file = failed_file .. ", or " .. f
607 else
608 failed_file = f
609 end
610 end
611 end
612 if not found then
613 ok = false
614 failed_dirname = dirname
615 break
616 end
617 end
618 end
619 if ok then
620 for dirname, dirdata in pairs(dirs) do
621 vars[name.."_"..dirname] = dirdata.dir
622 end
623 vars[name.."_DIR"] = prefix
624 break
625 end
626 end
627 if not ok then
628 return nil, "Could not find expected file "..failed_file.." for "..name.." -- you may have to install "..name.." in your system and/or pass "..name.."_DIR or "..name.."_"..failed_dirname.." to the luarocks command. Example: luarocks install "..rockspec.name.." "..name.."_DIR=/usr/local", "dependency"
629 end
630 end
631 end
632 return true
633 end
634
635 --- Recursively scan dependencies, to build a transitive closure of all
636 -- dependent packages.
637 -- @param results table: The results table being built.
638 -- @param missing table: The table of missing dependencies being recursively built.
639 -- @param manifest table: The manifest table containing dependencies.
640 -- @param name string: Package name.
641 -- @param version string: Package version.
642 -- @return (table, table): The results and a table of missing dependencies.
643 function scan_deps(results, missing, manifest, name, version, deps_mode)
644 assert(type(results) == "table")
645 assert(type(missing) == "table")
646 assert(type(manifest) == "table")
647 assert(type(name) == "string")
648 assert(type(version) == "string")
649
650 local fetch = require("luarocks.fetch")
651
652 local err
653 if results[name] then
654 return results, missing
655 end
656 if not manifest.dependencies then manifest.dependencies = {} end
657 local dependencies = manifest.dependencies
658 if not dependencies[name] then dependencies[name] = {} end
659 local dependencies_name = dependencies[name]
660 local deplist = dependencies_name[version]
661 local rockspec, err
662 if not deplist then
663 rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version))
664 if err then
665 missing[name.." "..version] = err
666 return results, missing
667 end
668 dependencies_name[version] = rockspec.dependencies
669 else
670 rockspec = { dependencies = deplist }
671 end
672 local matched, failures = match_deps(rockspec, nil, deps_mode)
673 for _, match in pairs(matched) do
674 results, missing = scan_deps(results, missing, manifest, match.name, match.version, deps_mode)
675 end
676 if next(failures) then
677 for _, failure in pairs(failures) do
678 missing[show_dep(failure)] = "failed"
679 end
680 end
681 results[name] = version
682 return results, missing
683 end
684
685 local valid_deps_modes = {
686 one = true,
687 order = true,
688 all = true,
689 none = true,
690 }
691
692 function check_deps_mode_flag(flag)
693 return valid_deps_modes[flag]
694 end
695
696 function get_deps_mode(flags)
697 if flags["deps-mode"] then
698 return flags["deps-mode"]
699 else
700 return cfg.deps_mode
701 end
702 end
703
704 function deps_mode_to_flag(deps_mode)
705 return "--deps-mode="..deps_mode
706 end