aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPeter Melnichenko <mpeterval@gmail.com>2016-10-19 21:37:26 +0300
committerPeter Melnichenko <mpeterval@gmail.com>2016-10-19 23:16:56 +0300
commitfd913d6aab284668854a71a97ac64e9a7dbdd0b8 (patch)
tree09d25a6d6a39d28e6a097816bb477cfda5c5140b /src
parent9c239f33a929d300a45b74cc968fb34ffd526150 (diff)
downloadluarocks-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.lua54
-rw-r--r--src/luarocks/repos.lua170
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("^[\\/]*", ""))
470end 470end
471 471
472local function find_providers(file, root) 472local 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
486end
487
488local 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
524end 525end
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.
535function 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])
546end
547
526return manif 548return 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
163end 163end
164 164
165local 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
179end
180
181function repos.should_wrap_bin_scripts(rockspec) 165function 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
191end 175end
192 176
177local 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
188end
189
190local 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)
199end
200
201local 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
213end
214
215local 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
236end
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)
265end 315end
266 316
267local 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"
280end
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