diff options
| author | Peter Melnichenko <mpeterval@gmail.com> | 2016-10-19 21:37:26 +0300 |
|---|---|---|
| committer | Peter Melnichenko <mpeterval@gmail.com> | 2016-10-19 23:16:56 +0300 |
| commit | fd913d6aab284668854a71a97ac64e9a7dbdd0b8 (patch) | |
| tree | 09d25a6d6a39d28e6a097816bb477cfda5c5140b /src | |
| parent | 9c239f33a929d300a45b74cc968fb34ffd526150 (diff) | |
| download | luarocks-fd913d6aab284668854a71a97ac64e9a7dbdd0b8.tar.gz luarocks-fd913d6aab284668854a71a97ac64e9a7dbdd0b8.tar.bz2 luarocks-fd913d6aab284668854a71a97ac64e9a7dbdd0b8.zip | |
Fix conflict resolution on deploy/delete
When deploying or deleting files, resolve conflicts purely
based on module names and command names, not file names.
Also, don't assume that in case of a conflict both packages have the
same file providing the module or command; it can be false due to binary
wrappers and `path_to_module("mod/init.lua")` == `path_to_module("mod.lua").
Diffstat (limited to 'src')
| -rw-r--r-- | src/luarocks/manif.lua | 54 | ||||
| -rw-r--r-- | src/luarocks/repos.lua | 170 |
2 files changed, 146 insertions, 78 deletions
diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua index a8bbf279..1b75452b 100644 --- a/src/luarocks/manif.lua +++ b/src/luarocks/manif.lua | |||
| @@ -469,33 +469,34 @@ local function relative_path(from_dir, to_file) | |||
| 469 | return (to_file:sub(#from_dir + 1):gsub("^[\\/]*", "")) | 469 | return (to_file:sub(#from_dir + 1):gsub("^[\\/]*", "")) |
| 470 | end | 470 | end |
| 471 | 471 | ||
| 472 | local function find_providers(file, root) | 472 | local function file_manifest_coordinates(manifest, file, root) |
| 473 | assert(type(file) == "string") | ||
| 474 | root = root or cfg.root_dir | ||
| 475 | |||
| 476 | local manifest, err = manif_core.load_local_manifest(path.rocks_dir(root)) | ||
| 477 | if not manifest then | ||
| 478 | return nil, "untracked" | ||
| 479 | end | ||
| 480 | local deploy_bin = path.deploy_bin_dir(root) | 473 | local deploy_bin = path.deploy_bin_dir(root) |
| 481 | local deploy_lua = path.deploy_lua_dir(root) | 474 | local deploy_lua = path.deploy_lua_dir(root) |
| 482 | local deploy_lib = path.deploy_lib_dir(root) | 475 | local deploy_lib = path.deploy_lib_dir(root) |
| 483 | local key, manifest_tbl | ||
| 484 | 476 | ||
| 485 | if util.starts_with(file, deploy_lua) then | 477 | if util.starts_with(file, deploy_lua) then |
| 486 | manifest_tbl = manifest.modules | 478 | return "modules", path.path_to_module(relative_path(deploy_lua, file):gsub("\\", "/")), deploy_lua |
| 487 | key = path.path_to_module(relative_path(deploy_lua, file):gsub("\\", "/")) | ||
| 488 | elseif util.starts_with(file, deploy_lib) then | 479 | elseif util.starts_with(file, deploy_lib) then |
| 489 | manifest_tbl = manifest.modules | 480 | return "modules", path.path_to_module(relative_path(deploy_lib, file):gsub("\\", "/")), deploy_lib |
| 490 | key = path.path_to_module(relative_path(deploy_lib, file):gsub("\\", "/")) | ||
| 491 | elseif util.starts_with(file, deploy_bin) then | 481 | elseif util.starts_with(file, deploy_bin) then |
| 492 | manifest_tbl = manifest.commands | 482 | return "commands", relative_path(deploy_bin, file), deploy_bin |
| 493 | key = relative_path(deploy_bin, file) | ||
| 494 | else | 483 | else |
| 495 | assert(false, "Assertion failed: '"..file.."' is not a deployed file.") | 484 | assert(false, "Assertion failed: '"..file.."' is not a deployed file.") |
| 496 | end | 485 | end |
| 486 | end | ||
| 487 | |||
| 488 | local function find_providers(file, root) | ||
| 489 | assert(type(file) == "string") | ||
| 490 | root = root or cfg.root_dir | ||
| 491 | |||
| 492 | local manifest, err = manif_core.load_local_manifest(path.rocks_dir(root)) | ||
| 493 | if not manifest then | ||
| 494 | return nil, "untracked" | ||
| 495 | end | ||
| 496 | |||
| 497 | local type_key, key = file_manifest_coordinates(manifest, file, root) | ||
| 497 | 498 | ||
| 498 | local providers = manifest_tbl[key] | 499 | local providers = manifest[type_key][key] |
| 499 | if not providers then | 500 | if not providers then |
| 500 | return nil, "untracked" | 501 | return nil, "untracked" |
| 501 | end | 502 | end |
| @@ -523,4 +524,25 @@ function manif.find_next_provider(file, root) | |||
| 523 | end | 524 | end |
| 524 | end | 525 | end |
| 525 | 526 | ||
| 527 | --- Given a file conflicting with a module or command | ||
| 528 | -- provided by a version of a package, return which file | ||
| 529 | -- in that version corresponds to the conflicting item. | ||
| 530 | -- @param name string: name of the package with conflicting module or command. | ||
| 531 | -- @param version string: version of the package with conflicting module or command. | ||
| 532 | -- @param file string: full, unversioned path to a deployed file. | ||
| 533 | -- @return string: full, unversioned path to a deployed file in | ||
| 534 | -- given package that conflicts with given file. | ||
| 535 | function manif.find_conflicting_file(name, version, file, root) | ||
| 536 | root = root or cfg.root_dir | ||
| 537 | |||
| 538 | local manifest = manif_core.load_local_manifest(path.rocks_dir(root)) | ||
| 539 | if not manifest then | ||
| 540 | return | ||
| 541 | end | ||
| 542 | |||
| 543 | local entry_table = manifest.repository[name][version][1] | ||
| 544 | local type_key, key, deploy_dir = file_manifest_coordinates(manifest, file, root) | ||
| 545 | return dir.path(deploy_dir, entry_table[type_key][key]) | ||
| 546 | end | ||
| 547 | |||
| 526 | return manif | 548 | return manif |
diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua index a1cf2bb4..762192a3 100644 --- a/src/luarocks/repos.lua +++ b/src/luarocks/repos.lua | |||
| @@ -162,22 +162,6 @@ local function install_binary(source, target, name, version) | |||
| 162 | end | 162 | end |
| 163 | end | 163 | end |
| 164 | 164 | ||
| 165 | local function resolve_conflict(target, deploy_dir, name, version) | ||
| 166 | local cname, cversion = manif.find_current_provider(target) | ||
| 167 | if not cname then | ||
| 168 | return nil, cversion | ||
| 169 | end | ||
| 170 | if name ~= cname or deps.compare_versions(version, cversion) then | ||
| 171 | local versioned = path.versioned_name(target, deploy_dir, cname, cversion) | ||
| 172 | local ok, err = fs.make_dir(dir.dir_name(versioned)) | ||
| 173 | if not ok then return nil, err end | ||
| 174 | fs.move(target, versioned) | ||
| 175 | return target | ||
| 176 | else | ||
| 177 | return path.versioned_name(target, deploy_dir, name, version) | ||
| 178 | end | ||
| 179 | end | ||
| 180 | |||
| 181 | function repos.should_wrap_bin_scripts(rockspec) | 165 | function repos.should_wrap_bin_scripts(rockspec) |
| 182 | assert(type(rockspec) == "table") | 166 | assert(type(rockspec) == "table") |
| 183 | 167 | ||
| @@ -190,11 +174,71 @@ function repos.should_wrap_bin_scripts(rockspec) | |||
| 190 | return true | 174 | return true |
| 191 | end | 175 | end |
| 192 | 176 | ||
| 177 | local function find_suffixed(file, suffix) | ||
| 178 | local filenames = {file} | ||
| 179 | if suffix and suffix ~= "" then | ||
| 180 | table.insert(filenames, 1, file .. suffix) | ||
| 181 | end | ||
| 182 | |||
| 183 | for _, filename in ipairs(filenames) do | ||
| 184 | if fs.exists(filename) then | ||
| 185 | return filename | ||
| 186 | end | ||
| 187 | end | ||
| 188 | end | ||
| 189 | |||
| 190 | local function move_suffixed(from_file, to_file, suffix) | ||
| 191 | local suffixed_from_file = find_suffixed(from_file, suffix) | ||
| 192 | if not suffixed_from_file then | ||
| 193 | return nil, "File not found" | ||
| 194 | end | ||
| 195 | |||
| 196 | suffix = suffixed_from_file:sub(#from_file + 1) | ||
| 197 | local suffixed_to_file = to_file .. suffix | ||
| 198 | return fs.move(suffixed_from_file, suffixed_to_file) | ||
| 199 | end | ||
| 200 | |||
| 201 | local function delete_suffixed(file, suffix) | ||
| 202 | local suffixed_file = find_suffixed(file, suffix) | ||
| 203 | if not suffixed_file then | ||
| 204 | return nil, "File not found", "not found" | ||
| 205 | end | ||
| 206 | |||
| 207 | fs.delete(suffixed_file) | ||
| 208 | if fs.exists(suffixed_file) then | ||
| 209 | return nil, "Failed deleting " .. suffixed_file, "fail" | ||
| 210 | end | ||
| 211 | |||
| 212 | return true | ||
| 213 | end | ||
| 214 | |||
| 215 | local function resolve_conflict(target, deploy_dir, name, version, cur_name, cur_version, suffix) | ||
| 216 | if name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then | ||
| 217 | -- New version has priority. Move currently provided version back using versioned name. | ||
| 218 | local cur_target = manif.find_conflicting_file(cur_name, cur_version, target) | ||
| 219 | local versioned = path.versioned_name(cur_target, deploy_dir, cur_name, cur_version) | ||
| 220 | |||
| 221 | local ok, err = fs.make_dir(dir.dir_name(versioned)) | ||
| 222 | if not ok then | ||
| 223 | return nil, err | ||
| 224 | end | ||
| 225 | |||
| 226 | ok, err = move_suffixed(cur_target, versioned, suffix) | ||
| 227 | if not ok then | ||
| 228 | return nil, err | ||
| 229 | end | ||
| 230 | |||
| 231 | return target | ||
| 232 | else | ||
| 233 | -- Current version has priority, deploy new version using versioned name. | ||
| 234 | return path.versioned_name(target, deploy_dir, name, version) | ||
| 235 | end | ||
| 236 | end | ||
| 237 | |||
| 193 | --- Deploy a package from the rocks subdirectory. | 238 | --- Deploy a package from the rocks subdirectory. |
| 194 | -- It is maintained that for each file the one that is provided | 239 | -- It is maintained that for each module and command the one that is provided |
| 195 | -- by the newest version of the lexicographically smallest package | 240 | -- by the newest version of the lexicographically smallest package |
| 196 | -- is installed using unversioned name, and other versions of the file | 241 | -- is installed using unversioned name, and other versions use versioned names. |
| 197 | -- use versioned names. | ||
| 198 | -- @param name string: name of package | 242 | -- @param name string: name of package |
| 199 | -- @param version string: exact package version in string format | 243 | -- @param version string: exact package version in string format |
| 200 | -- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped. | 244 | -- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped. |
| @@ -206,34 +250,40 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode) | |||
| 206 | assert(type(version) == "string") | 250 | assert(type(version) == "string") |
| 207 | assert(type(wrap_bin_scripts) == "boolean") | 251 | assert(type(wrap_bin_scripts) == "boolean") |
| 208 | 252 | ||
| 209 | local function deploy_file_tree(file_tree, path_fn, deploy_dir, move_fn) | 253 | local function deploy_file_tree(file_tree, path_fn, deploy_dir, move_fn, suffix) |
| 210 | local source_dir = path_fn(name, version) | 254 | local source_dir = path_fn(name, version) |
| 211 | return recurse_rock_manifest_tree(file_tree, | 255 | return recurse_rock_manifest_tree(file_tree, |
| 212 | function(parent_path, parent_module, file) | 256 | function(parent_path, parent_module, file) |
| 213 | local source = dir.path(source_dir, parent_path, file) | 257 | local source = dir.path(source_dir, parent_path, file) |
| 214 | local target = dir.path(deploy_dir, parent_path, file) | 258 | local target = dir.path(deploy_dir, parent_path, file) |
| 215 | local ok, err | 259 | |
| 260 | local cur_name, cur_version = manif.find_current_provider(target) | ||
| 261 | if cur_name then | ||
| 262 | local resolve_err | ||
| 263 | target, resolve_err = resolve_conflict(target, deploy_dir, name, version, cur_name, cur_version, suffix) | ||
| 264 | if not target then | ||
| 265 | return nil, resolve_err | ||
| 266 | end | ||
| 267 | end | ||
| 268 | |||
| 216 | if fs.exists(target) then | 269 | if fs.exists(target) then |
| 217 | local new_target, err = resolve_conflict(target, deploy_dir, name, version) | 270 | local backup = target |
| 218 | if err == "untracked" then | 271 | repeat |
| 219 | local backup = target | 272 | backup = backup.."~" |
| 220 | repeat | 273 | until not fs.exists(backup) -- slight race condition here, but shouldn't be a problem. |
| 221 | backup = backup.."~" | 274 | |
| 222 | until not fs.exists(backup) -- slight race condition here, but shouldn't be a problem. | 275 | util.printerr("Warning: "..target.." is not tracked by this installation of LuaRocks. Moving it to "..backup) |
| 223 | util.printerr("Warning: "..target.." is not tracked by this installation of LuaRocks. Moving it to "..backup) | 276 | local ok, err = fs.move(target, backup) |
| 224 | fs.move(target, backup) | 277 | if not ok then |
| 225 | elseif err then | 278 | return nil, err |
| 226 | return nil, err.." Cannot install new version." | ||
| 227 | else | ||
| 228 | target = new_target | ||
| 229 | end | 279 | end |
| 230 | end | 280 | end |
| 231 | ok, err = fs.make_dir(dir.dir_name(target)) | 281 | |
| 282 | local ok, err = fs.make_dir(dir.dir_name(target)) | ||
| 232 | if not ok then return nil, err end | 283 | if not ok then return nil, err end |
| 233 | ok, err = move_fn(source, target, name, version) | 284 | ok, err = move_fn(source, target, name, version) |
| 234 | fs.remove_dir_tree_if_empty(dir.dir_name(source)) | 285 | fs.remove_dir_tree_if_empty(dir.dir_name(source)) |
| 235 | if not ok then return nil, err end | 286 | return ok, err |
| 236 | return true | ||
| 237 | end | 287 | end |
| 238 | ) | 288 | ) |
| 239 | end | 289 | end |
| @@ -243,7 +293,7 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode) | |||
| 243 | local ok, err = true | 293 | local ok, err = true |
| 244 | if rock_manifest.bin then | 294 | if rock_manifest.bin then |
| 245 | local move_bin_fn = wrap_bin_scripts and install_binary or fs.copy_binary | 295 | local move_bin_fn = wrap_bin_scripts and install_binary or fs.copy_binary |
| 246 | ok, err = deploy_file_tree(rock_manifest.bin, path.bin_dir, cfg.deploy_bin_dir, move_bin_fn) | 296 | ok, err = deploy_file_tree(rock_manifest.bin, path.bin_dir, cfg.deploy_bin_dir, move_bin_fn, cfg.wrapper_suffix) |
| 247 | end | 297 | end |
| 248 | local function make_mover(perms) | 298 | local function make_mover(perms) |
| 249 | return function (src, dest) | 299 | return function (src, dest) |
| @@ -264,26 +314,10 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode) | |||
| 264 | return manif.update_manifest(name, version, nil, deps_mode) | 314 | return manif.update_manifest(name, version, nil, deps_mode) |
| 265 | end | 315 | end |
| 266 | 316 | ||
| 267 | local function delete_suffixed(filename, suffix) | ||
| 268 | local filenames = { filename } | ||
| 269 | if suffix and suffix ~= "" then filenames = { filename..suffix, filename } end | ||
| 270 | for _, name in ipairs(filenames) do | ||
| 271 | if fs.exists(name) then | ||
| 272 | fs.delete(name) | ||
| 273 | if fs.exists(name) then | ||
| 274 | return nil, "Failed deleting "..name, "fail" | ||
| 275 | end | ||
| 276 | return true, name | ||
| 277 | end | ||
| 278 | end | ||
| 279 | return false, "File not found", "not found" | ||
| 280 | end | ||
| 281 | |||
| 282 | --- Delete a package from the local repository. | 317 | --- Delete a package from the local repository. |
| 283 | -- It is maintained that for each file the one that is provided | 318 | -- It is maintained that for each module and command the one that is provided |
| 284 | -- by the newest version of the lexicographically smallest package | 319 | -- by the newest version of the lexicographically smallest package |
| 285 | -- is installed using unversioned name, and other versions of the file | 320 | -- is installed using unversioned name, and other versions use versioned names. |
| 286 | -- use versioned names. | ||
| 287 | -- @param name string: name of package | 321 | -- @param name string: name of package |
| 288 | -- @param version string: exact package version in string format | 322 | -- @param version string: exact package version in string format |
| 289 | -- @param deps_mode: string: Which trees to check dependencies for: | 323 | -- @param deps_mode: string: Which trees to check dependencies for: |
| @@ -303,19 +337,31 @@ function repos.delete_version(name, version, deps_mode, quick) | |||
| 303 | function(parent_path, parent_module, file) | 337 | function(parent_path, parent_module, file) |
| 304 | local target = dir.path(deploy_dir, parent_path, file) | 338 | local target = dir.path(deploy_dir, parent_path, file) |
| 305 | local versioned = path.versioned_name(target, deploy_dir, name, version) | 339 | local versioned = path.versioned_name(target, deploy_dir, name, version) |
| 306 | local ok, name, err = delete_suffixed(versioned, suffix) | 340 | |
| 341 | local ok, err, err_type = delete_suffixed(versioned, suffix) | ||
| 307 | if ok then | 342 | if ok then |
| 308 | fs.remove_dir_tree_if_empty(dir.dir_name(versioned)) | 343 | fs.remove_dir_tree_if_empty(dir.dir_name(versioned)) |
| 309 | return true | 344 | return true |
| 345 | elseif err_type == "fail" then | ||
| 346 | return nil, err | ||
| 347 | end | ||
| 348 | |||
| 349 | ok, err = delete_suffixed(target, suffix) | ||
| 350 | if not ok then | ||
| 351 | return nil, err | ||
| 310 | end | 352 | end |
| 311 | if err == "fail" then return nil, name end | 353 | |
| 312 | ok, name, err = delete_suffixed(target, suffix) | ||
| 313 | if err == "fail" then return nil, name end | ||
| 314 | if not quick then | 354 | if not quick then |
| 315 | local next_name, next_version = manif.find_next_provider(target) | 355 | local next_name, next_version = manif.find_next_provider(target) |
| 316 | if next_name then | 356 | if next_name then |
| 317 | local versioned = path.versioned_name(name, deploy_dir, next_name, next_version) | 357 | local next_target = manif.find_conflicting_file(next_name, next_version, target) |
| 318 | fs.move(versioned, name) | 358 | local next_versioned = path.versioned_name(next_target, deploy_dir, next_name, next_version) |
| 359 | |||
| 360 | ok, err = move_suffixed(next_versioned, next_target, suffix) | ||
| 361 | if not ok then | ||
| 362 | return nil, err | ||
| 363 | end | ||
| 364 | |||
| 319 | fs.remove_dir_tree_if_empty(dir.dir_name(versioned)) | 365 | fs.remove_dir_tree_if_empty(dir.dir_name(versioned)) |
| 320 | end | 366 | end |
| 321 | end | 367 | end |
| @@ -340,7 +386,7 @@ function repos.delete_version(name, version, deps_mode, quick) | |||
| 340 | if ok and rock_manifest.lib then | 386 | if ok and rock_manifest.lib then |
| 341 | ok, err = delete_deployed_file_tree(rock_manifest.lib, cfg.deploy_lib_dir) | 387 | ok, err = delete_deployed_file_tree(rock_manifest.lib, cfg.deploy_lib_dir) |
| 342 | end | 388 | end |
| 343 | if err then return nil, err end | 389 | if not ok then return nil, err end |
| 344 | 390 | ||
| 345 | fs.delete(path.install_dir(name, version)) | 391 | fs.delete(path.install_dir(name, version)) |
| 346 | if not get_installed_versions(name) then | 392 | if not get_installed_versions(name) then |
