diff options
Diffstat (limited to 'src/luarocks/deps.lua')
-rw-r--r-- | src/luarocks/deps.lua | 316 |
1 files changed, 216 insertions, 100 deletions
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua index 7f695d9c..d54c30de 100644 --- a/src/luarocks/deps.lua +++ b/src/luarocks/deps.lua | |||
@@ -11,64 +11,114 @@ local util = require("luarocks.util") | |||
11 | local vers = require("luarocks.core.vers") | 11 | local vers = require("luarocks.core.vers") |
12 | local queries = require("luarocks.queries") | 12 | local queries = require("luarocks.queries") |
13 | local builtin = require("luarocks.build.builtin") | 13 | local builtin = require("luarocks.build.builtin") |
14 | local deplocks = require("luarocks.deplocks") | ||
14 | 15 | ||
15 | --- Attempt to match a dependency to an installed rock. | 16 | --- Generate a function that matches dep queries against the manifest, |
16 | -- @param dep table: A dependency parsed in table format. | 17 | -- taking into account rocks_provided, the blacklist and the lockfile. |
17 | -- @param blacklist table: Versions that can't be accepted. Table where keys | 18 | -- @param deps_mode "one", "none", "all" or "order" |
18 | -- are program versions and values are 'true'. | 19 | -- @param rocks_provided a one-level table mapping names to versions, |
20 | -- listing rocks to consider provided by the VM | ||
19 | -- @param rocks_provided table: A table of auto-provided dependencies. | 21 | -- @param rocks_provided table: A table of auto-provided dependencies. |
20 | -- by this Lua implementation for the given dependency. | 22 | -- by this Lua implementation for the given dependency. |
21 | -- @return string or nil: latest installed version of the rock matching the dependency | 23 | -- @param depskey key to use when matching the lockfile ("dependencies", |
22 | -- or nil if it could not be matched. | 24 | -- "build_dependencies", etc.) |
23 | local function match_dep(dep, blacklist, deps_mode, rocks_provided) | 25 | -- @param blacklist a two-level table mapping names to versions to boolean, |
24 | assert(type(dep) == "table") | 26 | -- listing rocks to not match |
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 | ||
32 | local function prepare_get_versions(deps_mode, rocks_provided, depskey, blacklist) | ||
33 | assert(type(deps_mode) == "string") | ||
25 | assert(type(rocks_provided) == "table") | 34 | assert(type(rocks_provided) == "table") |
26 | 35 | assert(type(depskey) == "string") | |
27 | local versions, locations | 36 | assert(type(blacklist) == "table" or blacklist == nil) |
28 | local provided = rocks_provided[dep.name] | 37 | |
29 | if provided then | 38 | return function(dep) |
30 | -- Provided rocks have higher priority than manifest's rocks. | 39 | local versions, locations |
31 | versions, locations = { provided }, {} | 40 | local provided = rocks_provided[dep.name] |
32 | else | 41 | if provided then |
33 | versions, locations = manif.get_versions(dep, deps_mode) | 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 blacklist and blacklist[dep.name] then | ||
52 | local orig_versions = versions | ||
53 | versions = {} | ||
54 | for _, v in ipairs(orig_versions) do | ||
55 | if not blacklist[dep.name][v] then | ||
56 | table.insert(versions, v) | ||
57 | end | ||
58 | end | ||
59 | end | ||
60 | |||
61 | local lockversion = deplocks.get(depskey, dep.name) | ||
62 | |||
63 | return versions, locations, lockversion, provided ~= nil | ||
34 | end | 64 | end |
65 | end | ||
35 | 66 | ||
67 | --- Attempt to match a dependency to an installed rock. | ||
68 | -- @param blacklist table: Versions that can't be accepted. Table where keys | ||
69 | -- are program versions and values are 'true'. | ||
70 | -- @param get_versions a getter function obtained via prepare_get_versions | ||
71 | -- @return (string, string, table) or (nil, nil, table): | ||
72 | -- 1. latest installed version of the rock matching the dependency | ||
73 | -- 2. location where the installed version is installed | ||
74 | -- 3. the 'dep' query table | ||
75 | -- 4. true if provided via VM | ||
76 | -- or | ||
77 | -- 1. nil | ||
78 | -- 2. nil | ||
79 | -- 3. either 'dep' or an alternative query to be used | ||
80 | -- 4. false | ||
81 | local function match_dep(dep, get_versions) | ||
82 | assert(type(dep) == "table") | ||
83 | assert(type(get_versions) == "function") | ||
84 | |||
85 | local versions, locations, lockversion, provided = get_versions(dep) | ||
86 | |||
36 | local latest_version | 87 | local latest_version |
37 | local latest_vstring | 88 | local latest_vstring |
38 | for _, vstring in ipairs(versions) do | 89 | for _, vstring in ipairs(versions) do |
39 | if not blacklist or not blacklist[vstring] then | 90 | local version = vers.parse_version(vstring) |
40 | local version = vers.parse_version(vstring) | 91 | if vers.match_constraints(version, dep.constraints) then |
41 | if vers.match_constraints(version, dep.constraints) then | 92 | if not latest_version or version > latest_version then |
42 | if not latest_version or version > latest_version then | 93 | latest_version = version |
43 | latest_version = version | 94 | latest_vstring = vstring |
44 | latest_vstring = vstring | ||
45 | end | ||
46 | end | 95 | end |
47 | end | 96 | end |
48 | end | 97 | end |
49 | return latest_vstring, locations[latest_vstring] | 98 | |
99 | if lockversion and not locations[lockversion] then | ||
100 | local latest_matching_msg = "" | ||
101 | if latest_vstring and latest_vstring ~= lockversion then | ||
102 | latest_matching_msg = " (latest matching is " .. latest_vstring .. ")" | ||
103 | end | ||
104 | util.printout("Forcing " .. dep.name .. " to pinned version " .. lockversion .. latest_matching_msg) | ||
105 | return nil, nil, queries.new(dep.name, lockversion) | ||
106 | end | ||
107 | |||
108 | return latest_vstring, locations[latest_vstring], dep, provided | ||
50 | end | 109 | end |
51 | 110 | ||
52 | --- Attempt to match dependencies of a rockspec to installed rocks. | 111 | local function match_all_deps(dependencies, get_versions) |
53 | -- @param dependencies table: The table of dependencies. | 112 | assert(type(dependencies) == "table") |
54 | -- @param rocks_provided table: The table of auto-provided dependencies. | 113 | assert(type(get_versions) == "function") |
55 | -- @param blacklist table or nil: Program versions to not use as valid matches. | 114 | |
56 | -- Table where keys are program names and values are tables where keys | ||
57 | -- are program versions and values are 'true'. | ||
58 | -- @return table, table, table: A table where keys are dependencies parsed | ||
59 | -- in table format and values are tables containing fields 'name' and | ||
60 | -- version' representing matches; a table of missing dependencies | ||
61 | -- parsed as tables; and a table of "no-upgrade" missing dependencies | ||
62 | -- (to be used in plugin modules so that a plugin does not force upgrade of | ||
63 | -- its parent application). | ||
64 | function deps.match_deps(dependencies, rocks_provided, blacklist, deps_mode) | ||
65 | assert(type(blacklist) == "table" or not blacklist) | ||
66 | local matched, missing, no_upgrade = {}, {}, {} | 115 | local matched, missing, no_upgrade = {}, {}, {} |
67 | 116 | ||
68 | for _, dep in ipairs(dependencies) do | 117 | for _, dep in ipairs(dependencies) do |
69 | local found = match_dep(dep, blacklist and blacklist[dep.name] or nil, deps_mode, rocks_provided) | 118 | local found, _, provided |
119 | found, _, dep, provided = match_dep(dep, get_versions) | ||
70 | if found then | 120 | if found then |
71 | if not rocks_provided[dep.name] then | 121 | if not provided then |
72 | matched[dep] = {name = dep.name, version = found} | 122 | matched[dep] = {name = dep.name, version = found} |
73 | end | 123 | end |
74 | else | 124 | else |
@@ -82,20 +132,35 @@ function deps.match_deps(dependencies, rocks_provided, blacklist, deps_mode) | |||
82 | return matched, missing, no_upgrade | 132 | return matched, missing, no_upgrade |
83 | end | 133 | end |
84 | 134 | ||
85 | --- Return a set of values of a table. | 135 | --- Attempt to match dependencies of a rockspec to installed rocks. |
86 | -- @param tbl table: The input table. | 136 | -- @param dependencies table: The table of dependencies. |
87 | -- @return table: The array of keys. | 137 | -- @param rocks_provided table: The table of auto-provided dependencies. |
88 | local function values_set(tbl) | 138 | -- @param blacklist table or nil: Program versions to not use as valid matches. |
89 | local set = {} | 139 | -- Table where keys are program names and values are tables where keys |
90 | for _, v in pairs(tbl) do | 140 | -- are program versions and values are 'true'. |
91 | set[v] = true | 141 | -- @param deps_mode string: Which trees to check dependencies for |
92 | end | 142 | -- @return table, table, table: A table where keys are dependencies parsed |
93 | return set | 143 | -- in table format and values are tables containing fields 'name' and |
144 | -- version' representing matches; a table of missing dependencies | ||
145 | -- parsed as tables; and a table of "no-upgrade" missing dependencies | ||
146 | -- (to be used in plugin modules so that a plugin does not force upgrade of | ||
147 | -- its parent application). | ||
148 | function deps.match_deps(dependencies, rocks_provided, blacklist, deps_mode) | ||
149 | assert(type(dependencies) == "table") | ||
150 | assert(type(rocks_provided) == "table") | ||
151 | assert(type(blacklist) == "table" or blacklist == nil) | ||
152 | assert(type(deps_mode) == "string") | ||
153 | |||
154 | local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies", blacklist) | ||
155 | return match_all_deps(dependencies, get_versions) | ||
94 | end | 156 | end |
95 | 157 | ||
96 | local function rock_status(name, deps_mode, rocks_provided) | 158 | local function rock_status(name, get_versions) |
97 | local installed = match_dep(queries.new(name), nil, deps_mode, rocks_provided) | 159 | assert(type(name) == "string") |
98 | local installation_type = rocks_provided[name] and "provided by VM" or "installed" | 160 | assert(type(get_versions) == "function") |
161 | |||
162 | local installed, _, _, provided = match_dep(queries.new(name), get_versions) | ||
163 | local installation_type = provided and "provided by VM" or "installed" | ||
99 | return installed and installed.." "..installation_type or "not installed" | 164 | return installed and installed.." "..installation_type or "not installed" |
100 | end | 165 | end |
101 | 166 | ||
@@ -103,7 +168,7 @@ end | |||
103 | -- @param name string: package name. | 168 | -- @param name string: package name. |
104 | -- @param version string: package version. | 169 | -- @param version string: package version. |
105 | -- @param dependencies table: array of dependencies. | 170 | -- @param dependencies table: array of dependencies. |
106 | -- @param deps_mode string: Which trees to check dependencies for: | 171 | -- @param deps_mode string: Which trees to check dependencies for |
107 | -- @param rocks_provided table: A table of auto-dependencies provided | 172 | -- @param rocks_provided table: A table of auto-dependencies provided |
108 | -- by this Lua implementation for the given dependency. | 173 | -- by this Lua implementation for the given dependency. |
109 | -- "one" for the current default tree, "all" for all trees, | 174 | -- "one" for the current default tree, "all" for all trees, |
@@ -114,58 +179,53 @@ function deps.report_missing_dependencies(name, version, dependencies, deps_mode | |||
114 | assert(type(dependencies) == "table") | 179 | assert(type(dependencies) == "table") |
115 | assert(type(deps_mode) == "string") | 180 | assert(type(deps_mode) == "string") |
116 | assert(type(rocks_provided) == "table") | 181 | assert(type(rocks_provided) == "table") |
182 | |||
183 | if deps_mode == "none" then | ||
184 | return | ||
185 | end | ||
186 | |||
187 | local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies") | ||
117 | 188 | ||
118 | local first_missing_dep = true | 189 | local first_missing_dep = true |
119 | 190 | ||
120 | for _, dep in ipairs(dependencies) do | 191 | for _, dep in ipairs(dependencies) do |
121 | if not match_dep(dep, nil, deps_mode, rocks_provided) then | 192 | local found, _ |
193 | found, _, dep = match_dep(dep, get_versions) | ||
194 | if not found then | ||
122 | if first_missing_dep then | 195 | if first_missing_dep then |
123 | util.printout(("Missing dependencies for %s %s:"):format(name, version)) | 196 | util.printout(("Missing dependencies for %s %s:"):format(name, version)) |
124 | first_missing_dep = false | 197 | first_missing_dep = false |
125 | end | 198 | end |
126 | 199 | ||
127 | util.printout((" %s (%s)"):format(tostring(dep), rock_status(dep.name, deps_mode, rocks_provided))) | 200 | util.printout((" %s (%s)"):format(tostring(dep), rock_status(dep.name, get_versions))) |
128 | end | 201 | end |
129 | end | 202 | end |
130 | end | 203 | end |
131 | 204 | ||
132 | function deps.fulfill_dependency(dep, deps_mode, name, version, rocks_provided, verify) | 205 | function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey) |
133 | assert(dep:type() == "query") | 206 | assert(dep:type() == "query") |
134 | assert(type(deps_mode) == "string" or deps_mode == nil) | 207 | assert(type(deps_mode) == "string" or deps_mode == nil) |
135 | assert(type(name) == "string" or name == nil) | ||
136 | assert(type(version) == "string" or version == nil) | ||
137 | assert(type(rocks_provided) == "table" or rocks_provided == nil) | 208 | assert(type(rocks_provided) == "table" or rocks_provided == nil) |
138 | assert(type(verify) == "boolean" or verify == nil) | 209 | assert(type(verify) == "boolean" or verify == nil) |
210 | assert(type(depskey) == "string") | ||
211 | |||
139 | deps_mode = deps_mode or "all" | 212 | deps_mode = deps_mode or "all" |
140 | rocks_provided = rocks_provided or {} | 213 | rocks_provided = rocks_provided or {} |
141 | 214 | ||
142 | local found, where = match_dep(dep, nil, deps_mode, rocks_provided) | 215 | local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey) |
216 | |||
217 | local found, where | ||
218 | found, where, dep = match_dep(dep, get_versions) | ||
143 | if found then | 219 | if found then |
220 | local tree_manifests = manif.load_rocks_tree_manifests(deps_mode) | ||
221 | manif.scan_dependencies(dep.name, found, tree_manifests, deplocks.proxy(depskey)) | ||
144 | return true, found, where | 222 | return true, found, where |
145 | end | 223 | end |
146 | 224 | ||
147 | local search = require("luarocks.search") | 225 | local search = require("luarocks.search") |
148 | local install = require("luarocks.cmd.install") | 226 | local install = require("luarocks.cmd.install") |
149 | 227 | ||
150 | if name and version then | 228 | local url, search_err = search.find_suitable_rock(dep, true) |
151 | util.printout(("%s %s depends on %s (%s)"):format( | ||
152 | name, version, tostring(dep), rock_status(dep.name, deps_mode, rocks_provided))) | ||
153 | else | ||
154 | util.printout(("Fulfilling dependency on %s (%s)"):format( | ||
155 | tostring(dep), rock_status(dep.name, deps_mode, rocks_provided))) | ||
156 | end | ||
157 | |||
158 | if dep.constraints[1] and dep.constraints[1].no_upgrade then | ||
159 | util.printerr("This version of "..name.." is designed for use with") | ||
160 | util.printerr(tostring(dep)..", but is configured to avoid upgrading it") | ||
161 | util.printerr("automatically. Please upgrade "..dep.name.." with") | ||
162 | util.printerr(" luarocks install "..dep.name) | ||
163 | util.printerr("or choose an older version of "..name.." with") | ||
164 | util.printerr(" luarocks search "..name) | ||
165 | return nil, "Failed matching dependencies" | ||
166 | end | ||
167 | |||
168 | local url, search_err = search.find_suitable_rock(dep) | ||
169 | if not url then | 229 | if not url then |
170 | return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err | 230 | return nil, "Could not satisfy dependency "..tostring(dep)..": "..search_err |
171 | end | 231 | end |
@@ -181,27 +241,12 @@ function deps.fulfill_dependency(dep, deps_mode, name, version, rocks_provided, | |||
181 | return nil, "Failed installing dependency: "..url.." - "..install_err, errcode | 241 | return nil, "Failed installing dependency: "..url.." - "..install_err, errcode |
182 | end | 242 | end |
183 | 243 | ||
184 | found, where = match_dep(dep, nil, deps_mode, rocks_provided) | 244 | found, where = match_dep(dep, get_versions) |
185 | assert(found) | 245 | assert(found) |
186 | return true, found, where | 246 | return true, found, where |
187 | end | 247 | end |
188 | 248 | ||
189 | --- Check dependencies of a rock and attempt to install any missing ones. | 249 | local function check_supported_platforms(rockspec) |
190 | -- Packages are installed using the LuaRocks "install" command. | ||
191 | -- Aborts the program if a dependency could not be fulfilled. | ||
192 | -- @param rockspec table: A rockspec in table format. | ||
193 | -- @param depskey string: Rockspec key to fetch to get dependency table. | ||
194 | -- @param deps_mode string | ||
195 | -- @param verify boolean | ||
196 | -- @return boolean or (nil, string, [string]): True if no errors occurred, or | ||
197 | -- nil and an error message if any test failed, followed by an optional | ||
198 | -- error code. | ||
199 | function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify) | ||
200 | assert(type(rockspec) == "table") | ||
201 | assert(type(depskey) == "string") | ||
202 | assert(type(deps_mode) == "string") | ||
203 | assert(type(verify) == "boolean" or verify == nil) | ||
204 | |||
205 | if rockspec.supported_platforms and next(rockspec.supported_platforms) then | 250 | if rockspec.supported_platforms and next(rockspec.supported_platforms) then |
206 | local all_negative = true | 251 | local all_negative = true |
207 | local supported = false | 252 | local supported = false |
@@ -225,14 +270,82 @@ function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify) | |||
225 | return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms." | 270 | return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms." |
226 | end | 271 | end |
227 | end | 272 | end |
273 | |||
274 | return true | ||
275 | end | ||
228 | 276 | ||
229 | deps.report_missing_dependencies(rockspec.name, rockspec.version, rockspec[depskey], deps_mode, rockspec.rocks_provided) | 277 | --- Check dependencies of a rock and attempt to install any missing ones. |
278 | -- Packages are installed using the LuaRocks "install" command. | ||
279 | -- Aborts the program if a dependency could not be fulfilled. | ||
280 | -- @param rockspec table: A rockspec in table format. | ||
281 | -- @param depskey string: Rockspec key to fetch to get dependency table. | ||
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. | ||
288 | function 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 dname, dversion in deplocks.each(depskey) do | ||
305 | local dep = queries.new(dname, dversion) | ||
306 | |||
307 | util.printout(("%s %s is pinned to %s (%s)"):format( | ||
308 | name, version, tostring(dep), rock_status(dep.name, get_versions))) | ||
309 | |||
310 | local ok, err = deps.fulfill_dependency(dep, "none", rocks_provided, verify, depskey) | ||
311 | if not ok then | ||
312 | return nil, err | ||
313 | end | ||
314 | end | ||
315 | util.printout() | ||
316 | return true | ||
317 | elseif err then | ||
318 | util.warning(err) | ||
319 | end | ||
320 | |||
321 | ok, err = check_supported_platforms(rockspec) | ||
322 | if not ok then | ||
323 | return nil, err | ||
324 | end | ||
325 | |||
326 | deps.report_missing_dependencies(name, version, rockspec[depskey], deps_mode, rocks_provided) | ||
230 | 327 | ||
231 | util.printout() | 328 | util.printout() |
329 | |||
330 | local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey) | ||
232 | for _, dep in ipairs(rockspec[depskey]) do | 331 | for _, dep in ipairs(rockspec[depskey]) do |
233 | local ok, err = deps.fulfill_dependency(dep, deps_mode, rockspec.name, rockspec.version, rockspec.rocks_provided, verify) | 332 | |
234 | if not ok then | 333 | util.printout(("%s %s depends on %s (%s)"):format( |
235 | return nil, err | 334 | name, version, tostring(dep), rock_status(dep.name, get_versions))) |
335 | |||
336 | local ok, found_or_err, _, no_upgrade = deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey) | ||
337 | if ok then | ||
338 | deplocks.add(depskey, dep.name, found_or_err) | ||
339 | else | ||
340 | if no_upgrade then | ||
341 | util.printerr("This version of "..name.." is designed for use with") | ||
342 | util.printerr(tostring(dep)..", but is configured to avoid upgrading it") | ||
343 | util.printerr("automatically. Please upgrade "..dep.name.." with") | ||
344 | util.printerr(" luarocks install "..dep.name) | ||
345 | util.printerr("or look for a suitable version of "..name.." with") | ||
346 | util.printerr(" luarocks search "..name) | ||
347 | end | ||
348 | return nil, found_or_err | ||
236 | end | 349 | end |
237 | end | 350 | end |
238 | 351 | ||
@@ -500,7 +613,10 @@ function deps.scan_deps(results, manifest, name, version, deps_mode) | |||
500 | else | 613 | else |
501 | rocks_provided = util.get_rocks_provided() | 614 | rocks_provided = util.get_rocks_provided() |
502 | end | 615 | end |
503 | local matched = deps.match_deps(dependencies, rocks_provided, nil, deps_mode) | 616 | |
617 | local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies") | ||
618 | |||
619 | local matched = match_all_deps(dependencies, get_versions) | ||
504 | results[name] = version | 620 | results[name] = version |
505 | for _, match in pairs(matched) do | 621 | for _, match in pairs(matched) do |
506 | deps.scan_deps(results, manifest, match.name, match.version, deps_mode) | 622 | deps.scan_deps(results, manifest, match.name, match.version, deps_mode) |