aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHisham Muhammad <hisham@gobolinux.org>2016-11-03 18:16:02 -0200
committerGitHub <noreply@github.com>2016-11-03 18:16:02 -0200
commitbfab09559cdbbc7e163490129ac7d3c4060b905d (patch)
treeb2cb4e9eda30c0ccabad972393dd4435e6ed9573 /src
parent9822682e68b252b049e2fa6ef9d052d7ac739378 (diff)
parentcee2d3abfc1c06f382787d7bf7d2110a52ca0e76 (diff)
downloadluarocks-bfab09559cdbbc7e163490129ac7d3c4060b905d.tar.gz
luarocks-bfab09559cdbbc7e163490129ac7d3c4060b905d.tar.bz2
luarocks-bfab09559cdbbc7e163490129ac7d3c4060b905d.zip
Merge pull request #644 from mpeterv/fix-conflict-resolve
Fix conflict resolution on deploy/remove w.r.t. C/Lua modules with same name
Diffstat (limited to 'src')
-rw-r--r--src/luarocks/manif.lua132
-rw-r--r--src/luarocks/repos.lua256
2 files changed, 205 insertions, 183 deletions
diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua
index 86209d06..88a13f10 100644
--- a/src/luarocks/manif.lua
+++ b/src/luarocks/manif.lua
@@ -540,85 +540,89 @@ function manif.zip_manifests()
540 end 540 end
541end 541end
542 542
543local function relative_path(from_dir, to_file) 543--- Get type and name of an item (a module or a command) provided by a file.
544 -- It is assumed that `from_dir` is prefix of `to_file`. 544-- @param deploy_type string: rock manifest subtree the file comes from ("bin", "lua", or "lib").
545 return (to_file:sub(#from_dir + 1):gsub("^[\\/]*", "")) 545-- @param file_path string: path to the file relatively to deploy_type subdirectory.
546-- @return (string, string): item type ("module" or "command") and name.
547function manif.get_provided_item(deploy_type, file_path)
548 assert(type(deploy_type) == "string")
549 assert(type(file_path) == "string")
550 local item_type = deploy_type == "bin" and "command" or "module"
551 local item_name = item_type == "command" and file_path or path.path_to_module(file_path)
552 return item_type, item_name
546end 553end
547 554
548local function file_manifest_coordinates(manifest, file, root) 555local function get_providers(item_type, item_name, repo)
549 local deploy_bin = path.deploy_bin_dir(root) 556 assert(type(item_type) == "string")
550 local deploy_lua = path.deploy_lua_dir(root) 557 assert(type(item_name) == "string")
551 local deploy_lib = path.deploy_lib_dir(root) 558 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
552 559 local manifest = manif_core.load_local_manifest(rocks_dir)
553 if util.starts_with(file, deploy_lua) then 560 return manifest and manifest[item_type .. "s"][item_name]
554 return "modules", path.path_to_module(relative_path(deploy_lua, file):gsub("\\", "/")), deploy_lua
555 elseif util.starts_with(file, deploy_lib) then
556 return "modules", path.path_to_module(relative_path(deploy_lib, file):gsub("\\", "/")), deploy_lib
557 elseif util.starts_with(file, deploy_bin) then
558 return "commands", relative_path(deploy_bin, file), deploy_bin
559 else
560 assert(false, "Assertion failed: '"..file.."' is not a deployed file.")
561 end
562end 561end
563 562
564local function find_providers(file, root) 563--- Given a name of a module or a command, figure out which rock name and version
565 assert(type(file) == "string") 564-- correspond to it in the rock tree manifest.
566 root = root or cfg.root_dir 565-- @param item_type string: "module" or "command".
567 566-- @param item_name string: module or command name.
568 local manifest, err = manif_core.load_local_manifest(path.rocks_dir(root)) 567-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
569 if not manifest then 568-- @return (string, string) or nil: name and version of the provider rock or nil if there
570 return nil, "untracked" 569-- is no provider.
570function manif.get_current_provider(item_type, item_name, repo)
571 local providers = get_providers(item_type, item_name, repo)
572 if providers then
573 return providers[1]:match("([^/]*)/([^/]*)")
571 end 574 end
575end
572 576
573 local type_key, key = file_manifest_coordinates(manifest, file, root) 577function manif.get_next_provider(item_type, item_name, repo)
574 578 local providers = get_providers(item_type, item_name, repo)
575 local providers = manifest[type_key][key] 579 if providers and providers[2] then
576 if not providers then 580 return providers[2]:match("([^/]*)/([^/]*)")
577 return nil, "untracked"
578 end 581 end
579 return providers
580end 582end
581 583
582--- Given a path of a deployed file, figure out which rock name and version 584--- Given a name of a module or a command provided by a package, figure out
583-- correspond to it in the tree manifest. 585-- which file provides it.
584-- @param file string: The full path of a deployed file. 586-- @param name string: package name.
587-- @param version string: package version.
588-- @param item_type string: "module" or "command".
589-- @param item_name string: module or command name.
585-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used. 590-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
586-- @return string, string: name and version of the provider rock. 591-- @return (string, string): rock manifest subtree the file comes from ("bin", "lua", or "lib")
587function manif.find_current_provider(file, root) 592-- and path to the providing file relatively to that subtree.
588 local providers, err = find_providers(file, root) 593function manif.get_providing_file(name, version, item_type, item_name, repo)
589 if not providers then return nil, err end 594 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
590 return providers[1]:match("([^/]*)/([^/]*)") 595 local manifest = manif_core.load_local_manifest(rocks_dir)
591end
592 596
593function manif.find_next_provider(file, root) 597 local entry_table = manifest.repository[name][version][1]
594 local providers, err = find_providers(file, root) 598 local file_path = entry_table[item_type .. "s"][item_name]
595 if not providers then return nil, err end 599
596 if providers[2] then 600 if item_type == "command" then
597 return providers[2]:match("([^/]*)/([^/]*)") 601 return "bin", file_path
598 else
599 return nil
600 end 602 end
601end
602 603
603--- Given a file conflicting with a module or command 604 -- A module can be in "lua" or "lib". Decide based on extension first:
604-- provided by a version of a package, return which file 605 -- most likely Lua modules are in "lua/" and C modules are in "lib/".
605-- in that version corresponds to the conflicting item. 606 if file_path:match("%." .. cfg.lua_extension .. "$") then
606-- @param name string: name of the package with conflicting module or command. 607 return "lua", file_path
607-- @param version string: version of the package with conflicting module or command. 608 elseif file_path:match("%." .. cfg.lib_extension .. "$") then
608-- @param file string: full, unversioned path to a deployed file. 609 return "lib", file_path
609-- @return string: full, unversioned path to a deployed file in
610-- given package that conflicts with given file.
611function manif.find_conflicting_file(name, version, file, root)
612 root = root or cfg.root_dir
613
614 local manifest = manif_core.load_local_manifest(path.rocks_dir(root))
615 if not manifest then
616 return
617 end 610 end
618 611
619 local entry_table = manifest.repository[name][version][1] 612 -- Fallback to rock manifest scanning.
620 local type_key, key, deploy_dir = file_manifest_coordinates(manifest, file, root) 613 local rock_manifest = manif.load_rock_manifest(name, version)
621 return dir.path(deploy_dir, entry_table[type_key][key]) 614 local subtree = rock_manifest.lib
615
616 for path_part in file_path:gmatch("[^/]+") do
617 if type(subtree) == "table" then
618 subtree = subtree[path_part]
619 else
620 -- Assume it's in "lua/" if it's not in "lib/".
621 return "lua", file_path
622 end
623 end
624
625 return type(subtree) == "string" and "lib" or "lua", file_path
622end 626end
623 627
624return manif 628return manif
diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua
index d4d9694e..5d5eac70 100644
--- a/src/luarocks/repos.lua
+++ b/src/luarocks/repos.lua
@@ -11,6 +11,23 @@ local dir = require("luarocks.dir")
11local manif = require("luarocks.manif") 11local manif = require("luarocks.manif")
12local deps = require("luarocks.deps") 12local deps = require("luarocks.deps")
13 13
14-- Tree of files installed by a package are stored
15-- in its rock manifest. Some of these files have to
16-- be deployed to locations where Lua can load them as
17-- modules or where they can be used as commands.
18-- These files are characterised by pair
19-- (deploy_type, file_path), where deploy_type is the first
20-- component of the file path and file_path is the rest of the
21-- path. Only files with deploy_type in {"lua", "lib", "bin"}
22-- are deployed somewhere.
23-- Each deployed file provides an "item". An item is
24-- characterised by pair (item_type, item_name).
25-- item_type is "command" for files with deploy_type
26-- "bin" and "module" for deploy_type in {"lua", "lib"}.
27-- item_name is same as file_path for commands
28-- and is produced using path.path_to_module(file_path)
29-- for modules.
30
14--- Get all installed versions of a package. 31--- Get all installed versions of a package.
15-- @param name string: a package name. 32-- @param name string: a package name.
16-- @return table or nil: An array of strings listing installed 33-- @return table or nil: An array of strings listing installed
@@ -192,44 +209,56 @@ end
192local function delete_suffixed(file, suffix) 209local function delete_suffixed(file, suffix)
193 local suffixed_file, err = find_suffixed(file, suffix) 210 local suffixed_file, err = find_suffixed(file, suffix)
194 if not suffixed_file then 211 if not suffixed_file then
195 return nil, "Could not remove " .. file .. ": " .. err, "not found" 212 return nil, "Could not remove " .. file .. ": " .. err
196 end 213 end
197 214
198 fs.delete(suffixed_file) 215 fs.delete(suffixed_file)
199 if fs.exists(suffixed_file) then 216 if fs.exists(suffixed_file) then
200 return nil, "Failed deleting " .. suffixed_file .. ": file still exists", "fail" 217 return nil, "Failed deleting " .. suffixed_file .. ": file still exists"
201 end 218 end
202 219
203 return true 220 return true
204end 221end
205 222
206local function resolve_conflict(target, deploy_dir, name, version, cur_name, cur_version, suffix) 223-- Files can be deployed using versioned and non-versioned names.
207 if name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then 224-- Several items with same type and name can exist if they are
225-- provided by different packages or versions. In any case
226-- item from the newest version of lexicographically smallest package
227-- is deployed using non-versioned name and others use versioned names.
228
229local function get_deploy_paths(name, version, deploy_type, file_path)
230 local deploy_dir = cfg["deploy_" .. deploy_type .. "_dir"]
231 local non_versioned = dir.path(deploy_dir, file_path)
232 local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
233 return non_versioned, versioned
234end
235
236local function prepare_target(name, version, deploy_type, file_path, suffix)
237 local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
238 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
239 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
240
241 if not cur_name then
242 return non_versioned
243 elseif name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then
208 -- New version has priority. Move currently provided version back using versioned name. 244 -- New version has priority. Move currently provided version back using versioned name.
209 local cur_target = manif.find_conflicting_file(cur_name, cur_version, target) 245 local cur_deploy_type, cur_file_path = manif.get_providing_file(cur_name, cur_version, item_type, item_name)
210 local versioned = path.versioned_name(cur_target, deploy_dir, cur_name, cur_version) 246 local cur_non_versioned, cur_versioned = get_deploy_paths(cur_name, cur_version, cur_deploy_type, cur_file_path)
211 247
212 local ok, err = fs.make_dir(dir.dir_name(versioned)) 248 local dir_ok, dir_err = fs.make_dir(dir.dir_name(cur_versioned))
213 if not ok then 249 if not dir_ok then return nil, dir_err end
214 return nil, err
215 end
216 250
217 ok, err = move_suffixed(cur_target, versioned, suffix) 251 local move_ok, move_err = move_suffixed(cur_non_versioned, cur_versioned, suffix)
218 if not ok then 252 if not move_ok then return nil, move_err end
219 return nil, err
220 end
221 253
222 return target 254 return non_versioned
223 else 255 else
224 -- Current version has priority, deploy new version using versioned name. 256 -- Current version has priority, deploy new version using versioned name.
225 return path.versioned_name(target, deploy_dir, name, version) 257 return versioned
226 end 258 end
227end 259end
228 260
229--- Deploy a package from the rocks subdirectory. 261--- Deploy a package from the rocks subdirectory.
230-- It is maintained that for each module and command the one that is provided
231-- by the newest version of the lexicographically smallest package
232-- is installed using unversioned name, and other versions use versioned names.
233-- @param name string: name of package 262-- @param name string: name of package
234-- @param version string: exact package version in string format 263-- @param version string: exact package version in string format
235-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped. 264-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped.
@@ -241,49 +270,44 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode)
241 assert(type(version) == "string") 270 assert(type(version) == "string")
242 assert(type(wrap_bin_scripts) == "boolean") 271 assert(type(wrap_bin_scripts) == "boolean")
243 272
244 local function deploy_file_tree(file_tree, path_fn, deploy_dir, move_fn, suffix) 273 local rock_manifest = manif.load_rock_manifest(name, version)
245 local source_dir = path_fn(name, version)
246 return recurse_rock_manifest_tree(file_tree,
247 function(parent_path, parent_module, file)
248 local source = dir.path(source_dir, parent_path, file)
249 local target = dir.path(deploy_dir, parent_path, file)
250
251 local cur_name, cur_version = manif.find_current_provider(target)
252 if cur_name then
253 local resolve_err
254 target, resolve_err = resolve_conflict(target, deploy_dir, name, version, cur_name, cur_version, suffix)
255 if not target then
256 return nil, resolve_err
257 end
258 end
259 274
260 local ok, err = fs.make_dir(dir.dir_name(target)) 275 local function deploy_file_tree(deploy_type, source_dir, move_fn, suffix)
261 if not ok then return nil, err end 276 if not rock_manifest[deploy_type] then
277 return true
278 end
262 279
263 local suffixed_target, mover = move_fn(source, target, name, version) 280 return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
264 if fs.exists(suffixed_target) then 281 local file_path = parent_path .. file
265 local backup = suffixed_target 282 local source = dir.path(source_dir, file_path)
266 repeat
267 backup = backup.."~"
268 until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
269
270 util.printerr("Warning: "..suffixed_target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
271 local ok, err = fs.move(suffixed_target, backup)
272 if not ok then
273 return nil, err
274 end
275 end
276 283
277 ok, err = mover() 284 local target, prepare_err = prepare_target(name, version, deploy_type, file_path, suffix)
278 fs.remove_dir_tree_if_empty(dir.dir_name(source)) 285 if not target then return nil, prepare_err end
279 return ok, err 286
287 local dir_ok, dir_err = fs.make_dir(dir.dir_name(target))
288 if not dir_ok then return nil, dir_err end
289
290 local suffixed_target, mover = move_fn(source, target)
291 if fs.exists(suffixed_target) then
292 local backup = suffixed_target
293 repeat
294 backup = backup.."~"
295 until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
296
297 util.printerr("Warning: "..suffixed_target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
298 local move_ok, move_err = fs.move(suffixed_target, backup)
299 if not move_ok then return nil, move_err end
280 end 300 end
281 )
282 end
283 301
284 local rock_manifest = manif.load_rock_manifest(name, version) 302 local move_ok, move_err = mover()
303 if not move_ok then return nil, move_err end
304
305 fs.remove_dir_tree_if_empty(dir.dir_name(source))
306 return true
307 end)
308 end
285 309
286 local function install_binary(source, target, name, version) 310 local function install_binary(source, target)
287 if wrap_bin_scripts and fs.is_lua(source) then 311 if wrap_bin_scripts and fs.is_lua(source) then
288 return target .. (cfg.wrapper_suffix or ""), function() return fs.wrap_script(source, target, name, version) end 312 return target .. (cfg.wrapper_suffix or ""), function() return fs.wrap_script(source, target, name, version) end
289 else 313 else
@@ -297,28 +321,19 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode)
297 end 321 end
298 end 322 end
299 323
300 local ok, err = true 324 local ok, err = deploy_file_tree("bin", path.bin_dir(name, version), install_binary, cfg.wrapper_suffix)
301 if rock_manifest.bin then 325 if not ok then return nil, err end
302 ok, err = deploy_file_tree(rock_manifest.bin, path.bin_dir, cfg.deploy_bin_dir, install_binary, cfg.wrapper_suffix)
303 end
304 if ok and rock_manifest.lua then
305 ok, err = deploy_file_tree(rock_manifest.lua, path.lua_dir, cfg.deploy_lua_dir, make_mover(cfg.perm_read))
306 end
307 if ok and rock_manifest.lib then
308 ok, err = deploy_file_tree(rock_manifest.lib, path.lib_dir, cfg.deploy_lib_dir, make_mover(cfg.perm_exec))
309 end
310 326
311 if not ok then 327 ok, err = deploy_file_tree("lua", path.lua_dir(name, version), make_mover(cfg.perm_read))
312 return nil, err 328 if not ok then return nil, err end
313 end 329
330 ok, err = deploy_file_tree("lib", path.lib_dir(name, version), make_mover(cfg.perm_exec))
331 if not ok then return nil, err end
314 332
315 return manif.add_to_manifest(name, version, nil, deps_mode) 333 return manif.add_to_manifest(name, version, nil, deps_mode)
316end 334end
317 335
318--- Delete a package from the local repository. 336--- Delete a package from the local repository.
319-- It is maintained that for each module and command the one that is provided
320-- by the newest version of the lexicographically smallest package
321-- is installed using unversioned name, and other versions use versioned names.
322-- @param name string: name of package 337-- @param name string: name of package
323-- @param version string: exact package version in string format 338-- @param version string: exact package version in string format
324-- @param deps_mode: string: Which trees to check dependencies for: 339-- @param deps_mode: string: Which trees to check dependencies for:
@@ -333,67 +348,70 @@ function repos.delete_version(name, version, deps_mode, quick)
333 assert(type(version) == "string") 348 assert(type(version) == "string")
334 assert(type(deps_mode) == "string") 349 assert(type(deps_mode) == "string")
335 350
336 local function delete_deployed_file_tree(file_tree, deploy_dir, suffix) 351 local rock_manifest = manif.load_rock_manifest(name, version)
337 return recurse_rock_manifest_tree(file_tree, 352 if not rock_manifest then
338 function(parent_path, parent_module, file) 353 return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
339 local target = dir.path(deploy_dir, parent_path, file) 354 end
340 local versioned = path.versioned_name(target, deploy_dir, name, version)
341
342 local ok, err, err_type = delete_suffixed(versioned, suffix)
343 if ok then
344 fs.remove_dir_tree_if_empty(dir.dir_name(versioned))
345 return true
346 elseif err_type == "fail" then
347 return nil, err
348 end
349 355
350 ok, err = delete_suffixed(target, suffix) 356 local function delete_deployed_file_tree(deploy_type, suffix)
351 if not ok then 357 if not rock_manifest[deploy_type] then
352 return nil, err 358 return true
353 end 359 end
360
361 return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
362 local file_path = parent_path .. file
363 local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
364
365 -- Figure out if the file is deployed using versioned or non-versioned name.
366 local target
367 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
368 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
354 369
355 if not quick then 370 if cur_name == name and cur_version == version then
356 local next_name, next_version = manif.find_next_provider(target) 371 -- This package has highest priority, should be in non-versioned location.
357 if next_name then 372 target = non_versioned
358 local next_target = manif.find_conflicting_file(next_name, next_version, target) 373 else
359 local next_versioned = path.versioned_name(next_target, deploy_dir, next_name, next_version) 374 target = versioned
375 end
376
377 local ok, err = delete_suffixed(target, suffix)
378 if not ok then return nil, err end
379
380 if not quick and target == non_versioned then
381 -- If another package provides this file, move its version
382 -- into non-versioned location instead.
383 local next_name, next_version = manif.get_next_provider(item_type, item_name)
360 384
361 ok, err = move_suffixed(next_versioned, next_target, suffix) 385 if next_name then
362 if not ok then 386 local next_deploy_type, next_file_path = manif.get_providing_file(next_name, next_version, item_type, item_name)
363 return nil, err 387 local next_non_versioned, next_versioned = get_deploy_paths(next_name, next_version, next_deploy_type, next_file_path)
364 end
365 388
366 fs.remove_dir_tree_if_empty(dir.dir_name(versioned)) 389 local move_ok, move_err = move_suffixed(next_versioned, next_non_versioned, suffix)
367 end 390 if not move_ok then return nil, move_err end
391
392 fs.remove_dir_tree_if_empty(dir.dir_name(next_versioned))
368 end 393 end
369 fs.remove_dir_tree_if_empty(dir.dir_name(target))
370 return true
371 end 394 end
372 )
373 end
374 395
375 local rock_manifest = manif.load_rock_manifest(name, version) 396 fs.remove_dir_tree_if_empty(dir.dir_name(target))
376 if not rock_manifest then 397 return true
377 return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?" 398 end)
378 end
379
380 local ok, err = true
381 if rock_manifest.bin then
382 ok, err = delete_deployed_file_tree(rock_manifest.bin, cfg.deploy_bin_dir, cfg.wrapper_suffix)
383 end
384 if ok and rock_manifest.lua then
385 ok, err = delete_deployed_file_tree(rock_manifest.lua, cfg.deploy_lua_dir)
386 end
387 if ok and rock_manifest.lib then
388 ok, err = delete_deployed_file_tree(rock_manifest.lib, cfg.deploy_lib_dir)
389 end 399 end
400
401 local ok, err = delete_deployed_file_tree("bin", cfg.wrapper_suffix)
402 if not ok then return nil, err end
403
404 ok, err = delete_deployed_file_tree("lua")
405 if not ok then return nil, err end
406
407 ok, err = delete_deployed_file_tree("lib")
390 if not ok then return nil, err end 408 if not ok then return nil, err end
391 409
392 fs.delete(path.install_dir(name, version)) 410 fs.delete(path.install_dir(name, version))
393 if not get_installed_versions(name) then 411 if not get_installed_versions(name) then
394 fs.delete(dir.path(cfg.rocks_dir, name)) 412 fs.delete(dir.path(cfg.rocks_dir, name))
395 end 413 end
396 414
397 if quick then 415 if quick then
398 return true 416 return true
399 end 417 end