Grey-Echo 3b69cf992e This is an important refactor of the way documentation generation works
* Installs luarocks WITH it's executable (easy to install other rocks if necessary)
* Use Lua supplied with luarocks
* Create Utils/luadocumentor.bat, which works with RELATIVE PATH ! -> Everybody can generate the doc
* Updated launch files accordingly
2017-04-05 01:26:39 +02:00

423 lines
16 KiB
Lua

--- Functions for managing the repository on disk.
local repos = {}
package.loaded["luarocks.repos"] = repos
local fs = require("luarocks.fs")
local path = require("luarocks.path")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local deps = require("luarocks.deps")
-- Tree of files installed by a package are stored
-- in its rock manifest. Some of these files have to
-- be deployed to locations where Lua can load them as
-- modules or where they can be used as commands.
-- These files are characterised by pair
-- (deploy_type, file_path), where deploy_type is the first
-- component of the file path and file_path is the rest of the
-- path. Only files with deploy_type in {"lua", "lib", "bin"}
-- are deployed somewhere.
-- Each deployed file provides an "item". An item is
-- characterised by pair (item_type, item_name).
-- item_type is "command" for files with deploy_type
-- "bin" and "module" for deploy_type in {"lua", "lib"}.
-- item_name is same as file_path for commands
-- and is produced using path.path_to_module(file_path)
-- for modules.
--- Get all installed versions of a package.
-- @param name string: a package name.
-- @return table or nil: An array of strings listing installed
-- versions of a package, or nil if none is available.
local function get_installed_versions(name)
assert(type(name) == "string")
local dirs = fs.list_dir(path.versions_dir(name))
return (dirs and #dirs > 0) and dirs or nil
end
--- Check if a package exists in a local repository.
-- Version numbers are compared as exact string comparison.
-- @param name string: name of package
-- @param version string: package version in string format
-- @return boolean: true if a package is installed,
-- false otherwise.
function repos.is_installed(name, version)
assert(type(name) == "string")
assert(type(version) == "string")
return fs.is_dir(path.install_dir(name, version))
end
local function recurse_rock_manifest_tree(file_tree, action)
assert(type(file_tree) == "table")
assert(type(action) == "function")
local function do_recurse_rock_manifest_tree(tree, parent_path, parent_module)
for file, sub in pairs(tree) do
if type(sub) == "table" then
local ok, err = do_recurse_rock_manifest_tree(sub, parent_path..file.."/", parent_module..file..".")
if not ok then return nil, err end
else
local ok, err = action(parent_path, parent_module, file)
if not ok then return nil, err end
end
end
return true
end
return do_recurse_rock_manifest_tree(file_tree, "", "")
end
local function store_package_data(result, name, file_tree)
if not file_tree then return end
return recurse_rock_manifest_tree(file_tree,
function(parent_path, parent_module, file)
local pathname = parent_path..file
result[path.path_to_module(pathname)] = pathname
return true
end
)
end
--- Obtain a list of modules within an installed package.
-- @param package string: The package name; for example "luasocket"
-- @param version string: The exact version number including revision;
-- for example "2.0.1-1".
-- @return table: A table of modules where keys are module identifiers
-- in "foo.bar" format and values are pathnames in architecture-dependent
-- "foo/bar.so" format. If no modules are found or if package or version
-- are invalid, an empty table is returned.
function repos.package_modules(package, version)
assert(type(package) == "string")
assert(type(version) == "string")
local result = {}
local rock_manifest = manif.load_rock_manifest(package, version)
store_package_data(result, package, rock_manifest.lib)
store_package_data(result, package, rock_manifest.lua)
return result
end
--- Obtain a list of command-line scripts within an installed package.
-- @param package string: The package name; for example "luasocket"
-- @param version string: The exact version number including revision;
-- for example "2.0.1-1".
-- @return table: A table of items where keys are command names
-- as strings and values are pathnames in architecture-dependent
-- ".../bin/foo" format. If no modules are found or if package or version
-- are invalid, an empty table is returned.
function repos.package_commands(package, version)
assert(type(package) == "string")
assert(type(version) == "string")
local result = {}
local rock_manifest = manif.load_rock_manifest(package, version)
store_package_data(result, package, rock_manifest.bin)
return result
end
--- Check if a rock contains binary executables.
-- @param name string: name of an installed rock
-- @param version string: version of an installed rock
-- @return boolean: returns true if rock contains platform-specific
-- binary executables, or false if it is a pure-Lua rock.
function repos.has_binaries(name, version)
assert(type(name) == "string")
assert(type(version) == "string")
local rock_manifest = manif.load_rock_manifest(name, version)
if rock_manifest.bin then
for name, md5 in pairs(rock_manifest.bin) do
-- TODO verify that it is the same file. If it isn't, find the actual command.
if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, name)) then
return true
end
end
end
return false
end
function repos.run_hook(rockspec, hook_name)
assert(type(rockspec) == "table")
assert(type(hook_name) == "string")
local hooks = rockspec.hooks
if not hooks then
return true
end
if cfg.hooks_enabled == false then
return nil, "This rockspec contains hooks, which are blocked by the 'hooks_enabled' setting in your LuaRocks configuration."
end
if not hooks.substituted_variables then
util.variable_substitutions(hooks, rockspec.variables)
hooks.substituted_variables = true
end
local hook = hooks[hook_name]
if hook then
util.printout(hook)
if not fs.execute(hook) then
return nil, "Failed running "..hook_name.." hook."
end
end
return true
end
function repos.should_wrap_bin_scripts(rockspec)
assert(type(rockspec) == "table")
if cfg.wrap_bin_scripts ~= nil then
return cfg.wrap_bin_scripts
end
if rockspec.deploy and rockspec.deploy.wrap_bin_scripts == false then
return false
end
return true
end
local function find_suffixed(file, suffix)
local filenames = {file}
if suffix and suffix ~= "" then
table.insert(filenames, 1, file .. suffix)
end
for _, filename in ipairs(filenames) do
if fs.exists(filename) then
return filename
end
end
return nil, table.concat(filenames, ", ") .. " not found"
end
local function move_suffixed(from_file, to_file, suffix)
local suffixed_from_file, err = find_suffixed(from_file, suffix)
if not suffixed_from_file then
return nil, "Could not move " .. from_file .. " to " .. to_file .. ": " .. err
end
suffix = suffixed_from_file:sub(#from_file + 1)
local suffixed_to_file = to_file .. suffix
return fs.move(suffixed_from_file, suffixed_to_file)
end
local function delete_suffixed(file, suffix)
local suffixed_file, err = find_suffixed(file, suffix)
if not suffixed_file then
return nil, "Could not remove " .. file .. ": " .. err
end
fs.delete(suffixed_file)
if fs.exists(suffixed_file) then
return nil, "Failed deleting " .. suffixed_file .. ": file still exists"
end
return true
end
-- Files can be deployed using versioned and non-versioned names.
-- Several items with same type and name can exist if they are
-- provided by different packages or versions. In any case
-- item from the newest version of lexicographically smallest package
-- is deployed using non-versioned name and others use versioned names.
local function get_deploy_paths(name, version, deploy_type, file_path)
local deploy_dir = cfg["deploy_" .. deploy_type .. "_dir"]
local non_versioned = dir.path(deploy_dir, file_path)
local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
return non_versioned, versioned
end
local function prepare_target(name, version, deploy_type, file_path, suffix)
local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
if not cur_name then
return non_versioned
elseif name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then
-- New version has priority. Move currently provided version back using versioned name.
local cur_deploy_type, cur_file_path = manif.get_providing_file(cur_name, cur_version, item_type, item_name)
local cur_non_versioned, cur_versioned = get_deploy_paths(cur_name, cur_version, cur_deploy_type, cur_file_path)
local dir_ok, dir_err = fs.make_dir(dir.dir_name(cur_versioned))
if not dir_ok then return nil, dir_err end
local move_ok, move_err = move_suffixed(cur_non_versioned, cur_versioned, suffix)
if not move_ok then return nil, move_err end
return non_versioned
else
-- Current version has priority, deploy new version using versioned name.
return versioned
end
end
--- Deploy a package from the rocks subdirectory.
-- @param name string: name of package
-- @param version string: exact package version in string format
-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode)
assert(type(name) == "string")
assert(type(version) == "string")
assert(type(wrap_bin_scripts) == "boolean")
local rock_manifest = manif.load_rock_manifest(name, version)
local function deploy_file_tree(deploy_type, source_dir, move_fn, suffix)
if not rock_manifest[deploy_type] then
return true
end
return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
local file_path = parent_path .. file
local source = dir.path(source_dir, file_path)
local target, prepare_err = prepare_target(name, version, deploy_type, file_path, suffix)
if not target then return nil, prepare_err end
local dir_ok, dir_err = fs.make_dir(dir.dir_name(target))
if not dir_ok then return nil, dir_err end
local suffixed_target, mover = move_fn(source, target)
if fs.exists(suffixed_target) then
local backup = suffixed_target
repeat
backup = backup.."~"
until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
util.printerr("Warning: "..suffixed_target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
local move_ok, move_err = fs.move(suffixed_target, backup)
if not move_ok then return nil, move_err end
end
local move_ok, move_err = mover()
if not move_ok then return nil, move_err end
fs.remove_dir_tree_if_empty(dir.dir_name(source))
return true
end)
end
local function install_binary(source, target)
if wrap_bin_scripts and fs.is_lua(source) then
return target .. (cfg.wrapper_suffix or ""), function() return fs.wrap_script(source, target, name, version) end
else
return target, function() return fs.copy_binary(source, target) end
end
end
local function make_mover(perms)
return function(source, target)
return target, function() return fs.move(source, target, perms) end
end
end
local ok, err = deploy_file_tree("bin", path.bin_dir(name, version), install_binary, cfg.wrapper_suffix)
if not ok then return nil, err end
ok, err = deploy_file_tree("lua", path.lua_dir(name, version), make_mover(cfg.perm_read))
if not ok then return nil, err end
ok, err = deploy_file_tree("lib", path.lib_dir(name, version), make_mover(cfg.perm_exec))
if not ok then return nil, err end
return manif.add_to_manifest(name, version, nil, deps_mode)
end
--- Delete a package from the local repository.
-- @param name string: name of package
-- @param version string: exact package version in string format
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @param quick boolean: do not try to fix the versioned name
-- of another version that provides the same module that
-- was deleted. This is used during 'purge', as every module
-- will be eventually deleted.
function repos.delete_version(name, version, deps_mode, quick)
assert(type(name) == "string")
assert(type(version) == "string")
assert(type(deps_mode) == "string")
local rock_manifest = manif.load_rock_manifest(name, version)
if not rock_manifest then
return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
end
local function delete_deployed_file_tree(deploy_type, suffix)
if not rock_manifest[deploy_type] then
return true
end
return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
local file_path = parent_path .. file
local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
-- Figure out if the file is deployed using versioned or non-versioned name.
local target
local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
if cur_name == name and cur_version == version then
-- This package has highest priority, should be in non-versioned location.
target = non_versioned
else
target = versioned
end
local ok, err = delete_suffixed(target, suffix)
if not ok then return nil, err end
if not quick and target == non_versioned then
-- If another package provides this file, move its version
-- into non-versioned location instead.
local next_name, next_version = manif.get_next_provider(item_type, item_name)
if next_name then
local next_deploy_type, next_file_path = manif.get_providing_file(next_name, next_version, item_type, item_name)
local next_non_versioned, next_versioned = get_deploy_paths(next_name, next_version, next_deploy_type, next_file_path)
local move_ok, move_err = move_suffixed(next_versioned, next_non_versioned, suffix)
if not move_ok then return nil, move_err end
fs.remove_dir_tree_if_empty(dir.dir_name(next_versioned))
end
end
fs.remove_dir_tree_if_empty(dir.dir_name(target))
return true
end)
end
local ok, err = delete_deployed_file_tree("bin", cfg.wrapper_suffix)
if not ok then return nil, err end
ok, err = delete_deployed_file_tree("lua")
if not ok then return nil, err end
ok, err = delete_deployed_file_tree("lib")
if not ok then return nil, err end
fs.delete(path.install_dir(name, version))
if not get_installed_versions(name) then
fs.delete(dir.path(cfg.rocks_dir, name))
end
if quick then
return true
end
return manif.remove_from_manifest(name, version, nil, deps_mode)
end
return repos