From 75902ff48a5bdc2cd00b492deb9f8aaa1d35bd3d Mon Sep 17 00:00:00 2001 From: Hisham Date: Fri, 28 Oct 2016 20:15:15 -0200 Subject: Duplicate files to preserve git-blame --- src/luarocks/base/build.lua | 443 ++++++++++++++++++++++++++++++++ src/luarocks/base/config_cmd.lua | 71 ++++++ src/luarocks/base/doc.lua | 155 +++++++++++ src/luarocks/base/download.lua | 107 ++++++++ src/luarocks/base/help.lua | 117 +++++++++ src/luarocks/base/install.lua | 183 +++++++++++++ src/luarocks/base/lint.lua | 53 ++++ src/luarocks/base/list.lua | 95 +++++++ src/luarocks/base/make.lua | 86 +++++++ src/luarocks/base/new_version.lua | 199 +++++++++++++++ src/luarocks/base/pack.lua | 193 ++++++++++++++ src/luarocks/base/path_cmd.lua | 68 +++++ src/luarocks/base/purge.lua | 77 ++++++ src/luarocks/base/remove.lua | 165 ++++++++++++ src/luarocks/base/search.lua | 482 +++++++++++++++++++++++++++++++++++ src/luarocks/base/show.lua | 158 ++++++++++++ src/luarocks/base/unpack.lua | 164 ++++++++++++ src/luarocks/base/upload.lua | 94 +++++++ src/luarocks/base/write_rockspec.lua | 376 +++++++++++++++++++++++++++ src/luarocks/build.lua | 443 -------------------------------- src/luarocks/cmd/build.lua | 443 ++++++++++++++++++++++++++++++++ src/luarocks/cmd/config_cmd.lua | 71 ++++++ src/luarocks/cmd/doc.lua | 155 +++++++++++ src/luarocks/cmd/download.lua | 107 ++++++++ src/luarocks/cmd/help.lua | 117 +++++++++ src/luarocks/cmd/install.lua | 183 +++++++++++++ src/luarocks/cmd/lint.lua | 53 ++++ src/luarocks/cmd/list.lua | 95 +++++++ src/luarocks/cmd/make.lua | 86 +++++++ src/luarocks/cmd/new_version.lua | 199 +++++++++++++++ src/luarocks/cmd/pack.lua | 193 ++++++++++++++ src/luarocks/cmd/path_cmd.lua | 68 +++++ src/luarocks/cmd/purge.lua | 77 ++++++ src/luarocks/cmd/remove.lua | 165 ++++++++++++ src/luarocks/cmd/search.lua | 482 +++++++++++++++++++++++++++++++++++ src/luarocks/cmd/show.lua | 158 ++++++++++++ src/luarocks/cmd/unpack.lua | 164 ++++++++++++ src/luarocks/cmd/upload.lua | 94 +++++++ src/luarocks/cmd/write_rockspec.lua | 376 +++++++++++++++++++++++++++ src/luarocks/config_cmd.lua | 71 ------ src/luarocks/doc.lua | 155 ----------- src/luarocks/download.lua | 107 -------- src/luarocks/help.lua | 117 --------- src/luarocks/install.lua | 183 ------------- src/luarocks/lint.lua | 53 ---- src/luarocks/list.lua | 95 ------- src/luarocks/make.lua | 86 ------- src/luarocks/new_version.lua | 199 --------------- src/luarocks/pack.lua | 193 -------------- src/luarocks/path_cmd.lua | 68 ----- src/luarocks/purge.lua | 77 ------ src/luarocks/remove.lua | 165 ------------ src/luarocks/search.lua | 482 ----------------------------------- src/luarocks/show.lua | 158 ------------ src/luarocks/unpack.lua | 164 ------------ src/luarocks/upload.lua | 94 ------- src/luarocks/write_rockspec.lua | 376 --------------------------- 57 files changed, 6572 insertions(+), 3286 deletions(-) create mode 100644 src/luarocks/base/build.lua create mode 100644 src/luarocks/base/config_cmd.lua create mode 100644 src/luarocks/base/doc.lua create mode 100644 src/luarocks/base/download.lua create mode 100644 src/luarocks/base/help.lua create mode 100644 src/luarocks/base/install.lua create mode 100644 src/luarocks/base/lint.lua create mode 100644 src/luarocks/base/list.lua create mode 100644 src/luarocks/base/make.lua create mode 100644 src/luarocks/base/new_version.lua create mode 100644 src/luarocks/base/pack.lua create mode 100644 src/luarocks/base/path_cmd.lua create mode 100644 src/luarocks/base/purge.lua create mode 100644 src/luarocks/base/remove.lua create mode 100644 src/luarocks/base/search.lua create mode 100644 src/luarocks/base/show.lua create mode 100644 src/luarocks/base/unpack.lua create mode 100644 src/luarocks/base/upload.lua create mode 100644 src/luarocks/base/write_rockspec.lua delete mode 100644 src/luarocks/build.lua create mode 100644 src/luarocks/cmd/build.lua create mode 100644 src/luarocks/cmd/config_cmd.lua create mode 100644 src/luarocks/cmd/doc.lua create mode 100644 src/luarocks/cmd/download.lua create mode 100644 src/luarocks/cmd/help.lua create mode 100644 src/luarocks/cmd/install.lua create mode 100644 src/luarocks/cmd/lint.lua create mode 100644 src/luarocks/cmd/list.lua create mode 100644 src/luarocks/cmd/make.lua create mode 100644 src/luarocks/cmd/new_version.lua create mode 100644 src/luarocks/cmd/pack.lua create mode 100644 src/luarocks/cmd/path_cmd.lua create mode 100644 src/luarocks/cmd/purge.lua create mode 100644 src/luarocks/cmd/remove.lua create mode 100644 src/luarocks/cmd/search.lua create mode 100644 src/luarocks/cmd/show.lua create mode 100644 src/luarocks/cmd/unpack.lua create mode 100644 src/luarocks/cmd/upload.lua create mode 100644 src/luarocks/cmd/write_rockspec.lua delete mode 100644 src/luarocks/config_cmd.lua delete mode 100644 src/luarocks/doc.lua delete mode 100644 src/luarocks/download.lua delete mode 100644 src/luarocks/help.lua delete mode 100644 src/luarocks/install.lua delete mode 100644 src/luarocks/lint.lua delete mode 100644 src/luarocks/list.lua delete mode 100644 src/luarocks/make.lua delete mode 100644 src/luarocks/new_version.lua delete mode 100644 src/luarocks/pack.lua delete mode 100644 src/luarocks/path_cmd.lua delete mode 100644 src/luarocks/purge.lua delete mode 100644 src/luarocks/remove.lua delete mode 100644 src/luarocks/search.lua delete mode 100644 src/luarocks/show.lua delete mode 100644 src/luarocks/unpack.lua delete mode 100644 src/luarocks/upload.lua delete mode 100644 src/luarocks/write_rockspec.lua (limited to 'src') diff --git a/src/luarocks/base/build.lua b/src/luarocks/base/build.lua new file mode 100644 index 00000000..f3b054d2 --- /dev/null +++ b/src/luarocks/base/build.lua @@ -0,0 +1,443 @@ + +--- Module implementing the LuaRocks "build" command. +-- Builds a rock, compiling its C parts if any. +local build = {} + +local pack = require("luarocks.pack") +local path = require("luarocks.path") +local util = require("luarocks.util") +local repos = require("luarocks.repos") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local deps = require("luarocks.deps") +local writer = require("luarocks.manif.writer") +local remove = require("luarocks.remove") +local cfg = require("luarocks.core.cfg") + +build.help_summary = "Build/compile a rock." +build.help_arguments = "[--pack-binary-rock] [--keep] {|| []}" +build.help = [[ +Build and install a rock, compiling its C parts if any. +Argument may be a rockspec file, a source rock file +or the name of a rock to be fetched from a repository. + +--pack-binary-rock Do not install rock. Instead, produce a .rock file + with the contents of compilation in the current + directory. + +--keep Do not remove previously installed versions of the + rock after building a new one. This behavior can + be made permanent by setting keep_other_versions=true + in the configuration file. + +--branch= Override the `source.branch` field in the loaded + rockspec. Allows to specify a different branch to + fetch. Particularly for SCM rocks. + +--only-deps Installs only the dependencies of the rock. + +]]..util.deps_mode_help() + +--- Install files to a given location. +-- Takes a table where the array part is a list of filenames to be copied. +-- In the hash part, other keys, if is_module_path is set, are identifiers +-- in Lua module format, to indicate which subdirectory the file should be +-- copied to. For example, install_files({["foo.bar"] = "src/bar.lua"}, "boo") +-- will copy src/bar.lua to boo/foo. +-- @param files table or nil: A table containing a list of files to copy in +-- the format described above. If nil is passed, this function is a no-op. +-- Directories should be delimited by forward slashes as in internet URLs. +-- @param location string: The base directory files should be copied to. +-- @param is_module_path boolean: True if string keys in files should be +-- interpreted as dotted module paths. +-- @param perms string: Permissions of the newly created files installed. +-- Directories are always created with the default permissions. +-- @return boolean or (nil, string): True if succeeded or +-- nil and an error message. +local function install_files(files, location, is_module_path, perms) + assert(type(files) == "table" or not files) + assert(type(location) == "string") + if files then + for k, file in pairs(files) do + local dest = location + local filename = dir.base_name(file) + if type(k) == "string" then + local modname = k + if is_module_path then + dest = dir.path(location, path.module_to_path(modname)) + local ok, err = fs.make_dir(dest) + if not ok then return nil, err end + if filename:match("%.lua$") then + local basename = modname:match("([^.]+)$") + filename = basename..".lua" + end + else + dest = dir.path(location, dir.dir_name(modname)) + local ok, err = fs.make_dir(dest) + if not ok then return nil, err end + filename = dir.base_name(modname) + end + else + local ok, err = fs.make_dir(dest) + if not ok then return nil, err end + end + local ok = fs.copy(dir.path(file), dir.path(dest, filename), perms) + if not ok then + return nil, "Failed copying "..file + end + end + end + return true +end + +--- Write to the current directory the contents of a table, +-- where each key is a file name and its value is the file content. +-- @param files table: The table of files to be written. +local function extract_from_rockspec(files) + for name, content in pairs(files) do + local fd = io.open(dir.path(fs.current_dir(), name), "w+") + fd:write(content) + fd:close() + end +end + +--- Applies patches inlined in the build.patches section +-- and extracts files inlined in the build.extra_files section +-- of a rockspec. +-- @param rockspec table: A rockspec table. +-- @return boolean or (nil, string): True if succeeded or +-- nil and an error message. +function build.apply_patches(rockspec) + assert(type(rockspec) == "table") + + local build_spec = rockspec.build + if build_spec.extra_files then + extract_from_rockspec(build_spec.extra_files) + end + if build_spec.patches then + extract_from_rockspec(build_spec.patches) + for patch, patchdata in util.sortedpairs(build_spec.patches) do + util.printout("Applying patch "..patch.."...") + local ok, err = fs.apply_patch(tostring(patch), patchdata) + if not ok then + return nil, "Failed applying patch "..patch + end + end + end + return true +end + +local function install_default_docs(name, version) + local patterns = { "readme", "license", "copying", ".*%.md" } + local dest = dir.path(path.install_dir(name, version), "doc") + local has_dir = false + for file in fs.dir() do + for _, pattern in ipairs(patterns) do + if file:lower():match("^"..pattern) then + if not has_dir then + fs.make_dir(dest) + has_dir = true + end + fs.copy(file, dest, cfg.perm_read) + break + end + end + end +end + +local function check_macosx_deployment_target(rockspec) + local target = rockspec.build.macosx_deployment_target + local function minor(version) + return tonumber(version and version:match("^[^.]+%.([^.]+)")) + end + local function patch_variable(var, target) + if rockspec.variables[var]:match("MACOSX_DEPLOYMENT_TARGET") then + rockspec.variables[var] = (rockspec.variables[var]):gsub("MACOSX_DEPLOYMENT_TARGET=[^ ]*", "MACOSX_DEPLOYMENT_TARGET="..target) + else + rockspec.variables[var] = "env MACOSX_DEPLOYMENT_TARGET="..target.." "..rockspec.variables[var] + end + end + if cfg.platforms.macosx and rockspec:format_is_at_least("3.0") and target then + local version = util.popen_read("sw_vers -productVersion") + local versionminor = minor(version) + local targetminor = minor(target) + if targetminor > versionminor then + return nil, ("This rock requires Mac OSX 10.%d, and you are running 10.%d."):format(targetminor, versionminor) + end + patch_variable("CC", target) + patch_variable("LD", target) + end + return true +end + +--- Build and install a rock given a rockspec. +-- @param rockspec_file string: local or remote filename of a rockspec. +-- @param need_to_fetch boolean: true if sources need to be fetched, +-- false if the rockspec was obtained from inside a source rock. +-- @param minimal_mode boolean: true if there's no need to fetch, +-- unpack or change dir (this is used by "luarocks make"). Implies +-- need_to_fetch = false. +-- @param deps_mode string: Dependency mode: "one" for the current default tree, +-- "all" for all trees, "order" for all trees with priority >= the current default, +-- "none" for no trees. +-- @param build_only_deps boolean: true to build the listed dependencies only. +-- @return (string, string) or (nil, string, [string]): Name and version of +-- installed rock if succeeded or nil and an error message followed by an error code. +function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps) + assert(type(rockspec_file) == "string") + assert(type(need_to_fetch) == "boolean") + + local rockspec, err, errcode = fetch.load_rockspec(rockspec_file) + if err then + return nil, err, errcode + elseif not rockspec.build then + return nil, "Rockspec error: build table not specified" + elseif not rockspec.build.type then + return nil, "Rockspec error: build type not specified" + end + + local ok + if not build_only_deps then + ok, err, errcode = deps.check_external_deps(rockspec, "build") + if err then + return nil, err, errcode + end + end + + if deps_mode == "none" then + util.printerr("Warning: skipping dependency checks.") + else + local ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) + if err then + return nil, err, errcode + end + end + + local name, version = rockspec.name, rockspec.version + if build_only_deps then + util.printout("Stopping after installing dependencies for " ..name.." "..version) + util.printout() + return name, version + end + + if repos.is_installed(name, version) then + repos.delete_version(name, version, deps_mode) + end + + if not minimal_mode then + local source_dir + if need_to_fetch then + ok, source_dir, errcode = fetch.fetch_sources(rockspec, true) + if not ok then + return nil, source_dir, errcode + end + local ok, err = fs.change_dir(source_dir) + if not ok then return nil, err end + elseif rockspec.source.file then + local ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then + return nil, err + end + end + fs.change_dir(rockspec.source.dir) + end + + local dirs = { + lua = { name = path.lua_dir(name, version), is_module_path = true, perms = cfg.perm_read }, + lib = { name = path.lib_dir(name, version), is_module_path = true, perms = cfg.perm_exec }, + conf = { name = path.conf_dir(name, version), is_module_path = false, perms = cfg.perm_read }, + bin = { name = path.bin_dir(name, version), is_module_path = false, perms = cfg.perm_exec }, + } + + for _, d in pairs(dirs) do + local ok, err = fs.make_dir(d.name) + if not ok then return nil, err end + end + local rollback = util.schedule_function(function() + fs.delete(path.install_dir(name, version)) + fs.remove_dir_if_empty(path.versions_dir(name)) + end) + + local build_spec = rockspec.build + + if not minimal_mode then + ok, err = build.apply_patches(rockspec) + if err then + return nil, err + end + end + + ok, err = check_macosx_deployment_target(rockspec) + if not ok then + return nil, err + end + + if build_spec.type ~= "none" then + + -- Temporary compatibility + if build_spec.type == "module" then + util.printout("Do not use 'module' as a build type. Use 'builtin' instead.") + build_spec.type = "builtin" + end + + if cfg.accepted_build_types and util.array_contains(cfg.accepted_build_types, build_spec.type) then + return nil, "This rockspec uses the '"..build_spec.type.."' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration." + end + + local build_type + ok, build_type = pcall(require, "luarocks.build." .. build_spec.type) + if not ok or not type(build_type) == "table" then + return nil, "Failed initializing build back-end for build type '"..build_spec.type.."': "..build_type + end + + ok, err = build_type.run(rockspec) + if not ok then + return nil, "Build error: " .. err + end + end + + if build_spec.install then + for id, install_dir in pairs(dirs) do + ok, err = install_files(build_spec.install[id], install_dir.name, install_dir.is_module_path, install_dir.perms) + if not ok then + return nil, err + end + end + end + + local copy_directories = build_spec.copy_directories + local copying_default = false + if not copy_directories then + copy_directories = {"doc"} + copying_default = true + end + + local any_docs = false + for _, copy_dir in pairs(copy_directories) do + if fs.is_dir(copy_dir) then + local dest = dir.path(path.install_dir(name, version), copy_dir) + fs.make_dir(dest) + fs.copy_contents(copy_dir, dest) + any_docs = true + else + if not copying_default then + return nil, "Directory '"..copy_dir.."' not found" + end + end + end + + if not any_docs then + install_default_docs(name, version) + end + + for _, d in pairs(dirs) do + fs.remove_dir_if_empty(d.name) + end + + fs.pop_dir() + + fs.copy(rockspec.local_filename, path.rockspec_file(name, version), cfg.perm_read) + if need_to_fetch then + fs.pop_dir() + end + + ok, err = writer.make_rock_manifest(name, version) + if err then return nil, err end + + ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) + if err then return nil, err end + + util.remove_scheduled_function(rollback) + rollback = util.schedule_function(function() + repos.delete_version(name, version, deps_mode) + end) + + ok, err = repos.run_hook(rockspec, "post_install") + if err then return nil, err end + + util.announce_install(rockspec) + util.remove_scheduled_function(rollback) + return name, version +end + +--- Build and install a rock. +-- @param rock_file string: local or remote filename of a rock. +-- @param need_to_fetch boolean: true if sources need to be fetched, +-- false if the rockspec was obtained from inside a source rock. +-- @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 build_only_deps boolean: true to build the listed dependencies only. +-- @return boolean or (nil, string, [string]): True if build was successful, +-- or false and an error message and an optional error code. +function build.build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps) + assert(type(rock_file) == "string") + assert(type(need_to_fetch) == "boolean") + + local ok, err, errcode + local unpack_dir + unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file) + if not unpack_dir then + return nil, err, errcode + end + local rockspec_file = path.rockspec_name_from_rock(rock_file) + ok, err = fs.change_dir(unpack_dir) + if not ok then return nil, err end + ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps) + fs.pop_dir() + return ok, err, errcode +end + +local function do_build(name, version, deps_mode, build_only_deps) + if name:match("%.rockspec$") then + return build.build_rockspec(name, true, false, deps_mode, build_only_deps) + elseif name:match("%.src%.rock$") then + return build.build_rock(name, false, deps_mode, build_only_deps) + elseif name:match("%.all%.rock$") then + local install = require("luarocks.install") + local install_fun = build_only_deps and install.install_binary_rock_deps or install.install_binary_rock + return install_fun(name, deps_mode) + elseif name:match("%.rock$") then + return build.build_rock(name, true, deps_mode, build_only_deps) + elseif not name:match(dir.separator) then + local search = require("luarocks.search") + return search.act_on_src_or_rockspec(do_build, name:lower(), version, nil, deps_mode, build_only_deps) + end + return nil, "Don't know what to do with "..name +end + +--- Driver function for "build" command. +-- @param name string: A local or remote rockspec or rock file. +-- If a package name is given, forwards the request to "search" and, +-- if returned a result, installs the matching rock. +-- @param version string: When passing a package name, a version number may +-- also be given. +-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an +-- error message otherwise. exitcode is optionally returned. +function build.command(flags, name, version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("build") + end + assert(type(version) == "string" or not version) + + if flags["pack-binary-rock"] then + return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags)) + else + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + ok, err = do_build(name, version, deps.get_deps_mode(flags), flags["only-deps"]) + if not ok then return nil, err end + name, version = ok, err + if flags["only-deps"] then + return name, version + end + if (not flags["keep"]) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) + if not ok then util.printerr(err) end + end + return name, version + end +end + +return build diff --git a/src/luarocks/base/config_cmd.lua b/src/luarocks/base/config_cmd.lua new file mode 100644 index 00000000..b68f7898 --- /dev/null +++ b/src/luarocks/base/config_cmd.lua @@ -0,0 +1,71 @@ +--- Module implementing the LuaRocks "config" command. +-- Queries information about the LuaRocks configuration. +local config_cmd = {} + +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") + +config_cmd.help_summary = "Query information about the LuaRocks configuration." +config_cmd.help_arguments = "" +config_cmd.help = [[ +--lua-incdir Path to Lua header files. + +--lua-libdir Path to Lua library files. + +--lua-ver Lua version (in major.minor format). e.g. 5.1 + +--system-config Location of the system config file. + +--user-config Location of the user config file. + +--rock-trees Rocks trees in use. First the user tree, then the system tree. +]] + +local function config_file(conf) + print(dir.normalize(conf.file)) + if conf.ok then + return true + else + return nil, "file not found" + end +end + +--- Driver function for "config" command. +-- @return boolean: True if succeeded, nil on errors. +function config_cmd.command(flags) + if flags["lua-incdir"] then + print(cfg.variables.LUA_INCDIR) + return true + end + if flags["lua-libdir"] then + print(cfg.variables.LUA_LIBDIR) + return true + end + if flags["lua-ver"] then + print(cfg.lua_version) + return true + end + local conf = cfg.which_config() + if flags["system-config"] then + return config_file(conf.system) + end + if flags["user-config"] then + return config_file(conf.user) + end + if flags["rock-trees"] then + for _, tree in ipairs(cfg.rocks_trees) do + if type(tree) == "string" then + util.printout(dir.normalize(tree)) + else + local name = tree.name and "\t"..tree.name or "" + util.printout(dir.normalize(tree.root)..name) + end + end + return true + end + + return nil, "Please provide a flag for querying configuration values. "..util.see_help("config") +end + +return config_cmd diff --git a/src/luarocks/base/doc.lua b/src/luarocks/base/doc.lua new file mode 100644 index 00000000..5d521276 --- /dev/null +++ b/src/luarocks/base/doc.lua @@ -0,0 +1,155 @@ + +--- Module implementing the LuaRocks "doc" command. +-- Shows documentation for an installed rock. +local doc = {} + +local util = require("luarocks.util") +local search = require("luarocks.search") +local path = require("luarocks.path") +local dir = require("luarocks.dir") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local download = require("luarocks.download") + +doc.help_summary = "Show documentation for an installed rock." + +doc.help = [[ + is an existing package name. +Without any flags, tries to load the documentation +using a series of heuristics. +With these flags, return only the desired information: + +--home Open the home page of project. +--list List documentation files only. + +For more information about a rock, see the 'show' command. +]] + +local function show_homepage(homepage, name, version) + if not homepage then + return nil, "No 'homepage' field in rockspec for "..name.." "..version + end + util.printout("Opening "..homepage.." ...") + fs.browser(homepage) + return true +end + +local function try_to_open_homepage(name, version) + local temp_dir, err = fs.make_temp_dir("doc-"..name.."-"..(version or "")) + if not temp_dir then + return nil, "Failed creating temporary directory: "..err + end + util.schedule_function(fs.delete, temp_dir) + local ok, err = fs.change_dir(temp_dir) + if not ok then return nil, err end + local filename, err = download.download("rockspec", name, version) + if not filename then return nil, err end + local rockspec, err = fetch.load_local_rockspec(filename) + if not rockspec then return nil, err end + fs.pop_dir() + local descript = rockspec.description or {} + if not descript.homepage then return nil, "No homepage defined for "..name end + return show_homepage(descript.homepage, name, version) +end + +--- Driver function for "doc" command. +-- @param name or nil: an existing package name. +-- @param version string or nil: a version may also be passed. +-- @return boolean: True if succeeded, nil on errors. +function doc.command(flags, name, version) + if not name then + return nil, "Argument missing. "..util.see_help("doc") + end + + name = name:lower() + + local iname, iversion, repo = search.pick_installed_rock(name, version, flags["tree"]) + if not iname then + util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...") + return try_to_open_homepage(name, version) + end + name, version = iname, iversion + + local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo)) + if not rockspec then return nil,err end + local descript = rockspec.description or {} + + if flags["home"] then + return show_homepage(descript.homepage, name, version) + end + + local directory = path.install_dir(name,version,repo) + + local docdir + local directories = { "doc", "docs" } + for _, d in ipairs(directories) do + local dirname = dir.path(directory, d) + if fs.is_dir(dirname) then + docdir = dirname + break + end + end + if not docdir then + if descript.homepage and not flags["list"] then + util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...") + fs.browser(descript.homepage) + return true + end + return nil, "Documentation directory not found for "..name.." "..version + end + + docdir = dir.normalize(docdir):gsub("/+", "/") + local files = fs.find(docdir) + local htmlpatt = "%.html?$" + local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" } + local basenames = { "index", "readme", "manual" } + + local porcelain = flags["porcelain"] + if #files > 0 then + util.title("Documentation files for "..name.." "..version, porcelain) + if porcelain then + for _, file in ipairs(files) do + util.printout(docdir.."/"..file) + end + else + util.printout(docdir.."/") + for _, file in ipairs(files) do + util.printout("\t"..file) + end + end + end + + if flags["list"] then + return true + end + + for _, extension in ipairs(extensions) do + for _, basename in ipairs(basenames) do + local filename = basename..extension + local found + for _, file in ipairs(files) do + if file:lower():match(filename) and ((not found) or #file < #found) then + found = file + end + end + if found then + local pathname = dir.path(docdir, found) + util.printout() + util.printout("Opening "..pathname.." ...") + util.printout() + local ok = fs.browser(pathname) + if not ok and not pathname:match(htmlpatt) then + local fd = io.open(pathname, "r") + util.printout(fd:read("*a")) + fd:close() + end + return true + end + end + end + + return true +end + + +return doc diff --git a/src/luarocks/base/download.lua b/src/luarocks/base/download.lua new file mode 100644 index 00000000..557d1b65 --- /dev/null +++ b/src/luarocks/base/download.lua @@ -0,0 +1,107 @@ + +--- Module implementing the luarocks "download" command. +-- Download a rock from the repository. +local download = {} + +local util = require("luarocks.util") +local path = require("luarocks.path") +local fetch = require("luarocks.fetch") +local search = require("luarocks.search") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local cfg = require("luarocks.core.cfg") + +download.help_summary = "Download a specific rock file from a rocks server." +download.help_arguments = "[--all] [--arch= | --source | --rockspec] [ []]" + +download.help = [[ +--all Download all files if there are multiple matches. +--source Download .src.rock if available. +--rockspec Download .rockspec if available. +--arch= Download rock for a specific architecture. +]] + +local function get_file(filename) + local protocol, pathname = dir.split_url(filename) + if protocol == "file" then + local ok, err = fs.copy(pathname, fs.current_dir(), cfg.perm_read) + if ok then + return pathname + else + return nil, err + end + else + return fetch.fetch_url(filename) + end +end + +function download.download(arch, name, version, all) + local query = search.make_query(name, version) + if arch then query.arch = arch end + local search_err + + if all then + if name == "" then query.exact_name = false end + local results = search.search_repos(query) + local has_result = false + local all_ok = true + local any_err = "" + for name, result in pairs(results) do + for version, items in pairs(result) do + for _, item in ipairs(items) do + -- Ignore provided rocks. + if item.arch ~= "installed" then + has_result = true + local filename = path.make_url(item.repo, name, version, item.arch) + local ok, err = get_file(filename) + if not ok then + all_ok = false + any_err = any_err .. "\n" .. err + end + end + end + end + end + + if has_result then + return all_ok, any_err + end + else + local url + url, search_err = search.find_suitable_rock(query) + if url then + return get_file(url) + end + end + return nil, "Could not find a result named "..name..(version and " "..version or "").. + (search_err and ": "..search_err or ".") +end + +--- Driver function for the "download" command. +-- @param name string: a rock name. +-- @param version string or nil: if the name of a package is given, a +-- version may also be passed. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +function download.command(flags, name, version) + assert(type(version) == "string" or not version) + if type(name) ~= "string" and not flags["all"] then + return nil, "Argument missing. "..util.see_help("download") + end + if not name then name, version = "", "" end + + local arch + + if flags["source"] then + arch = "src" + elseif flags["rockspec"] then + arch = "rockspec" + elseif flags["arch"] then + arch = flags["arch"] + end + + local dl, err = download.download(arch, name:lower(), version, flags["all"]) + return dl and true, err +end + +return download diff --git a/src/luarocks/base/help.lua b/src/luarocks/base/help.lua new file mode 100644 index 00000000..d27c3a50 --- /dev/null +++ b/src/luarocks/base/help.lua @@ -0,0 +1,117 @@ + +--- Module implementing the LuaRocks "help" command. +-- This is a generic help display module, which +-- uses a global table called "commands" to find commands +-- to show help for; each command should be represented by a +-- table containing "help" and "help_summary" fields. +local help = {} + +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") + +local program = util.this_program("luarocks") + +help.help_summary = "Help on commands. Type '"..program.." help ' for more." + +help.help_arguments = "[]" +help.help = [[ + is the command to show help for. +]] + +local function print_banner() + util.printout("\nLuaRocks "..cfg.program_version..", a module deployment system for Lua") +end + +local function print_section(section) + util.printout("\n"..section) +end + +local function get_status(status) + if status then + return "ok" + else + return "not found" + end +end + +--- Driver function for the "help" command. +-- @param command string or nil: command to show help for; if not +-- given, help summaries for all commands are shown. +-- @return boolean or (nil, string): true if there were no errors +-- or nil and an error message if an invalid command was requested. +function help.command(flags, command) + if not command then + local conf = cfg.which_config() + print_banner() + print_section("NAME") + util.printout("\t"..program..[[ - ]]..program_description) + print_section("SYNOPSIS") + util.printout("\t"..program..[[ [--from= | --only-from=] [--to=] [VAR=VALUE]... [] ]]) + print_section("GENERAL OPTIONS") + util.printout([[ + These apply to all commands, as appropriate: + + --server= Fetch rocks/rockspecs from this server + (takes priority over config file) + --only-server= Fetch rocks/rockspecs from this server only + (overrides any entries in the config file) + --only-sources= Restrict downloads to paths matching the + given URL. + --tree= Which tree to operate on. + --local Use the tree in the user's home directory. + To enable it, see ']]..program..[[ help path'. + --verbose Display verbose output of commands executed. + --timeout= Timeout on network operations, in seconds. + 0 means no timeout (wait forever). + Default is ]]..tostring(cfg.connection_timeout)..[[.]]) + print_section("VARIABLES") + util.printout([[ + Variables from the "variables" table of the configuration file + can be overriden with VAR=VALUE assignments.]]) + print_section("COMMANDS") + for name, command in util.sortedpairs(commands) do + local cmd = require(command) + util.printout("", name) + util.printout("\t", cmd.help_summary) + end + print_section("CONFIGURATION") + util.printout("\tLua version: " .. cfg.lua_version) + util.printout("\tConfiguration files:") + util.printout("\t\tSystem: ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.ok) ..")") + if conf.user.file then + util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.ok) ..")\n") + else + util.printout("\t\tUser : disabled in this LuaRocks installation.\n") + end + util.printout("\tRocks trees in use: ") + for _, tree in ipairs(cfg.rocks_trees) do + if type(tree) == "string" then + util.printout("\t\t"..dir.normalize(tree)) + else + local name = tree.name and " (\""..tree.name.."\")" or "" + util.printout("\t\t"..dir.normalize(tree.root)..name) + end + end + else + command = command:gsub("-", "_") + local cmd = commands[command] and require(commands[command]) + if cmd then + local arguments = cmd.help_arguments or "" + print_banner() + print_section("NAME") + util.printout("\t"..program.." "..command.." - "..cmd.help_summary) + print_section("SYNOPSIS") + util.printout("\t"..program.." "..command.." "..arguments) + print_section("DESCRIPTION") + util.printout("",(cmd.help:gsub("\n","\n\t"):gsub("\n\t$",""))) + print_section("SEE ALSO") + util.printout("","'"..program.." help' for general options and configuration.\n") + else + return nil, "Unknown command: "..command + end + end + return true +end + +return help diff --git a/src/luarocks/base/install.lua b/src/luarocks/base/install.lua new file mode 100644 index 00000000..c9b085f5 --- /dev/null +++ b/src/luarocks/base/install.lua @@ -0,0 +1,183 @@ +--- Module implementing the LuaRocks "install" command. +-- Installs binary rocks. +local install = {} + +local path = require("luarocks.path") +local repos = require("luarocks.repos") +local fetch = require("luarocks.fetch") +local util = require("luarocks.util") +local fs = require("luarocks.fs") +local deps = require("luarocks.deps") +local writer = require("luarocks.manif.writer") +local remove = require("luarocks.remove") +local cfg = require("luarocks.core.cfg") + +install.help_summary = "Install a rock." + +install.help_arguments = "{| []}" + +install.help = [[ +Argument may be the name of a rock to be fetched from a repository +or a filename of a locally available rock. + +--keep Do not remove previously installed versions of the + rock after installing a new one. This behavior can + be made permanent by setting keep_other_versions=true + in the configuration file. + +--only-deps Installs only the dependencies of the rock. +]]..util.deps_mode_help() + + +--- Install a binary rock. +-- @param rock_file string: local or remote filename of a rock. +-- @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. +-- @return (string, string) or (nil, string, [string]): Name and version of +-- installed rock if succeeded or nil and an error message followed by an error code. +function install.install_binary_rock(rock_file, deps_mode) + assert(type(rock_file) == "string") + + local name, version, arch = path.parse_name(rock_file) + if not name then + return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." + end + + if arch ~= "all" and arch ~= cfg.arch then + return nil, "Incompatible architecture "..arch, "arch" + end + if repos.is_installed(name, version) then + repos.delete_version(name, version, deps_mode) + end + + local rollback = util.schedule_function(function() + fs.delete(path.install_dir(name, version)) + fs.remove_dir_if_empty(path.versions_dir(name)) + end) + + local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) + if not ok then return nil, err, errcode end + + local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version)) + if err then + return nil, "Failed loading rockspec for installed package: "..err, errcode + end + + if deps_mode == "none" then + util.printerr("Warning: skipping dependency checks.") + else + ok, err, errcode = deps.check_external_deps(rockspec, "install") + if err then return nil, err, errcode end + end + + -- For compatibility with .rock files built with LuaRocks 1 + if not fs.exists(path.rock_manifest_file(name, version)) then + ok, err = writer.make_rock_manifest(name, version) + if err then return nil, err end + end + + if deps_mode ~= "none" then + ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) + if err then return nil, err, errcode end + end + + ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) + if err then return nil, err end + + util.remove_scheduled_function(rollback) + rollback = util.schedule_function(function() + repos.delete_version(name, version, deps_mode) + end) + + ok, err = repos.run_hook(rockspec, "post_install") + if err then return nil, err end + + util.announce_install(rockspec) + util.remove_scheduled_function(rollback) + return name, version +end + +--- Installs the dependencies of a binary rock. +-- @param rock_file string: local or remote filename of a rock. +-- @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. +-- @return (string, string) or (nil, string, [string]): Name and version of +-- the rock whose dependencies were installed if succeeded or nil and an error message +-- followed by an error code. +function install.install_binary_rock_deps(rock_file, deps_mode) + assert(type(rock_file) == "string") + + local name, version, arch = path.parse_name(rock_file) + if not name then + return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." + end + + if arch ~= "all" and arch ~= cfg.arch then + return nil, "Incompatible architecture "..arch, "arch" + end + + local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) + if not ok then return nil, err, errcode end + + local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version)) + if err then + return nil, "Failed loading rockspec for installed package: "..err, errcode + end + + ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) + if err then return nil, err, errcode end + + util.printout() + util.printout("Successfully installed dependencies for " ..name.." "..version) + + return name, version +end + +--- Driver function for the "install" command. +-- @param name string: name of a binary rock. If an URL or pathname +-- to a binary rock is given, fetches and installs it. If a rockspec or a +-- source rock is given, forwards the request to the "build" command. +-- If a package name is given, forwards the request to "search" and, +-- if returned a result, installs the matching rock. +-- @param version string: When passing a package name, a version number +-- may also be given. +-- @return boolean or (nil, string, exitcode): True if installation was +-- successful, nil and an error message otherwise. exitcode is optionally returned. +function install.command(flags, name, version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("install") + end + + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + + if name:match("%.rockspec$") or name:match("%.src%.rock$") then + local build = require("luarocks.build") + return build.command(flags, name) + elseif name:match("%.rock$") then + if flags["only-deps"] then + ok, err = install.install_binary_rock_deps(name, deps.get_deps_mode(flags)) + else + ok, err = install.install_binary_rock(name, deps.get_deps_mode(flags)) + end + if not ok then return nil, err end + name, version = ok, err + if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) + if not ok then util.printerr(err) end + end + return name, version + else + local search = require("luarocks.search") + local url, err = search.find_suitable_rock(search.make_query(name:lower(), version)) + if not url then + return nil, err + end + util.printout("Installing "..url) + return install.command(flags, url) + end +end + +return install diff --git a/src/luarocks/base/lint.lua b/src/luarocks/base/lint.lua new file mode 100644 index 00000000..c9ea45ea --- /dev/null +++ b/src/luarocks/base/lint.lua @@ -0,0 +1,53 @@ + +--- Module implementing the LuaRocks "lint" command. +-- Utility function that checks syntax of the rockspec. +local lint = {} + +local util = require("luarocks.util") +local download = require("luarocks.download") +local fetch = require("luarocks.fetch") + +lint.help_summary = "Check syntax of a rockspec." +lint.help_arguments = "" +lint.help = [[ +This is a utility function that checks the syntax of a rockspec. + +It returns success or failure if the text of a rockspec is +syntactically correct. +]] + +function lint.command(flags, input) + if not input then + return nil, "Argument missing. "..util.see_help("lint") + end + + local filename = input + if not input:match(".rockspec$") then + local err + filename, err = download.download("rockspec", input:lower()) + if not filename then + return nil, err + end + end + + local rs, err = fetch.load_local_rockspec(filename) + if not rs then + return nil, "Failed loading rockspec: "..err + end + + local ok = true + + -- This should have been done in the type checker, + -- but it would break compatibility of other commands. + -- Making 'lint' alone be stricter shouldn't be a problem, + -- because extra-strict checks is what lint-type commands + -- are all about. + if not rs.description.license then + util.printerr("Rockspec has no license field.") + ok = false + end + + return ok, ok or filename.." failed consistency checks." +end + +return lint diff --git a/src/luarocks/base/list.lua b/src/luarocks/base/list.lua new file mode 100644 index 00000000..45f1a26f --- /dev/null +++ b/src/luarocks/base/list.lua @@ -0,0 +1,95 @@ + +--- Module implementing the LuaRocks "list" command. +-- Lists currently installed rocks. +local list = {} + +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local path = require("luarocks.path") + +list.help_summary = "List currently installed rocks." +list.help_arguments = "[--porcelain] " +list.help = [[ + is a substring of a rock name to filter by. + +--outdated List only rocks for which there is a + higher version available in the rocks server. + +--porcelain Produce machine-friendly output. +]] + +local function check_outdated(trees, query) + local results_installed = {} + for _, tree in ipairs(trees) do + search.manifest_search(results_installed, path.rocks_dir(tree), query) + end + local outdated = {} + for name, versions in util.sortedpairs(results_installed) do + versions = util.keys(versions) + table.sort(versions, deps.compare_versions) + local latest_installed = versions[1] + + local query_available = search.make_query(name:lower()) + query.exact_name = true + local results_available, err = search.search_repos(query_available) + + if results_available[name] then + local available_versions = util.keys(results_available[name]) + table.sort(available_versions, deps.compare_versions) + local latest_available = available_versions[1] + local latest_available_repo = results_available[name][latest_available][1].repo + + if deps.compare_versions(latest_available, latest_installed) then + table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo }) + end + end + end + return outdated +end + +local function list_outdated(trees, query, porcelain) + util.title("Outdated rocks:", porcelain) + local outdated = check_outdated(trees, query) + for _, item in ipairs(outdated) do + if porcelain then + util.printout(item.name, item.installed, item.available, item.repo) + else + util.printout(item.name) + util.printout(" "..item.installed.." < "..item.available.." at "..item.repo) + util.printout() + end + end + return true +end + +--- Driver function for "list" command. +-- @param filter string or nil: A substring of a rock name to filter by. +-- @param version string or nil: a version may also be passed. +-- @return boolean: True if succeeded, nil on errors. +function list.command(flags, filter, version) + local query = search.make_query(filter and filter:lower() or "", version) + query.exact_name = false + local trees = cfg.rocks_trees + if flags["tree"] then + trees = { flags["tree"] } + end + + if flags["outdated"] then + return list_outdated(trees, query, flags["porcelain"]) + end + + local results = {} + for _, tree in ipairs(trees) do + local ok, err, errcode = search.manifest_search(results, path.rocks_dir(tree), query) + if not ok and errcode ~= "open" then + util.warning(err) + end + end + util.title("Installed rocks:", flags["porcelain"]) + search.print_results(results, flags["porcelain"]) + return true +end + +return list diff --git a/src/luarocks/base/make.lua b/src/luarocks/base/make.lua new file mode 100644 index 00000000..eb38bff0 --- /dev/null +++ b/src/luarocks/base/make.lua @@ -0,0 +1,86 @@ + +--- Module implementing the LuaRocks "make" command. +-- Builds sources in the current directory, but unlike "build", +-- it does not fetch sources, etc., assuming everything is +-- available in the current directory. +local make = {} + +local build = require("luarocks.build") +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local fetch = require("luarocks.fetch") +local pack = require("luarocks.pack") +local remove = require("luarocks.remove") +local deps = require("luarocks.deps") + +make.help_summary = "Compile package in current directory using a rockspec." +make.help_arguments = "[--pack-binary-rock] []" +make.help = [[ +Builds sources in the current directory, but unlike "build", +it does not fetch sources, etc., assuming everything is +available in the current directory. If no argument is given, +it looks for a rockspec in the current directory and in "rockspec/" +and "rockspecs/" subdirectories, picking the rockspec with newest version +or without version name. If rockspecs for different rocks are found +or there are several rockspecs without version, you must specify which to use, +through the command-line. + +This command is useful as a tool for debugging rockspecs. +To install rocks, you'll normally want to use the "install" and +"build" commands. See the help on those for details. + +--pack-binary-rock Do not install rock. Instead, produce a .rock file + with the contents of compilation in the current + directory. + +--keep Do not remove previously installed versions of the + rock after installing a new one. This behavior can + be made permanent by setting keep_other_versions=true + in the configuration file. + +--branch= Override the `source.branch` field in the loaded + rockspec. Allows to specify a different branch to + fetch. Particularly for SCM rocks. + +]] + +--- Driver function for "make" command. +-- @param name string: A local rockspec. +-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an +-- error message otherwise. exitcode is optionally returned. +function make.command(flags, rockspec) + assert(type(rockspec) == "string" or not rockspec) + + if not rockspec then + local err + rockspec, err = util.get_default_rockspec() + if not rockspec then + return nil, err + end + end + if not rockspec:match("rockspec$") then + return nil, "Invalid argument: 'make' takes a rockspec as a parameter. "..util.see_help("make") + end + + if flags["pack-binary-rock"] then + local rspec, err, errcode = fetch.load_rockspec(rockspec) + if not rspec then + return nil, err + end + return pack.pack_binary_rock(rspec.name, rspec.version, build.build_rockspec, rockspec, false, true, deps.get_deps_mode(flags)) + else + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + ok, err = build.build_rockspec(rockspec, false, true, deps.get_deps_mode(flags)) + if not ok then return nil, err end + local name, version = ok, err + if (not flags["keep"]) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) + if not ok then util.printerr(err) end + end + return name, version + end +end + +return make diff --git a/src/luarocks/base/new_version.lua b/src/luarocks/base/new_version.lua new file mode 100644 index 00000000..b13dbb97 --- /dev/null +++ b/src/luarocks/base/new_version.lua @@ -0,0 +1,199 @@ + +--- Module implementing the LuaRocks "new_version" command. +-- Utility function that writes a new rockspec, updating data from a previous one. +local new_version = {} + +local util = require("luarocks.util") +local download = require("luarocks.download") +local fetch = require("luarocks.fetch") +local persist = require("luarocks.persist") +local fs = require("luarocks.fs") +local type_check = require("luarocks.type_check") + +new_version.help_summary = "Auto-write a rockspec for a new version of a rock." +new_version.help_arguments = "[--tag=] [|] [] []" +new_version.help = [[ +This is a utility function that writes a new rockspec, updating data +from a previous one. + +If a package name is given, it downloads the latest rockspec from the +default server. If a rockspec is given, it uses it instead. If no argument +is given, it looks for a rockspec same way 'luarocks make' does. + +If the version number is not given and tag is passed using --tag, +it is used as the version, with 'v' removed from beginning. +Otherwise, it only increments the revision number of the given +(or downloaded) rockspec. + +If a URL is given, it replaces the one from the old rockspec with the +given URL. If a URL is not given and a new version is given, it tries +to guess the new URL by replacing occurrences of the version number +in the URL or tag. It also tries to download the new URL to determine +the new MD5 checksum. + +If a tag is given, it replaces the one from the old rockspec. If there is +an old tag but no new one passed, it is guessed in the same way URL is. + +WARNING: it writes the new rockspec to the current directory, +overwriting the file if it already exists. +]] + +local function try_replace(tbl, field, old, new) + if not tbl[field] then + return false + end + local old_field = tbl[field] + local new_field = tbl[field]:gsub(old, new) + if new_field ~= old_field then + util.printout("Guessing new '"..field.."' field as "..new_field) + tbl[field] = new_field + return true + end + return false +end + +-- Try to download source file using URL from a rockspec. +-- If it specified MD5, update it. +-- @return (true, false) if MD5 was not specified or it stayed same, +-- (true, true) if MD5 changed, (nil, string) on error. +local function check_url_and_update_md5(out_rs) + local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-"..out_rs.package) + if not file then + util.printerr("Warning: invalid URL - "..temp_dir) + return true, false + end + + local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir) + if not inferred_dir then + return nil, found_dir + end + + if found_dir and found_dir ~= inferred_dir then + out_rs.source.dir = found_dir + end + + if file then + if out_rs.source.md5 then + util.printout("File successfully downloaded. Updating MD5 checksum...") + local new_md5, err = fs.get_md5(file) + if not new_md5 then + return nil, err + end + local old_md5 = out_rs.source.md5 + out_rs.source.md5 = new_md5 + return true, new_md5 ~= old_md5 + else + util.printout("File successfully downloaded.") + return true, false + end + end +end + +local function update_source_section(out_rs, url, tag, old_ver, new_ver) + if tag then + out_rs.source.tag = tag + end + if url then + out_rs.source.url = url + return check_url_and_update_md5(out_rs) + end + if new_ver == old_ver then + return true + end + if out_rs.source.dir then + try_replace(out_rs.source, "dir", old_ver, new_ver) + end + if out_rs.source.file then + try_replace(out_rs.source, "file", old_ver, new_ver) + end + if try_replace(out_rs.source, "url", old_ver, new_ver) then + return check_url_and_update_md5(out_rs) + end + if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then + return true + end + -- Couldn't replace anything significant, use the old URL. + local ok, md5_changed = check_url_and_update_md5(out_rs) + if not ok then + return nil, md5_changed + end + if md5_changed then + util.printerr("Warning: URL is the same, but MD5 has changed. Old rockspec is broken.") + end + return true +end + +function new_version.command(flags, input, version, url) + if not input then + local err + input, err = util.get_default_rockspec() + if not input then + return nil, err + end + end + assert(type(input) == "string") + + local filename, err + if input:match("rockspec$") then + filename, err = fetch.fetch_url(input) + if not filename then + return nil, err + end + else + filename, err = download.download("rockspec", input:lower()) + if not filename then + return nil, err + end + end + + local valid_rs, err = fetch.load_rockspec(filename) + if not valid_rs then + return nil, err + end + + local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$") + local new_ver, new_rev + + if flags.tag and not version then + version = flags.tag:gsub("^v", "") + end + + if version then + new_ver, new_rev = version:match("(.*)%-(%d+)$") + new_rev = tonumber(new_rev) + if not new_rev then + new_ver = version + new_rev = 1 + end + else + new_ver = old_ver + new_rev = tonumber(old_rev) + 1 + end + local new_rockver = new_ver:gsub("-", "") + + local out_rs, err = persist.load_into_table(filename) + local out_name = out_rs.package:lower() + out_rs.version = new_rockver.."-"..new_rev + + local ok, err = update_source_section(out_rs, url, flags.tag, old_ver, new_ver) + if not ok then return nil, err end + + if out_rs.build and out_rs.build.type == "module" then + out_rs.build.type = "builtin" + end + + local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec" + + persist.save_from_table(out_filename, out_rs, type_check.rockspec_order) + + util.printout("Wrote "..out_filename) + + local valid_out_rs, err = fetch.load_local_rockspec(out_filename) + if not valid_out_rs then + return nil, "Failed loading generated rockspec: "..err + end + + return true +end + +return new_version diff --git a/src/luarocks/base/pack.lua b/src/luarocks/base/pack.lua new file mode 100644 index 00000000..655cbf37 --- /dev/null +++ b/src/luarocks/base/pack.lua @@ -0,0 +1,193 @@ + +--- Module implementing the LuaRocks "pack" command. +-- Creates a rock, packing sources or binaries. +local pack = {} + +local unpack = unpack or table.unpack + +local path = require("luarocks.path") +local repos = require("luarocks.repos") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local manif = require("luarocks.manif") +local search = require("luarocks.search") + +pack.help_summary = "Create a rock, packing sources or binaries." +pack.help_arguments = "{| []}" +pack.help = [[ +Argument may be a rockspec file, for creating a source rock, +or the name of an installed package, for creating a binary rock. +In the latter case, the app version may be given as a second +argument. +]] + +--- Create a source rock. +-- Packages a rockspec and its required source files in a rock +-- file with the .src.rock extension, which can later be built and +-- installed with the "build" command. +-- @param rockspec_file string: An URL or pathname for a rockspec file. +-- @return string or (nil, string): The filename of the resulting +-- .src.rock file; or nil and an error message. +function pack.pack_source_rock(rockspec_file) + assert(type(rockspec_file) == "string") + + local rockspec, err = fetch.load_rockspec(rockspec_file) + if err then + return nil, "Error loading rockspec: "..err + end + rockspec_file = rockspec.local_filename + + local name_version = rockspec.name .. "-" .. rockspec.version + local rock_file = fs.absolute_name(name_version .. ".src.rock") + + local source_file, source_dir = fetch.fetch_sources(rockspec, false) + if not source_file then + return nil, source_dir + end + local ok, err = fs.change_dir(source_dir) + if not ok then return nil, err end + + fs.delete(rock_file) + fs.copy(rockspec_file, source_dir, cfg.perm_read) + if not fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) then + return nil, "Failed packing "..rock_file + end + fs.pop_dir() + + return rock_file +end + +local function copy_back_files(name, version, file_tree, deploy_dir, pack_dir, perms) + local ok, err = fs.make_dir(pack_dir) + if not ok then return nil, err end + for file, sub in pairs(file_tree) do + local source = dir.path(deploy_dir, file) + local target = dir.path(pack_dir, file) + if type(sub) == "table" then + local ok, err = copy_back_files(name, version, sub, source, target) + if not ok then return nil, err end + else + local versioned = path.versioned_name(source, deploy_dir, name, version) + if fs.exists(versioned) then + fs.copy(versioned, target, perms) + else + fs.copy(source, target, perms) + end + end + end + return true +end + +-- @param name string: Name of package to pack. +-- @param version string or nil: A version number may also be passed. +-- @param tree string or nil: An optional tree to pick the package from. +-- @return string or (nil, string): The filename of the resulting +-- .src.rock file; or nil and an error message. +local function do_pack_binary_rock(name, version, tree) + assert(type(name) == "string") + assert(type(version) == "string" or not version) + + local repo, repo_url + name, version, repo, repo_url = search.pick_installed_rock(name, version, tree) + if not name then + return nil, version + end + + local root = path.root_dir(repo_url) + local prefix = path.install_dir(name, version, root) + if not fs.exists(prefix) then + return nil, "'"..name.." "..version.."' does not seem to be an installed rock." + end + + local rock_manifest, err = manif.load_rock_manifest(name, version, root) + if not rock_manifest then return nil, err end + + local name_version = name .. "-" .. version + local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock") + + local temp_dir = fs.make_temp_dir("pack") + fs.copy_contents(prefix, temp_dir) + + local is_binary = false + if rock_manifest.lib then + local ok, err = copy_back_files(name, version, rock_manifest.lib, path.deploy_lib_dir(root), dir.path(temp_dir, "lib"), cfg.perm_exec) + if not ok then return nil, "Failed copying back files: " .. err end + is_binary = true + end + if rock_manifest.lua then + local ok, err = copy_back_files(name, version, rock_manifest.lua, path.deploy_lua_dir(root), dir.path(temp_dir, "lua"), cfg.perm_read) + if not ok then return nil, "Failed copying back files: " .. err end + end + + local ok, err = fs.change_dir(temp_dir) + if not ok then return nil, err end + if not is_binary and not repos.has_binaries(name, version) then + rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.") + end + fs.delete(rock_file) + if not fs.zip(rock_file, unpack(fs.list_dir())) then + return nil, "Failed packing "..rock_file + end + fs.pop_dir() + fs.delete(temp_dir) + return rock_file +end + +function pack.pack_binary_rock(name, version, cmd, ...) + + -- The --pack-binary-rock option for "luarocks build" basically performs + -- "luarocks build" on a temporary tree and then "luarocks pack". The + -- alternative would require refactoring parts of luarocks.build and + -- luarocks.pack, which would save a few file operations: the idea would be + -- to shave off the final deploy steps from the build phase and the initial + -- collect steps from the pack phase. + + local temp_dir, err = fs.make_temp_dir("luarocks-build-pack-"..dir.base_name(name)) + if not temp_dir then + return nil, "Failed creating temporary directory: "..err + end + util.schedule_function(fs.delete, temp_dir) + + path.use_tree(temp_dir) + local ok, err = cmd(...) + if not ok then + return nil, err + end + local rname, rversion = path.parse_name(name) + if not rname then + rname, rversion = name, version + end + return do_pack_binary_rock(rname, rversion, temp_dir) +end + +--- Driver function for the "pack" command. +-- @param arg string: may be a rockspec file, for creating a source rock, +-- or the name of an installed package, for creating a binary rock. +-- @param version string or nil: if the name of a package is given, a +-- version may also be passed. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +function pack.command(flags, arg, version) + assert(type(version) == "string" or not version) + if type(arg) ~= "string" then + return nil, "Argument missing. "..util.see_help("pack") + end + + local file, err + if arg:match(".*%.rockspec") then + file, err = pack.pack_source_rock(arg) + else + file, err = do_pack_binary_rock(arg:lower(), version, flags["tree"]) + end + if err then + return nil, err + else + util.printout("Packed: "..file) + return true + end +end + +return pack diff --git a/src/luarocks/base/path_cmd.lua b/src/luarocks/base/path_cmd.lua new file mode 100644 index 00000000..516a0c47 --- /dev/null +++ b/src/luarocks/base/path_cmd.lua @@ -0,0 +1,68 @@ + +--- @module luarocks.path_cmd +-- Driver for the `luarocks path` command. +local path_cmd = {} + +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + +path_cmd.help_summary = "Return the currently configured package path." +path_cmd.help_arguments = "" +path_cmd.help = [[ +Returns the package path currently configured for this installation +of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH. + +--bin Adds the system path to the output + +--append Appends the paths to the existing paths. Default is to prefix + the LR paths to the existing paths. + +--lr-path Exports the Lua path (not formatted as shell command) + +--lr-cpath Exports the Lua cpath (not formatted as shell command) + +--lr-bin Exports the system path (not formatted as shell command) + + +On Unix systems, you may run: + eval `luarocks path` +And on Windows: + luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat" +]] + +--- Driver function for "path" command. +-- @return boolean This function always succeeds. +function path_cmd.command(flags) + local lr_path, lr_cpath, lr_bin = cfg.package_paths(flags["tree"]) + local path_sep = cfg.export_path_separator + + if flags["lr-path"] then + util.printout(util.remove_path_dupes(lr_path, ';')) + return true + elseif flags["lr-cpath"] then + util.printout(util.remove_path_dupes(lr_cpath, ';')) + return true + elseif flags["lr-bin"] then + util.printout(util.remove_path_dupes(lr_bin, path_sep)) + return true + end + + if flags["append"] then + lr_path = package.path .. ";" .. lr_path + lr_cpath = package.cpath .. ";" .. lr_cpath + lr_bin = os.getenv("PATH") .. path_sep .. lr_bin + else + lr_path = lr_path.. ";" .. package.path + lr_cpath = lr_cpath .. ";" .. package.cpath + lr_bin = lr_bin .. path_sep .. os.getenv("PATH") + end + + util.printout(cfg.export_lua_path:format(util.remove_path_dupes(lr_path, ';'))) + util.printout(cfg.export_lua_cpath:format(util.remove_path_dupes(lr_cpath, ';'))) + if flags["bin"] then + util.printout(cfg.export_path:format(util.remove_path_dupes(lr_bin, path_sep))) + end + return true +end + +return path_cmd diff --git a/src/luarocks/base/purge.lua b/src/luarocks/base/purge.lua new file mode 100644 index 00000000..50f290c8 --- /dev/null +++ b/src/luarocks/base/purge.lua @@ -0,0 +1,77 @@ + +--- Module implementing the LuaRocks "purge" command. +-- Remove all rocks from a given tree. +local purge = {} + +local util = require("luarocks.util") +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local repos = require("luarocks.repos") +local writer = require("luarocks.manif.writer") +local cfg = require("luarocks.core.cfg") +local remove = require("luarocks.remove") + +purge.help_summary = "Remove all installed rocks from a tree." +purge.help_arguments = "--tree= [--old-versions]" +purge.help = [[ +This command removes rocks en masse from a given tree. +By default, it removes all rocks from a tree. + +The --tree argument is mandatory: luarocks purge does not +assume a default tree. + +--old-versions Keep the highest-numbered version of each + rock and remove the other ones. By default + it only removes old versions if they are + not needed as dependencies. This can be + overridden with the flag --force. +]] + +function purge.command(flags) + local tree = flags["tree"] + + if type(tree) ~= "string" then + return nil, "The --tree argument is mandatory. "..util.see_help("purge") + end + + local results = {} + local query = search.make_query("") + query.exact_name = false + if not fs.is_dir(tree) then + return nil, "Directory not found: "..tree + end + + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + + search.manifest_search(results, path.rocks_dir(tree), query) + + local sort = function(a,b) return deps.compare_versions(b,a) end + if flags["old-versions"] then + sort = deps.compare_versions + end + + for package, versions in util.sortedpairs(results) do + for version, _ in util.sortedpairs(versions, sort) do + if flags["old-versions"] then + util.printout("Keeping "..package.." "..version.."...") + local ok, err = remove.remove_other_versions(package, version, flags["force"], flags["force-fast"]) + if not ok then + util.printerr(err) + end + break + else + util.printout("Removing "..package.." "..version.."...") + local ok, err = repos.delete_version(package, version, "none", true) + if not ok then + util.printerr(err) + end + end + end + end + return writer.make_manifest(cfg.rocks_dir, "one") +end + +return purge diff --git a/src/luarocks/base/remove.lua b/src/luarocks/base/remove.lua new file mode 100644 index 00000000..e7f37604 --- /dev/null +++ b/src/luarocks/base/remove.lua @@ -0,0 +1,165 @@ + +--- Module implementing the LuaRocks "remove" command. +-- Uninstalls rocks. +local remove = {} + +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local fetch = require("luarocks.fetch") +local repos = require("luarocks.repos") +local path = require("luarocks.path") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local fs = require("luarocks.fs") + +remove.help_summary = "Uninstall a rock." +remove.help_arguments = "[--force|--force-fast] []" +remove.help = [[ +Argument is the name of a rock to be uninstalled. +If a version is not given, try to remove all versions at once. +Will only perform the removal if it does not break dependencies. +To override this check and force the removal, use --force. +To perform a forced removal without reporting dependency issues, +use --force-fast. + +]]..util.deps_mode_help() + +--- Obtain a list of packages that depend on the given set of packages +-- (where all packages of the set are versions of one program). +-- @param name string: the name of a program +-- @param versions array of string: the versions to be deleted. +-- @return array of string: an empty table if no packages depend on any +-- of the given list, or an array of strings in "name/version" format. +local function check_dependents(name, versions, deps_mode) + local dependents = {} + local blacklist = {} + blacklist[name] = {} + for version, _ in pairs(versions) do + blacklist[name][version] = true + end + local local_rocks = {} + local query_all = search.make_query("") + query_all.exact_name = false + search.manifest_search(local_rocks, cfg.rocks_dir, query_all) + local_rocks[name] = nil + for rock_name, rock_versions in pairs(local_rocks) do + for rock_version, _ in pairs(rock_versions) do + local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version)) + if rockspec then + local _, missing = deps.match_deps(rockspec, blacklist, deps_mode) + if missing[name] then + table.insert(dependents, { name = rock_name, version = rock_version }) + end + end + end + end + return dependents +end + +--- Delete given versions of a program. +-- @param name string: the name of a program +-- @param versions array of string: the versions to be deleted. +-- @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. +-- @return boolean or (nil, string): true on success or nil and an error message. +local function delete_versions(name, versions, deps_mode) + + for version, _ in pairs(versions) do + util.printout("Removing "..name.." "..version.."...") + local ok, err = repos.delete_version(name, version, deps_mode) + if not ok then return nil, err end + end + + return true +end + +function remove.remove_search_results(results, name, deps_mode, force, fast) + local versions = results[name] + + local version = next(versions) + local second = next(versions, version) + + local dependents = {} + if not fast then + util.printout("Checking stability of dependencies in the absence of") + util.printout(name.." "..table.concat(util.keys(versions), ", ").."...") + util.printout() + dependents = check_dependents(name, versions, deps_mode) + end + + if #dependents > 0 then + if force or fast then + util.printerr("The following packages may be broken by this forced removal:") + for _, dependent in ipairs(dependents) do + util.printerr(dependent.name.." "..dependent.version) + end + util.printerr() + else + if not second then + util.printerr("Will not remove "..name.." "..version..".") + util.printerr("Removing it would break dependencies for: ") + else + util.printerr("Will not remove installed versions of "..name..".") + util.printerr("Removing them would break dependencies for: ") + end + for _, dependent in ipairs(dependents) do + util.printerr(dependent.name.." "..dependent.version) + end + util.printerr() + util.printerr("Use --force to force removal (warning: this may break modules).") + return nil, "Failed removing." + end + end + + local ok, err = delete_versions(name, versions, deps_mode) + if not ok then return nil, err end + + util.printout("Removal successful.") + return true +end + +function remove.remove_other_versions(name, version, force, fast) + local results = {} + search.manifest_search(results, cfg.rocks_dir, { name = name, exact_name = true, constraints = {{ op = "~=", version = version}} }) + if results[name] then + return remove.remove_search_results(results, name, cfg.deps_mode, force, fast) + end + return true +end + +--- Driver function for the "remove" command. +-- @param name string: name of a rock. If a version is given, refer to +-- a specific version; otherwise, try to remove all versions. +-- @param version string: When passing a package name, a version number +-- may also be given. +-- @return boolean or (nil, string, exitcode): True if removal was +-- successful, nil and an error message otherwise. exitcode is optionally returned. +function remove.command(flags, name, version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("remove") + end + + local deps_mode = flags["deps-mode"] or cfg.deps_mode + + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + + local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$") + local filename = name + if rock_type then + name, version = path.parse_name(filename) + if not name then return nil, "Invalid "..rock_type.." filename: "..filename end + end + + local results = {} + name = name:lower() + search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version)) + if not results[name] then + return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir) + end + + return remove.remove_search_results(results, name, deps_mode, flags["force"], flags["force-fast"]) +end + +return remove diff --git a/src/luarocks/base/search.lua b/src/luarocks/base/search.lua new file mode 100644 index 00000000..44eff694 --- /dev/null +++ b/src/luarocks/base/search.lua @@ -0,0 +1,482 @@ + +--- Module implementing the LuaRocks "search" command. +-- Queries LuaRocks servers. +local search = {} + + +local dir = require("luarocks.dir") +local path = require("luarocks.path") +local manif = require("luarocks.manif") +local deps = require("luarocks.deps") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") + +search.help_summary = "Query the LuaRocks servers." +search.help_arguments = "[--source] [--binary] { [] | --all }" +search.help = [[ +--source Return only rockspecs and source rocks, + to be used with the "build" command. +--binary Return only pure Lua and binary rocks (rocks that can be used + with the "install" command without requiring a C toolchain). +--all List all contents of the server that are suitable to + this platform, do not filter by name. +]] + +--- Convert the arch field of a query table to table format. +-- @param query table: A query table. +local function query_arch_as_table(query) + local format = type(query.arch) + if format == "table" then + return + elseif format == "nil" then + local accept = {} + accept["src"] = true + accept["all"] = true + accept["rockspec"] = true + accept["installed"] = true + accept[cfg.arch] = true + query.arch = accept + elseif format == "string" then + local accept = {} + for a in query.arch:gmatch("[%w_-]+") do + accept[a] = true + end + query.arch = accept + end +end + +--- Store a search result (a rock or rockspec) in the results table. +-- @param results table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- @param name string: Package name. +-- @param version string: Package version. +-- @param arch string: Architecture of rock ("all", "src" or platform +-- identifier), "rockspec" or "installed" +-- @param repo string: Pathname of a local repository of URL of +-- rocks server. +local function store_result(results, name, version, arch, repo) + assert(type(results) == "table") + assert(type(name) == "string") + assert(type(version) == "string") + assert(type(arch) == "string") + assert(type(repo) == "string") + + if not results[name] then results[name] = {} end + if not results[name][version] then results[name][version] = {} end + table.insert(results[name][version], { + arch = arch, + repo = repo + }) +end + +--- Test the name field of a query. +-- If query has a boolean field exact_name set to false, +-- then substring match is performed; otherwise, exact string +-- comparison is done. +-- @param query table: A query in dependency table format. +-- @param name string: A package name. +-- @return boolean: True if names match, false otherwise. +local function match_name(query, name) + assert(type(query) == "table") + assert(type(name) == "string") + if query.exact_name == false then + return name:find(query.name, 0, true) and true or false + else + return name == query.name + end +end + +--- Store a match in a results table if version matches query. +-- Name, version, arch and repository path are stored in a given +-- table, optionally checking if version and arch (if given) match +-- a query. +-- @param results table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- @param repo string: URL or pathname of the repository. +-- @param name string: The name of the package being tested. +-- @param version string: The version of the package being tested. +-- @param arch string: The arch of the package being tested. +-- @param query table: A table describing the query in dependency +-- format (for example, {name = "filesystem", exact_name = false, +-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). +-- If the arch field is omitted, the local architecture (cfg.arch) +-- is used. The special value "any" is also recognized, returning all +-- matches regardless of architecture. +local function store_if_match(results, repo, name, version, arch, query) + if match_name(query, name) then + if query.arch[arch] or query.arch["any"] then + if deps.match_constraints(deps.parse_version(version), query.constraints) then + store_result(results, name, version, arch, repo) + end + end + end +end + +--- Perform search on a local repository. +-- @param repo string: The pathname of the local repository. +-- @param query table: A table describing the query in dependency +-- format (for example, {name = "filesystem", exact_name = false, +-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). +-- If the arch field is omitted, the local architecture (cfg.arch) +-- is used. The special value "any" is also recognized, returning all +-- matches regardless of architecture. +-- @param results table or nil: If given, this table will store the +-- results; if not given, a new table will be created. +-- @return table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- If a table was given in the "results" parameter, that is the result value. +function search.disk_search(repo, query, results) + assert(type(repo) == "string") + assert(type(query) == "table") + assert(type(results) == "table" or not results) + + local fs = require("luarocks.fs") + + if not results then + results = {} + end + query_arch_as_table(query) + + for name in fs.dir(repo) do + local pathname = dir.path(repo, name) + local rname, rversion, rarch = path.parse_name(name) + + if rname and (pathname:match(".rockspec$") or pathname:match(".rock$")) then + store_if_match(results, repo, rname, rversion, rarch, query) + elseif fs.is_dir(pathname) then + for version in fs.dir(pathname) do + if version:match("-%d+$") then + store_if_match(results, repo, name, version, "installed", query) + end + end + end + end + return results +end + +--- Perform search on a rocks server or tree. +-- @param results table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- @param repo string: The URL of a rocks server or +-- the pathname of a rocks tree (as returned by path.rocks_dir()). +-- @param query table: A table describing the query in dependency +-- format (for example, {name = "filesystem", exact_name = false, +-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). +-- If the arch field is omitted, the local architecture (cfg.arch) +-- is used. The special value "any" is also recognized, returning all +-- matches regardless of architecture. +-- @param lua_version string: Lua version in "5.x" format, defaults to installed version. +-- @return true or, in case of errors, nil, an error message and an optional error code. +function search.manifest_search(results, repo, query, lua_version) + assert(type(results) == "table") + assert(type(repo) == "string") + assert(type(query) == "table") + + query_arch_as_table(query) + local manifest, err, errcode = manif.load_manifest(repo, lua_version) + if not manifest then + return nil, err, errcode + end + for name, versions in pairs(manifest.repository) do + for version, items in pairs(versions) do + for _, item in ipairs(items) do + store_if_match(results, repo, name, version, item.arch, query) + end + end + end + return true +end + +--- Search on all configured rocks servers. +-- @param query table: A dependency query. +-- @param lua_version string: Lua version in "5.x" format, defaults to installed version. +-- @return table: A table where keys are package names +-- and values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +function search.search_repos(query, lua_version) + assert(type(query) == "table") + + local results = {} + for _, repo in ipairs(cfg.rocks_servers) do + if not cfg.disabled_servers[repo] then + if type(repo) == "string" then + repo = { repo } + end + for _, mirror in ipairs(repo) do + local protocol, pathname = dir.split_url(mirror) + if protocol == "file" then + mirror = pathname + end + local ok, err, errcode = search.manifest_search(results, mirror, query, lua_version) + if errcode == "network" then + cfg.disabled_servers[repo] = true + end + if ok then + break + else + util.warning("Failed searching manifest: "..err) + end + end + end + end + -- search through rocks in cfg.rocks_provided + local provided_repo = "provided by VM or rocks_provided" + for name, versions in pairs(cfg.rocks_provided) do + store_if_match(results, provided_repo, name, versions, "installed", query) + end + return results +end + +--- Prepare a query in dependency table format. +-- @param name string: The query name. +-- @param version string or nil: +-- @return table: A query in table format +function search.make_query(name, version) + assert(type(name) == "string") + assert(type(version) == "string" or not version) + + local query = { + name = name, + constraints = {} + } + if version then + table.insert(query.constraints, { op = "==", version = deps.parse_version(version)}) + end + return query +end + +--- Get the URL for the latest in a set of versions. +-- @param name string: The package name to be used in the URL. +-- @param versions table: An array of version informations, as stored +-- in search results tables. +-- @return string or nil: the URL for the latest version if one could +-- be picked, or nil. +local function pick_latest_version(name, versions) + assert(type(name) == "string") + assert(type(versions) == "table") + + local vtables = {} + for v, _ in pairs(versions) do + table.insert(vtables, deps.parse_version(v)) + end + table.sort(vtables) + local version = vtables[#vtables].string + local items = versions[version] + if items then + local pick = 1 + for i, item in ipairs(items) do + if (item.arch == 'src' and items[pick].arch == 'rockspec') + or (item.arch ~= 'src' and item.arch ~= 'rockspec') then + pick = i + end + end + return path.make_url(items[pick].repo, name, version, items[pick].arch) + end + return nil +end + +-- Find out which other Lua versions provide rock versions matching a query, +-- @param query table: A dependency query matching a single rock. +-- @return table: array of Lua versions supported, in "5.x" format. +local function supported_lua_versions(query) + local results = {} + + for lua_version in util.lua_versions() do + if lua_version ~= cfg.lua_version then + if search.search_repos(query, lua_version)[query.name] then + table.insert(results, lua_version) + end + end + end + + return results +end + +--- Attempt to get a single URL for a given search for a rock. +-- @param query table: A dependency query matching a single rock. +-- @return string or (nil, string): URL for latest matching version +-- of the rock if it was found, or nil followed by an error message. +function search.find_suitable_rock(query) + assert(type(query) == "table") + + local results = search.search_repos(query) + local first_rock = next(results) + if not first_rock then + if cfg.rocks_provided[query.name] == nil then + -- Check if constraints are satisfiable with other Lua versions. + local lua_versions = supported_lua_versions(query) + + if #lua_versions ~= 0 then + -- Build a nice message in "only Lua 5.x and 5.y but not 5.z." format + for i, lua_version in ipairs(lua_versions) do + lua_versions[i] = "Lua "..lua_version + end + + local versions_message = "only "..table.concat(lua_versions, " and ").. + " but not Lua "..cfg.lua_version.."." + + if #query.constraints == 0 then + return nil, query.name.." supports "..versions_message + elseif #query.constraints == 1 and query.constraints[1].op == "==" then + return nil, query.name.." "..query.constraints[1].version.string.." supports "..versions_message + else + return nil, "Matching "..query.name.." versions support "..versions_message + end + end + end + + return nil, "No results matching query were found." + elseif next(results, first_rock) then + -- Shouldn't happen as query must match only one package. + return nil, "Several rocks matched query." + elseif cfg.rocks_provided[query.name] ~= nil then + -- Do not install versions listed in cfg.rocks_provided. + return nil, "Rock "..query.name.." "..cfg.rocks_provided[query.name].. + " was found but it is provided by VM or 'rocks_provided' in the config file." + else + return pick_latest_version(query.name, results[first_rock]) + end +end + +--- Print a list of rocks/rockspecs on standard output. +-- @param results table: A table where keys are package names and versions +-- are tables matching version strings to an array of rocks servers. +-- @param porcelain boolean or nil: A flag to force machine-friendly output. +function search.print_results(results, porcelain) + assert(type(results) == "table") + assert(type(porcelain) == "boolean" or not porcelain) + + for package, versions in util.sortedpairs(results) do + if not porcelain then + util.printout(package) + end + for version, repos in util.sortedpairs(versions, deps.compare_versions) do + for _, repo in ipairs(repos) do + repo.repo = dir.normalize(repo.repo) + if porcelain then + util.printout(package, version, repo.arch, repo.repo) + else + util.printout(" "..version.." ("..repo.arch..") - "..repo.repo) + end + end + end + if not porcelain then + util.printout() + end + end +end + +--- Splits a list of search results into two lists, one for "source" results +-- to be used with the "build" command, and one for "binary" results to be +-- used with the "install" command. +-- @param results table: A search results table. +-- @return (table, table): Two tables, one for source and one for binary +-- results. +local function split_source_and_binary_results(results) + local sources, binaries = {}, {} + for name, versions in pairs(results) do + for version, repositories in pairs(versions) do + for _, repo in ipairs(repositories) do + local where = sources + if repo.arch == "all" or repo.arch == cfg.arch then + where = binaries + end + store_result(where, name, version, repo.arch, repo.repo) + end + end + end + return sources, binaries +end + +--- Given a name and optionally a version, try to find in the rocks +-- servers a single .src.rock or .rockspec file that satisfies +-- the request, and run the given function on it; or display to the +-- user possibilities if it couldn't narrow down a single match. +-- @param action function: A function that takes a .src.rock or +-- .rockspec URL as a parameter. +-- @param name string: A rock name +-- @param version string or nil: A version number may also be given. +-- @return The result of the action function, or nil and an error message. +function search.act_on_src_or_rockspec(action, name, version, ...) + assert(type(action) == "function") + assert(type(name) == "string") + assert(type(version) == "string" or not version) + + local query = search.make_query(name, version) + query.arch = "src|rockspec" + local url, err = search.find_suitable_rock(query) + if not url then + return nil, "Could not find a result named "..name..(version and " "..version or "")..": "..err + end + return action(url, ...) +end + +function search.pick_installed_rock(name, version, given_tree) + local results = {} + local query = search.make_query(name, version) + query.exact_name = true + local tree_map = {} + local trees = cfg.rocks_trees + if given_tree then + trees = { given_tree } + end + for _, tree in ipairs(trees) do + local rocks_dir = path.rocks_dir(tree) + tree_map[rocks_dir] = tree + search.manifest_search(results, rocks_dir, query) + end + + if not next(results) then -- + return nil,"cannot find package "..name.." "..(version or "").."\nUse 'list' to find installed rocks." + end + + version = nil + local repo_url + local package, versions = util.sortedpairs(results)() + --question: what do we do about multiple versions? This should + --give us the latest version on the last repo (which is usually the global one) + for vs, repositories in util.sortedpairs(versions, deps.compare_versions) do + if not version then version = vs end + for _, rp in ipairs(repositories) do repo_url = rp.repo end + end + + local repo = tree_map[repo_url] + return name, version, repo, repo_url +end + +--- Driver function for "search" command. +-- @param name string: A substring of a rock name to search. +-- @param version string or nil: a version may also be passed. +-- @return boolean or (nil, string): True if build was successful; nil and an +-- error message otherwise. +function search.command(flags, name, version) + if flags["all"] then + name, version = "", nil + end + + if type(name) ~= "string" and not flags["all"] then + return nil, "Enter name and version or use --all. "..util.see_help("search") + end + + local query = search.make_query(name:lower(), version) + query.exact_name = false + local results, err = search.search_repos(query) + local porcelain = flags["porcelain"] + util.title("Search results:", porcelain, "=") + local sources, binaries = split_source_and_binary_results(results) + if next(sources) and not flags["binary"] then + util.title("Rockspecs and source rocks:", porcelain) + search.print_results(sources, porcelain) + end + if next(binaries) and not flags["source"] then + util.title("Binary and pure-Lua rocks:", porcelain) + search.print_results(binaries, porcelain) + end + return true +end + +return search diff --git a/src/luarocks/base/show.lua b/src/luarocks/base/show.lua new file mode 100644 index 00000000..1ff81e08 --- /dev/null +++ b/src/luarocks/base/show.lua @@ -0,0 +1,158 @@ +--- Module implementing the LuaRocks "show" command. +-- Shows information about an installed rock. +local show = {} + +local search = require("luarocks.search") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local path = require("luarocks.path") +local deps = require("luarocks.deps") +local fetch = require("luarocks.fetch") +local manif = require("luarocks.manif") + +show.help_summary = "Show information about an installed rock." + +show.help = [[ + is an existing package name. +Without any flags, show all module information. +With these flags, return only the desired information: + +--home home page of project +--modules all modules provided by this package as used by require() +--deps packages this package depends on +--rockspec the full path of the rockspec file +--mversion the package version +--rock-tree local tree where rock is installed +--rock-dir data directory of the installed rock +]] + +local function keys_as_string(t, sep) + local keys = util.keys(t) + table.sort(keys) + return table.concat(keys, sep or " ") +end + +local function word_wrap(line) + local width = tonumber(os.getenv("COLUMNS")) or 80 + if width > 80 then width = 80 end + if #line > width then + local brk = width + while brk > 0 and line:sub(brk, brk) ~= " " do + brk = brk - 1 + end + if brk > 0 then + return line:sub(1, brk-1) .. "\n" .. word_wrap(line:sub(brk+1)) + end + end + return line +end + +local function format_text(text) + text = text:gsub("^%s*",""):gsub("%s$", ""):gsub("\n[ \t]+","\n"):gsub("([^\n])\n([^\n])","%1 %2") + local paragraphs = util.split_string(text, "\n\n") + for n, line in ipairs(paragraphs) do + paragraphs[n] = word_wrap(line) + end + return (table.concat(paragraphs, "\n\n"):gsub("%s$", "")) +end + +local function installed_rock_label(name, tree) + local installed, version + if cfg.rocks_provided[name] then + installed, version = true, cfg.rocks_provided[name] + else + installed, version = search.pick_installed_rock(name, nil, tree) + end + return installed and "(using "..version..")" or "(missing)" +end + +--- Driver function for "show" command. +-- @param name or nil: an existing package name. +-- @param version string or nil: a version may also be passed. +-- @return boolean: True if succeeded, nil on errors. +function show.command(flags, name, version) + if not name then + return nil, "Argument missing. "..util.see_help("show") + end + + local repo, repo_url + name, version, repo, repo_url = search.pick_installed_rock(name:lower(), version, flags["tree"]) + if not name then + return nil, version + end + + local directory = path.install_dir(name,version,repo) + local rockspec_file = path.rockspec_file(name, version, repo) + local rockspec, err = fetch.load_local_rockspec(rockspec_file) + if not rockspec then return nil,err end + + local descript = rockspec.description or {} + local manifest, err = manif.load_manifest(repo_url) + if not manifest then return nil,err end + local minfo = manifest.repository[name][version][1] + + if flags["rock-tree"] then util.printout(path.rocks_tree_to_string(repo)) + elseif flags["rock-dir"] then util.printout(directory) + elseif flags["home"] then util.printout(descript.homepage) + elseif flags["issues"] then util.printout(descript.issues_url) + elseif flags["labels"] then util.printout(descript.labels and table.concat(descript.labels, "\n")) + elseif flags["modules"] then util.printout(keys_as_string(minfo.modules, "\n")) + elseif flags["deps"] then util.printout(keys_as_string(minfo.dependencies)) + elseif flags["rockspec"] then util.printout(rockspec_file) + elseif flags["mversion"] then util.printout(version) + else + util.printout() + util.printout(rockspec.package.." "..rockspec.version.." - "..(descript.summary or "")) + util.printout() + if descript.detailed then + util.printout(format_text(descript.detailed)) + util.printout() + end + if descript.license then + util.printout("License: ", descript.license) + end + if descript.homepage then + util.printout("Homepage: ", descript.homepage) + end + if descript.issues_url then + util.printout("Issues: ", descript.issues_url) + end + if descript.labels then + util.printout("Labels: ", table.concat(descript.labels, ", ")) + end + util.printout("Installed in: ", path.rocks_tree_to_string(repo)) + if next(minfo.modules) then + util.printout() + util.printout("Modules:") + for mod, filename in util.sortedpairs(minfo.modules) do + util.printout("\t"..mod.." ("..path.which(mod, filename, name, version, repo, manifest)..")") + end + end + local direct_deps = {} + if #rockspec.dependencies > 0 then + util.printout() + util.printout("Depends on:") + for _, dep in ipairs(rockspec.dependencies) do + direct_deps[dep.name] = true + util.printout("\t"..deps.show_dep(dep).." "..installed_rock_label(dep.name, flags["tree"])) + end + end + local has_indirect_deps + for dep_name in util.sortedpairs(minfo.dependencies) do + if not direct_deps[dep_name] then + if not has_indirect_deps then + util.printout() + util.printout("Indirectly pulling:") + has_indirect_deps = true + end + + util.printout("\t"..dep_name.." "..installed_rock_label(dep_name, flags["tree"])) + end + end + util.printout() + end + return true +end + + +return show diff --git a/src/luarocks/base/unpack.lua b/src/luarocks/base/unpack.lua new file mode 100644 index 00000000..c50701b0 --- /dev/null +++ b/src/luarocks/base/unpack.lua @@ -0,0 +1,164 @@ + +--- Module implementing the LuaRocks "unpack" command. +-- Unpack the contents of a rock. +local unpack = {} + +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local build = require("luarocks.build") +local dir = require("luarocks.dir") +local cfg = require("luarocks.core.cfg") + +unpack.help_summary = "Unpack the contents of a rock." +unpack.help_arguments = "[--force] {| []}" +unpack.help = [[ +Unpacks the contents of a rock in a newly created directory. +Argument may be a rock file, or the name of a rock in a rocks server. +In the latter case, the app version may be given as a second argument. + +--force Unpack files even if the output directory already exists. +]] + +--- Load a rockspec file to the given directory, fetches the source +-- files specified in the rockspec, and unpack them inside the directory. +-- @param rockspec_file string: The URL for a rockspec file. +-- @param dir_name string: The directory where to store and unpack files. +-- @return table or (nil, string): the loaded rockspec table or +-- nil and an error message. +local function unpack_rockspec(rockspec_file, dir_name) + assert(type(rockspec_file) == "string") + assert(type(dir_name) == "string") + + local rockspec, err = fetch.load_rockspec(rockspec_file) + if not rockspec then + return nil, "Failed loading rockspec "..rockspec_file..": "..err + end + local ok, err = fs.change_dir(dir_name) + if not ok then return nil, err end + local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".") + if not ok then + return nil, sources_dir + end + ok, err = fs.change_dir(sources_dir) + if not ok then return nil, err end + ok, err = build.apply_patches(rockspec) + fs.pop_dir() + if not ok then return nil, err end + return rockspec +end + +--- Load a .rock file to the given directory and unpack it inside it. +-- @param rock_file string: The URL for a .rock file. +-- @param dir_name string: The directory where to unpack. +-- @param kind string: the kind of rock file, as in the second-level +-- extension in the rock filename (eg. "src", "all", "linux-x86") +-- @return table or (nil, string): the loaded rockspec table or +-- nil and an error message. +local function unpack_rock(rock_file, dir_name, kind) + assert(type(rock_file) == "string") + assert(type(dir_name) == "string") + + local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name) + if not ok then + return nil, "Failed unzipping rock "..rock_file, errcode + end + ok, err = fs.change_dir(dir_name) + if not ok then return nil, err end + local rockspec_file = dir_name..".rockspec" + local rockspec, err = fetch.load_rockspec(rockspec_file) + if not rockspec then + return nil, "Failed loading rockspec "..rockspec_file..": "..err + end + if kind == "src" then + if rockspec.source.file then + local ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then + return nil, err + end + ok, err = fs.change_dir(rockspec.source.dir) + if not ok then return nil, err end + ok, err = build.apply_patches(rockspec) + fs.pop_dir() + if not ok then return nil, err end + end + end + return rockspec +end + +--- Create a directory and perform the necessary actions so that +-- the sources for the rock and its rockspec are unpacked inside it, +-- laid out properly so that the 'make' command is able to build the module. +-- @param file string: A rockspec or .rock URL. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +local function run_unpacker(file, force) + assert(type(file) == "string") + + local base_name = dir.base_name(file) + local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$") + if not extension then + dir_name, extension = base_name:match("(.*)%.(rockspec)$") + kind = "rockspec" + end + if not extension then + return nil, file.." does not seem to be a valid filename." + end + + local exists = fs.exists(dir_name) + if exists and not force then + return nil, "Directory "..dir_name.." already exists." + end + if not exists then + local ok, err = fs.make_dir(dir_name) + if not ok then return nil, err end + end + local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name)) + + local rockspec, err + if extension == "rock" then + rockspec, err = unpack_rock(file, dir_name, kind) + elseif extension == "rockspec" then + rockspec, err = unpack_rockspec(file, dir_name) + end + if not rockspec then + return nil, err + end + if kind == "src" or kind == "rockspec" then + if rockspec.source.dir ~= "." then + local ok = fs.copy(rockspec.local_filename, rockspec.source.dir, cfg.perm_read) + if not ok then + return nil, "Failed copying unpacked rockspec into unpacked source directory." + end + end + util.printout() + util.printout("Done. You may now enter directory ") + util.printout(dir.path(dir_name, rockspec.source.dir)) + util.printout("and type 'luarocks make' to build.") + end + util.remove_scheduled_function(rollback) + return true +end + +--- Driver function for the "unpack" command. +-- @param name string: may be a rock filename, for unpacking a +-- rock file or the name of a rock to be fetched and unpacked. +-- @param version string or nil: if the name of a package is given, a +-- version may also be passed. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +function unpack.command(flags, name, version) + assert(type(version) == "string" or not version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("unpack") + end + + if name:match(".*%.rock") or name:match(".*%.rockspec") then + return run_unpacker(name, flags["force"]) + else + local search = require("luarocks.search") + return search.act_on_src_or_rockspec(run_unpacker, name:lower(), version) + end +end + +return unpack diff --git a/src/luarocks/base/upload.lua b/src/luarocks/base/upload.lua new file mode 100644 index 00000000..baee47ab --- /dev/null +++ b/src/luarocks/base/upload.lua @@ -0,0 +1,94 @@ + +local upload = {} + +local util = require("luarocks.util") +local fetch = require("luarocks.fetch") +local pack = require("luarocks.pack") +local cfg = require("luarocks.core.cfg") +local Api = require("luarocks.upload.api") + +upload.help_summary = "Upload a rockspec to the public rocks repository." +upload.help_arguments = "[--skip-pack] [--api-key=] [--force] " +upload.help = [[ + Pack a source rock file (.src.rock extension), + upload rockspec and source rock to server. +--skip-pack Do not pack and send source rock. +--api-key= Give it an API key. It will be stored for subsequent uses. +--force Replace existing rockspec if the same revision of + a module already exists. This should be used only + in case of upload mistakes: when updating a rockspec, + increment the revision number instead. +]] + +function upload.command(flags, fname) + if not fname then + return nil, "Missing rockspec. "..util.see_help("upload") + end + + local api, err = Api.new(flags) + if not api then + return nil, err + end + if cfg.verbose then + api.debug = true + end + + local rockspec, err, errcode = fetch.load_rockspec(fname) + if err then + return nil, err, errcode + end + + util.printout("Sending " .. tostring(fname) .. " ...") + local res, err = api:method("check_rockspec", { + package = rockspec.package, + version = rockspec.version + }) + if not res then return nil, err end + + if not res.module then + util.printout("Will create new module (" .. tostring(rockspec.package) .. ")") + end + if res.version and not flags["force"] then + return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload") + end + + local rock_fname + if not flags["skip-pack"] and not rockspec.version:match("^scm") then + util.printout("Packing " .. tostring(rockspec.package)) + rock_fname, err = pack.pack_source_rock(fname) + if not rock_fname then + return nil, err + end + end + + local multipart = require("luarocks.upload.multipart") + + res, err = api:method("upload", nil, { + rockspec_file = multipart.new_file(fname) + }) + if not res then return nil, err end + + if res.is_new and #res.manifests == 0 then + util.printerr("Warning: module not added to root manifest due to name taken.") + end + + local module_url = res.module_url + + if rock_fname then + if (not res.version) or (not res.version.id) then + return nil, "Invalid response from server." + end + util.printout(("Sending " .. tostring(rock_fname) .. " ...")) + res, err = api:method("upload_rock/" .. ("%d"):format(res.version.id), nil, { + rock_file = multipart.new_file(rock_fname) + }) + if not res then return nil, err end + end + + util.printout() + util.printout("Done: " .. tostring(module_url)) + util.printout() + return true +end + +return upload diff --git a/src/luarocks/base/write_rockspec.lua b/src/luarocks/base/write_rockspec.lua new file mode 100644 index 00000000..be563eaa --- /dev/null +++ b/src/luarocks/base/write_rockspec.lua @@ -0,0 +1,376 @@ + +local write_rockspec = {} + +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local persist = require("luarocks.persist") +local type_check = require("luarocks.type_check") +local util = require("luarocks.util") +local deps = require("luarocks.deps") + +write_rockspec.help_summary = "Write a template for a rockspec file." +write_rockspec.help_arguments = "[--output= ...] [] [] [|]" +write_rockspec.help = [[ +This command writes an initial version of a rockspec file, +based on a name, a version, and a location (an URL or a local path). +If only two arguments are given, the first one is considered the name and the +second one is the location. +If only one argument is given, it must be the location. +If no arguments are given, current directory is used as location. +LuaRocks will attempt to infer name and version if not given, +using 'scm' as default version. + +Note that the generated file is a _starting point_ for writing a +rockspec, and is not guaranteed to be complete or correct. + +--output= Write the rockspec with the given filename. + If not given, a file is written in the current + directory with a filename based on given name and version. +--license="" A license string, such as "MIT/X11" or "GNU GPL v3". +--summary="" A short one-line description summary. +--detailed="" A longer description string. +--homepage= Project homepage. +--lua-version= Supported Lua versions. Accepted values are "5.1", "5.2", + "5.3", "5.1,5.2", "5.2,5.3", or "5.1,5.2,5.3". +--rockspec-format= Rockspec format version, such as "1.0" or "1.1". +--tag= Tag to use. Will attempt to extract version number from it. +--lib=[,] A comma-separated list of libraries that C files need to + link to. +]] + +local function open_file(name) + return io.open(dir.path(fs.current_dir(), name), "r") +end + +local function get_url(rockspec) + local file, temp_dir, err_code, err_file, err_temp_dir = fetch.fetch_sources(rockspec, false) + if err_code == "source.dir" then + file, temp_dir = err_file, err_temp_dir + elseif not file then + util.warning("Could not fetch sources - "..temp_dir) + return false + end + util.printout("File successfully downloaded. Making checksum and checking base dir...") + if fetch.is_basic_protocol(rockspec.source.protocol) then + rockspec.source.md5 = fs.get_md5(file) + end + local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, rockspec.source.url) + return true, found_dir or inferred_dir, temp_dir +end + +local function configure_lua_version(rockspec, luaver) + if luaver == "5.1" then + table.insert(rockspec.dependencies, "lua ~> 5.1") + elseif luaver == "5.2" then + table.insert(rockspec.dependencies, "lua ~> 5.2") + elseif luaver == "5.3" then + table.insert(rockspec.dependencies, "lua ~> 5.3") + elseif luaver == "5.1,5.2" then + table.insert(rockspec.dependencies, "lua >= 5.1, < 5.3") + elseif luaver == "5.2,5.3" then + table.insert(rockspec.dependencies, "lua >= 5.2, < 5.4") + elseif luaver == "5.1,5.2,5.3" then + table.insert(rockspec.dependencies, "lua >= 5.1, < 5.4") + else + util.warning("Please specify supported Lua version with --lua-version=. "..util.see_help("write_rockspec")) + end +end + +local function detect_description() + local fd = open_file("README.md") or open_file("README") + if not fd then return end + local data = fd:read("*a") + fd:close() + local paragraph = data:match("\n\n([^%[].-)\n\n") + if not paragraph then paragraph = data:match("\n\n(.*)") end + local summary, detailed + if paragraph then + detailed = paragraph + + if #paragraph < 80 then + summary = paragraph:gsub("\n", "") + else + summary = paragraph:gsub("\n", " "):match("([^.]*%.) ") + end + end + return summary, detailed +end + +local function detect_mit_license(data) + local strip_copyright = (data:gsub("Copyright [^\n]*\n", "")) + local sum = 0 + for i = 1, #strip_copyright do + local num = string.byte(strip_copyright:sub(i,i)) + if num > 32 and num <= 128 then + sum = sum + num + end + end + return sum == 78656 +end + +local simple_scm_protocols = { + git = true, ["git+http"] = true, ["git+https"] = true, + hg = true, ["hg+http"] = true, ["hg+https"] = true +} + +local function detect_url_from_command(program, args, directory) + local command = fs.Q(cfg.variables[program:upper()]).. " "..args + local pipe = io.popen(fs.command_at(directory, fs.quiet_stderr(command))) + if not pipe then return nil end + local url = pipe:read("*a"):match("^([^\r\n]+)") + pipe:close() + if not url then return nil end + if not util.starts_with(url, program.."://") then + url = program.."+"..url + end + + if simple_scm_protocols[dir.split_url(url)] then + return url + end +end + +local function detect_scm_url(directory) + return detect_url_from_command("git", "config --get remote.origin.url", directory) or + detect_url_from_command("hg", "paths default", directory) +end + +local function show_license(rockspec) + local fd = open_file("COPYING") or open_file("LICENSE") or open_file("MIT-LICENSE.txt") + if not fd then return nil end + local data = fd:read("*a") + fd:close() + local is_mit = detect_mit_license(data) + util.title("License for "..rockspec.package..":") + util.printout(data) + util.printout() + return is_mit +end + +local function get_cmod_name(file) + local fd = open_file(file) + if not fd then return nil end + local data = fd:read("*a") + fd:close() + return (data:match("int%s+luaopen_([a-zA-Z0-9_]+)")) +end + +local luamod_blacklist = { + test = true, + tests = true, +} + +local function fill_as_builtin(rockspec, libs) + rockspec.build.type = "builtin" + rockspec.build.modules = {} + local prefix = "" + + for _, parent in ipairs({"src", "lua"}) do + if fs.is_dir(parent) then + fs.change_dir(parent) + prefix = parent.."/" + break + end + end + + local incdirs, libdirs + if libs then + incdirs, libdirs = {}, {} + for _, lib in ipairs(libs) do + local upper = lib:upper() + incdirs[#incdirs+1] = "$("..upper.."_INCDIR)" + libdirs[#libdirs+1] = "$("..upper.."_LIBDIR)" + end + end + + for _, file in ipairs(fs.find()) do + local luamod = file:match("(.*)%.lua$") + if luamod and not luamod_blacklist[luamod] then + rockspec.build.modules[path.path_to_module(file)] = prefix..file + else + local cmod = file:match("(.*)%.c$") + if cmod then + local modname = get_cmod_name(file) or path.path_to_module(file:gsub("%.c$", ".lua")) + rockspec.build.modules[modname] = { + sources = prefix..file, + libraries = libs, + incdirs = incdirs, + libdirs = libdirs, + } + end + end + end + + for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do + if fs.is_dir(directory) then + if not rockspec.build.copy_directories then + rockspec.build.copy_directories = {} + end + table.insert(rockspec.build.copy_directories, directory) + end + end + + if prefix ~= "" then + fs.pop_dir() + end +end + +local function rockspec_cleanup(rockspec) + rockspec.source.file = nil + rockspec.source.protocol = nil + rockspec.variables = nil + rockspec.name = nil + rockspec.format_is_at_least = nil +end + +function write_rockspec.command(flags, name, version, url_or_dir) + if not name then + url_or_dir = "." + elseif not version then + url_or_dir = name + name = nil + elseif not url_or_dir then + url_or_dir = version + version = nil + end + + if flags["tag"] then + if not version then + version = flags["tag"]:gsub("^v", "") + end + end + + local protocol, pathname = dir.split_url(url_or_dir) + if protocol == "file" then + if pathname == "." then + name = name or dir.base_name(fs.current_dir()) + end + elseif fetch.is_basic_protocol(protocol) then + local filename = dir.base_name(url_or_dir) + local newname, newversion = filename:match("(.*)-([^-]+)") + if newname then + name = name or newname + version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "") + end + else + name = name or dir.base_name(url_or_dir):gsub("%.[^.]+$", "") + end + + if not name then + return nil, "Could not infer rock name. "..util.see_help("write_rockspec") + end + version = version or "scm" + + local filename = flags["output"] or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec") + + local rockspec = { + rockspec_format = flags["rockspec-format"], + package = name, + name = name:lower(), + version = version.."-1", + source = { + url = "*** please add URL for source tarball, zip or repository here ***", + tag = flags["tag"], + }, + description = { + summary = flags["summary"] or "*** please specify description summary ***", + detailed = flags["detailed"] or "*** please enter a detailed description ***", + homepage = flags["homepage"] or "*** please enter a project homepage ***", + license = flags["license"] or "*** please specify a license ***", + }, + dependencies = {}, + build = {}, + } + path.configure_paths(rockspec) + rockspec.source.protocol = protocol + rockspec.format_is_at_least = deps.format_is_at_least + + configure_lua_version(rockspec, flags["lua-version"]) + + local local_dir = url_or_dir + + if url_or_dir:match("://") then + rockspec.source.url = url_or_dir + rockspec.source.file = dir.base_name(url_or_dir) + rockspec.source.dir = "dummy" + if not fetch.is_basic_protocol(rockspec.source.protocol) then + if version ~= "scm" then + rockspec.source.tag = flags["tag"] or "v" .. version + end + end + rockspec.source.dir = nil + local ok, base_dir, temp_dir = get_url(rockspec) + if ok then + if base_dir ~= dir.base_name(url_or_dir) then + rockspec.source.dir = base_dir + end + end + if base_dir then + local_dir = dir.path(temp_dir, base_dir) + else + local_dir = nil + end + else + rockspec.source.url = detect_scm_url(local_dir) or rockspec.source.url + end + + if not local_dir then + local_dir = "." + end + + if not flags["homepage"] then + local url_protocol, url_path = dir.split_url(rockspec.source.url) + + if simple_scm_protocols[url_protocol] then + for _, domain in ipairs({"github.com", "bitbucket.org", "gitlab.com"}) do + if util.starts_with(url_path, domain) then + rockspec.description.homepage = "https://"..url_path:gsub("%.git$", "") + break + end + end + end + end + + local libs = nil + if flags["lib"] then + libs = {} + rockspec.external_dependencies = {} + for lib in flags["lib"]:gmatch("([^,]+)") do + table.insert(libs, lib) + rockspec.external_dependencies[lib:upper()] = { + library = lib + } + end + end + + local ok, err = fs.change_dir(local_dir) + if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end + + if (not flags["summary"]) or (not flags["detailed"]) then + local summary, detailed = detect_description() + rockspec.description.summary = flags["summary"] or summary + rockspec.description.detailed = flags["detailed"] or detailed + end + + local is_mit = show_license(rockspec) + + if is_mit and not flags["license"] then + rockspec.description.license = "MIT" + end + + fill_as_builtin(rockspec, libs) + + rockspec_cleanup(rockspec) + + persist.save_from_table(filename, rockspec, type_check.rockspec_order) + + util.printout() + util.printout("Wrote template at "..filename.." -- you should now edit and finish it.") + util.printout() + + return true +end + +return write_rockspec diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua deleted file mode 100644 index f3b054d2..00000000 --- a/src/luarocks/build.lua +++ /dev/null @@ -1,443 +0,0 @@ - ---- Module implementing the LuaRocks "build" command. --- Builds a rock, compiling its C parts if any. -local build = {} - -local pack = require("luarocks.pack") -local path = require("luarocks.path") -local util = require("luarocks.util") -local repos = require("luarocks.repos") -local fetch = require("luarocks.fetch") -local fs = require("luarocks.fs") -local dir = require("luarocks.dir") -local deps = require("luarocks.deps") -local writer = require("luarocks.manif.writer") -local remove = require("luarocks.remove") -local cfg = require("luarocks.core.cfg") - -build.help_summary = "Build/compile a rock." -build.help_arguments = "[--pack-binary-rock] [--keep] {|| []}" -build.help = [[ -Build and install a rock, compiling its C parts if any. -Argument may be a rockspec file, a source rock file -or the name of a rock to be fetched from a repository. - ---pack-binary-rock Do not install rock. Instead, produce a .rock file - with the contents of compilation in the current - directory. - ---keep Do not remove previously installed versions of the - rock after building a new one. This behavior can - be made permanent by setting keep_other_versions=true - in the configuration file. - ---branch= Override the `source.branch` field in the loaded - rockspec. Allows to specify a different branch to - fetch. Particularly for SCM rocks. - ---only-deps Installs only the dependencies of the rock. - -]]..util.deps_mode_help() - ---- Install files to a given location. --- Takes a table where the array part is a list of filenames to be copied. --- In the hash part, other keys, if is_module_path is set, are identifiers --- in Lua module format, to indicate which subdirectory the file should be --- copied to. For example, install_files({["foo.bar"] = "src/bar.lua"}, "boo") --- will copy src/bar.lua to boo/foo. --- @param files table or nil: A table containing a list of files to copy in --- the format described above. If nil is passed, this function is a no-op. --- Directories should be delimited by forward slashes as in internet URLs. --- @param location string: The base directory files should be copied to. --- @param is_module_path boolean: True if string keys in files should be --- interpreted as dotted module paths. --- @param perms string: Permissions of the newly created files installed. --- Directories are always created with the default permissions. --- @return boolean or (nil, string): True if succeeded or --- nil and an error message. -local function install_files(files, location, is_module_path, perms) - assert(type(files) == "table" or not files) - assert(type(location) == "string") - if files then - for k, file in pairs(files) do - local dest = location - local filename = dir.base_name(file) - if type(k) == "string" then - local modname = k - if is_module_path then - dest = dir.path(location, path.module_to_path(modname)) - local ok, err = fs.make_dir(dest) - if not ok then return nil, err end - if filename:match("%.lua$") then - local basename = modname:match("([^.]+)$") - filename = basename..".lua" - end - else - dest = dir.path(location, dir.dir_name(modname)) - local ok, err = fs.make_dir(dest) - if not ok then return nil, err end - filename = dir.base_name(modname) - end - else - local ok, err = fs.make_dir(dest) - if not ok then return nil, err end - end - local ok = fs.copy(dir.path(file), dir.path(dest, filename), perms) - if not ok then - return nil, "Failed copying "..file - end - end - end - return true -end - ---- Write to the current directory the contents of a table, --- where each key is a file name and its value is the file content. --- @param files table: The table of files to be written. -local function extract_from_rockspec(files) - for name, content in pairs(files) do - local fd = io.open(dir.path(fs.current_dir(), name), "w+") - fd:write(content) - fd:close() - end -end - ---- Applies patches inlined in the build.patches section --- and extracts files inlined in the build.extra_files section --- of a rockspec. --- @param rockspec table: A rockspec table. --- @return boolean or (nil, string): True if succeeded or --- nil and an error message. -function build.apply_patches(rockspec) - assert(type(rockspec) == "table") - - local build_spec = rockspec.build - if build_spec.extra_files then - extract_from_rockspec(build_spec.extra_files) - end - if build_spec.patches then - extract_from_rockspec(build_spec.patches) - for patch, patchdata in util.sortedpairs(build_spec.patches) do - util.printout("Applying patch "..patch.."...") - local ok, err = fs.apply_patch(tostring(patch), patchdata) - if not ok then - return nil, "Failed applying patch "..patch - end - end - end - return true -end - -local function install_default_docs(name, version) - local patterns = { "readme", "license", "copying", ".*%.md" } - local dest = dir.path(path.install_dir(name, version), "doc") - local has_dir = false - for file in fs.dir() do - for _, pattern in ipairs(patterns) do - if file:lower():match("^"..pattern) then - if not has_dir then - fs.make_dir(dest) - has_dir = true - end - fs.copy(file, dest, cfg.perm_read) - break - end - end - end -end - -local function check_macosx_deployment_target(rockspec) - local target = rockspec.build.macosx_deployment_target - local function minor(version) - return tonumber(version and version:match("^[^.]+%.([^.]+)")) - end - local function patch_variable(var, target) - if rockspec.variables[var]:match("MACOSX_DEPLOYMENT_TARGET") then - rockspec.variables[var] = (rockspec.variables[var]):gsub("MACOSX_DEPLOYMENT_TARGET=[^ ]*", "MACOSX_DEPLOYMENT_TARGET="..target) - else - rockspec.variables[var] = "env MACOSX_DEPLOYMENT_TARGET="..target.." "..rockspec.variables[var] - end - end - if cfg.platforms.macosx and rockspec:format_is_at_least("3.0") and target then - local version = util.popen_read("sw_vers -productVersion") - local versionminor = minor(version) - local targetminor = minor(target) - if targetminor > versionminor then - return nil, ("This rock requires Mac OSX 10.%d, and you are running 10.%d."):format(targetminor, versionminor) - end - patch_variable("CC", target) - patch_variable("LD", target) - end - return true -end - ---- Build and install a rock given a rockspec. --- @param rockspec_file string: local or remote filename of a rockspec. --- @param need_to_fetch boolean: true if sources need to be fetched, --- false if the rockspec was obtained from inside a source rock. --- @param minimal_mode boolean: true if there's no need to fetch, --- unpack or change dir (this is used by "luarocks make"). Implies --- need_to_fetch = false. --- @param deps_mode string: Dependency mode: "one" for the current default tree, --- "all" for all trees, "order" for all trees with priority >= the current default, --- "none" for no trees. --- @param build_only_deps boolean: true to build the listed dependencies only. --- @return (string, string) or (nil, string, [string]): Name and version of --- installed rock if succeeded or nil and an error message followed by an error code. -function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps) - assert(type(rockspec_file) == "string") - assert(type(need_to_fetch) == "boolean") - - local rockspec, err, errcode = fetch.load_rockspec(rockspec_file) - if err then - return nil, err, errcode - elseif not rockspec.build then - return nil, "Rockspec error: build table not specified" - elseif not rockspec.build.type then - return nil, "Rockspec error: build type not specified" - end - - local ok - if not build_only_deps then - ok, err, errcode = deps.check_external_deps(rockspec, "build") - if err then - return nil, err, errcode - end - end - - if deps_mode == "none" then - util.printerr("Warning: skipping dependency checks.") - else - local ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) - if err then - return nil, err, errcode - end - end - - local name, version = rockspec.name, rockspec.version - if build_only_deps then - util.printout("Stopping after installing dependencies for " ..name.." "..version) - util.printout() - return name, version - end - - if repos.is_installed(name, version) then - repos.delete_version(name, version, deps_mode) - end - - if not minimal_mode then - local source_dir - if need_to_fetch then - ok, source_dir, errcode = fetch.fetch_sources(rockspec, true) - if not ok then - return nil, source_dir, errcode - end - local ok, err = fs.change_dir(source_dir) - if not ok then return nil, err end - elseif rockspec.source.file then - local ok, err = fs.unpack_archive(rockspec.source.file) - if not ok then - return nil, err - end - end - fs.change_dir(rockspec.source.dir) - end - - local dirs = { - lua = { name = path.lua_dir(name, version), is_module_path = true, perms = cfg.perm_read }, - lib = { name = path.lib_dir(name, version), is_module_path = true, perms = cfg.perm_exec }, - conf = { name = path.conf_dir(name, version), is_module_path = false, perms = cfg.perm_read }, - bin = { name = path.bin_dir(name, version), is_module_path = false, perms = cfg.perm_exec }, - } - - for _, d in pairs(dirs) do - local ok, err = fs.make_dir(d.name) - if not ok then return nil, err end - end - local rollback = util.schedule_function(function() - fs.delete(path.install_dir(name, version)) - fs.remove_dir_if_empty(path.versions_dir(name)) - end) - - local build_spec = rockspec.build - - if not minimal_mode then - ok, err = build.apply_patches(rockspec) - if err then - return nil, err - end - end - - ok, err = check_macosx_deployment_target(rockspec) - if not ok then - return nil, err - end - - if build_spec.type ~= "none" then - - -- Temporary compatibility - if build_spec.type == "module" then - util.printout("Do not use 'module' as a build type. Use 'builtin' instead.") - build_spec.type = "builtin" - end - - if cfg.accepted_build_types and util.array_contains(cfg.accepted_build_types, build_spec.type) then - return nil, "This rockspec uses the '"..build_spec.type.."' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration." - end - - local build_type - ok, build_type = pcall(require, "luarocks.build." .. build_spec.type) - if not ok or not type(build_type) == "table" then - return nil, "Failed initializing build back-end for build type '"..build_spec.type.."': "..build_type - end - - ok, err = build_type.run(rockspec) - if not ok then - return nil, "Build error: " .. err - end - end - - if build_spec.install then - for id, install_dir in pairs(dirs) do - ok, err = install_files(build_spec.install[id], install_dir.name, install_dir.is_module_path, install_dir.perms) - if not ok then - return nil, err - end - end - end - - local copy_directories = build_spec.copy_directories - local copying_default = false - if not copy_directories then - copy_directories = {"doc"} - copying_default = true - end - - local any_docs = false - for _, copy_dir in pairs(copy_directories) do - if fs.is_dir(copy_dir) then - local dest = dir.path(path.install_dir(name, version), copy_dir) - fs.make_dir(dest) - fs.copy_contents(copy_dir, dest) - any_docs = true - else - if not copying_default then - return nil, "Directory '"..copy_dir.."' not found" - end - end - end - - if not any_docs then - install_default_docs(name, version) - end - - for _, d in pairs(dirs) do - fs.remove_dir_if_empty(d.name) - end - - fs.pop_dir() - - fs.copy(rockspec.local_filename, path.rockspec_file(name, version), cfg.perm_read) - if need_to_fetch then - fs.pop_dir() - end - - ok, err = writer.make_rock_manifest(name, version) - if err then return nil, err end - - ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) - if err then return nil, err end - - util.remove_scheduled_function(rollback) - rollback = util.schedule_function(function() - repos.delete_version(name, version, deps_mode) - end) - - ok, err = repos.run_hook(rockspec, "post_install") - if err then return nil, err end - - util.announce_install(rockspec) - util.remove_scheduled_function(rollback) - return name, version -end - ---- Build and install a rock. --- @param rock_file string: local or remote filename of a rock. --- @param need_to_fetch boolean: true if sources need to be fetched, --- false if the rockspec was obtained from inside a source rock. --- @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 build_only_deps boolean: true to build the listed dependencies only. --- @return boolean or (nil, string, [string]): True if build was successful, --- or false and an error message and an optional error code. -function build.build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps) - assert(type(rock_file) == "string") - assert(type(need_to_fetch) == "boolean") - - local ok, err, errcode - local unpack_dir - unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file) - if not unpack_dir then - return nil, err, errcode - end - local rockspec_file = path.rockspec_name_from_rock(rock_file) - ok, err = fs.change_dir(unpack_dir) - if not ok then return nil, err end - ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps) - fs.pop_dir() - return ok, err, errcode -end - -local function do_build(name, version, deps_mode, build_only_deps) - if name:match("%.rockspec$") then - return build.build_rockspec(name, true, false, deps_mode, build_only_deps) - elseif name:match("%.src%.rock$") then - return build.build_rock(name, false, deps_mode, build_only_deps) - elseif name:match("%.all%.rock$") then - local install = require("luarocks.install") - local install_fun = build_only_deps and install.install_binary_rock_deps or install.install_binary_rock - return install_fun(name, deps_mode) - elseif name:match("%.rock$") then - return build.build_rock(name, true, deps_mode, build_only_deps) - elseif not name:match(dir.separator) then - local search = require("luarocks.search") - return search.act_on_src_or_rockspec(do_build, name:lower(), version, nil, deps_mode, build_only_deps) - end - return nil, "Don't know what to do with "..name -end - ---- Driver function for "build" command. --- @param name string: A local or remote rockspec or rock file. --- If a package name is given, forwards the request to "search" and, --- if returned a result, installs the matching rock. --- @param version string: When passing a package name, a version number may --- also be given. --- @return boolean or (nil, string, exitcode): True if build was successful; nil and an --- error message otherwise. exitcode is optionally returned. -function build.command(flags, name, version) - if type(name) ~= "string" then - return nil, "Argument missing. "..util.see_help("build") - end - assert(type(version) == "string" or not version) - - if flags["pack-binary-rock"] then - return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags)) - else - local ok, err = fs.check_command_permissions(flags) - if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - ok, err = do_build(name, version, deps.get_deps_mode(flags), flags["only-deps"]) - if not ok then return nil, err end - name, version = ok, err - if flags["only-deps"] then - return name, version - end - if (not flags["keep"]) and not cfg.keep_other_versions then - local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) - if not ok then util.printerr(err) end - end - return name, version - end -end - -return build diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua new file mode 100644 index 00000000..f3b054d2 --- /dev/null +++ b/src/luarocks/cmd/build.lua @@ -0,0 +1,443 @@ + +--- Module implementing the LuaRocks "build" command. +-- Builds a rock, compiling its C parts if any. +local build = {} + +local pack = require("luarocks.pack") +local path = require("luarocks.path") +local util = require("luarocks.util") +local repos = require("luarocks.repos") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local deps = require("luarocks.deps") +local writer = require("luarocks.manif.writer") +local remove = require("luarocks.remove") +local cfg = require("luarocks.core.cfg") + +build.help_summary = "Build/compile a rock." +build.help_arguments = "[--pack-binary-rock] [--keep] {|| []}" +build.help = [[ +Build and install a rock, compiling its C parts if any. +Argument may be a rockspec file, a source rock file +or the name of a rock to be fetched from a repository. + +--pack-binary-rock Do not install rock. Instead, produce a .rock file + with the contents of compilation in the current + directory. + +--keep Do not remove previously installed versions of the + rock after building a new one. This behavior can + be made permanent by setting keep_other_versions=true + in the configuration file. + +--branch= Override the `source.branch` field in the loaded + rockspec. Allows to specify a different branch to + fetch. Particularly for SCM rocks. + +--only-deps Installs only the dependencies of the rock. + +]]..util.deps_mode_help() + +--- Install files to a given location. +-- Takes a table where the array part is a list of filenames to be copied. +-- In the hash part, other keys, if is_module_path is set, are identifiers +-- in Lua module format, to indicate which subdirectory the file should be +-- copied to. For example, install_files({["foo.bar"] = "src/bar.lua"}, "boo") +-- will copy src/bar.lua to boo/foo. +-- @param files table or nil: A table containing a list of files to copy in +-- the format described above. If nil is passed, this function is a no-op. +-- Directories should be delimited by forward slashes as in internet URLs. +-- @param location string: The base directory files should be copied to. +-- @param is_module_path boolean: True if string keys in files should be +-- interpreted as dotted module paths. +-- @param perms string: Permissions of the newly created files installed. +-- Directories are always created with the default permissions. +-- @return boolean or (nil, string): True if succeeded or +-- nil and an error message. +local function install_files(files, location, is_module_path, perms) + assert(type(files) == "table" or not files) + assert(type(location) == "string") + if files then + for k, file in pairs(files) do + local dest = location + local filename = dir.base_name(file) + if type(k) == "string" then + local modname = k + if is_module_path then + dest = dir.path(location, path.module_to_path(modname)) + local ok, err = fs.make_dir(dest) + if not ok then return nil, err end + if filename:match("%.lua$") then + local basename = modname:match("([^.]+)$") + filename = basename..".lua" + end + else + dest = dir.path(location, dir.dir_name(modname)) + local ok, err = fs.make_dir(dest) + if not ok then return nil, err end + filename = dir.base_name(modname) + end + else + local ok, err = fs.make_dir(dest) + if not ok then return nil, err end + end + local ok = fs.copy(dir.path(file), dir.path(dest, filename), perms) + if not ok then + return nil, "Failed copying "..file + end + end + end + return true +end + +--- Write to the current directory the contents of a table, +-- where each key is a file name and its value is the file content. +-- @param files table: The table of files to be written. +local function extract_from_rockspec(files) + for name, content in pairs(files) do + local fd = io.open(dir.path(fs.current_dir(), name), "w+") + fd:write(content) + fd:close() + end +end + +--- Applies patches inlined in the build.patches section +-- and extracts files inlined in the build.extra_files section +-- of a rockspec. +-- @param rockspec table: A rockspec table. +-- @return boolean or (nil, string): True if succeeded or +-- nil and an error message. +function build.apply_patches(rockspec) + assert(type(rockspec) == "table") + + local build_spec = rockspec.build + if build_spec.extra_files then + extract_from_rockspec(build_spec.extra_files) + end + if build_spec.patches then + extract_from_rockspec(build_spec.patches) + for patch, patchdata in util.sortedpairs(build_spec.patches) do + util.printout("Applying patch "..patch.."...") + local ok, err = fs.apply_patch(tostring(patch), patchdata) + if not ok then + return nil, "Failed applying patch "..patch + end + end + end + return true +end + +local function install_default_docs(name, version) + local patterns = { "readme", "license", "copying", ".*%.md" } + local dest = dir.path(path.install_dir(name, version), "doc") + local has_dir = false + for file in fs.dir() do + for _, pattern in ipairs(patterns) do + if file:lower():match("^"..pattern) then + if not has_dir then + fs.make_dir(dest) + has_dir = true + end + fs.copy(file, dest, cfg.perm_read) + break + end + end + end +end + +local function check_macosx_deployment_target(rockspec) + local target = rockspec.build.macosx_deployment_target + local function minor(version) + return tonumber(version and version:match("^[^.]+%.([^.]+)")) + end + local function patch_variable(var, target) + if rockspec.variables[var]:match("MACOSX_DEPLOYMENT_TARGET") then + rockspec.variables[var] = (rockspec.variables[var]):gsub("MACOSX_DEPLOYMENT_TARGET=[^ ]*", "MACOSX_DEPLOYMENT_TARGET="..target) + else + rockspec.variables[var] = "env MACOSX_DEPLOYMENT_TARGET="..target.." "..rockspec.variables[var] + end + end + if cfg.platforms.macosx and rockspec:format_is_at_least("3.0") and target then + local version = util.popen_read("sw_vers -productVersion") + local versionminor = minor(version) + local targetminor = minor(target) + if targetminor > versionminor then + return nil, ("This rock requires Mac OSX 10.%d, and you are running 10.%d."):format(targetminor, versionminor) + end + patch_variable("CC", target) + patch_variable("LD", target) + end + return true +end + +--- Build and install a rock given a rockspec. +-- @param rockspec_file string: local or remote filename of a rockspec. +-- @param need_to_fetch boolean: true if sources need to be fetched, +-- false if the rockspec was obtained from inside a source rock. +-- @param minimal_mode boolean: true if there's no need to fetch, +-- unpack or change dir (this is used by "luarocks make"). Implies +-- need_to_fetch = false. +-- @param deps_mode string: Dependency mode: "one" for the current default tree, +-- "all" for all trees, "order" for all trees with priority >= the current default, +-- "none" for no trees. +-- @param build_only_deps boolean: true to build the listed dependencies only. +-- @return (string, string) or (nil, string, [string]): Name and version of +-- installed rock if succeeded or nil and an error message followed by an error code. +function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps) + assert(type(rockspec_file) == "string") + assert(type(need_to_fetch) == "boolean") + + local rockspec, err, errcode = fetch.load_rockspec(rockspec_file) + if err then + return nil, err, errcode + elseif not rockspec.build then + return nil, "Rockspec error: build table not specified" + elseif not rockspec.build.type then + return nil, "Rockspec error: build type not specified" + end + + local ok + if not build_only_deps then + ok, err, errcode = deps.check_external_deps(rockspec, "build") + if err then + return nil, err, errcode + end + end + + if deps_mode == "none" then + util.printerr("Warning: skipping dependency checks.") + else + local ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) + if err then + return nil, err, errcode + end + end + + local name, version = rockspec.name, rockspec.version + if build_only_deps then + util.printout("Stopping after installing dependencies for " ..name.." "..version) + util.printout() + return name, version + end + + if repos.is_installed(name, version) then + repos.delete_version(name, version, deps_mode) + end + + if not minimal_mode then + local source_dir + if need_to_fetch then + ok, source_dir, errcode = fetch.fetch_sources(rockspec, true) + if not ok then + return nil, source_dir, errcode + end + local ok, err = fs.change_dir(source_dir) + if not ok then return nil, err end + elseif rockspec.source.file then + local ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then + return nil, err + end + end + fs.change_dir(rockspec.source.dir) + end + + local dirs = { + lua = { name = path.lua_dir(name, version), is_module_path = true, perms = cfg.perm_read }, + lib = { name = path.lib_dir(name, version), is_module_path = true, perms = cfg.perm_exec }, + conf = { name = path.conf_dir(name, version), is_module_path = false, perms = cfg.perm_read }, + bin = { name = path.bin_dir(name, version), is_module_path = false, perms = cfg.perm_exec }, + } + + for _, d in pairs(dirs) do + local ok, err = fs.make_dir(d.name) + if not ok then return nil, err end + end + local rollback = util.schedule_function(function() + fs.delete(path.install_dir(name, version)) + fs.remove_dir_if_empty(path.versions_dir(name)) + end) + + local build_spec = rockspec.build + + if not minimal_mode then + ok, err = build.apply_patches(rockspec) + if err then + return nil, err + end + end + + ok, err = check_macosx_deployment_target(rockspec) + if not ok then + return nil, err + end + + if build_spec.type ~= "none" then + + -- Temporary compatibility + if build_spec.type == "module" then + util.printout("Do not use 'module' as a build type. Use 'builtin' instead.") + build_spec.type = "builtin" + end + + if cfg.accepted_build_types and util.array_contains(cfg.accepted_build_types, build_spec.type) then + return nil, "This rockspec uses the '"..build_spec.type.."' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration." + end + + local build_type + ok, build_type = pcall(require, "luarocks.build." .. build_spec.type) + if not ok or not type(build_type) == "table" then + return nil, "Failed initializing build back-end for build type '"..build_spec.type.."': "..build_type + end + + ok, err = build_type.run(rockspec) + if not ok then + return nil, "Build error: " .. err + end + end + + if build_spec.install then + for id, install_dir in pairs(dirs) do + ok, err = install_files(build_spec.install[id], install_dir.name, install_dir.is_module_path, install_dir.perms) + if not ok then + return nil, err + end + end + end + + local copy_directories = build_spec.copy_directories + local copying_default = false + if not copy_directories then + copy_directories = {"doc"} + copying_default = true + end + + local any_docs = false + for _, copy_dir in pairs(copy_directories) do + if fs.is_dir(copy_dir) then + local dest = dir.path(path.install_dir(name, version), copy_dir) + fs.make_dir(dest) + fs.copy_contents(copy_dir, dest) + any_docs = true + else + if not copying_default then + return nil, "Directory '"..copy_dir.."' not found" + end + end + end + + if not any_docs then + install_default_docs(name, version) + end + + for _, d in pairs(dirs) do + fs.remove_dir_if_empty(d.name) + end + + fs.pop_dir() + + fs.copy(rockspec.local_filename, path.rockspec_file(name, version), cfg.perm_read) + if need_to_fetch then + fs.pop_dir() + end + + ok, err = writer.make_rock_manifest(name, version) + if err then return nil, err end + + ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) + if err then return nil, err end + + util.remove_scheduled_function(rollback) + rollback = util.schedule_function(function() + repos.delete_version(name, version, deps_mode) + end) + + ok, err = repos.run_hook(rockspec, "post_install") + if err then return nil, err end + + util.announce_install(rockspec) + util.remove_scheduled_function(rollback) + return name, version +end + +--- Build and install a rock. +-- @param rock_file string: local or remote filename of a rock. +-- @param need_to_fetch boolean: true if sources need to be fetched, +-- false if the rockspec was obtained from inside a source rock. +-- @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 build_only_deps boolean: true to build the listed dependencies only. +-- @return boolean or (nil, string, [string]): True if build was successful, +-- or false and an error message and an optional error code. +function build.build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps) + assert(type(rock_file) == "string") + assert(type(need_to_fetch) == "boolean") + + local ok, err, errcode + local unpack_dir + unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file) + if not unpack_dir then + return nil, err, errcode + end + local rockspec_file = path.rockspec_name_from_rock(rock_file) + ok, err = fs.change_dir(unpack_dir) + if not ok then return nil, err end + ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps) + fs.pop_dir() + return ok, err, errcode +end + +local function do_build(name, version, deps_mode, build_only_deps) + if name:match("%.rockspec$") then + return build.build_rockspec(name, true, false, deps_mode, build_only_deps) + elseif name:match("%.src%.rock$") then + return build.build_rock(name, false, deps_mode, build_only_deps) + elseif name:match("%.all%.rock$") then + local install = require("luarocks.install") + local install_fun = build_only_deps and install.install_binary_rock_deps or install.install_binary_rock + return install_fun(name, deps_mode) + elseif name:match("%.rock$") then + return build.build_rock(name, true, deps_mode, build_only_deps) + elseif not name:match(dir.separator) then + local search = require("luarocks.search") + return search.act_on_src_or_rockspec(do_build, name:lower(), version, nil, deps_mode, build_only_deps) + end + return nil, "Don't know what to do with "..name +end + +--- Driver function for "build" command. +-- @param name string: A local or remote rockspec or rock file. +-- If a package name is given, forwards the request to "search" and, +-- if returned a result, installs the matching rock. +-- @param version string: When passing a package name, a version number may +-- also be given. +-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an +-- error message otherwise. exitcode is optionally returned. +function build.command(flags, name, version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("build") + end + assert(type(version) == "string" or not version) + + if flags["pack-binary-rock"] then + return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags)) + else + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + ok, err = do_build(name, version, deps.get_deps_mode(flags), flags["only-deps"]) + if not ok then return nil, err end + name, version = ok, err + if flags["only-deps"] then + return name, version + end + if (not flags["keep"]) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) + if not ok then util.printerr(err) end + end + return name, version + end +end + +return build diff --git a/src/luarocks/cmd/config_cmd.lua b/src/luarocks/cmd/config_cmd.lua new file mode 100644 index 00000000..b68f7898 --- /dev/null +++ b/src/luarocks/cmd/config_cmd.lua @@ -0,0 +1,71 @@ +--- Module implementing the LuaRocks "config" command. +-- Queries information about the LuaRocks configuration. +local config_cmd = {} + +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") + +config_cmd.help_summary = "Query information about the LuaRocks configuration." +config_cmd.help_arguments = "" +config_cmd.help = [[ +--lua-incdir Path to Lua header files. + +--lua-libdir Path to Lua library files. + +--lua-ver Lua version (in major.minor format). e.g. 5.1 + +--system-config Location of the system config file. + +--user-config Location of the user config file. + +--rock-trees Rocks trees in use. First the user tree, then the system tree. +]] + +local function config_file(conf) + print(dir.normalize(conf.file)) + if conf.ok then + return true + else + return nil, "file not found" + end +end + +--- Driver function for "config" command. +-- @return boolean: True if succeeded, nil on errors. +function config_cmd.command(flags) + if flags["lua-incdir"] then + print(cfg.variables.LUA_INCDIR) + return true + end + if flags["lua-libdir"] then + print(cfg.variables.LUA_LIBDIR) + return true + end + if flags["lua-ver"] then + print(cfg.lua_version) + return true + end + local conf = cfg.which_config() + if flags["system-config"] then + return config_file(conf.system) + end + if flags["user-config"] then + return config_file(conf.user) + end + if flags["rock-trees"] then + for _, tree in ipairs(cfg.rocks_trees) do + if type(tree) == "string" then + util.printout(dir.normalize(tree)) + else + local name = tree.name and "\t"..tree.name or "" + util.printout(dir.normalize(tree.root)..name) + end + end + return true + end + + return nil, "Please provide a flag for querying configuration values. "..util.see_help("config") +end + +return config_cmd diff --git a/src/luarocks/cmd/doc.lua b/src/luarocks/cmd/doc.lua new file mode 100644 index 00000000..5d521276 --- /dev/null +++ b/src/luarocks/cmd/doc.lua @@ -0,0 +1,155 @@ + +--- Module implementing the LuaRocks "doc" command. +-- Shows documentation for an installed rock. +local doc = {} + +local util = require("luarocks.util") +local search = require("luarocks.search") +local path = require("luarocks.path") +local dir = require("luarocks.dir") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local download = require("luarocks.download") + +doc.help_summary = "Show documentation for an installed rock." + +doc.help = [[ + is an existing package name. +Without any flags, tries to load the documentation +using a series of heuristics. +With these flags, return only the desired information: + +--home Open the home page of project. +--list List documentation files only. + +For more information about a rock, see the 'show' command. +]] + +local function show_homepage(homepage, name, version) + if not homepage then + return nil, "No 'homepage' field in rockspec for "..name.." "..version + end + util.printout("Opening "..homepage.." ...") + fs.browser(homepage) + return true +end + +local function try_to_open_homepage(name, version) + local temp_dir, err = fs.make_temp_dir("doc-"..name.."-"..(version or "")) + if not temp_dir then + return nil, "Failed creating temporary directory: "..err + end + util.schedule_function(fs.delete, temp_dir) + local ok, err = fs.change_dir(temp_dir) + if not ok then return nil, err end + local filename, err = download.download("rockspec", name, version) + if not filename then return nil, err end + local rockspec, err = fetch.load_local_rockspec(filename) + if not rockspec then return nil, err end + fs.pop_dir() + local descript = rockspec.description or {} + if not descript.homepage then return nil, "No homepage defined for "..name end + return show_homepage(descript.homepage, name, version) +end + +--- Driver function for "doc" command. +-- @param name or nil: an existing package name. +-- @param version string or nil: a version may also be passed. +-- @return boolean: True if succeeded, nil on errors. +function doc.command(flags, name, version) + if not name then + return nil, "Argument missing. "..util.see_help("doc") + end + + name = name:lower() + + local iname, iversion, repo = search.pick_installed_rock(name, version, flags["tree"]) + if not iname then + util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...") + return try_to_open_homepage(name, version) + end + name, version = iname, iversion + + local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo)) + if not rockspec then return nil,err end + local descript = rockspec.description or {} + + if flags["home"] then + return show_homepage(descript.homepage, name, version) + end + + local directory = path.install_dir(name,version,repo) + + local docdir + local directories = { "doc", "docs" } + for _, d in ipairs(directories) do + local dirname = dir.path(directory, d) + if fs.is_dir(dirname) then + docdir = dirname + break + end + end + if not docdir then + if descript.homepage and not flags["list"] then + util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...") + fs.browser(descript.homepage) + return true + end + return nil, "Documentation directory not found for "..name.." "..version + end + + docdir = dir.normalize(docdir):gsub("/+", "/") + local files = fs.find(docdir) + local htmlpatt = "%.html?$" + local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" } + local basenames = { "index", "readme", "manual" } + + local porcelain = flags["porcelain"] + if #files > 0 then + util.title("Documentation files for "..name.." "..version, porcelain) + if porcelain then + for _, file in ipairs(files) do + util.printout(docdir.."/"..file) + end + else + util.printout(docdir.."/") + for _, file in ipairs(files) do + util.printout("\t"..file) + end + end + end + + if flags["list"] then + return true + end + + for _, extension in ipairs(extensions) do + for _, basename in ipairs(basenames) do + local filename = basename..extension + local found + for _, file in ipairs(files) do + if file:lower():match(filename) and ((not found) or #file < #found) then + found = file + end + end + if found then + local pathname = dir.path(docdir, found) + util.printout() + util.printout("Opening "..pathname.." ...") + util.printout() + local ok = fs.browser(pathname) + if not ok and not pathname:match(htmlpatt) then + local fd = io.open(pathname, "r") + util.printout(fd:read("*a")) + fd:close() + end + return true + end + end + end + + return true +end + + +return doc diff --git a/src/luarocks/cmd/download.lua b/src/luarocks/cmd/download.lua new file mode 100644 index 00000000..557d1b65 --- /dev/null +++ b/src/luarocks/cmd/download.lua @@ -0,0 +1,107 @@ + +--- Module implementing the luarocks "download" command. +-- Download a rock from the repository. +local download = {} + +local util = require("luarocks.util") +local path = require("luarocks.path") +local fetch = require("luarocks.fetch") +local search = require("luarocks.search") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local cfg = require("luarocks.core.cfg") + +download.help_summary = "Download a specific rock file from a rocks server." +download.help_arguments = "[--all] [--arch= | --source | --rockspec] [ []]" + +download.help = [[ +--all Download all files if there are multiple matches. +--source Download .src.rock if available. +--rockspec Download .rockspec if available. +--arch= Download rock for a specific architecture. +]] + +local function get_file(filename) + local protocol, pathname = dir.split_url(filename) + if protocol == "file" then + local ok, err = fs.copy(pathname, fs.current_dir(), cfg.perm_read) + if ok then + return pathname + else + return nil, err + end + else + return fetch.fetch_url(filename) + end +end + +function download.download(arch, name, version, all) + local query = search.make_query(name, version) + if arch then query.arch = arch end + local search_err + + if all then + if name == "" then query.exact_name = false end + local results = search.search_repos(query) + local has_result = false + local all_ok = true + local any_err = "" + for name, result in pairs(results) do + for version, items in pairs(result) do + for _, item in ipairs(items) do + -- Ignore provided rocks. + if item.arch ~= "installed" then + has_result = true + local filename = path.make_url(item.repo, name, version, item.arch) + local ok, err = get_file(filename) + if not ok then + all_ok = false + any_err = any_err .. "\n" .. err + end + end + end + end + end + + if has_result then + return all_ok, any_err + end + else + local url + url, search_err = search.find_suitable_rock(query) + if url then + return get_file(url) + end + end + return nil, "Could not find a result named "..name..(version and " "..version or "").. + (search_err and ": "..search_err or ".") +end + +--- Driver function for the "download" command. +-- @param name string: a rock name. +-- @param version string or nil: if the name of a package is given, a +-- version may also be passed. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +function download.command(flags, name, version) + assert(type(version) == "string" or not version) + if type(name) ~= "string" and not flags["all"] then + return nil, "Argument missing. "..util.see_help("download") + end + if not name then name, version = "", "" end + + local arch + + if flags["source"] then + arch = "src" + elseif flags["rockspec"] then + arch = "rockspec" + elseif flags["arch"] then + arch = flags["arch"] + end + + local dl, err = download.download(arch, name:lower(), version, flags["all"]) + return dl and true, err +end + +return download diff --git a/src/luarocks/cmd/help.lua b/src/luarocks/cmd/help.lua new file mode 100644 index 00000000..d27c3a50 --- /dev/null +++ b/src/luarocks/cmd/help.lua @@ -0,0 +1,117 @@ + +--- Module implementing the LuaRocks "help" command. +-- This is a generic help display module, which +-- uses a global table called "commands" to find commands +-- to show help for; each command should be represented by a +-- table containing "help" and "help_summary" fields. +local help = {} + +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") + +local program = util.this_program("luarocks") + +help.help_summary = "Help on commands. Type '"..program.." help ' for more." + +help.help_arguments = "[]" +help.help = [[ + is the command to show help for. +]] + +local function print_banner() + util.printout("\nLuaRocks "..cfg.program_version..", a module deployment system for Lua") +end + +local function print_section(section) + util.printout("\n"..section) +end + +local function get_status(status) + if status then + return "ok" + else + return "not found" + end +end + +--- Driver function for the "help" command. +-- @param command string or nil: command to show help for; if not +-- given, help summaries for all commands are shown. +-- @return boolean or (nil, string): true if there were no errors +-- or nil and an error message if an invalid command was requested. +function help.command(flags, command) + if not command then + local conf = cfg.which_config() + print_banner() + print_section("NAME") + util.printout("\t"..program..[[ - ]]..program_description) + print_section("SYNOPSIS") + util.printout("\t"..program..[[ [--from= | --only-from=] [--to=] [VAR=VALUE]... [] ]]) + print_section("GENERAL OPTIONS") + util.printout([[ + These apply to all commands, as appropriate: + + --server= Fetch rocks/rockspecs from this server + (takes priority over config file) + --only-server= Fetch rocks/rockspecs from this server only + (overrides any entries in the config file) + --only-sources= Restrict downloads to paths matching the + given URL. + --tree= Which tree to operate on. + --local Use the tree in the user's home directory. + To enable it, see ']]..program..[[ help path'. + --verbose Display verbose output of commands executed. + --timeout= Timeout on network operations, in seconds. + 0 means no timeout (wait forever). + Default is ]]..tostring(cfg.connection_timeout)..[[.]]) + print_section("VARIABLES") + util.printout([[ + Variables from the "variables" table of the configuration file + can be overriden with VAR=VALUE assignments.]]) + print_section("COMMANDS") + for name, command in util.sortedpairs(commands) do + local cmd = require(command) + util.printout("", name) + util.printout("\t", cmd.help_summary) + end + print_section("CONFIGURATION") + util.printout("\tLua version: " .. cfg.lua_version) + util.printout("\tConfiguration files:") + util.printout("\t\tSystem: ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.ok) ..")") + if conf.user.file then + util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.ok) ..")\n") + else + util.printout("\t\tUser : disabled in this LuaRocks installation.\n") + end + util.printout("\tRocks trees in use: ") + for _, tree in ipairs(cfg.rocks_trees) do + if type(tree) == "string" then + util.printout("\t\t"..dir.normalize(tree)) + else + local name = tree.name and " (\""..tree.name.."\")" or "" + util.printout("\t\t"..dir.normalize(tree.root)..name) + end + end + else + command = command:gsub("-", "_") + local cmd = commands[command] and require(commands[command]) + if cmd then + local arguments = cmd.help_arguments or "" + print_banner() + print_section("NAME") + util.printout("\t"..program.." "..command.." - "..cmd.help_summary) + print_section("SYNOPSIS") + util.printout("\t"..program.." "..command.." "..arguments) + print_section("DESCRIPTION") + util.printout("",(cmd.help:gsub("\n","\n\t"):gsub("\n\t$",""))) + print_section("SEE ALSO") + util.printout("","'"..program.." help' for general options and configuration.\n") + else + return nil, "Unknown command: "..command + end + end + return true +end + +return help diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua new file mode 100644 index 00000000..c9b085f5 --- /dev/null +++ b/src/luarocks/cmd/install.lua @@ -0,0 +1,183 @@ +--- Module implementing the LuaRocks "install" command. +-- Installs binary rocks. +local install = {} + +local path = require("luarocks.path") +local repos = require("luarocks.repos") +local fetch = require("luarocks.fetch") +local util = require("luarocks.util") +local fs = require("luarocks.fs") +local deps = require("luarocks.deps") +local writer = require("luarocks.manif.writer") +local remove = require("luarocks.remove") +local cfg = require("luarocks.core.cfg") + +install.help_summary = "Install a rock." + +install.help_arguments = "{| []}" + +install.help = [[ +Argument may be the name of a rock to be fetched from a repository +or a filename of a locally available rock. + +--keep Do not remove previously installed versions of the + rock after installing a new one. This behavior can + be made permanent by setting keep_other_versions=true + in the configuration file. + +--only-deps Installs only the dependencies of the rock. +]]..util.deps_mode_help() + + +--- Install a binary rock. +-- @param rock_file string: local or remote filename of a rock. +-- @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. +-- @return (string, string) or (nil, string, [string]): Name and version of +-- installed rock if succeeded or nil and an error message followed by an error code. +function install.install_binary_rock(rock_file, deps_mode) + assert(type(rock_file) == "string") + + local name, version, arch = path.parse_name(rock_file) + if not name then + return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." + end + + if arch ~= "all" and arch ~= cfg.arch then + return nil, "Incompatible architecture "..arch, "arch" + end + if repos.is_installed(name, version) then + repos.delete_version(name, version, deps_mode) + end + + local rollback = util.schedule_function(function() + fs.delete(path.install_dir(name, version)) + fs.remove_dir_if_empty(path.versions_dir(name)) + end) + + local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) + if not ok then return nil, err, errcode end + + local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version)) + if err then + return nil, "Failed loading rockspec for installed package: "..err, errcode + end + + if deps_mode == "none" then + util.printerr("Warning: skipping dependency checks.") + else + ok, err, errcode = deps.check_external_deps(rockspec, "install") + if err then return nil, err, errcode end + end + + -- For compatibility with .rock files built with LuaRocks 1 + if not fs.exists(path.rock_manifest_file(name, version)) then + ok, err = writer.make_rock_manifest(name, version) + if err then return nil, err end + end + + if deps_mode ~= "none" then + ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) + if err then return nil, err, errcode end + end + + ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) + if err then return nil, err end + + util.remove_scheduled_function(rollback) + rollback = util.schedule_function(function() + repos.delete_version(name, version, deps_mode) + end) + + ok, err = repos.run_hook(rockspec, "post_install") + if err then return nil, err end + + util.announce_install(rockspec) + util.remove_scheduled_function(rollback) + return name, version +end + +--- Installs the dependencies of a binary rock. +-- @param rock_file string: local or remote filename of a rock. +-- @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. +-- @return (string, string) or (nil, string, [string]): Name and version of +-- the rock whose dependencies were installed if succeeded or nil and an error message +-- followed by an error code. +function install.install_binary_rock_deps(rock_file, deps_mode) + assert(type(rock_file) == "string") + + local name, version, arch = path.parse_name(rock_file) + if not name then + return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." + end + + if arch ~= "all" and arch ~= cfg.arch then + return nil, "Incompatible architecture "..arch, "arch" + end + + local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) + if not ok then return nil, err, errcode end + + local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version)) + if err then + return nil, "Failed loading rockspec for installed package: "..err, errcode + end + + ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) + if err then return nil, err, errcode end + + util.printout() + util.printout("Successfully installed dependencies for " ..name.." "..version) + + return name, version +end + +--- Driver function for the "install" command. +-- @param name string: name of a binary rock. If an URL or pathname +-- to a binary rock is given, fetches and installs it. If a rockspec or a +-- source rock is given, forwards the request to the "build" command. +-- If a package name is given, forwards the request to "search" and, +-- if returned a result, installs the matching rock. +-- @param version string: When passing a package name, a version number +-- may also be given. +-- @return boolean or (nil, string, exitcode): True if installation was +-- successful, nil and an error message otherwise. exitcode is optionally returned. +function install.command(flags, name, version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("install") + end + + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + + if name:match("%.rockspec$") or name:match("%.src%.rock$") then + local build = require("luarocks.build") + return build.command(flags, name) + elseif name:match("%.rock$") then + if flags["only-deps"] then + ok, err = install.install_binary_rock_deps(name, deps.get_deps_mode(flags)) + else + ok, err = install.install_binary_rock(name, deps.get_deps_mode(flags)) + end + if not ok then return nil, err end + name, version = ok, err + if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) + if not ok then util.printerr(err) end + end + return name, version + else + local search = require("luarocks.search") + local url, err = search.find_suitable_rock(search.make_query(name:lower(), version)) + if not url then + return nil, err + end + util.printout("Installing "..url) + return install.command(flags, url) + end +end + +return install diff --git a/src/luarocks/cmd/lint.lua b/src/luarocks/cmd/lint.lua new file mode 100644 index 00000000..c9ea45ea --- /dev/null +++ b/src/luarocks/cmd/lint.lua @@ -0,0 +1,53 @@ + +--- Module implementing the LuaRocks "lint" command. +-- Utility function that checks syntax of the rockspec. +local lint = {} + +local util = require("luarocks.util") +local download = require("luarocks.download") +local fetch = require("luarocks.fetch") + +lint.help_summary = "Check syntax of a rockspec." +lint.help_arguments = "" +lint.help = [[ +This is a utility function that checks the syntax of a rockspec. + +It returns success or failure if the text of a rockspec is +syntactically correct. +]] + +function lint.command(flags, input) + if not input then + return nil, "Argument missing. "..util.see_help("lint") + end + + local filename = input + if not input:match(".rockspec$") then + local err + filename, err = download.download("rockspec", input:lower()) + if not filename then + return nil, err + end + end + + local rs, err = fetch.load_local_rockspec(filename) + if not rs then + return nil, "Failed loading rockspec: "..err + end + + local ok = true + + -- This should have been done in the type checker, + -- but it would break compatibility of other commands. + -- Making 'lint' alone be stricter shouldn't be a problem, + -- because extra-strict checks is what lint-type commands + -- are all about. + if not rs.description.license then + util.printerr("Rockspec has no license field.") + ok = false + end + + return ok, ok or filename.." failed consistency checks." +end + +return lint diff --git a/src/luarocks/cmd/list.lua b/src/luarocks/cmd/list.lua new file mode 100644 index 00000000..45f1a26f --- /dev/null +++ b/src/luarocks/cmd/list.lua @@ -0,0 +1,95 @@ + +--- Module implementing the LuaRocks "list" command. +-- Lists currently installed rocks. +local list = {} + +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local path = require("luarocks.path") + +list.help_summary = "List currently installed rocks." +list.help_arguments = "[--porcelain] " +list.help = [[ + is a substring of a rock name to filter by. + +--outdated List only rocks for which there is a + higher version available in the rocks server. + +--porcelain Produce machine-friendly output. +]] + +local function check_outdated(trees, query) + local results_installed = {} + for _, tree in ipairs(trees) do + search.manifest_search(results_installed, path.rocks_dir(tree), query) + end + local outdated = {} + for name, versions in util.sortedpairs(results_installed) do + versions = util.keys(versions) + table.sort(versions, deps.compare_versions) + local latest_installed = versions[1] + + local query_available = search.make_query(name:lower()) + query.exact_name = true + local results_available, err = search.search_repos(query_available) + + if results_available[name] then + local available_versions = util.keys(results_available[name]) + table.sort(available_versions, deps.compare_versions) + local latest_available = available_versions[1] + local latest_available_repo = results_available[name][latest_available][1].repo + + if deps.compare_versions(latest_available, latest_installed) then + table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo }) + end + end + end + return outdated +end + +local function list_outdated(trees, query, porcelain) + util.title("Outdated rocks:", porcelain) + local outdated = check_outdated(trees, query) + for _, item in ipairs(outdated) do + if porcelain then + util.printout(item.name, item.installed, item.available, item.repo) + else + util.printout(item.name) + util.printout(" "..item.installed.." < "..item.available.." at "..item.repo) + util.printout() + end + end + return true +end + +--- Driver function for "list" command. +-- @param filter string or nil: A substring of a rock name to filter by. +-- @param version string or nil: a version may also be passed. +-- @return boolean: True if succeeded, nil on errors. +function list.command(flags, filter, version) + local query = search.make_query(filter and filter:lower() or "", version) + query.exact_name = false + local trees = cfg.rocks_trees + if flags["tree"] then + trees = { flags["tree"] } + end + + if flags["outdated"] then + return list_outdated(trees, query, flags["porcelain"]) + end + + local results = {} + for _, tree in ipairs(trees) do + local ok, err, errcode = search.manifest_search(results, path.rocks_dir(tree), query) + if not ok and errcode ~= "open" then + util.warning(err) + end + end + util.title("Installed rocks:", flags["porcelain"]) + search.print_results(results, flags["porcelain"]) + return true +end + +return list diff --git a/src/luarocks/cmd/make.lua b/src/luarocks/cmd/make.lua new file mode 100644 index 00000000..eb38bff0 --- /dev/null +++ b/src/luarocks/cmd/make.lua @@ -0,0 +1,86 @@ + +--- Module implementing the LuaRocks "make" command. +-- Builds sources in the current directory, but unlike "build", +-- it does not fetch sources, etc., assuming everything is +-- available in the current directory. +local make = {} + +local build = require("luarocks.build") +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local fetch = require("luarocks.fetch") +local pack = require("luarocks.pack") +local remove = require("luarocks.remove") +local deps = require("luarocks.deps") + +make.help_summary = "Compile package in current directory using a rockspec." +make.help_arguments = "[--pack-binary-rock] []" +make.help = [[ +Builds sources in the current directory, but unlike "build", +it does not fetch sources, etc., assuming everything is +available in the current directory. If no argument is given, +it looks for a rockspec in the current directory and in "rockspec/" +and "rockspecs/" subdirectories, picking the rockspec with newest version +or without version name. If rockspecs for different rocks are found +or there are several rockspecs without version, you must specify which to use, +through the command-line. + +This command is useful as a tool for debugging rockspecs. +To install rocks, you'll normally want to use the "install" and +"build" commands. See the help on those for details. + +--pack-binary-rock Do not install rock. Instead, produce a .rock file + with the contents of compilation in the current + directory. + +--keep Do not remove previously installed versions of the + rock after installing a new one. This behavior can + be made permanent by setting keep_other_versions=true + in the configuration file. + +--branch= Override the `source.branch` field in the loaded + rockspec. Allows to specify a different branch to + fetch. Particularly for SCM rocks. + +]] + +--- Driver function for "make" command. +-- @param name string: A local rockspec. +-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an +-- error message otherwise. exitcode is optionally returned. +function make.command(flags, rockspec) + assert(type(rockspec) == "string" or not rockspec) + + if not rockspec then + local err + rockspec, err = util.get_default_rockspec() + if not rockspec then + return nil, err + end + end + if not rockspec:match("rockspec$") then + return nil, "Invalid argument: 'make' takes a rockspec as a parameter. "..util.see_help("make") + end + + if flags["pack-binary-rock"] then + local rspec, err, errcode = fetch.load_rockspec(rockspec) + if not rspec then + return nil, err + end + return pack.pack_binary_rock(rspec.name, rspec.version, build.build_rockspec, rockspec, false, true, deps.get_deps_mode(flags)) + else + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + ok, err = build.build_rockspec(rockspec, false, true, deps.get_deps_mode(flags)) + if not ok then return nil, err end + local name, version = ok, err + if (not flags["keep"]) and not cfg.keep_other_versions then + local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) + if not ok then util.printerr(err) end + end + return name, version + end +end + +return make diff --git a/src/luarocks/cmd/new_version.lua b/src/luarocks/cmd/new_version.lua new file mode 100644 index 00000000..b13dbb97 --- /dev/null +++ b/src/luarocks/cmd/new_version.lua @@ -0,0 +1,199 @@ + +--- Module implementing the LuaRocks "new_version" command. +-- Utility function that writes a new rockspec, updating data from a previous one. +local new_version = {} + +local util = require("luarocks.util") +local download = require("luarocks.download") +local fetch = require("luarocks.fetch") +local persist = require("luarocks.persist") +local fs = require("luarocks.fs") +local type_check = require("luarocks.type_check") + +new_version.help_summary = "Auto-write a rockspec for a new version of a rock." +new_version.help_arguments = "[--tag=] [|] [] []" +new_version.help = [[ +This is a utility function that writes a new rockspec, updating data +from a previous one. + +If a package name is given, it downloads the latest rockspec from the +default server. If a rockspec is given, it uses it instead. If no argument +is given, it looks for a rockspec same way 'luarocks make' does. + +If the version number is not given and tag is passed using --tag, +it is used as the version, with 'v' removed from beginning. +Otherwise, it only increments the revision number of the given +(or downloaded) rockspec. + +If a URL is given, it replaces the one from the old rockspec with the +given URL. If a URL is not given and a new version is given, it tries +to guess the new URL by replacing occurrences of the version number +in the URL or tag. It also tries to download the new URL to determine +the new MD5 checksum. + +If a tag is given, it replaces the one from the old rockspec. If there is +an old tag but no new one passed, it is guessed in the same way URL is. + +WARNING: it writes the new rockspec to the current directory, +overwriting the file if it already exists. +]] + +local function try_replace(tbl, field, old, new) + if not tbl[field] then + return false + end + local old_field = tbl[field] + local new_field = tbl[field]:gsub(old, new) + if new_field ~= old_field then + util.printout("Guessing new '"..field.."' field as "..new_field) + tbl[field] = new_field + return true + end + return false +end + +-- Try to download source file using URL from a rockspec. +-- If it specified MD5, update it. +-- @return (true, false) if MD5 was not specified or it stayed same, +-- (true, true) if MD5 changed, (nil, string) on error. +local function check_url_and_update_md5(out_rs) + local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-"..out_rs.package) + if not file then + util.printerr("Warning: invalid URL - "..temp_dir) + return true, false + end + + local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir) + if not inferred_dir then + return nil, found_dir + end + + if found_dir and found_dir ~= inferred_dir then + out_rs.source.dir = found_dir + end + + if file then + if out_rs.source.md5 then + util.printout("File successfully downloaded. Updating MD5 checksum...") + local new_md5, err = fs.get_md5(file) + if not new_md5 then + return nil, err + end + local old_md5 = out_rs.source.md5 + out_rs.source.md5 = new_md5 + return true, new_md5 ~= old_md5 + else + util.printout("File successfully downloaded.") + return true, false + end + end +end + +local function update_source_section(out_rs, url, tag, old_ver, new_ver) + if tag then + out_rs.source.tag = tag + end + if url then + out_rs.source.url = url + return check_url_and_update_md5(out_rs) + end + if new_ver == old_ver then + return true + end + if out_rs.source.dir then + try_replace(out_rs.source, "dir", old_ver, new_ver) + end + if out_rs.source.file then + try_replace(out_rs.source, "file", old_ver, new_ver) + end + if try_replace(out_rs.source, "url", old_ver, new_ver) then + return check_url_and_update_md5(out_rs) + end + if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then + return true + end + -- Couldn't replace anything significant, use the old URL. + local ok, md5_changed = check_url_and_update_md5(out_rs) + if not ok then + return nil, md5_changed + end + if md5_changed then + util.printerr("Warning: URL is the same, but MD5 has changed. Old rockspec is broken.") + end + return true +end + +function new_version.command(flags, input, version, url) + if not input then + local err + input, err = util.get_default_rockspec() + if not input then + return nil, err + end + end + assert(type(input) == "string") + + local filename, err + if input:match("rockspec$") then + filename, err = fetch.fetch_url(input) + if not filename then + return nil, err + end + else + filename, err = download.download("rockspec", input:lower()) + if not filename then + return nil, err + end + end + + local valid_rs, err = fetch.load_rockspec(filename) + if not valid_rs then + return nil, err + end + + local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$") + local new_ver, new_rev + + if flags.tag and not version then + version = flags.tag:gsub("^v", "") + end + + if version then + new_ver, new_rev = version:match("(.*)%-(%d+)$") + new_rev = tonumber(new_rev) + if not new_rev then + new_ver = version + new_rev = 1 + end + else + new_ver = old_ver + new_rev = tonumber(old_rev) + 1 + end + local new_rockver = new_ver:gsub("-", "") + + local out_rs, err = persist.load_into_table(filename) + local out_name = out_rs.package:lower() + out_rs.version = new_rockver.."-"..new_rev + + local ok, err = update_source_section(out_rs, url, flags.tag, old_ver, new_ver) + if not ok then return nil, err end + + if out_rs.build and out_rs.build.type == "module" then + out_rs.build.type = "builtin" + end + + local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec" + + persist.save_from_table(out_filename, out_rs, type_check.rockspec_order) + + util.printout("Wrote "..out_filename) + + local valid_out_rs, err = fetch.load_local_rockspec(out_filename) + if not valid_out_rs then + return nil, "Failed loading generated rockspec: "..err + end + + return true +end + +return new_version diff --git a/src/luarocks/cmd/pack.lua b/src/luarocks/cmd/pack.lua new file mode 100644 index 00000000..655cbf37 --- /dev/null +++ b/src/luarocks/cmd/pack.lua @@ -0,0 +1,193 @@ + +--- Module implementing the LuaRocks "pack" command. +-- Creates a rock, packing sources or binaries. +local pack = {} + +local unpack = unpack or table.unpack + +local path = require("luarocks.path") +local repos = require("luarocks.repos") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local manif = require("luarocks.manif") +local search = require("luarocks.search") + +pack.help_summary = "Create a rock, packing sources or binaries." +pack.help_arguments = "{| []}" +pack.help = [[ +Argument may be a rockspec file, for creating a source rock, +or the name of an installed package, for creating a binary rock. +In the latter case, the app version may be given as a second +argument. +]] + +--- Create a source rock. +-- Packages a rockspec and its required source files in a rock +-- file with the .src.rock extension, which can later be built and +-- installed with the "build" command. +-- @param rockspec_file string: An URL or pathname for a rockspec file. +-- @return string or (nil, string): The filename of the resulting +-- .src.rock file; or nil and an error message. +function pack.pack_source_rock(rockspec_file) + assert(type(rockspec_file) == "string") + + local rockspec, err = fetch.load_rockspec(rockspec_file) + if err then + return nil, "Error loading rockspec: "..err + end + rockspec_file = rockspec.local_filename + + local name_version = rockspec.name .. "-" .. rockspec.version + local rock_file = fs.absolute_name(name_version .. ".src.rock") + + local source_file, source_dir = fetch.fetch_sources(rockspec, false) + if not source_file then + return nil, source_dir + end + local ok, err = fs.change_dir(source_dir) + if not ok then return nil, err end + + fs.delete(rock_file) + fs.copy(rockspec_file, source_dir, cfg.perm_read) + if not fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) then + return nil, "Failed packing "..rock_file + end + fs.pop_dir() + + return rock_file +end + +local function copy_back_files(name, version, file_tree, deploy_dir, pack_dir, perms) + local ok, err = fs.make_dir(pack_dir) + if not ok then return nil, err end + for file, sub in pairs(file_tree) do + local source = dir.path(deploy_dir, file) + local target = dir.path(pack_dir, file) + if type(sub) == "table" then + local ok, err = copy_back_files(name, version, sub, source, target) + if not ok then return nil, err end + else + local versioned = path.versioned_name(source, deploy_dir, name, version) + if fs.exists(versioned) then + fs.copy(versioned, target, perms) + else + fs.copy(source, target, perms) + end + end + end + return true +end + +-- @param name string: Name of package to pack. +-- @param version string or nil: A version number may also be passed. +-- @param tree string or nil: An optional tree to pick the package from. +-- @return string or (nil, string): The filename of the resulting +-- .src.rock file; or nil and an error message. +local function do_pack_binary_rock(name, version, tree) + assert(type(name) == "string") + assert(type(version) == "string" or not version) + + local repo, repo_url + name, version, repo, repo_url = search.pick_installed_rock(name, version, tree) + if not name then + return nil, version + end + + local root = path.root_dir(repo_url) + local prefix = path.install_dir(name, version, root) + if not fs.exists(prefix) then + return nil, "'"..name.." "..version.."' does not seem to be an installed rock." + end + + local rock_manifest, err = manif.load_rock_manifest(name, version, root) + if not rock_manifest then return nil, err end + + local name_version = name .. "-" .. version + local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock") + + local temp_dir = fs.make_temp_dir("pack") + fs.copy_contents(prefix, temp_dir) + + local is_binary = false + if rock_manifest.lib then + local ok, err = copy_back_files(name, version, rock_manifest.lib, path.deploy_lib_dir(root), dir.path(temp_dir, "lib"), cfg.perm_exec) + if not ok then return nil, "Failed copying back files: " .. err end + is_binary = true + end + if rock_manifest.lua then + local ok, err = copy_back_files(name, version, rock_manifest.lua, path.deploy_lua_dir(root), dir.path(temp_dir, "lua"), cfg.perm_read) + if not ok then return nil, "Failed copying back files: " .. err end + end + + local ok, err = fs.change_dir(temp_dir) + if not ok then return nil, err end + if not is_binary and not repos.has_binaries(name, version) then + rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.") + end + fs.delete(rock_file) + if not fs.zip(rock_file, unpack(fs.list_dir())) then + return nil, "Failed packing "..rock_file + end + fs.pop_dir() + fs.delete(temp_dir) + return rock_file +end + +function pack.pack_binary_rock(name, version, cmd, ...) + + -- The --pack-binary-rock option for "luarocks build" basically performs + -- "luarocks build" on a temporary tree and then "luarocks pack". The + -- alternative would require refactoring parts of luarocks.build and + -- luarocks.pack, which would save a few file operations: the idea would be + -- to shave off the final deploy steps from the build phase and the initial + -- collect steps from the pack phase. + + local temp_dir, err = fs.make_temp_dir("luarocks-build-pack-"..dir.base_name(name)) + if not temp_dir then + return nil, "Failed creating temporary directory: "..err + end + util.schedule_function(fs.delete, temp_dir) + + path.use_tree(temp_dir) + local ok, err = cmd(...) + if not ok then + return nil, err + end + local rname, rversion = path.parse_name(name) + if not rname then + rname, rversion = name, version + end + return do_pack_binary_rock(rname, rversion, temp_dir) +end + +--- Driver function for the "pack" command. +-- @param arg string: may be a rockspec file, for creating a source rock, +-- or the name of an installed package, for creating a binary rock. +-- @param version string or nil: if the name of a package is given, a +-- version may also be passed. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +function pack.command(flags, arg, version) + assert(type(version) == "string" or not version) + if type(arg) ~= "string" then + return nil, "Argument missing. "..util.see_help("pack") + end + + local file, err + if arg:match(".*%.rockspec") then + file, err = pack.pack_source_rock(arg) + else + file, err = do_pack_binary_rock(arg:lower(), version, flags["tree"]) + end + if err then + return nil, err + else + util.printout("Packed: "..file) + return true + end +end + +return pack diff --git a/src/luarocks/cmd/path_cmd.lua b/src/luarocks/cmd/path_cmd.lua new file mode 100644 index 00000000..516a0c47 --- /dev/null +++ b/src/luarocks/cmd/path_cmd.lua @@ -0,0 +1,68 @@ + +--- @module luarocks.path_cmd +-- Driver for the `luarocks path` command. +local path_cmd = {} + +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + +path_cmd.help_summary = "Return the currently configured package path." +path_cmd.help_arguments = "" +path_cmd.help = [[ +Returns the package path currently configured for this installation +of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH. + +--bin Adds the system path to the output + +--append Appends the paths to the existing paths. Default is to prefix + the LR paths to the existing paths. + +--lr-path Exports the Lua path (not formatted as shell command) + +--lr-cpath Exports the Lua cpath (not formatted as shell command) + +--lr-bin Exports the system path (not formatted as shell command) + + +On Unix systems, you may run: + eval `luarocks path` +And on Windows: + luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat" +]] + +--- Driver function for "path" command. +-- @return boolean This function always succeeds. +function path_cmd.command(flags) + local lr_path, lr_cpath, lr_bin = cfg.package_paths(flags["tree"]) + local path_sep = cfg.export_path_separator + + if flags["lr-path"] then + util.printout(util.remove_path_dupes(lr_path, ';')) + return true + elseif flags["lr-cpath"] then + util.printout(util.remove_path_dupes(lr_cpath, ';')) + return true + elseif flags["lr-bin"] then + util.printout(util.remove_path_dupes(lr_bin, path_sep)) + return true + end + + if flags["append"] then + lr_path = package.path .. ";" .. lr_path + lr_cpath = package.cpath .. ";" .. lr_cpath + lr_bin = os.getenv("PATH") .. path_sep .. lr_bin + else + lr_path = lr_path.. ";" .. package.path + lr_cpath = lr_cpath .. ";" .. package.cpath + lr_bin = lr_bin .. path_sep .. os.getenv("PATH") + end + + util.printout(cfg.export_lua_path:format(util.remove_path_dupes(lr_path, ';'))) + util.printout(cfg.export_lua_cpath:format(util.remove_path_dupes(lr_cpath, ';'))) + if flags["bin"] then + util.printout(cfg.export_path:format(util.remove_path_dupes(lr_bin, path_sep))) + end + return true +end + +return path_cmd diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua new file mode 100644 index 00000000..50f290c8 --- /dev/null +++ b/src/luarocks/cmd/purge.lua @@ -0,0 +1,77 @@ + +--- Module implementing the LuaRocks "purge" command. +-- Remove all rocks from a given tree. +local purge = {} + +local util = require("luarocks.util") +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local repos = require("luarocks.repos") +local writer = require("luarocks.manif.writer") +local cfg = require("luarocks.core.cfg") +local remove = require("luarocks.remove") + +purge.help_summary = "Remove all installed rocks from a tree." +purge.help_arguments = "--tree= [--old-versions]" +purge.help = [[ +This command removes rocks en masse from a given tree. +By default, it removes all rocks from a tree. + +The --tree argument is mandatory: luarocks purge does not +assume a default tree. + +--old-versions Keep the highest-numbered version of each + rock and remove the other ones. By default + it only removes old versions if they are + not needed as dependencies. This can be + overridden with the flag --force. +]] + +function purge.command(flags) + local tree = flags["tree"] + + if type(tree) ~= "string" then + return nil, "The --tree argument is mandatory. "..util.see_help("purge") + end + + local results = {} + local query = search.make_query("") + query.exact_name = false + if not fs.is_dir(tree) then + return nil, "Directory not found: "..tree + end + + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + + search.manifest_search(results, path.rocks_dir(tree), query) + + local sort = function(a,b) return deps.compare_versions(b,a) end + if flags["old-versions"] then + sort = deps.compare_versions + end + + for package, versions in util.sortedpairs(results) do + for version, _ in util.sortedpairs(versions, sort) do + if flags["old-versions"] then + util.printout("Keeping "..package.." "..version.."...") + local ok, err = remove.remove_other_versions(package, version, flags["force"], flags["force-fast"]) + if not ok then + util.printerr(err) + end + break + else + util.printout("Removing "..package.." "..version.."...") + local ok, err = repos.delete_version(package, version, "none", true) + if not ok then + util.printerr(err) + end + end + end + end + return writer.make_manifest(cfg.rocks_dir, "one") +end + +return purge diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua new file mode 100644 index 00000000..e7f37604 --- /dev/null +++ b/src/luarocks/cmd/remove.lua @@ -0,0 +1,165 @@ + +--- Module implementing the LuaRocks "remove" command. +-- Uninstalls rocks. +local remove = {} + +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local fetch = require("luarocks.fetch") +local repos = require("luarocks.repos") +local path = require("luarocks.path") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local fs = require("luarocks.fs") + +remove.help_summary = "Uninstall a rock." +remove.help_arguments = "[--force|--force-fast] []" +remove.help = [[ +Argument is the name of a rock to be uninstalled. +If a version is not given, try to remove all versions at once. +Will only perform the removal if it does not break dependencies. +To override this check and force the removal, use --force. +To perform a forced removal without reporting dependency issues, +use --force-fast. + +]]..util.deps_mode_help() + +--- Obtain a list of packages that depend on the given set of packages +-- (where all packages of the set are versions of one program). +-- @param name string: the name of a program +-- @param versions array of string: the versions to be deleted. +-- @return array of string: an empty table if no packages depend on any +-- of the given list, or an array of strings in "name/version" format. +local function check_dependents(name, versions, deps_mode) + local dependents = {} + local blacklist = {} + blacklist[name] = {} + for version, _ in pairs(versions) do + blacklist[name][version] = true + end + local local_rocks = {} + local query_all = search.make_query("") + query_all.exact_name = false + search.manifest_search(local_rocks, cfg.rocks_dir, query_all) + local_rocks[name] = nil + for rock_name, rock_versions in pairs(local_rocks) do + for rock_version, _ in pairs(rock_versions) do + local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version)) + if rockspec then + local _, missing = deps.match_deps(rockspec, blacklist, deps_mode) + if missing[name] then + table.insert(dependents, { name = rock_name, version = rock_version }) + end + end + end + end + return dependents +end + +--- Delete given versions of a program. +-- @param name string: the name of a program +-- @param versions array of string: the versions to be deleted. +-- @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. +-- @return boolean or (nil, string): true on success or nil and an error message. +local function delete_versions(name, versions, deps_mode) + + for version, _ in pairs(versions) do + util.printout("Removing "..name.." "..version.."...") + local ok, err = repos.delete_version(name, version, deps_mode) + if not ok then return nil, err end + end + + return true +end + +function remove.remove_search_results(results, name, deps_mode, force, fast) + local versions = results[name] + + local version = next(versions) + local second = next(versions, version) + + local dependents = {} + if not fast then + util.printout("Checking stability of dependencies in the absence of") + util.printout(name.." "..table.concat(util.keys(versions), ", ").."...") + util.printout() + dependents = check_dependents(name, versions, deps_mode) + end + + if #dependents > 0 then + if force or fast then + util.printerr("The following packages may be broken by this forced removal:") + for _, dependent in ipairs(dependents) do + util.printerr(dependent.name.." "..dependent.version) + end + util.printerr() + else + if not second then + util.printerr("Will not remove "..name.." "..version..".") + util.printerr("Removing it would break dependencies for: ") + else + util.printerr("Will not remove installed versions of "..name..".") + util.printerr("Removing them would break dependencies for: ") + end + for _, dependent in ipairs(dependents) do + util.printerr(dependent.name.." "..dependent.version) + end + util.printerr() + util.printerr("Use --force to force removal (warning: this may break modules).") + return nil, "Failed removing." + end + end + + local ok, err = delete_versions(name, versions, deps_mode) + if not ok then return nil, err end + + util.printout("Removal successful.") + return true +end + +function remove.remove_other_versions(name, version, force, fast) + local results = {} + search.manifest_search(results, cfg.rocks_dir, { name = name, exact_name = true, constraints = {{ op = "~=", version = version}} }) + if results[name] then + return remove.remove_search_results(results, name, cfg.deps_mode, force, fast) + end + return true +end + +--- Driver function for the "remove" command. +-- @param name string: name of a rock. If a version is given, refer to +-- a specific version; otherwise, try to remove all versions. +-- @param version string: When passing a package name, a version number +-- may also be given. +-- @return boolean or (nil, string, exitcode): True if removal was +-- successful, nil and an error message otherwise. exitcode is optionally returned. +function remove.command(flags, name, version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("remove") + end + + local deps_mode = flags["deps-mode"] or cfg.deps_mode + + local ok, err = fs.check_command_permissions(flags) + if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end + + local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$") + local filename = name + if rock_type then + name, version = path.parse_name(filename) + if not name then return nil, "Invalid "..rock_type.." filename: "..filename end + end + + local results = {} + name = name:lower() + search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version)) + if not results[name] then + return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir) + end + + return remove.remove_search_results(results, name, deps_mode, flags["force"], flags["force-fast"]) +end + +return remove diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua new file mode 100644 index 00000000..44eff694 --- /dev/null +++ b/src/luarocks/cmd/search.lua @@ -0,0 +1,482 @@ + +--- Module implementing the LuaRocks "search" command. +-- Queries LuaRocks servers. +local search = {} + + +local dir = require("luarocks.dir") +local path = require("luarocks.path") +local manif = require("luarocks.manif") +local deps = require("luarocks.deps") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") + +search.help_summary = "Query the LuaRocks servers." +search.help_arguments = "[--source] [--binary] { [] | --all }" +search.help = [[ +--source Return only rockspecs and source rocks, + to be used with the "build" command. +--binary Return only pure Lua and binary rocks (rocks that can be used + with the "install" command without requiring a C toolchain). +--all List all contents of the server that are suitable to + this platform, do not filter by name. +]] + +--- Convert the arch field of a query table to table format. +-- @param query table: A query table. +local function query_arch_as_table(query) + local format = type(query.arch) + if format == "table" then + return + elseif format == "nil" then + local accept = {} + accept["src"] = true + accept["all"] = true + accept["rockspec"] = true + accept["installed"] = true + accept[cfg.arch] = true + query.arch = accept + elseif format == "string" then + local accept = {} + for a in query.arch:gmatch("[%w_-]+") do + accept[a] = true + end + query.arch = accept + end +end + +--- Store a search result (a rock or rockspec) in the results table. +-- @param results table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- @param name string: Package name. +-- @param version string: Package version. +-- @param arch string: Architecture of rock ("all", "src" or platform +-- identifier), "rockspec" or "installed" +-- @param repo string: Pathname of a local repository of URL of +-- rocks server. +local function store_result(results, name, version, arch, repo) + assert(type(results) == "table") + assert(type(name) == "string") + assert(type(version) == "string") + assert(type(arch) == "string") + assert(type(repo) == "string") + + if not results[name] then results[name] = {} end + if not results[name][version] then results[name][version] = {} end + table.insert(results[name][version], { + arch = arch, + repo = repo + }) +end + +--- Test the name field of a query. +-- If query has a boolean field exact_name set to false, +-- then substring match is performed; otherwise, exact string +-- comparison is done. +-- @param query table: A query in dependency table format. +-- @param name string: A package name. +-- @return boolean: True if names match, false otherwise. +local function match_name(query, name) + assert(type(query) == "table") + assert(type(name) == "string") + if query.exact_name == false then + return name:find(query.name, 0, true) and true or false + else + return name == query.name + end +end + +--- Store a match in a results table if version matches query. +-- Name, version, arch and repository path are stored in a given +-- table, optionally checking if version and arch (if given) match +-- a query. +-- @param results table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- @param repo string: URL or pathname of the repository. +-- @param name string: The name of the package being tested. +-- @param version string: The version of the package being tested. +-- @param arch string: The arch of the package being tested. +-- @param query table: A table describing the query in dependency +-- format (for example, {name = "filesystem", exact_name = false, +-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). +-- If the arch field is omitted, the local architecture (cfg.arch) +-- is used. The special value "any" is also recognized, returning all +-- matches regardless of architecture. +local function store_if_match(results, repo, name, version, arch, query) + if match_name(query, name) then + if query.arch[arch] or query.arch["any"] then + if deps.match_constraints(deps.parse_version(version), query.constraints) then + store_result(results, name, version, arch, repo) + end + end + end +end + +--- Perform search on a local repository. +-- @param repo string: The pathname of the local repository. +-- @param query table: A table describing the query in dependency +-- format (for example, {name = "filesystem", exact_name = false, +-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). +-- If the arch field is omitted, the local architecture (cfg.arch) +-- is used. The special value "any" is also recognized, returning all +-- matches regardless of architecture. +-- @param results table or nil: If given, this table will store the +-- results; if not given, a new table will be created. +-- @return table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- If a table was given in the "results" parameter, that is the result value. +function search.disk_search(repo, query, results) + assert(type(repo) == "string") + assert(type(query) == "table") + assert(type(results) == "table" or not results) + + local fs = require("luarocks.fs") + + if not results then + results = {} + end + query_arch_as_table(query) + + for name in fs.dir(repo) do + local pathname = dir.path(repo, name) + local rname, rversion, rarch = path.parse_name(name) + + if rname and (pathname:match(".rockspec$") or pathname:match(".rock$")) then + store_if_match(results, repo, rname, rversion, rarch, query) + elseif fs.is_dir(pathname) then + for version in fs.dir(pathname) do + if version:match("-%d+$") then + store_if_match(results, repo, name, version, "installed", query) + end + end + end + end + return results +end + +--- Perform search on a rocks server or tree. +-- @param results table: The results table, where keys are package names and +-- values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +-- @param repo string: The URL of a rocks server or +-- the pathname of a rocks tree (as returned by path.rocks_dir()). +-- @param query table: A table describing the query in dependency +-- format (for example, {name = "filesystem", exact_name = false, +-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). +-- If the arch field is omitted, the local architecture (cfg.arch) +-- is used. The special value "any" is also recognized, returning all +-- matches regardless of architecture. +-- @param lua_version string: Lua version in "5.x" format, defaults to installed version. +-- @return true or, in case of errors, nil, an error message and an optional error code. +function search.manifest_search(results, repo, query, lua_version) + assert(type(results) == "table") + assert(type(repo) == "string") + assert(type(query) == "table") + + query_arch_as_table(query) + local manifest, err, errcode = manif.load_manifest(repo, lua_version) + if not manifest then + return nil, err, errcode + end + for name, versions in pairs(manifest.repository) do + for version, items in pairs(versions) do + for _, item in ipairs(items) do + store_if_match(results, repo, name, version, item.arch, query) + end + end + end + return true +end + +--- Search on all configured rocks servers. +-- @param query table: A dependency query. +-- @param lua_version string: Lua version in "5.x" format, defaults to installed version. +-- @return table: A table where keys are package names +-- and values are tables matching version strings to arrays of +-- tables with fields "arch" and "repo". +function search.search_repos(query, lua_version) + assert(type(query) == "table") + + local results = {} + for _, repo in ipairs(cfg.rocks_servers) do + if not cfg.disabled_servers[repo] then + if type(repo) == "string" then + repo = { repo } + end + for _, mirror in ipairs(repo) do + local protocol, pathname = dir.split_url(mirror) + if protocol == "file" then + mirror = pathname + end + local ok, err, errcode = search.manifest_search(results, mirror, query, lua_version) + if errcode == "network" then + cfg.disabled_servers[repo] = true + end + if ok then + break + else + util.warning("Failed searching manifest: "..err) + end + end + end + end + -- search through rocks in cfg.rocks_provided + local provided_repo = "provided by VM or rocks_provided" + for name, versions in pairs(cfg.rocks_provided) do + store_if_match(results, provided_repo, name, versions, "installed", query) + end + return results +end + +--- Prepare a query in dependency table format. +-- @param name string: The query name. +-- @param version string or nil: +-- @return table: A query in table format +function search.make_query(name, version) + assert(type(name) == "string") + assert(type(version) == "string" or not version) + + local query = { + name = name, + constraints = {} + } + if version then + table.insert(query.constraints, { op = "==", version = deps.parse_version(version)}) + end + return query +end + +--- Get the URL for the latest in a set of versions. +-- @param name string: The package name to be used in the URL. +-- @param versions table: An array of version informations, as stored +-- in search results tables. +-- @return string or nil: the URL for the latest version if one could +-- be picked, or nil. +local function pick_latest_version(name, versions) + assert(type(name) == "string") + assert(type(versions) == "table") + + local vtables = {} + for v, _ in pairs(versions) do + table.insert(vtables, deps.parse_version(v)) + end + table.sort(vtables) + local version = vtables[#vtables].string + local items = versions[version] + if items then + local pick = 1 + for i, item in ipairs(items) do + if (item.arch == 'src' and items[pick].arch == 'rockspec') + or (item.arch ~= 'src' and item.arch ~= 'rockspec') then + pick = i + end + end + return path.make_url(items[pick].repo, name, version, items[pick].arch) + end + return nil +end + +-- Find out which other Lua versions provide rock versions matching a query, +-- @param query table: A dependency query matching a single rock. +-- @return table: array of Lua versions supported, in "5.x" format. +local function supported_lua_versions(query) + local results = {} + + for lua_version in util.lua_versions() do + if lua_version ~= cfg.lua_version then + if search.search_repos(query, lua_version)[query.name] then + table.insert(results, lua_version) + end + end + end + + return results +end + +--- Attempt to get a single URL for a given search for a rock. +-- @param query table: A dependency query matching a single rock. +-- @return string or (nil, string): URL for latest matching version +-- of the rock if it was found, or nil followed by an error message. +function search.find_suitable_rock(query) + assert(type(query) == "table") + + local results = search.search_repos(query) + local first_rock = next(results) + if not first_rock then + if cfg.rocks_provided[query.name] == nil then + -- Check if constraints are satisfiable with other Lua versions. + local lua_versions = supported_lua_versions(query) + + if #lua_versions ~= 0 then + -- Build a nice message in "only Lua 5.x and 5.y but not 5.z." format + for i, lua_version in ipairs(lua_versions) do + lua_versions[i] = "Lua "..lua_version + end + + local versions_message = "only "..table.concat(lua_versions, " and ").. + " but not Lua "..cfg.lua_version.."." + + if #query.constraints == 0 then + return nil, query.name.." supports "..versions_message + elseif #query.constraints == 1 and query.constraints[1].op == "==" then + return nil, query.name.." "..query.constraints[1].version.string.." supports "..versions_message + else + return nil, "Matching "..query.name.." versions support "..versions_message + end + end + end + + return nil, "No results matching query were found." + elseif next(results, first_rock) then + -- Shouldn't happen as query must match only one package. + return nil, "Several rocks matched query." + elseif cfg.rocks_provided[query.name] ~= nil then + -- Do not install versions listed in cfg.rocks_provided. + return nil, "Rock "..query.name.." "..cfg.rocks_provided[query.name].. + " was found but it is provided by VM or 'rocks_provided' in the config file." + else + return pick_latest_version(query.name, results[first_rock]) + end +end + +--- Print a list of rocks/rockspecs on standard output. +-- @param results table: A table where keys are package names and versions +-- are tables matching version strings to an array of rocks servers. +-- @param porcelain boolean or nil: A flag to force machine-friendly output. +function search.print_results(results, porcelain) + assert(type(results) == "table") + assert(type(porcelain) == "boolean" or not porcelain) + + for package, versions in util.sortedpairs(results) do + if not porcelain then + util.printout(package) + end + for version, repos in util.sortedpairs(versions, deps.compare_versions) do + for _, repo in ipairs(repos) do + repo.repo = dir.normalize(repo.repo) + if porcelain then + util.printout(package, version, repo.arch, repo.repo) + else + util.printout(" "..version.." ("..repo.arch..") - "..repo.repo) + end + end + end + if not porcelain then + util.printout() + end + end +end + +--- Splits a list of search results into two lists, one for "source" results +-- to be used with the "build" command, and one for "binary" results to be +-- used with the "install" command. +-- @param results table: A search results table. +-- @return (table, table): Two tables, one for source and one for binary +-- results. +local function split_source_and_binary_results(results) + local sources, binaries = {}, {} + for name, versions in pairs(results) do + for version, repositories in pairs(versions) do + for _, repo in ipairs(repositories) do + local where = sources + if repo.arch == "all" or repo.arch == cfg.arch then + where = binaries + end + store_result(where, name, version, repo.arch, repo.repo) + end + end + end + return sources, binaries +end + +--- Given a name and optionally a version, try to find in the rocks +-- servers a single .src.rock or .rockspec file that satisfies +-- the request, and run the given function on it; or display to the +-- user possibilities if it couldn't narrow down a single match. +-- @param action function: A function that takes a .src.rock or +-- .rockspec URL as a parameter. +-- @param name string: A rock name +-- @param version string or nil: A version number may also be given. +-- @return The result of the action function, or nil and an error message. +function search.act_on_src_or_rockspec(action, name, version, ...) + assert(type(action) == "function") + assert(type(name) == "string") + assert(type(version) == "string" or not version) + + local query = search.make_query(name, version) + query.arch = "src|rockspec" + local url, err = search.find_suitable_rock(query) + if not url then + return nil, "Could not find a result named "..name..(version and " "..version or "")..": "..err + end + return action(url, ...) +end + +function search.pick_installed_rock(name, version, given_tree) + local results = {} + local query = search.make_query(name, version) + query.exact_name = true + local tree_map = {} + local trees = cfg.rocks_trees + if given_tree then + trees = { given_tree } + end + for _, tree in ipairs(trees) do + local rocks_dir = path.rocks_dir(tree) + tree_map[rocks_dir] = tree + search.manifest_search(results, rocks_dir, query) + end + + if not next(results) then -- + return nil,"cannot find package "..name.." "..(version or "").."\nUse 'list' to find installed rocks." + end + + version = nil + local repo_url + local package, versions = util.sortedpairs(results)() + --question: what do we do about multiple versions? This should + --give us the latest version on the last repo (which is usually the global one) + for vs, repositories in util.sortedpairs(versions, deps.compare_versions) do + if not version then version = vs end + for _, rp in ipairs(repositories) do repo_url = rp.repo end + end + + local repo = tree_map[repo_url] + return name, version, repo, repo_url +end + +--- Driver function for "search" command. +-- @param name string: A substring of a rock name to search. +-- @param version string or nil: a version may also be passed. +-- @return boolean or (nil, string): True if build was successful; nil and an +-- error message otherwise. +function search.command(flags, name, version) + if flags["all"] then + name, version = "", nil + end + + if type(name) ~= "string" and not flags["all"] then + return nil, "Enter name and version or use --all. "..util.see_help("search") + end + + local query = search.make_query(name:lower(), version) + query.exact_name = false + local results, err = search.search_repos(query) + local porcelain = flags["porcelain"] + util.title("Search results:", porcelain, "=") + local sources, binaries = split_source_and_binary_results(results) + if next(sources) and not flags["binary"] then + util.title("Rockspecs and source rocks:", porcelain) + search.print_results(sources, porcelain) + end + if next(binaries) and not flags["source"] then + util.title("Binary and pure-Lua rocks:", porcelain) + search.print_results(binaries, porcelain) + end + return true +end + +return search diff --git a/src/luarocks/cmd/show.lua b/src/luarocks/cmd/show.lua new file mode 100644 index 00000000..1ff81e08 --- /dev/null +++ b/src/luarocks/cmd/show.lua @@ -0,0 +1,158 @@ +--- Module implementing the LuaRocks "show" command. +-- Shows information about an installed rock. +local show = {} + +local search = require("luarocks.search") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local path = require("luarocks.path") +local deps = require("luarocks.deps") +local fetch = require("luarocks.fetch") +local manif = require("luarocks.manif") + +show.help_summary = "Show information about an installed rock." + +show.help = [[ + is an existing package name. +Without any flags, show all module information. +With these flags, return only the desired information: + +--home home page of project +--modules all modules provided by this package as used by require() +--deps packages this package depends on +--rockspec the full path of the rockspec file +--mversion the package version +--rock-tree local tree where rock is installed +--rock-dir data directory of the installed rock +]] + +local function keys_as_string(t, sep) + local keys = util.keys(t) + table.sort(keys) + return table.concat(keys, sep or " ") +end + +local function word_wrap(line) + local width = tonumber(os.getenv("COLUMNS")) or 80 + if width > 80 then width = 80 end + if #line > width then + local brk = width + while brk > 0 and line:sub(brk, brk) ~= " " do + brk = brk - 1 + end + if brk > 0 then + return line:sub(1, brk-1) .. "\n" .. word_wrap(line:sub(brk+1)) + end + end + return line +end + +local function format_text(text) + text = text:gsub("^%s*",""):gsub("%s$", ""):gsub("\n[ \t]+","\n"):gsub("([^\n])\n([^\n])","%1 %2") + local paragraphs = util.split_string(text, "\n\n") + for n, line in ipairs(paragraphs) do + paragraphs[n] = word_wrap(line) + end + return (table.concat(paragraphs, "\n\n"):gsub("%s$", "")) +end + +local function installed_rock_label(name, tree) + local installed, version + if cfg.rocks_provided[name] then + installed, version = true, cfg.rocks_provided[name] + else + installed, version = search.pick_installed_rock(name, nil, tree) + end + return installed and "(using "..version..")" or "(missing)" +end + +--- Driver function for "show" command. +-- @param name or nil: an existing package name. +-- @param version string or nil: a version may also be passed. +-- @return boolean: True if succeeded, nil on errors. +function show.command(flags, name, version) + if not name then + return nil, "Argument missing. "..util.see_help("show") + end + + local repo, repo_url + name, version, repo, repo_url = search.pick_installed_rock(name:lower(), version, flags["tree"]) + if not name then + return nil, version + end + + local directory = path.install_dir(name,version,repo) + local rockspec_file = path.rockspec_file(name, version, repo) + local rockspec, err = fetch.load_local_rockspec(rockspec_file) + if not rockspec then return nil,err end + + local descript = rockspec.description or {} + local manifest, err = manif.load_manifest(repo_url) + if not manifest then return nil,err end + local minfo = manifest.repository[name][version][1] + + if flags["rock-tree"] then util.printout(path.rocks_tree_to_string(repo)) + elseif flags["rock-dir"] then util.printout(directory) + elseif flags["home"] then util.printout(descript.homepage) + elseif flags["issues"] then util.printout(descript.issues_url) + elseif flags["labels"] then util.printout(descript.labels and table.concat(descript.labels, "\n")) + elseif flags["modules"] then util.printout(keys_as_string(minfo.modules, "\n")) + elseif flags["deps"] then util.printout(keys_as_string(minfo.dependencies)) + elseif flags["rockspec"] then util.printout(rockspec_file) + elseif flags["mversion"] then util.printout(version) + else + util.printout() + util.printout(rockspec.package.." "..rockspec.version.." - "..(descript.summary or "")) + util.printout() + if descript.detailed then + util.printout(format_text(descript.detailed)) + util.printout() + end + if descript.license then + util.printout("License: ", descript.license) + end + if descript.homepage then + util.printout("Homepage: ", descript.homepage) + end + if descript.issues_url then + util.printout("Issues: ", descript.issues_url) + end + if descript.labels then + util.printout("Labels: ", table.concat(descript.labels, ", ")) + end + util.printout("Installed in: ", path.rocks_tree_to_string(repo)) + if next(minfo.modules) then + util.printout() + util.printout("Modules:") + for mod, filename in util.sortedpairs(minfo.modules) do + util.printout("\t"..mod.." ("..path.which(mod, filename, name, version, repo, manifest)..")") + end + end + local direct_deps = {} + if #rockspec.dependencies > 0 then + util.printout() + util.printout("Depends on:") + for _, dep in ipairs(rockspec.dependencies) do + direct_deps[dep.name] = true + util.printout("\t"..deps.show_dep(dep).." "..installed_rock_label(dep.name, flags["tree"])) + end + end + local has_indirect_deps + for dep_name in util.sortedpairs(minfo.dependencies) do + if not direct_deps[dep_name] then + if not has_indirect_deps then + util.printout() + util.printout("Indirectly pulling:") + has_indirect_deps = true + end + + util.printout("\t"..dep_name.." "..installed_rock_label(dep_name, flags["tree"])) + end + end + util.printout() + end + return true +end + + +return show diff --git a/src/luarocks/cmd/unpack.lua b/src/luarocks/cmd/unpack.lua new file mode 100644 index 00000000..c50701b0 --- /dev/null +++ b/src/luarocks/cmd/unpack.lua @@ -0,0 +1,164 @@ + +--- Module implementing the LuaRocks "unpack" command. +-- Unpack the contents of a rock. +local unpack = {} + +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local build = require("luarocks.build") +local dir = require("luarocks.dir") +local cfg = require("luarocks.core.cfg") + +unpack.help_summary = "Unpack the contents of a rock." +unpack.help_arguments = "[--force] {| []}" +unpack.help = [[ +Unpacks the contents of a rock in a newly created directory. +Argument may be a rock file, or the name of a rock in a rocks server. +In the latter case, the app version may be given as a second argument. + +--force Unpack files even if the output directory already exists. +]] + +--- Load a rockspec file to the given directory, fetches the source +-- files specified in the rockspec, and unpack them inside the directory. +-- @param rockspec_file string: The URL for a rockspec file. +-- @param dir_name string: The directory where to store and unpack files. +-- @return table or (nil, string): the loaded rockspec table or +-- nil and an error message. +local function unpack_rockspec(rockspec_file, dir_name) + assert(type(rockspec_file) == "string") + assert(type(dir_name) == "string") + + local rockspec, err = fetch.load_rockspec(rockspec_file) + if not rockspec then + return nil, "Failed loading rockspec "..rockspec_file..": "..err + end + local ok, err = fs.change_dir(dir_name) + if not ok then return nil, err end + local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".") + if not ok then + return nil, sources_dir + end + ok, err = fs.change_dir(sources_dir) + if not ok then return nil, err end + ok, err = build.apply_patches(rockspec) + fs.pop_dir() + if not ok then return nil, err end + return rockspec +end + +--- Load a .rock file to the given directory and unpack it inside it. +-- @param rock_file string: The URL for a .rock file. +-- @param dir_name string: The directory where to unpack. +-- @param kind string: the kind of rock file, as in the second-level +-- extension in the rock filename (eg. "src", "all", "linux-x86") +-- @return table or (nil, string): the loaded rockspec table or +-- nil and an error message. +local function unpack_rock(rock_file, dir_name, kind) + assert(type(rock_file) == "string") + assert(type(dir_name) == "string") + + local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name) + if not ok then + return nil, "Failed unzipping rock "..rock_file, errcode + end + ok, err = fs.change_dir(dir_name) + if not ok then return nil, err end + local rockspec_file = dir_name..".rockspec" + local rockspec, err = fetch.load_rockspec(rockspec_file) + if not rockspec then + return nil, "Failed loading rockspec "..rockspec_file..": "..err + end + if kind == "src" then + if rockspec.source.file then + local ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then + return nil, err + end + ok, err = fs.change_dir(rockspec.source.dir) + if not ok then return nil, err end + ok, err = build.apply_patches(rockspec) + fs.pop_dir() + if not ok then return nil, err end + end + end + return rockspec +end + +--- Create a directory and perform the necessary actions so that +-- the sources for the rock and its rockspec are unpacked inside it, +-- laid out properly so that the 'make' command is able to build the module. +-- @param file string: A rockspec or .rock URL. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +local function run_unpacker(file, force) + assert(type(file) == "string") + + local base_name = dir.base_name(file) + local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$") + if not extension then + dir_name, extension = base_name:match("(.*)%.(rockspec)$") + kind = "rockspec" + end + if not extension then + return nil, file.." does not seem to be a valid filename." + end + + local exists = fs.exists(dir_name) + if exists and not force then + return nil, "Directory "..dir_name.." already exists." + end + if not exists then + local ok, err = fs.make_dir(dir_name) + if not ok then return nil, err end + end + local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name)) + + local rockspec, err + if extension == "rock" then + rockspec, err = unpack_rock(file, dir_name, kind) + elseif extension == "rockspec" then + rockspec, err = unpack_rockspec(file, dir_name) + end + if not rockspec then + return nil, err + end + if kind == "src" or kind == "rockspec" then + if rockspec.source.dir ~= "." then + local ok = fs.copy(rockspec.local_filename, rockspec.source.dir, cfg.perm_read) + if not ok then + return nil, "Failed copying unpacked rockspec into unpacked source directory." + end + end + util.printout() + util.printout("Done. You may now enter directory ") + util.printout(dir.path(dir_name, rockspec.source.dir)) + util.printout("and type 'luarocks make' to build.") + end + util.remove_scheduled_function(rollback) + return true +end + +--- Driver function for the "unpack" command. +-- @param name string: may be a rock filename, for unpacking a +-- rock file or the name of a rock to be fetched and unpacked. +-- @param version string or nil: if the name of a package is given, a +-- version may also be passed. +-- @return boolean or (nil, string): true if successful or nil followed +-- by an error message. +function unpack.command(flags, name, version) + assert(type(version) == "string" or not version) + if type(name) ~= "string" then + return nil, "Argument missing. "..util.see_help("unpack") + end + + if name:match(".*%.rock") or name:match(".*%.rockspec") then + return run_unpacker(name, flags["force"]) + else + local search = require("luarocks.search") + return search.act_on_src_or_rockspec(run_unpacker, name:lower(), version) + end +end + +return unpack diff --git a/src/luarocks/cmd/upload.lua b/src/luarocks/cmd/upload.lua new file mode 100644 index 00000000..baee47ab --- /dev/null +++ b/src/luarocks/cmd/upload.lua @@ -0,0 +1,94 @@ + +local upload = {} + +local util = require("luarocks.util") +local fetch = require("luarocks.fetch") +local pack = require("luarocks.pack") +local cfg = require("luarocks.core.cfg") +local Api = require("luarocks.upload.api") + +upload.help_summary = "Upload a rockspec to the public rocks repository." +upload.help_arguments = "[--skip-pack] [--api-key=] [--force] " +upload.help = [[ + Pack a source rock file (.src.rock extension), + upload rockspec and source rock to server. +--skip-pack Do not pack and send source rock. +--api-key= Give it an API key. It will be stored for subsequent uses. +--force Replace existing rockspec if the same revision of + a module already exists. This should be used only + in case of upload mistakes: when updating a rockspec, + increment the revision number instead. +]] + +function upload.command(flags, fname) + if not fname then + return nil, "Missing rockspec. "..util.see_help("upload") + end + + local api, err = Api.new(flags) + if not api then + return nil, err + end + if cfg.verbose then + api.debug = true + end + + local rockspec, err, errcode = fetch.load_rockspec(fname) + if err then + return nil, err, errcode + end + + util.printout("Sending " .. tostring(fname) .. " ...") + local res, err = api:method("check_rockspec", { + package = rockspec.package, + version = rockspec.version + }) + if not res then return nil, err end + + if not res.module then + util.printout("Will create new module (" .. tostring(rockspec.package) .. ")") + end + if res.version and not flags["force"] then + return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload") + end + + local rock_fname + if not flags["skip-pack"] and not rockspec.version:match("^scm") then + util.printout("Packing " .. tostring(rockspec.package)) + rock_fname, err = pack.pack_source_rock(fname) + if not rock_fname then + return nil, err + end + end + + local multipart = require("luarocks.upload.multipart") + + res, err = api:method("upload", nil, { + rockspec_file = multipart.new_file(fname) + }) + if not res then return nil, err end + + if res.is_new and #res.manifests == 0 then + util.printerr("Warning: module not added to root manifest due to name taken.") + end + + local module_url = res.module_url + + if rock_fname then + if (not res.version) or (not res.version.id) then + return nil, "Invalid response from server." + end + util.printout(("Sending " .. tostring(rock_fname) .. " ...")) + res, err = api:method("upload_rock/" .. ("%d"):format(res.version.id), nil, { + rock_file = multipart.new_file(rock_fname) + }) + if not res then return nil, err end + end + + util.printout() + util.printout("Done: " .. tostring(module_url)) + util.printout() + return true +end + +return upload diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua new file mode 100644 index 00000000..be563eaa --- /dev/null +++ b/src/luarocks/cmd/write_rockspec.lua @@ -0,0 +1,376 @@ + +local write_rockspec = {} + +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local persist = require("luarocks.persist") +local type_check = require("luarocks.type_check") +local util = require("luarocks.util") +local deps = require("luarocks.deps") + +write_rockspec.help_summary = "Write a template for a rockspec file." +write_rockspec.help_arguments = "[--output= ...] [] [] [|]" +write_rockspec.help = [[ +This command writes an initial version of a rockspec file, +based on a name, a version, and a location (an URL or a local path). +If only two arguments are given, the first one is considered the name and the +second one is the location. +If only one argument is given, it must be the location. +If no arguments are given, current directory is used as location. +LuaRocks will attempt to infer name and version if not given, +using 'scm' as default version. + +Note that the generated file is a _starting point_ for writing a +rockspec, and is not guaranteed to be complete or correct. + +--output= Write the rockspec with the given filename. + If not given, a file is written in the current + directory with a filename based on given name and version. +--license="" A license string, such as "MIT/X11" or "GNU GPL v3". +--summary="" A short one-line description summary. +--detailed="" A longer description string. +--homepage= Project homepage. +--lua-version= Supported Lua versions. Accepted values are "5.1", "5.2", + "5.3", "5.1,5.2", "5.2,5.3", or "5.1,5.2,5.3". +--rockspec-format= Rockspec format version, such as "1.0" or "1.1". +--tag= Tag to use. Will attempt to extract version number from it. +--lib=[,] A comma-separated list of libraries that C files need to + link to. +]] + +local function open_file(name) + return io.open(dir.path(fs.current_dir(), name), "r") +end + +local function get_url(rockspec) + local file, temp_dir, err_code, err_file, err_temp_dir = fetch.fetch_sources(rockspec, false) + if err_code == "source.dir" then + file, temp_dir = err_file, err_temp_dir + elseif not file then + util.warning("Could not fetch sources - "..temp_dir) + return false + end + util.printout("File successfully downloaded. Making checksum and checking base dir...") + if fetch.is_basic_protocol(rockspec.source.protocol) then + rockspec.source.md5 = fs.get_md5(file) + end + local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, rockspec.source.url) + return true, found_dir or inferred_dir, temp_dir +end + +local function configure_lua_version(rockspec, luaver) + if luaver == "5.1" then + table.insert(rockspec.dependencies, "lua ~> 5.1") + elseif luaver == "5.2" then + table.insert(rockspec.dependencies, "lua ~> 5.2") + elseif luaver == "5.3" then + table.insert(rockspec.dependencies, "lua ~> 5.3") + elseif luaver == "5.1,5.2" then + table.insert(rockspec.dependencies, "lua >= 5.1, < 5.3") + elseif luaver == "5.2,5.3" then + table.insert(rockspec.dependencies, "lua >= 5.2, < 5.4") + elseif luaver == "5.1,5.2,5.3" then + table.insert(rockspec.dependencies, "lua >= 5.1, < 5.4") + else + util.warning("Please specify supported Lua version with --lua-version=. "..util.see_help("write_rockspec")) + end +end + +local function detect_description() + local fd = open_file("README.md") or open_file("README") + if not fd then return end + local data = fd:read("*a") + fd:close() + local paragraph = data:match("\n\n([^%[].-)\n\n") + if not paragraph then paragraph = data:match("\n\n(.*)") end + local summary, detailed + if paragraph then + detailed = paragraph + + if #paragraph < 80 then + summary = paragraph:gsub("\n", "") + else + summary = paragraph:gsub("\n", " "):match("([^.]*%.) ") + end + end + return summary, detailed +end + +local function detect_mit_license(data) + local strip_copyright = (data:gsub("Copyright [^\n]*\n", "")) + local sum = 0 + for i = 1, #strip_copyright do + local num = string.byte(strip_copyright:sub(i,i)) + if num > 32 and num <= 128 then + sum = sum + num + end + end + return sum == 78656 +end + +local simple_scm_protocols = { + git = true, ["git+http"] = true, ["git+https"] = true, + hg = true, ["hg+http"] = true, ["hg+https"] = true +} + +local function detect_url_from_command(program, args, directory) + local command = fs.Q(cfg.variables[program:upper()]).. " "..args + local pipe = io.popen(fs.command_at(directory, fs.quiet_stderr(command))) + if not pipe then return nil end + local url = pipe:read("*a"):match("^([^\r\n]+)") + pipe:close() + if not url then return nil end + if not util.starts_with(url, program.."://") then + url = program.."+"..url + end + + if simple_scm_protocols[dir.split_url(url)] then + return url + end +end + +local function detect_scm_url(directory) + return detect_url_from_command("git", "config --get remote.origin.url", directory) or + detect_url_from_command("hg", "paths default", directory) +end + +local function show_license(rockspec) + local fd = open_file("COPYING") or open_file("LICENSE") or open_file("MIT-LICENSE.txt") + if not fd then return nil end + local data = fd:read("*a") + fd:close() + local is_mit = detect_mit_license(data) + util.title("License for "..rockspec.package..":") + util.printout(data) + util.printout() + return is_mit +end + +local function get_cmod_name(file) + local fd = open_file(file) + if not fd then return nil end + local data = fd:read("*a") + fd:close() + return (data:match("int%s+luaopen_([a-zA-Z0-9_]+)")) +end + +local luamod_blacklist = { + test = true, + tests = true, +} + +local function fill_as_builtin(rockspec, libs) + rockspec.build.type = "builtin" + rockspec.build.modules = {} + local prefix = "" + + for _, parent in ipairs({"src", "lua"}) do + if fs.is_dir(parent) then + fs.change_dir(parent) + prefix = parent.."/" + break + end + end + + local incdirs, libdirs + if libs then + incdirs, libdirs = {}, {} + for _, lib in ipairs(libs) do + local upper = lib:upper() + incdirs[#incdirs+1] = "$("..upper.."_INCDIR)" + libdirs[#libdirs+1] = "$("..upper.."_LIBDIR)" + end + end + + for _, file in ipairs(fs.find()) do + local luamod = file:match("(.*)%.lua$") + if luamod and not luamod_blacklist[luamod] then + rockspec.build.modules[path.path_to_module(file)] = prefix..file + else + local cmod = file:match("(.*)%.c$") + if cmod then + local modname = get_cmod_name(file) or path.path_to_module(file:gsub("%.c$", ".lua")) + rockspec.build.modules[modname] = { + sources = prefix..file, + libraries = libs, + incdirs = incdirs, + libdirs = libdirs, + } + end + end + end + + for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do + if fs.is_dir(directory) then + if not rockspec.build.copy_directories then + rockspec.build.copy_directories = {} + end + table.insert(rockspec.build.copy_directories, directory) + end + end + + if prefix ~= "" then + fs.pop_dir() + end +end + +local function rockspec_cleanup(rockspec) + rockspec.source.file = nil + rockspec.source.protocol = nil + rockspec.variables = nil + rockspec.name = nil + rockspec.format_is_at_least = nil +end + +function write_rockspec.command(flags, name, version, url_or_dir) + if not name then + url_or_dir = "." + elseif not version then + url_or_dir = name + name = nil + elseif not url_or_dir then + url_or_dir = version + version = nil + end + + if flags["tag"] then + if not version then + version = flags["tag"]:gsub("^v", "") + end + end + + local protocol, pathname = dir.split_url(url_or_dir) + if protocol == "file" then + if pathname == "." then + name = name or dir.base_name(fs.current_dir()) + end + elseif fetch.is_basic_protocol(protocol) then + local filename = dir.base_name(url_or_dir) + local newname, newversion = filename:match("(.*)-([^-]+)") + if newname then + name = name or newname + version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "") + end + else + name = name or dir.base_name(url_or_dir):gsub("%.[^.]+$", "") + end + + if not name then + return nil, "Could not infer rock name. "..util.see_help("write_rockspec") + end + version = version or "scm" + + local filename = flags["output"] or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec") + + local rockspec = { + rockspec_format = flags["rockspec-format"], + package = name, + name = name:lower(), + version = version.."-1", + source = { + url = "*** please add URL for source tarball, zip or repository here ***", + tag = flags["tag"], + }, + description = { + summary = flags["summary"] or "*** please specify description summary ***", + detailed = flags["detailed"] or "*** please enter a detailed description ***", + homepage = flags["homepage"] or "*** please enter a project homepage ***", + license = flags["license"] or "*** please specify a license ***", + }, + dependencies = {}, + build = {}, + } + path.configure_paths(rockspec) + rockspec.source.protocol = protocol + rockspec.format_is_at_least = deps.format_is_at_least + + configure_lua_version(rockspec, flags["lua-version"]) + + local local_dir = url_or_dir + + if url_or_dir:match("://") then + rockspec.source.url = url_or_dir + rockspec.source.file = dir.base_name(url_or_dir) + rockspec.source.dir = "dummy" + if not fetch.is_basic_protocol(rockspec.source.protocol) then + if version ~= "scm" then + rockspec.source.tag = flags["tag"] or "v" .. version + end + end + rockspec.source.dir = nil + local ok, base_dir, temp_dir = get_url(rockspec) + if ok then + if base_dir ~= dir.base_name(url_or_dir) then + rockspec.source.dir = base_dir + end + end + if base_dir then + local_dir = dir.path(temp_dir, base_dir) + else + local_dir = nil + end + else + rockspec.source.url = detect_scm_url(local_dir) or rockspec.source.url + end + + if not local_dir then + local_dir = "." + end + + if not flags["homepage"] then + local url_protocol, url_path = dir.split_url(rockspec.source.url) + + if simple_scm_protocols[url_protocol] then + for _, domain in ipairs({"github.com", "bitbucket.org", "gitlab.com"}) do + if util.starts_with(url_path, domain) then + rockspec.description.homepage = "https://"..url_path:gsub("%.git$", "") + break + end + end + end + end + + local libs = nil + if flags["lib"] then + libs = {} + rockspec.external_dependencies = {} + for lib in flags["lib"]:gmatch("([^,]+)") do + table.insert(libs, lib) + rockspec.external_dependencies[lib:upper()] = { + library = lib + } + end + end + + local ok, err = fs.change_dir(local_dir) + if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end + + if (not flags["summary"]) or (not flags["detailed"]) then + local summary, detailed = detect_description() + rockspec.description.summary = flags["summary"] or summary + rockspec.description.detailed = flags["detailed"] or detailed + end + + local is_mit = show_license(rockspec) + + if is_mit and not flags["license"] then + rockspec.description.license = "MIT" + end + + fill_as_builtin(rockspec, libs) + + rockspec_cleanup(rockspec) + + persist.save_from_table(filename, rockspec, type_check.rockspec_order) + + util.printout() + util.printout("Wrote template at "..filename.." -- you should now edit and finish it.") + util.printout() + + return true +end + +return write_rockspec diff --git a/src/luarocks/config_cmd.lua b/src/luarocks/config_cmd.lua deleted file mode 100644 index b68f7898..00000000 --- a/src/luarocks/config_cmd.lua +++ /dev/null @@ -1,71 +0,0 @@ ---- Module implementing the LuaRocks "config" command. --- Queries information about the LuaRocks configuration. -local config_cmd = {} - -local cfg = require("luarocks.core.cfg") -local util = require("luarocks.util") -local dir = require("luarocks.dir") - -config_cmd.help_summary = "Query information about the LuaRocks configuration." -config_cmd.help_arguments = "" -config_cmd.help = [[ ---lua-incdir Path to Lua header files. - ---lua-libdir Path to Lua library files. - ---lua-ver Lua version (in major.minor format). e.g. 5.1 - ---system-config Location of the system config file. - ---user-config Location of the user config file. - ---rock-trees Rocks trees in use. First the user tree, then the system tree. -]] - -local function config_file(conf) - print(dir.normalize(conf.file)) - if conf.ok then - return true - else - return nil, "file not found" - end -end - ---- Driver function for "config" command. --- @return boolean: True if succeeded, nil on errors. -function config_cmd.command(flags) - if flags["lua-incdir"] then - print(cfg.variables.LUA_INCDIR) - return true - end - if flags["lua-libdir"] then - print(cfg.variables.LUA_LIBDIR) - return true - end - if flags["lua-ver"] then - print(cfg.lua_version) - return true - end - local conf = cfg.which_config() - if flags["system-config"] then - return config_file(conf.system) - end - if flags["user-config"] then - return config_file(conf.user) - end - if flags["rock-trees"] then - for _, tree in ipairs(cfg.rocks_trees) do - if type(tree) == "string" then - util.printout(dir.normalize(tree)) - else - local name = tree.name and "\t"..tree.name or "" - util.printout(dir.normalize(tree.root)..name) - end - end - return true - end - - return nil, "Please provide a flag for querying configuration values. "..util.see_help("config") -end - -return config_cmd diff --git a/src/luarocks/doc.lua b/src/luarocks/doc.lua deleted file mode 100644 index 5d521276..00000000 --- a/src/luarocks/doc.lua +++ /dev/null @@ -1,155 +0,0 @@ - ---- Module implementing the LuaRocks "doc" command. --- Shows documentation for an installed rock. -local doc = {} - -local util = require("luarocks.util") -local search = require("luarocks.search") -local path = require("luarocks.path") -local dir = require("luarocks.dir") -local fetch = require("luarocks.fetch") -local fs = require("luarocks.fs") -local download = require("luarocks.download") - -doc.help_summary = "Show documentation for an installed rock." - -doc.help = [[ - is an existing package name. -Without any flags, tries to load the documentation -using a series of heuristics. -With these flags, return only the desired information: - ---home Open the home page of project. ---list List documentation files only. - -For more information about a rock, see the 'show' command. -]] - -local function show_homepage(homepage, name, version) - if not homepage then - return nil, "No 'homepage' field in rockspec for "..name.." "..version - end - util.printout("Opening "..homepage.." ...") - fs.browser(homepage) - return true -end - -local function try_to_open_homepage(name, version) - local temp_dir, err = fs.make_temp_dir("doc-"..name.."-"..(version or "")) - if not temp_dir then - return nil, "Failed creating temporary directory: "..err - end - util.schedule_function(fs.delete, temp_dir) - local ok, err = fs.change_dir(temp_dir) - if not ok then return nil, err end - local filename, err = download.download("rockspec", name, version) - if not filename then return nil, err end - local rockspec, err = fetch.load_local_rockspec(filename) - if not rockspec then return nil, err end - fs.pop_dir() - local descript = rockspec.description or {} - if not descript.homepage then return nil, "No homepage defined for "..name end - return show_homepage(descript.homepage, name, version) -end - ---- Driver function for "doc" command. --- @param name or nil: an existing package name. --- @param version string or nil: a version may also be passed. --- @return boolean: True if succeeded, nil on errors. -function doc.command(flags, name, version) - if not name then - return nil, "Argument missing. "..util.see_help("doc") - end - - name = name:lower() - - local iname, iversion, repo = search.pick_installed_rock(name, version, flags["tree"]) - if not iname then - util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...") - return try_to_open_homepage(name, version) - end - name, version = iname, iversion - - local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo)) - if not rockspec then return nil,err end - local descript = rockspec.description or {} - - if flags["home"] then - return show_homepage(descript.homepage, name, version) - end - - local directory = path.install_dir(name,version,repo) - - local docdir - local directories = { "doc", "docs" } - for _, d in ipairs(directories) do - local dirname = dir.path(directory, d) - if fs.is_dir(dirname) then - docdir = dirname - break - end - end - if not docdir then - if descript.homepage and not flags["list"] then - util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...") - fs.browser(descript.homepage) - return true - end - return nil, "Documentation directory not found for "..name.." "..version - end - - docdir = dir.normalize(docdir):gsub("/+", "/") - local files = fs.find(docdir) - local htmlpatt = "%.html?$" - local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" } - local basenames = { "index", "readme", "manual" } - - local porcelain = flags["porcelain"] - if #files > 0 then - util.title("Documentation files for "..name.." "..version, porcelain) - if porcelain then - for _, file in ipairs(files) do - util.printout(docdir.."/"..file) - end - else - util.printout(docdir.."/") - for _, file in ipairs(files) do - util.printout("\t"..file) - end - end - end - - if flags["list"] then - return true - end - - for _, extension in ipairs(extensions) do - for _, basename in ipairs(basenames) do - local filename = basename..extension - local found - for _, file in ipairs(files) do - if file:lower():match(filename) and ((not found) or #file < #found) then - found = file - end - end - if found then - local pathname = dir.path(docdir, found) - util.printout() - util.printout("Opening "..pathname.." ...") - util.printout() - local ok = fs.browser(pathname) - if not ok and not pathname:match(htmlpatt) then - local fd = io.open(pathname, "r") - util.printout(fd:read("*a")) - fd:close() - end - return true - end - end - end - - return true -end - - -return doc diff --git a/src/luarocks/download.lua b/src/luarocks/download.lua deleted file mode 100644 index 557d1b65..00000000 --- a/src/luarocks/download.lua +++ /dev/null @@ -1,107 +0,0 @@ - ---- Module implementing the luarocks "download" command. --- Download a rock from the repository. -local download = {} - -local util = require("luarocks.util") -local path = require("luarocks.path") -local fetch = require("luarocks.fetch") -local search = require("luarocks.search") -local fs = require("luarocks.fs") -local dir = require("luarocks.dir") -local cfg = require("luarocks.core.cfg") - -download.help_summary = "Download a specific rock file from a rocks server." -download.help_arguments = "[--all] [--arch= | --source | --rockspec] [ []]" - -download.help = [[ ---all Download all files if there are multiple matches. ---source Download .src.rock if available. ---rockspec Download .rockspec if available. ---arch= Download rock for a specific architecture. -]] - -local function get_file(filename) - local protocol, pathname = dir.split_url(filename) - if protocol == "file" then - local ok, err = fs.copy(pathname, fs.current_dir(), cfg.perm_read) - if ok then - return pathname - else - return nil, err - end - else - return fetch.fetch_url(filename) - end -end - -function download.download(arch, name, version, all) - local query = search.make_query(name, version) - if arch then query.arch = arch end - local search_err - - if all then - if name == "" then query.exact_name = false end - local results = search.search_repos(query) - local has_result = false - local all_ok = true - local any_err = "" - for name, result in pairs(results) do - for version, items in pairs(result) do - for _, item in ipairs(items) do - -- Ignore provided rocks. - if item.arch ~= "installed" then - has_result = true - local filename = path.make_url(item.repo, name, version, item.arch) - local ok, err = get_file(filename) - if not ok then - all_ok = false - any_err = any_err .. "\n" .. err - end - end - end - end - end - - if has_result then - return all_ok, any_err - end - else - local url - url, search_err = search.find_suitable_rock(query) - if url then - return get_file(url) - end - end - return nil, "Could not find a result named "..name..(version and " "..version or "").. - (search_err and ": "..search_err or ".") -end - ---- Driver function for the "download" command. --- @param name string: a rock name. --- @param version string or nil: if the name of a package is given, a --- version may also be passed. --- @return boolean or (nil, string): true if successful or nil followed --- by an error message. -function download.command(flags, name, version) - assert(type(version) == "string" or not version) - if type(name) ~= "string" and not flags["all"] then - return nil, "Argument missing. "..util.see_help("download") - end - if not name then name, version = "", "" end - - local arch - - if flags["source"] then - arch = "src" - elseif flags["rockspec"] then - arch = "rockspec" - elseif flags["arch"] then - arch = flags["arch"] - end - - local dl, err = download.download(arch, name:lower(), version, flags["all"]) - return dl and true, err -end - -return download diff --git a/src/luarocks/help.lua b/src/luarocks/help.lua deleted file mode 100644 index d27c3a50..00000000 --- a/src/luarocks/help.lua +++ /dev/null @@ -1,117 +0,0 @@ - ---- Module implementing the LuaRocks "help" command. --- This is a generic help display module, which --- uses a global table called "commands" to find commands --- to show help for; each command should be represented by a --- table containing "help" and "help_summary" fields. -local help = {} - -local util = require("luarocks.util") -local cfg = require("luarocks.core.cfg") -local dir = require("luarocks.dir") - -local program = util.this_program("luarocks") - -help.help_summary = "Help on commands. Type '"..program.." help ' for more." - -help.help_arguments = "[]" -help.help = [[ - is the command to show help for. -]] - -local function print_banner() - util.printout("\nLuaRocks "..cfg.program_version..", a module deployment system for Lua") -end - -local function print_section(section) - util.printout("\n"..section) -end - -local function get_status(status) - if status then - return "ok" - else - return "not found" - end -end - ---- Driver function for the "help" command. --- @param command string or nil: command to show help for; if not --- given, help summaries for all commands are shown. --- @return boolean or (nil, string): true if there were no errors --- or nil and an error message if an invalid command was requested. -function help.command(flags, command) - if not command then - local conf = cfg.which_config() - print_banner() - print_section("NAME") - util.printout("\t"..program..[[ - ]]..program_description) - print_section("SYNOPSIS") - util.printout("\t"..program..[[ [--from= | --only-from=] [--to=] [VAR=VALUE]... [] ]]) - print_section("GENERAL OPTIONS") - util.printout([[ - These apply to all commands, as appropriate: - - --server= Fetch rocks/rockspecs from this server - (takes priority over config file) - --only-server= Fetch rocks/rockspecs from this server only - (overrides any entries in the config file) - --only-sources= Restrict downloads to paths matching the - given URL. - --tree= Which tree to operate on. - --local Use the tree in the user's home directory. - To enable it, see ']]..program..[[ help path'. - --verbose Display verbose output of commands executed. - --timeout= Timeout on network operations, in seconds. - 0 means no timeout (wait forever). - Default is ]]..tostring(cfg.connection_timeout)..[[.]]) - print_section("VARIABLES") - util.printout([[ - Variables from the "variables" table of the configuration file - can be overriden with VAR=VALUE assignments.]]) - print_section("COMMANDS") - for name, command in util.sortedpairs(commands) do - local cmd = require(command) - util.printout("", name) - util.printout("\t", cmd.help_summary) - end - print_section("CONFIGURATION") - util.printout("\tLua version: " .. cfg.lua_version) - util.printout("\tConfiguration files:") - util.printout("\t\tSystem: ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.ok) ..")") - if conf.user.file then - util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.ok) ..")\n") - else - util.printout("\t\tUser : disabled in this LuaRocks installation.\n") - end - util.printout("\tRocks trees in use: ") - for _, tree in ipairs(cfg.rocks_trees) do - if type(tree) == "string" then - util.printout("\t\t"..dir.normalize(tree)) - else - local name = tree.name and " (\""..tree.name.."\")" or "" - util.printout("\t\t"..dir.normalize(tree.root)..name) - end - end - else - command = command:gsub("-", "_") - local cmd = commands[command] and require(commands[command]) - if cmd then - local arguments = cmd.help_arguments or "" - print_banner() - print_section("NAME") - util.printout("\t"..program.." "..command.." - "..cmd.help_summary) - print_section("SYNOPSIS") - util.printout("\t"..program.." "..command.." "..arguments) - print_section("DESCRIPTION") - util.printout("",(cmd.help:gsub("\n","\n\t"):gsub("\n\t$",""))) - print_section("SEE ALSO") - util.printout("","'"..program.." help' for general options and configuration.\n") - else - return nil, "Unknown command: "..command - end - end - return true -end - -return help diff --git a/src/luarocks/install.lua b/src/luarocks/install.lua deleted file mode 100644 index c9b085f5..00000000 --- a/src/luarocks/install.lua +++ /dev/null @@ -1,183 +0,0 @@ ---- Module implementing the LuaRocks "install" command. --- Installs binary rocks. -local install = {} - -local path = require("luarocks.path") -local repos = require("luarocks.repos") -local fetch = require("luarocks.fetch") -local util = require("luarocks.util") -local fs = require("luarocks.fs") -local deps = require("luarocks.deps") -local writer = require("luarocks.manif.writer") -local remove = require("luarocks.remove") -local cfg = require("luarocks.core.cfg") - -install.help_summary = "Install a rock." - -install.help_arguments = "{| []}" - -install.help = [[ -Argument may be the name of a rock to be fetched from a repository -or a filename of a locally available rock. - ---keep Do not remove previously installed versions of the - rock after installing a new one. This behavior can - be made permanent by setting keep_other_versions=true - in the configuration file. - ---only-deps Installs only the dependencies of the rock. -]]..util.deps_mode_help() - - ---- Install a binary rock. --- @param rock_file string: local or remote filename of a rock. --- @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. --- @return (string, string) or (nil, string, [string]): Name and version of --- installed rock if succeeded or nil and an error message followed by an error code. -function install.install_binary_rock(rock_file, deps_mode) - assert(type(rock_file) == "string") - - local name, version, arch = path.parse_name(rock_file) - if not name then - return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." - end - - if arch ~= "all" and arch ~= cfg.arch then - return nil, "Incompatible architecture "..arch, "arch" - end - if repos.is_installed(name, version) then - repos.delete_version(name, version, deps_mode) - end - - local rollback = util.schedule_function(function() - fs.delete(path.install_dir(name, version)) - fs.remove_dir_if_empty(path.versions_dir(name)) - end) - - local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) - if not ok then return nil, err, errcode end - - local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version)) - if err then - return nil, "Failed loading rockspec for installed package: "..err, errcode - end - - if deps_mode == "none" then - util.printerr("Warning: skipping dependency checks.") - else - ok, err, errcode = deps.check_external_deps(rockspec, "install") - if err then return nil, err, errcode end - end - - -- For compatibility with .rock files built with LuaRocks 1 - if not fs.exists(path.rock_manifest_file(name, version)) then - ok, err = writer.make_rock_manifest(name, version) - if err then return nil, err end - end - - if deps_mode ~= "none" then - ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) - if err then return nil, err, errcode end - end - - ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode) - if err then return nil, err end - - util.remove_scheduled_function(rollback) - rollback = util.schedule_function(function() - repos.delete_version(name, version, deps_mode) - end) - - ok, err = repos.run_hook(rockspec, "post_install") - if err then return nil, err end - - util.announce_install(rockspec) - util.remove_scheduled_function(rollback) - return name, version -end - ---- Installs the dependencies of a binary rock. --- @param rock_file string: local or remote filename of a rock. --- @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. --- @return (string, string) or (nil, string, [string]): Name and version of --- the rock whose dependencies were installed if succeeded or nil and an error message --- followed by an error code. -function install.install_binary_rock_deps(rock_file, deps_mode) - assert(type(rock_file) == "string") - - local name, version, arch = path.parse_name(rock_file) - if not name then - return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." - end - - if arch ~= "all" and arch ~= cfg.arch then - return nil, "Incompatible architecture "..arch, "arch" - end - - local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) - if not ok then return nil, err, errcode end - - local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version)) - if err then - return nil, "Failed loading rockspec for installed package: "..err, errcode - end - - ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode) - if err then return nil, err, errcode end - - util.printout() - util.printout("Successfully installed dependencies for " ..name.." "..version) - - return name, version -end - ---- Driver function for the "install" command. --- @param name string: name of a binary rock. If an URL or pathname --- to a binary rock is given, fetches and installs it. If a rockspec or a --- source rock is given, forwards the request to the "build" command. --- If a package name is given, forwards the request to "search" and, --- if returned a result, installs the matching rock. --- @param version string: When passing a package name, a version number --- may also be given. --- @return boolean or (nil, string, exitcode): True if installation was --- successful, nil and an error message otherwise. exitcode is optionally returned. -function install.command(flags, name, version) - if type(name) ~= "string" then - return nil, "Argument missing. "..util.see_help("install") - end - - local ok, err = fs.check_command_permissions(flags) - if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - - if name:match("%.rockspec$") or name:match("%.src%.rock$") then - local build = require("luarocks.build") - return build.command(flags, name) - elseif name:match("%.rock$") then - if flags["only-deps"] then - ok, err = install.install_binary_rock_deps(name, deps.get_deps_mode(flags)) - else - ok, err = install.install_binary_rock(name, deps.get_deps_mode(flags)) - end - if not ok then return nil, err end - name, version = ok, err - if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then - local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) - if not ok then util.printerr(err) end - end - return name, version - else - local search = require("luarocks.search") - local url, err = search.find_suitable_rock(search.make_query(name:lower(), version)) - if not url then - return nil, err - end - util.printout("Installing "..url) - return install.command(flags, url) - end -end - -return install diff --git a/src/luarocks/lint.lua b/src/luarocks/lint.lua deleted file mode 100644 index c9ea45ea..00000000 --- a/src/luarocks/lint.lua +++ /dev/null @@ -1,53 +0,0 @@ - ---- Module implementing the LuaRocks "lint" command. --- Utility function that checks syntax of the rockspec. -local lint = {} - -local util = require("luarocks.util") -local download = require("luarocks.download") -local fetch = require("luarocks.fetch") - -lint.help_summary = "Check syntax of a rockspec." -lint.help_arguments = "" -lint.help = [[ -This is a utility function that checks the syntax of a rockspec. - -It returns success or failure if the text of a rockspec is -syntactically correct. -]] - -function lint.command(flags, input) - if not input then - return nil, "Argument missing. "..util.see_help("lint") - end - - local filename = input - if not input:match(".rockspec$") then - local err - filename, err = download.download("rockspec", input:lower()) - if not filename then - return nil, err - end - end - - local rs, err = fetch.load_local_rockspec(filename) - if not rs then - return nil, "Failed loading rockspec: "..err - end - - local ok = true - - -- This should have been done in the type checker, - -- but it would break compatibility of other commands. - -- Making 'lint' alone be stricter shouldn't be a problem, - -- because extra-strict checks is what lint-type commands - -- are all about. - if not rs.description.license then - util.printerr("Rockspec has no license field.") - ok = false - end - - return ok, ok or filename.." failed consistency checks." -end - -return lint diff --git a/src/luarocks/list.lua b/src/luarocks/list.lua deleted file mode 100644 index 45f1a26f..00000000 --- a/src/luarocks/list.lua +++ /dev/null @@ -1,95 +0,0 @@ - ---- Module implementing the LuaRocks "list" command. --- Lists currently installed rocks. -local list = {} - -local search = require("luarocks.search") -local deps = require("luarocks.deps") -local cfg = require("luarocks.core.cfg") -local util = require("luarocks.util") -local path = require("luarocks.path") - -list.help_summary = "List currently installed rocks." -list.help_arguments = "[--porcelain] " -list.help = [[ - is a substring of a rock name to filter by. - ---outdated List only rocks for which there is a - higher version available in the rocks server. - ---porcelain Produce machine-friendly output. -]] - -local function check_outdated(trees, query) - local results_installed = {} - for _, tree in ipairs(trees) do - search.manifest_search(results_installed, path.rocks_dir(tree), query) - end - local outdated = {} - for name, versions in util.sortedpairs(results_installed) do - versions = util.keys(versions) - table.sort(versions, deps.compare_versions) - local latest_installed = versions[1] - - local query_available = search.make_query(name:lower()) - query.exact_name = true - local results_available, err = search.search_repos(query_available) - - if results_available[name] then - local available_versions = util.keys(results_available[name]) - table.sort(available_versions, deps.compare_versions) - local latest_available = available_versions[1] - local latest_available_repo = results_available[name][latest_available][1].repo - - if deps.compare_versions(latest_available, latest_installed) then - table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo }) - end - end - end - return outdated -end - -local function list_outdated(trees, query, porcelain) - util.title("Outdated rocks:", porcelain) - local outdated = check_outdated(trees, query) - for _, item in ipairs(outdated) do - if porcelain then - util.printout(item.name, item.installed, item.available, item.repo) - else - util.printout(item.name) - util.printout(" "..item.installed.." < "..item.available.." at "..item.repo) - util.printout() - end - end - return true -end - ---- Driver function for "list" command. --- @param filter string or nil: A substring of a rock name to filter by. --- @param version string or nil: a version may also be passed. --- @return boolean: True if succeeded, nil on errors. -function list.command(flags, filter, version) - local query = search.make_query(filter and filter:lower() or "", version) - query.exact_name = false - local trees = cfg.rocks_trees - if flags["tree"] then - trees = { flags["tree"] } - end - - if flags["outdated"] then - return list_outdated(trees, query, flags["porcelain"]) - end - - local results = {} - for _, tree in ipairs(trees) do - local ok, err, errcode = search.manifest_search(results, path.rocks_dir(tree), query) - if not ok and errcode ~= "open" then - util.warning(err) - end - end - util.title("Installed rocks:", flags["porcelain"]) - search.print_results(results, flags["porcelain"]) - return true -end - -return list diff --git a/src/luarocks/make.lua b/src/luarocks/make.lua deleted file mode 100644 index eb38bff0..00000000 --- a/src/luarocks/make.lua +++ /dev/null @@ -1,86 +0,0 @@ - ---- Module implementing the LuaRocks "make" command. --- Builds sources in the current directory, but unlike "build", --- it does not fetch sources, etc., assuming everything is --- available in the current directory. -local make = {} - -local build = require("luarocks.build") -local fs = require("luarocks.fs") -local util = require("luarocks.util") -local cfg = require("luarocks.core.cfg") -local fetch = require("luarocks.fetch") -local pack = require("luarocks.pack") -local remove = require("luarocks.remove") -local deps = require("luarocks.deps") - -make.help_summary = "Compile package in current directory using a rockspec." -make.help_arguments = "[--pack-binary-rock] []" -make.help = [[ -Builds sources in the current directory, but unlike "build", -it does not fetch sources, etc., assuming everything is -available in the current directory. If no argument is given, -it looks for a rockspec in the current directory and in "rockspec/" -and "rockspecs/" subdirectories, picking the rockspec with newest version -or without version name. If rockspecs for different rocks are found -or there are several rockspecs without version, you must specify which to use, -through the command-line. - -This command is useful as a tool for debugging rockspecs. -To install rocks, you'll normally want to use the "install" and -"build" commands. See the help on those for details. - ---pack-binary-rock Do not install rock. Instead, produce a .rock file - with the contents of compilation in the current - directory. - ---keep Do not remove previously installed versions of the - rock after installing a new one. This behavior can - be made permanent by setting keep_other_versions=true - in the configuration file. - ---branch= Override the `source.branch` field in the loaded - rockspec. Allows to specify a different branch to - fetch. Particularly for SCM rocks. - -]] - ---- Driver function for "make" command. --- @param name string: A local rockspec. --- @return boolean or (nil, string, exitcode): True if build was successful; nil and an --- error message otherwise. exitcode is optionally returned. -function make.command(flags, rockspec) - assert(type(rockspec) == "string" or not rockspec) - - if not rockspec then - local err - rockspec, err = util.get_default_rockspec() - if not rockspec then - return nil, err - end - end - if not rockspec:match("rockspec$") then - return nil, "Invalid argument: 'make' takes a rockspec as a parameter. "..util.see_help("make") - end - - if flags["pack-binary-rock"] then - local rspec, err, errcode = fetch.load_rockspec(rockspec) - if not rspec then - return nil, err - end - return pack.pack_binary_rock(rspec.name, rspec.version, build.build_rockspec, rockspec, false, true, deps.get_deps_mode(flags)) - else - local ok, err = fs.check_command_permissions(flags) - if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - ok, err = build.build_rockspec(rockspec, false, true, deps.get_deps_mode(flags)) - if not ok then return nil, err end - local name, version = ok, err - if (not flags["keep"]) and not cfg.keep_other_versions then - local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"]) - if not ok then util.printerr(err) end - end - return name, version - end -end - -return make diff --git a/src/luarocks/new_version.lua b/src/luarocks/new_version.lua deleted file mode 100644 index b13dbb97..00000000 --- a/src/luarocks/new_version.lua +++ /dev/null @@ -1,199 +0,0 @@ - ---- Module implementing the LuaRocks "new_version" command. --- Utility function that writes a new rockspec, updating data from a previous one. -local new_version = {} - -local util = require("luarocks.util") -local download = require("luarocks.download") -local fetch = require("luarocks.fetch") -local persist = require("luarocks.persist") -local fs = require("luarocks.fs") -local type_check = require("luarocks.type_check") - -new_version.help_summary = "Auto-write a rockspec for a new version of a rock." -new_version.help_arguments = "[--tag=] [|] [] []" -new_version.help = [[ -This is a utility function that writes a new rockspec, updating data -from a previous one. - -If a package name is given, it downloads the latest rockspec from the -default server. If a rockspec is given, it uses it instead. If no argument -is given, it looks for a rockspec same way 'luarocks make' does. - -If the version number is not given and tag is passed using --tag, -it is used as the version, with 'v' removed from beginning. -Otherwise, it only increments the revision number of the given -(or downloaded) rockspec. - -If a URL is given, it replaces the one from the old rockspec with the -given URL. If a URL is not given and a new version is given, it tries -to guess the new URL by replacing occurrences of the version number -in the URL or tag. It also tries to download the new URL to determine -the new MD5 checksum. - -If a tag is given, it replaces the one from the old rockspec. If there is -an old tag but no new one passed, it is guessed in the same way URL is. - -WARNING: it writes the new rockspec to the current directory, -overwriting the file if it already exists. -]] - -local function try_replace(tbl, field, old, new) - if not tbl[field] then - return false - end - local old_field = tbl[field] - local new_field = tbl[field]:gsub(old, new) - if new_field ~= old_field then - util.printout("Guessing new '"..field.."' field as "..new_field) - tbl[field] = new_field - return true - end - return false -end - --- Try to download source file using URL from a rockspec. --- If it specified MD5, update it. --- @return (true, false) if MD5 was not specified or it stayed same, --- (true, true) if MD5 changed, (nil, string) on error. -local function check_url_and_update_md5(out_rs) - local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-"..out_rs.package) - if not file then - util.printerr("Warning: invalid URL - "..temp_dir) - return true, false - end - - local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir) - if not inferred_dir then - return nil, found_dir - end - - if found_dir and found_dir ~= inferred_dir then - out_rs.source.dir = found_dir - end - - if file then - if out_rs.source.md5 then - util.printout("File successfully downloaded. Updating MD5 checksum...") - local new_md5, err = fs.get_md5(file) - if not new_md5 then - return nil, err - end - local old_md5 = out_rs.source.md5 - out_rs.source.md5 = new_md5 - return true, new_md5 ~= old_md5 - else - util.printout("File successfully downloaded.") - return true, false - end - end -end - -local function update_source_section(out_rs, url, tag, old_ver, new_ver) - if tag then - out_rs.source.tag = tag - end - if url then - out_rs.source.url = url - return check_url_and_update_md5(out_rs) - end - if new_ver == old_ver then - return true - end - if out_rs.source.dir then - try_replace(out_rs.source, "dir", old_ver, new_ver) - end - if out_rs.source.file then - try_replace(out_rs.source, "file", old_ver, new_ver) - end - if try_replace(out_rs.source, "url", old_ver, new_ver) then - return check_url_and_update_md5(out_rs) - end - if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then - return true - end - -- Couldn't replace anything significant, use the old URL. - local ok, md5_changed = check_url_and_update_md5(out_rs) - if not ok then - return nil, md5_changed - end - if md5_changed then - util.printerr("Warning: URL is the same, but MD5 has changed. Old rockspec is broken.") - end - return true -end - -function new_version.command(flags, input, version, url) - if not input then - local err - input, err = util.get_default_rockspec() - if not input then - return nil, err - end - end - assert(type(input) == "string") - - local filename, err - if input:match("rockspec$") then - filename, err = fetch.fetch_url(input) - if not filename then - return nil, err - end - else - filename, err = download.download("rockspec", input:lower()) - if not filename then - return nil, err - end - end - - local valid_rs, err = fetch.load_rockspec(filename) - if not valid_rs then - return nil, err - end - - local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$") - local new_ver, new_rev - - if flags.tag and not version then - version = flags.tag:gsub("^v", "") - end - - if version then - new_ver, new_rev = version:match("(.*)%-(%d+)$") - new_rev = tonumber(new_rev) - if not new_rev then - new_ver = version - new_rev = 1 - end - else - new_ver = old_ver - new_rev = tonumber(old_rev) + 1 - end - local new_rockver = new_ver:gsub("-", "") - - local out_rs, err = persist.load_into_table(filename) - local out_name = out_rs.package:lower() - out_rs.version = new_rockver.."-"..new_rev - - local ok, err = update_source_section(out_rs, url, flags.tag, old_ver, new_ver) - if not ok then return nil, err end - - if out_rs.build and out_rs.build.type == "module" then - out_rs.build.type = "builtin" - end - - local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec" - - persist.save_from_table(out_filename, out_rs, type_check.rockspec_order) - - util.printout("Wrote "..out_filename) - - local valid_out_rs, err = fetch.load_local_rockspec(out_filename) - if not valid_out_rs then - return nil, "Failed loading generated rockspec: "..err - end - - return true -end - -return new_version diff --git a/src/luarocks/pack.lua b/src/luarocks/pack.lua deleted file mode 100644 index 655cbf37..00000000 --- a/src/luarocks/pack.lua +++ /dev/null @@ -1,193 +0,0 @@ - ---- Module implementing the LuaRocks "pack" command. --- Creates a rock, packing sources or binaries. -local pack = {} - -local unpack = unpack or table.unpack - -local path = require("luarocks.path") -local repos = require("luarocks.repos") -local fetch = require("luarocks.fetch") -local fs = require("luarocks.fs") -local cfg = require("luarocks.core.cfg") -local util = require("luarocks.util") -local dir = require("luarocks.dir") -local manif = require("luarocks.manif") -local search = require("luarocks.search") - -pack.help_summary = "Create a rock, packing sources or binaries." -pack.help_arguments = "{| []}" -pack.help = [[ -Argument may be a rockspec file, for creating a source rock, -or the name of an installed package, for creating a binary rock. -In the latter case, the app version may be given as a second -argument. -]] - ---- Create a source rock. --- Packages a rockspec and its required source files in a rock --- file with the .src.rock extension, which can later be built and --- installed with the "build" command. --- @param rockspec_file string: An URL or pathname for a rockspec file. --- @return string or (nil, string): The filename of the resulting --- .src.rock file; or nil and an error message. -function pack.pack_source_rock(rockspec_file) - assert(type(rockspec_file) == "string") - - local rockspec, err = fetch.load_rockspec(rockspec_file) - if err then - return nil, "Error loading rockspec: "..err - end - rockspec_file = rockspec.local_filename - - local name_version = rockspec.name .. "-" .. rockspec.version - local rock_file = fs.absolute_name(name_version .. ".src.rock") - - local source_file, source_dir = fetch.fetch_sources(rockspec, false) - if not source_file then - return nil, source_dir - end - local ok, err = fs.change_dir(source_dir) - if not ok then return nil, err end - - fs.delete(rock_file) - fs.copy(rockspec_file, source_dir, cfg.perm_read) - if not fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) then - return nil, "Failed packing "..rock_file - end - fs.pop_dir() - - return rock_file -end - -local function copy_back_files(name, version, file_tree, deploy_dir, pack_dir, perms) - local ok, err = fs.make_dir(pack_dir) - if not ok then return nil, err end - for file, sub in pairs(file_tree) do - local source = dir.path(deploy_dir, file) - local target = dir.path(pack_dir, file) - if type(sub) == "table" then - local ok, err = copy_back_files(name, version, sub, source, target) - if not ok then return nil, err end - else - local versioned = path.versioned_name(source, deploy_dir, name, version) - if fs.exists(versioned) then - fs.copy(versioned, target, perms) - else - fs.copy(source, target, perms) - end - end - end - return true -end - --- @param name string: Name of package to pack. --- @param version string or nil: A version number may also be passed. --- @param tree string or nil: An optional tree to pick the package from. --- @return string or (nil, string): The filename of the resulting --- .src.rock file; or nil and an error message. -local function do_pack_binary_rock(name, version, tree) - assert(type(name) == "string") - assert(type(version) == "string" or not version) - - local repo, repo_url - name, version, repo, repo_url = search.pick_installed_rock(name, version, tree) - if not name then - return nil, version - end - - local root = path.root_dir(repo_url) - local prefix = path.install_dir(name, version, root) - if not fs.exists(prefix) then - return nil, "'"..name.." "..version.."' does not seem to be an installed rock." - end - - local rock_manifest, err = manif.load_rock_manifest(name, version, root) - if not rock_manifest then return nil, err end - - local name_version = name .. "-" .. version - local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock") - - local temp_dir = fs.make_temp_dir("pack") - fs.copy_contents(prefix, temp_dir) - - local is_binary = false - if rock_manifest.lib then - local ok, err = copy_back_files(name, version, rock_manifest.lib, path.deploy_lib_dir(root), dir.path(temp_dir, "lib"), cfg.perm_exec) - if not ok then return nil, "Failed copying back files: " .. err end - is_binary = true - end - if rock_manifest.lua then - local ok, err = copy_back_files(name, version, rock_manifest.lua, path.deploy_lua_dir(root), dir.path(temp_dir, "lua"), cfg.perm_read) - if not ok then return nil, "Failed copying back files: " .. err end - end - - local ok, err = fs.change_dir(temp_dir) - if not ok then return nil, err end - if not is_binary and not repos.has_binaries(name, version) then - rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.") - end - fs.delete(rock_file) - if not fs.zip(rock_file, unpack(fs.list_dir())) then - return nil, "Failed packing "..rock_file - end - fs.pop_dir() - fs.delete(temp_dir) - return rock_file -end - -function pack.pack_binary_rock(name, version, cmd, ...) - - -- The --pack-binary-rock option for "luarocks build" basically performs - -- "luarocks build" on a temporary tree and then "luarocks pack". The - -- alternative would require refactoring parts of luarocks.build and - -- luarocks.pack, which would save a few file operations: the idea would be - -- to shave off the final deploy steps from the build phase and the initial - -- collect steps from the pack phase. - - local temp_dir, err = fs.make_temp_dir("luarocks-build-pack-"..dir.base_name(name)) - if not temp_dir then - return nil, "Failed creating temporary directory: "..err - end - util.schedule_function(fs.delete, temp_dir) - - path.use_tree(temp_dir) - local ok, err = cmd(...) - if not ok then - return nil, err - end - local rname, rversion = path.parse_name(name) - if not rname then - rname, rversion = name, version - end - return do_pack_binary_rock(rname, rversion, temp_dir) -end - ---- Driver function for the "pack" command. --- @param arg string: may be a rockspec file, for creating a source rock, --- or the name of an installed package, for creating a binary rock. --- @param version string or nil: if the name of a package is given, a --- version may also be passed. --- @return boolean or (nil, string): true if successful or nil followed --- by an error message. -function pack.command(flags, arg, version) - assert(type(version) == "string" or not version) - if type(arg) ~= "string" then - return nil, "Argument missing. "..util.see_help("pack") - end - - local file, err - if arg:match(".*%.rockspec") then - file, err = pack.pack_source_rock(arg) - else - file, err = do_pack_binary_rock(arg:lower(), version, flags["tree"]) - end - if err then - return nil, err - else - util.printout("Packed: "..file) - return true - end -end - -return pack diff --git a/src/luarocks/path_cmd.lua b/src/luarocks/path_cmd.lua deleted file mode 100644 index 516a0c47..00000000 --- a/src/luarocks/path_cmd.lua +++ /dev/null @@ -1,68 +0,0 @@ - ---- @module luarocks.path_cmd --- Driver for the `luarocks path` command. -local path_cmd = {} - -local util = require("luarocks.util") -local cfg = require("luarocks.core.cfg") - -path_cmd.help_summary = "Return the currently configured package path." -path_cmd.help_arguments = "" -path_cmd.help = [[ -Returns the package path currently configured for this installation -of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH. - ---bin Adds the system path to the output - ---append Appends the paths to the existing paths. Default is to prefix - the LR paths to the existing paths. - ---lr-path Exports the Lua path (not formatted as shell command) - ---lr-cpath Exports the Lua cpath (not formatted as shell command) - ---lr-bin Exports the system path (not formatted as shell command) - - -On Unix systems, you may run: - eval `luarocks path` -And on Windows: - luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat" -]] - ---- Driver function for "path" command. --- @return boolean This function always succeeds. -function path_cmd.command(flags) - local lr_path, lr_cpath, lr_bin = cfg.package_paths(flags["tree"]) - local path_sep = cfg.export_path_separator - - if flags["lr-path"] then - util.printout(util.remove_path_dupes(lr_path, ';')) - return true - elseif flags["lr-cpath"] then - util.printout(util.remove_path_dupes(lr_cpath, ';')) - return true - elseif flags["lr-bin"] then - util.printout(util.remove_path_dupes(lr_bin, path_sep)) - return true - end - - if flags["append"] then - lr_path = package.path .. ";" .. lr_path - lr_cpath = package.cpath .. ";" .. lr_cpath - lr_bin = os.getenv("PATH") .. path_sep .. lr_bin - else - lr_path = lr_path.. ";" .. package.path - lr_cpath = lr_cpath .. ";" .. package.cpath - lr_bin = lr_bin .. path_sep .. os.getenv("PATH") - end - - util.printout(cfg.export_lua_path:format(util.remove_path_dupes(lr_path, ';'))) - util.printout(cfg.export_lua_cpath:format(util.remove_path_dupes(lr_cpath, ';'))) - if flags["bin"] then - util.printout(cfg.export_path:format(util.remove_path_dupes(lr_bin, path_sep))) - end - return true -end - -return path_cmd diff --git a/src/luarocks/purge.lua b/src/luarocks/purge.lua deleted file mode 100644 index 50f290c8..00000000 --- a/src/luarocks/purge.lua +++ /dev/null @@ -1,77 +0,0 @@ - ---- Module implementing the LuaRocks "purge" command. --- Remove all rocks from a given tree. -local purge = {} - -local util = require("luarocks.util") -local fs = require("luarocks.fs") -local path = require("luarocks.path") -local search = require("luarocks.search") -local deps = require("luarocks.deps") -local repos = require("luarocks.repos") -local writer = require("luarocks.manif.writer") -local cfg = require("luarocks.core.cfg") -local remove = require("luarocks.remove") - -purge.help_summary = "Remove all installed rocks from a tree." -purge.help_arguments = "--tree= [--old-versions]" -purge.help = [[ -This command removes rocks en masse from a given tree. -By default, it removes all rocks from a tree. - -The --tree argument is mandatory: luarocks purge does not -assume a default tree. - ---old-versions Keep the highest-numbered version of each - rock and remove the other ones. By default - it only removes old versions if they are - not needed as dependencies. This can be - overridden with the flag --force. -]] - -function purge.command(flags) - local tree = flags["tree"] - - if type(tree) ~= "string" then - return nil, "The --tree argument is mandatory. "..util.see_help("purge") - end - - local results = {} - local query = search.make_query("") - query.exact_name = false - if not fs.is_dir(tree) then - return nil, "Directory not found: "..tree - end - - local ok, err = fs.check_command_permissions(flags) - if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - - search.manifest_search(results, path.rocks_dir(tree), query) - - local sort = function(a,b) return deps.compare_versions(b,a) end - if flags["old-versions"] then - sort = deps.compare_versions - end - - for package, versions in util.sortedpairs(results) do - for version, _ in util.sortedpairs(versions, sort) do - if flags["old-versions"] then - util.printout("Keeping "..package.." "..version.."...") - local ok, err = remove.remove_other_versions(package, version, flags["force"], flags["force-fast"]) - if not ok then - util.printerr(err) - end - break - else - util.printout("Removing "..package.." "..version.."...") - local ok, err = repos.delete_version(package, version, "none", true) - if not ok then - util.printerr(err) - end - end - end - end - return writer.make_manifest(cfg.rocks_dir, "one") -end - -return purge diff --git a/src/luarocks/remove.lua b/src/luarocks/remove.lua deleted file mode 100644 index e7f37604..00000000 --- a/src/luarocks/remove.lua +++ /dev/null @@ -1,165 +0,0 @@ - ---- Module implementing the LuaRocks "remove" command. --- Uninstalls rocks. -local remove = {} - -local search = require("luarocks.search") -local deps = require("luarocks.deps") -local fetch = require("luarocks.fetch") -local repos = require("luarocks.repos") -local path = require("luarocks.path") -local util = require("luarocks.util") -local cfg = require("luarocks.core.cfg") -local fs = require("luarocks.fs") - -remove.help_summary = "Uninstall a rock." -remove.help_arguments = "[--force|--force-fast] []" -remove.help = [[ -Argument is the name of a rock to be uninstalled. -If a version is not given, try to remove all versions at once. -Will only perform the removal if it does not break dependencies. -To override this check and force the removal, use --force. -To perform a forced removal without reporting dependency issues, -use --force-fast. - -]]..util.deps_mode_help() - ---- Obtain a list of packages that depend on the given set of packages --- (where all packages of the set are versions of one program). --- @param name string: the name of a program --- @param versions array of string: the versions to be deleted. --- @return array of string: an empty table if no packages depend on any --- of the given list, or an array of strings in "name/version" format. -local function check_dependents(name, versions, deps_mode) - local dependents = {} - local blacklist = {} - blacklist[name] = {} - for version, _ in pairs(versions) do - blacklist[name][version] = true - end - local local_rocks = {} - local query_all = search.make_query("") - query_all.exact_name = false - search.manifest_search(local_rocks, cfg.rocks_dir, query_all) - local_rocks[name] = nil - for rock_name, rock_versions in pairs(local_rocks) do - for rock_version, _ in pairs(rock_versions) do - local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version)) - if rockspec then - local _, missing = deps.match_deps(rockspec, blacklist, deps_mode) - if missing[name] then - table.insert(dependents, { name = rock_name, version = rock_version }) - end - end - end - end - return dependents -end - ---- Delete given versions of a program. --- @param name string: the name of a program --- @param versions array of string: the versions to be deleted. --- @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. --- @return boolean or (nil, string): true on success or nil and an error message. -local function delete_versions(name, versions, deps_mode) - - for version, _ in pairs(versions) do - util.printout("Removing "..name.." "..version.."...") - local ok, err = repos.delete_version(name, version, deps_mode) - if not ok then return nil, err end - end - - return true -end - -function remove.remove_search_results(results, name, deps_mode, force, fast) - local versions = results[name] - - local version = next(versions) - local second = next(versions, version) - - local dependents = {} - if not fast then - util.printout("Checking stability of dependencies in the absence of") - util.printout(name.." "..table.concat(util.keys(versions), ", ").."...") - util.printout() - dependents = check_dependents(name, versions, deps_mode) - end - - if #dependents > 0 then - if force or fast then - util.printerr("The following packages may be broken by this forced removal:") - for _, dependent in ipairs(dependents) do - util.printerr(dependent.name.." "..dependent.version) - end - util.printerr() - else - if not second then - util.printerr("Will not remove "..name.." "..version..".") - util.printerr("Removing it would break dependencies for: ") - else - util.printerr("Will not remove installed versions of "..name..".") - util.printerr("Removing them would break dependencies for: ") - end - for _, dependent in ipairs(dependents) do - util.printerr(dependent.name.." "..dependent.version) - end - util.printerr() - util.printerr("Use --force to force removal (warning: this may break modules).") - return nil, "Failed removing." - end - end - - local ok, err = delete_versions(name, versions, deps_mode) - if not ok then return nil, err end - - util.printout("Removal successful.") - return true -end - -function remove.remove_other_versions(name, version, force, fast) - local results = {} - search.manifest_search(results, cfg.rocks_dir, { name = name, exact_name = true, constraints = {{ op = "~=", version = version}} }) - if results[name] then - return remove.remove_search_results(results, name, cfg.deps_mode, force, fast) - end - return true -end - ---- Driver function for the "remove" command. --- @param name string: name of a rock. If a version is given, refer to --- a specific version; otherwise, try to remove all versions. --- @param version string: When passing a package name, a version number --- may also be given. --- @return boolean or (nil, string, exitcode): True if removal was --- successful, nil and an error message otherwise. exitcode is optionally returned. -function remove.command(flags, name, version) - if type(name) ~= "string" then - return nil, "Argument missing. "..util.see_help("remove") - end - - local deps_mode = flags["deps-mode"] or cfg.deps_mode - - local ok, err = fs.check_command_permissions(flags) - if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end - - local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$") - local filename = name - if rock_type then - name, version = path.parse_name(filename) - if not name then return nil, "Invalid "..rock_type.." filename: "..filename end - end - - local results = {} - name = name:lower() - search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version)) - if not results[name] then - return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir) - end - - return remove.remove_search_results(results, name, deps_mode, flags["force"], flags["force-fast"]) -end - -return remove diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua deleted file mode 100644 index 44eff694..00000000 --- a/src/luarocks/search.lua +++ /dev/null @@ -1,482 +0,0 @@ - ---- Module implementing the LuaRocks "search" command. --- Queries LuaRocks servers. -local search = {} - - -local dir = require("luarocks.dir") -local path = require("luarocks.path") -local manif = require("luarocks.manif") -local deps = require("luarocks.deps") -local cfg = require("luarocks.core.cfg") -local util = require("luarocks.util") - -search.help_summary = "Query the LuaRocks servers." -search.help_arguments = "[--source] [--binary] { [] | --all }" -search.help = [[ ---source Return only rockspecs and source rocks, - to be used with the "build" command. ---binary Return only pure Lua and binary rocks (rocks that can be used - with the "install" command without requiring a C toolchain). ---all List all contents of the server that are suitable to - this platform, do not filter by name. -]] - ---- Convert the arch field of a query table to table format. --- @param query table: A query table. -local function query_arch_as_table(query) - local format = type(query.arch) - if format == "table" then - return - elseif format == "nil" then - local accept = {} - accept["src"] = true - accept["all"] = true - accept["rockspec"] = true - accept["installed"] = true - accept[cfg.arch] = true - query.arch = accept - elseif format == "string" then - local accept = {} - for a in query.arch:gmatch("[%w_-]+") do - accept[a] = true - end - query.arch = accept - end -end - ---- Store a search result (a rock or rockspec) in the results table. --- @param results table: The results table, where keys are package names and --- values are tables matching version strings to arrays of --- tables with fields "arch" and "repo". --- @param name string: Package name. --- @param version string: Package version. --- @param arch string: Architecture of rock ("all", "src" or platform --- identifier), "rockspec" or "installed" --- @param repo string: Pathname of a local repository of URL of --- rocks server. -local function store_result(results, name, version, arch, repo) - assert(type(results) == "table") - assert(type(name) == "string") - assert(type(version) == "string") - assert(type(arch) == "string") - assert(type(repo) == "string") - - if not results[name] then results[name] = {} end - if not results[name][version] then results[name][version] = {} end - table.insert(results[name][version], { - arch = arch, - repo = repo - }) -end - ---- Test the name field of a query. --- If query has a boolean field exact_name set to false, --- then substring match is performed; otherwise, exact string --- comparison is done. --- @param query table: A query in dependency table format. --- @param name string: A package name. --- @return boolean: True if names match, false otherwise. -local function match_name(query, name) - assert(type(query) == "table") - assert(type(name) == "string") - if query.exact_name == false then - return name:find(query.name, 0, true) and true or false - else - return name == query.name - end -end - ---- Store a match in a results table if version matches query. --- Name, version, arch and repository path are stored in a given --- table, optionally checking if version and arch (if given) match --- a query. --- @param results table: The results table, where keys are package names and --- values are tables matching version strings to arrays of --- tables with fields "arch" and "repo". --- @param repo string: URL or pathname of the repository. --- @param name string: The name of the package being tested. --- @param version string: The version of the package being tested. --- @param arch string: The arch of the package being tested. --- @param query table: A table describing the query in dependency --- format (for example, {name = "filesystem", exact_name = false, --- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). --- If the arch field is omitted, the local architecture (cfg.arch) --- is used. The special value "any" is also recognized, returning all --- matches regardless of architecture. -local function store_if_match(results, repo, name, version, arch, query) - if match_name(query, name) then - if query.arch[arch] or query.arch["any"] then - if deps.match_constraints(deps.parse_version(version), query.constraints) then - store_result(results, name, version, arch, repo) - end - end - end -end - ---- Perform search on a local repository. --- @param repo string: The pathname of the local repository. --- @param query table: A table describing the query in dependency --- format (for example, {name = "filesystem", exact_name = false, --- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). --- If the arch field is omitted, the local architecture (cfg.arch) --- is used. The special value "any" is also recognized, returning all --- matches regardless of architecture. --- @param results table or nil: If given, this table will store the --- results; if not given, a new table will be created. --- @return table: The results table, where keys are package names and --- values are tables matching version strings to arrays of --- tables with fields "arch" and "repo". --- If a table was given in the "results" parameter, that is the result value. -function search.disk_search(repo, query, results) - assert(type(repo) == "string") - assert(type(query) == "table") - assert(type(results) == "table" or not results) - - local fs = require("luarocks.fs") - - if not results then - results = {} - end - query_arch_as_table(query) - - for name in fs.dir(repo) do - local pathname = dir.path(repo, name) - local rname, rversion, rarch = path.parse_name(name) - - if rname and (pathname:match(".rockspec$") or pathname:match(".rock$")) then - store_if_match(results, repo, rname, rversion, rarch, query) - elseif fs.is_dir(pathname) then - for version in fs.dir(pathname) do - if version:match("-%d+$") then - store_if_match(results, repo, name, version, "installed", query) - end - end - end - end - return results -end - ---- Perform search on a rocks server or tree. --- @param results table: The results table, where keys are package names and --- values are tables matching version strings to arrays of --- tables with fields "arch" and "repo". --- @param repo string: The URL of a rocks server or --- the pathname of a rocks tree (as returned by path.rocks_dir()). --- @param query table: A table describing the query in dependency --- format (for example, {name = "filesystem", exact_name = false, --- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec"). --- If the arch field is omitted, the local architecture (cfg.arch) --- is used. The special value "any" is also recognized, returning all --- matches regardless of architecture. --- @param lua_version string: Lua version in "5.x" format, defaults to installed version. --- @return true or, in case of errors, nil, an error message and an optional error code. -function search.manifest_search(results, repo, query, lua_version) - assert(type(results) == "table") - assert(type(repo) == "string") - assert(type(query) == "table") - - query_arch_as_table(query) - local manifest, err, errcode = manif.load_manifest(repo, lua_version) - if not manifest then - return nil, err, errcode - end - for name, versions in pairs(manifest.repository) do - for version, items in pairs(versions) do - for _, item in ipairs(items) do - store_if_match(results, repo, name, version, item.arch, query) - end - end - end - return true -end - ---- Search on all configured rocks servers. --- @param query table: A dependency query. --- @param lua_version string: Lua version in "5.x" format, defaults to installed version. --- @return table: A table where keys are package names --- and values are tables matching version strings to arrays of --- tables with fields "arch" and "repo". -function search.search_repos(query, lua_version) - assert(type(query) == "table") - - local results = {} - for _, repo in ipairs(cfg.rocks_servers) do - if not cfg.disabled_servers[repo] then - if type(repo) == "string" then - repo = { repo } - end - for _, mirror in ipairs(repo) do - local protocol, pathname = dir.split_url(mirror) - if protocol == "file" then - mirror = pathname - end - local ok, err, errcode = search.manifest_search(results, mirror, query, lua_version) - if errcode == "network" then - cfg.disabled_servers[repo] = true - end - if ok then - break - else - util.warning("Failed searching manifest: "..err) - end - end - end - end - -- search through rocks in cfg.rocks_provided - local provided_repo = "provided by VM or rocks_provided" - for name, versions in pairs(cfg.rocks_provided) do - store_if_match(results, provided_repo, name, versions, "installed", query) - end - return results -end - ---- Prepare a query in dependency table format. --- @param name string: The query name. --- @param version string or nil: --- @return table: A query in table format -function search.make_query(name, version) - assert(type(name) == "string") - assert(type(version) == "string" or not version) - - local query = { - name = name, - constraints = {} - } - if version then - table.insert(query.constraints, { op = "==", version = deps.parse_version(version)}) - end - return query -end - ---- Get the URL for the latest in a set of versions. --- @param name string: The package name to be used in the URL. --- @param versions table: An array of version informations, as stored --- in search results tables. --- @return string or nil: the URL for the latest version if one could --- be picked, or nil. -local function pick_latest_version(name, versions) - assert(type(name) == "string") - assert(type(versions) == "table") - - local vtables = {} - for v, _ in pairs(versions) do - table.insert(vtables, deps.parse_version(v)) - end - table.sort(vtables) - local version = vtables[#vtables].string - local items = versions[version] - if items then - local pick = 1 - for i, item in ipairs(items) do - if (item.arch == 'src' and items[pick].arch == 'rockspec') - or (item.arch ~= 'src' and item.arch ~= 'rockspec') then - pick = i - end - end - return path.make_url(items[pick].repo, name, version, items[pick].arch) - end - return nil -end - --- Find out which other Lua versions provide rock versions matching a query, --- @param query table: A dependency query matching a single rock. --- @return table: array of Lua versions supported, in "5.x" format. -local function supported_lua_versions(query) - local results = {} - - for lua_version in util.lua_versions() do - if lua_version ~= cfg.lua_version then - if search.search_repos(query, lua_version)[query.name] then - table.insert(results, lua_version) - end - end - end - - return results -end - ---- Attempt to get a single URL for a given search for a rock. --- @param query table: A dependency query matching a single rock. --- @return string or (nil, string): URL for latest matching version --- of the rock if it was found, or nil followed by an error message. -function search.find_suitable_rock(query) - assert(type(query) == "table") - - local results = search.search_repos(query) - local first_rock = next(results) - if not first_rock then - if cfg.rocks_provided[query.name] == nil then - -- Check if constraints are satisfiable with other Lua versions. - local lua_versions = supported_lua_versions(query) - - if #lua_versions ~= 0 then - -- Build a nice message in "only Lua 5.x and 5.y but not 5.z." format - for i, lua_version in ipairs(lua_versions) do - lua_versions[i] = "Lua "..lua_version - end - - local versions_message = "only "..table.concat(lua_versions, " and ").. - " but not Lua "..cfg.lua_version.."." - - if #query.constraints == 0 then - return nil, query.name.." supports "..versions_message - elseif #query.constraints == 1 and query.constraints[1].op == "==" then - return nil, query.name.." "..query.constraints[1].version.string.." supports "..versions_message - else - return nil, "Matching "..query.name.." versions support "..versions_message - end - end - end - - return nil, "No results matching query were found." - elseif next(results, first_rock) then - -- Shouldn't happen as query must match only one package. - return nil, "Several rocks matched query." - elseif cfg.rocks_provided[query.name] ~= nil then - -- Do not install versions listed in cfg.rocks_provided. - return nil, "Rock "..query.name.." "..cfg.rocks_provided[query.name].. - " was found but it is provided by VM or 'rocks_provided' in the config file." - else - return pick_latest_version(query.name, results[first_rock]) - end -end - ---- Print a list of rocks/rockspecs on standard output. --- @param results table: A table where keys are package names and versions --- are tables matching version strings to an array of rocks servers. --- @param porcelain boolean or nil: A flag to force machine-friendly output. -function search.print_results(results, porcelain) - assert(type(results) == "table") - assert(type(porcelain) == "boolean" or not porcelain) - - for package, versions in util.sortedpairs(results) do - if not porcelain then - util.printout(package) - end - for version, repos in util.sortedpairs(versions, deps.compare_versions) do - for _, repo in ipairs(repos) do - repo.repo = dir.normalize(repo.repo) - if porcelain then - util.printout(package, version, repo.arch, repo.repo) - else - util.printout(" "..version.." ("..repo.arch..") - "..repo.repo) - end - end - end - if not porcelain then - util.printout() - end - end -end - ---- Splits a list of search results into two lists, one for "source" results --- to be used with the "build" command, and one for "binary" results to be --- used with the "install" command. --- @param results table: A search results table. --- @return (table, table): Two tables, one for source and one for binary --- results. -local function split_source_and_binary_results(results) - local sources, binaries = {}, {} - for name, versions in pairs(results) do - for version, repositories in pairs(versions) do - for _, repo in ipairs(repositories) do - local where = sources - if repo.arch == "all" or repo.arch == cfg.arch then - where = binaries - end - store_result(where, name, version, repo.arch, repo.repo) - end - end - end - return sources, binaries -end - ---- Given a name and optionally a version, try to find in the rocks --- servers a single .src.rock or .rockspec file that satisfies --- the request, and run the given function on it; or display to the --- user possibilities if it couldn't narrow down a single match. --- @param action function: A function that takes a .src.rock or --- .rockspec URL as a parameter. --- @param name string: A rock name --- @param version string or nil: A version number may also be given. --- @return The result of the action function, or nil and an error message. -function search.act_on_src_or_rockspec(action, name, version, ...) - assert(type(action) == "function") - assert(type(name) == "string") - assert(type(version) == "string" or not version) - - local query = search.make_query(name, version) - query.arch = "src|rockspec" - local url, err = search.find_suitable_rock(query) - if not url then - return nil, "Could not find a result named "..name..(version and " "..version or "")..": "..err - end - return action(url, ...) -end - -function search.pick_installed_rock(name, version, given_tree) - local results = {} - local query = search.make_query(name, version) - query.exact_name = true - local tree_map = {} - local trees = cfg.rocks_trees - if given_tree then - trees = { given_tree } - end - for _, tree in ipairs(trees) do - local rocks_dir = path.rocks_dir(tree) - tree_map[rocks_dir] = tree - search.manifest_search(results, rocks_dir, query) - end - - if not next(results) then -- - return nil,"cannot find package "..name.." "..(version or "").."\nUse 'list' to find installed rocks." - end - - version = nil - local repo_url - local package, versions = util.sortedpairs(results)() - --question: what do we do about multiple versions? This should - --give us the latest version on the last repo (which is usually the global one) - for vs, repositories in util.sortedpairs(versions, deps.compare_versions) do - if not version then version = vs end - for _, rp in ipairs(repositories) do repo_url = rp.repo end - end - - local repo = tree_map[repo_url] - return name, version, repo, repo_url -end - ---- Driver function for "search" command. --- @param name string: A substring of a rock name to search. --- @param version string or nil: a version may also be passed. --- @return boolean or (nil, string): True if build was successful; nil and an --- error message otherwise. -function search.command(flags, name, version) - if flags["all"] then - name, version = "", nil - end - - if type(name) ~= "string" and not flags["all"] then - return nil, "Enter name and version or use --all. "..util.see_help("search") - end - - local query = search.make_query(name:lower(), version) - query.exact_name = false - local results, err = search.search_repos(query) - local porcelain = flags["porcelain"] - util.title("Search results:", porcelain, "=") - local sources, binaries = split_source_and_binary_results(results) - if next(sources) and not flags["binary"] then - util.title("Rockspecs and source rocks:", porcelain) - search.print_results(sources, porcelain) - end - if next(binaries) and not flags["source"] then - util.title("Binary and pure-Lua rocks:", porcelain) - search.print_results(binaries, porcelain) - end - return true -end - -return search diff --git a/src/luarocks/show.lua b/src/luarocks/show.lua deleted file mode 100644 index 1ff81e08..00000000 --- a/src/luarocks/show.lua +++ /dev/null @@ -1,158 +0,0 @@ ---- Module implementing the LuaRocks "show" command. --- Shows information about an installed rock. -local show = {} - -local search = require("luarocks.search") -local cfg = require("luarocks.core.cfg") -local util = require("luarocks.util") -local path = require("luarocks.path") -local deps = require("luarocks.deps") -local fetch = require("luarocks.fetch") -local manif = require("luarocks.manif") - -show.help_summary = "Show information about an installed rock." - -show.help = [[ - is an existing package name. -Without any flags, show all module information. -With these flags, return only the desired information: - ---home home page of project ---modules all modules provided by this package as used by require() ---deps packages this package depends on ---rockspec the full path of the rockspec file ---mversion the package version ---rock-tree local tree where rock is installed ---rock-dir data directory of the installed rock -]] - -local function keys_as_string(t, sep) - local keys = util.keys(t) - table.sort(keys) - return table.concat(keys, sep or " ") -end - -local function word_wrap(line) - local width = tonumber(os.getenv("COLUMNS")) or 80 - if width > 80 then width = 80 end - if #line > width then - local brk = width - while brk > 0 and line:sub(brk, brk) ~= " " do - brk = brk - 1 - end - if brk > 0 then - return line:sub(1, brk-1) .. "\n" .. word_wrap(line:sub(brk+1)) - end - end - return line -end - -local function format_text(text) - text = text:gsub("^%s*",""):gsub("%s$", ""):gsub("\n[ \t]+","\n"):gsub("([^\n])\n([^\n])","%1 %2") - local paragraphs = util.split_string(text, "\n\n") - for n, line in ipairs(paragraphs) do - paragraphs[n] = word_wrap(line) - end - return (table.concat(paragraphs, "\n\n"):gsub("%s$", "")) -end - -local function installed_rock_label(name, tree) - local installed, version - if cfg.rocks_provided[name] then - installed, version = true, cfg.rocks_provided[name] - else - installed, version = search.pick_installed_rock(name, nil, tree) - end - return installed and "(using "..version..")" or "(missing)" -end - ---- Driver function for "show" command. --- @param name or nil: an existing package name. --- @param version string or nil: a version may also be passed. --- @return boolean: True if succeeded, nil on errors. -function show.command(flags, name, version) - if not name then - return nil, "Argument missing. "..util.see_help("show") - end - - local repo, repo_url - name, version, repo, repo_url = search.pick_installed_rock(name:lower(), version, flags["tree"]) - if not name then - return nil, version - end - - local directory = path.install_dir(name,version,repo) - local rockspec_file = path.rockspec_file(name, version, repo) - local rockspec, err = fetch.load_local_rockspec(rockspec_file) - if not rockspec then return nil,err end - - local descript = rockspec.description or {} - local manifest, err = manif.load_manifest(repo_url) - if not manifest then return nil,err end - local minfo = manifest.repository[name][version][1] - - if flags["rock-tree"] then util.printout(path.rocks_tree_to_string(repo)) - elseif flags["rock-dir"] then util.printout(directory) - elseif flags["home"] then util.printout(descript.homepage) - elseif flags["issues"] then util.printout(descript.issues_url) - elseif flags["labels"] then util.printout(descript.labels and table.concat(descript.labels, "\n")) - elseif flags["modules"] then util.printout(keys_as_string(minfo.modules, "\n")) - elseif flags["deps"] then util.printout(keys_as_string(minfo.dependencies)) - elseif flags["rockspec"] then util.printout(rockspec_file) - elseif flags["mversion"] then util.printout(version) - else - util.printout() - util.printout(rockspec.package.." "..rockspec.version.." - "..(descript.summary or "")) - util.printout() - if descript.detailed then - util.printout(format_text(descript.detailed)) - util.printout() - end - if descript.license then - util.printout("License: ", descript.license) - end - if descript.homepage then - util.printout("Homepage: ", descript.homepage) - end - if descript.issues_url then - util.printout("Issues: ", descript.issues_url) - end - if descript.labels then - util.printout("Labels: ", table.concat(descript.labels, ", ")) - end - util.printout("Installed in: ", path.rocks_tree_to_string(repo)) - if next(minfo.modules) then - util.printout() - util.printout("Modules:") - for mod, filename in util.sortedpairs(minfo.modules) do - util.printout("\t"..mod.." ("..path.which(mod, filename, name, version, repo, manifest)..")") - end - end - local direct_deps = {} - if #rockspec.dependencies > 0 then - util.printout() - util.printout("Depends on:") - for _, dep in ipairs(rockspec.dependencies) do - direct_deps[dep.name] = true - util.printout("\t"..deps.show_dep(dep).." "..installed_rock_label(dep.name, flags["tree"])) - end - end - local has_indirect_deps - for dep_name in util.sortedpairs(minfo.dependencies) do - if not direct_deps[dep_name] then - if not has_indirect_deps then - util.printout() - util.printout("Indirectly pulling:") - has_indirect_deps = true - end - - util.printout("\t"..dep_name.." "..installed_rock_label(dep_name, flags["tree"])) - end - end - util.printout() - end - return true -end - - -return show diff --git a/src/luarocks/unpack.lua b/src/luarocks/unpack.lua deleted file mode 100644 index c50701b0..00000000 --- a/src/luarocks/unpack.lua +++ /dev/null @@ -1,164 +0,0 @@ - ---- Module implementing the LuaRocks "unpack" command. --- Unpack the contents of a rock. -local unpack = {} - -local fetch = require("luarocks.fetch") -local fs = require("luarocks.fs") -local util = require("luarocks.util") -local build = require("luarocks.build") -local dir = require("luarocks.dir") -local cfg = require("luarocks.core.cfg") - -unpack.help_summary = "Unpack the contents of a rock." -unpack.help_arguments = "[--force] {| []}" -unpack.help = [[ -Unpacks the contents of a rock in a newly created directory. -Argument may be a rock file, or the name of a rock in a rocks server. -In the latter case, the app version may be given as a second argument. - ---force Unpack files even if the output directory already exists. -]] - ---- Load a rockspec file to the given directory, fetches the source --- files specified in the rockspec, and unpack them inside the directory. --- @param rockspec_file string: The URL for a rockspec file. --- @param dir_name string: The directory where to store and unpack files. --- @return table or (nil, string): the loaded rockspec table or --- nil and an error message. -local function unpack_rockspec(rockspec_file, dir_name) - assert(type(rockspec_file) == "string") - assert(type(dir_name) == "string") - - local rockspec, err = fetch.load_rockspec(rockspec_file) - if not rockspec then - return nil, "Failed loading rockspec "..rockspec_file..": "..err - end - local ok, err = fs.change_dir(dir_name) - if not ok then return nil, err end - local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".") - if not ok then - return nil, sources_dir - end - ok, err = fs.change_dir(sources_dir) - if not ok then return nil, err end - ok, err = build.apply_patches(rockspec) - fs.pop_dir() - if not ok then return nil, err end - return rockspec -end - ---- Load a .rock file to the given directory and unpack it inside it. --- @param rock_file string: The URL for a .rock file. --- @param dir_name string: The directory where to unpack. --- @param kind string: the kind of rock file, as in the second-level --- extension in the rock filename (eg. "src", "all", "linux-x86") --- @return table or (nil, string): the loaded rockspec table or --- nil and an error message. -local function unpack_rock(rock_file, dir_name, kind) - assert(type(rock_file) == "string") - assert(type(dir_name) == "string") - - local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name) - if not ok then - return nil, "Failed unzipping rock "..rock_file, errcode - end - ok, err = fs.change_dir(dir_name) - if not ok then return nil, err end - local rockspec_file = dir_name..".rockspec" - local rockspec, err = fetch.load_rockspec(rockspec_file) - if not rockspec then - return nil, "Failed loading rockspec "..rockspec_file..": "..err - end - if kind == "src" then - if rockspec.source.file then - local ok, err = fs.unpack_archive(rockspec.source.file) - if not ok then - return nil, err - end - ok, err = fs.change_dir(rockspec.source.dir) - if not ok then return nil, err end - ok, err = build.apply_patches(rockspec) - fs.pop_dir() - if not ok then return nil, err end - end - end - return rockspec -end - ---- Create a directory and perform the necessary actions so that --- the sources for the rock and its rockspec are unpacked inside it, --- laid out properly so that the 'make' command is able to build the module. --- @param file string: A rockspec or .rock URL. --- @return boolean or (nil, string): true if successful or nil followed --- by an error message. -local function run_unpacker(file, force) - assert(type(file) == "string") - - local base_name = dir.base_name(file) - local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$") - if not extension then - dir_name, extension = base_name:match("(.*)%.(rockspec)$") - kind = "rockspec" - end - if not extension then - return nil, file.." does not seem to be a valid filename." - end - - local exists = fs.exists(dir_name) - if exists and not force then - return nil, "Directory "..dir_name.." already exists." - end - if not exists then - local ok, err = fs.make_dir(dir_name) - if not ok then return nil, err end - end - local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name)) - - local rockspec, err - if extension == "rock" then - rockspec, err = unpack_rock(file, dir_name, kind) - elseif extension == "rockspec" then - rockspec, err = unpack_rockspec(file, dir_name) - end - if not rockspec then - return nil, err - end - if kind == "src" or kind == "rockspec" then - if rockspec.source.dir ~= "." then - local ok = fs.copy(rockspec.local_filename, rockspec.source.dir, cfg.perm_read) - if not ok then - return nil, "Failed copying unpacked rockspec into unpacked source directory." - end - end - util.printout() - util.printout("Done. You may now enter directory ") - util.printout(dir.path(dir_name, rockspec.source.dir)) - util.printout("and type 'luarocks make' to build.") - end - util.remove_scheduled_function(rollback) - return true -end - ---- Driver function for the "unpack" command. --- @param name string: may be a rock filename, for unpacking a --- rock file or the name of a rock to be fetched and unpacked. --- @param version string or nil: if the name of a package is given, a --- version may also be passed. --- @return boolean or (nil, string): true if successful or nil followed --- by an error message. -function unpack.command(flags, name, version) - assert(type(version) == "string" or not version) - if type(name) ~= "string" then - return nil, "Argument missing. "..util.see_help("unpack") - end - - if name:match(".*%.rock") or name:match(".*%.rockspec") then - return run_unpacker(name, flags["force"]) - else - local search = require("luarocks.search") - return search.act_on_src_or_rockspec(run_unpacker, name:lower(), version) - end -end - -return unpack diff --git a/src/luarocks/upload.lua b/src/luarocks/upload.lua deleted file mode 100644 index baee47ab..00000000 --- a/src/luarocks/upload.lua +++ /dev/null @@ -1,94 +0,0 @@ - -local upload = {} - -local util = require("luarocks.util") -local fetch = require("luarocks.fetch") -local pack = require("luarocks.pack") -local cfg = require("luarocks.core.cfg") -local Api = require("luarocks.upload.api") - -upload.help_summary = "Upload a rockspec to the public rocks repository." -upload.help_arguments = "[--skip-pack] [--api-key=] [--force] " -upload.help = [[ - Pack a source rock file (.src.rock extension), - upload rockspec and source rock to server. ---skip-pack Do not pack and send source rock. ---api-key= Give it an API key. It will be stored for subsequent uses. ---force Replace existing rockspec if the same revision of - a module already exists. This should be used only - in case of upload mistakes: when updating a rockspec, - increment the revision number instead. -]] - -function upload.command(flags, fname) - if not fname then - return nil, "Missing rockspec. "..util.see_help("upload") - end - - local api, err = Api.new(flags) - if not api then - return nil, err - end - if cfg.verbose then - api.debug = true - end - - local rockspec, err, errcode = fetch.load_rockspec(fname) - if err then - return nil, err, errcode - end - - util.printout("Sending " .. tostring(fname) .. " ...") - local res, err = api:method("check_rockspec", { - package = rockspec.package, - version = rockspec.version - }) - if not res then return nil, err end - - if not res.module then - util.printout("Will create new module (" .. tostring(rockspec.package) .. ")") - end - if res.version and not flags["force"] then - return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload") - end - - local rock_fname - if not flags["skip-pack"] and not rockspec.version:match("^scm") then - util.printout("Packing " .. tostring(rockspec.package)) - rock_fname, err = pack.pack_source_rock(fname) - if not rock_fname then - return nil, err - end - end - - local multipart = require("luarocks.upload.multipart") - - res, err = api:method("upload", nil, { - rockspec_file = multipart.new_file(fname) - }) - if not res then return nil, err end - - if res.is_new and #res.manifests == 0 then - util.printerr("Warning: module not added to root manifest due to name taken.") - end - - local module_url = res.module_url - - if rock_fname then - if (not res.version) or (not res.version.id) then - return nil, "Invalid response from server." - end - util.printout(("Sending " .. tostring(rock_fname) .. " ...")) - res, err = api:method("upload_rock/" .. ("%d"):format(res.version.id), nil, { - rock_file = multipart.new_file(rock_fname) - }) - if not res then return nil, err end - end - - util.printout() - util.printout("Done: " .. tostring(module_url)) - util.printout() - return true -end - -return upload diff --git a/src/luarocks/write_rockspec.lua b/src/luarocks/write_rockspec.lua deleted file mode 100644 index be563eaa..00000000 --- a/src/luarocks/write_rockspec.lua +++ /dev/null @@ -1,376 +0,0 @@ - -local write_rockspec = {} - -local cfg = require("luarocks.core.cfg") -local dir = require("luarocks.dir") -local fetch = require("luarocks.fetch") -local fs = require("luarocks.fs") -local path = require("luarocks.path") -local persist = require("luarocks.persist") -local type_check = require("luarocks.type_check") -local util = require("luarocks.util") -local deps = require("luarocks.deps") - -write_rockspec.help_summary = "Write a template for a rockspec file." -write_rockspec.help_arguments = "[--output= ...] [] [] [|]" -write_rockspec.help = [[ -This command writes an initial version of a rockspec file, -based on a name, a version, and a location (an URL or a local path). -If only two arguments are given, the first one is considered the name and the -second one is the location. -If only one argument is given, it must be the location. -If no arguments are given, current directory is used as location. -LuaRocks will attempt to infer name and version if not given, -using 'scm' as default version. - -Note that the generated file is a _starting point_ for writing a -rockspec, and is not guaranteed to be complete or correct. - ---output= Write the rockspec with the given filename. - If not given, a file is written in the current - directory with a filename based on given name and version. ---license="" A license string, such as "MIT/X11" or "GNU GPL v3". ---summary="" A short one-line description summary. ---detailed="" A longer description string. ---homepage= Project homepage. ---lua-version= Supported Lua versions. Accepted values are "5.1", "5.2", - "5.3", "5.1,5.2", "5.2,5.3", or "5.1,5.2,5.3". ---rockspec-format= Rockspec format version, such as "1.0" or "1.1". ---tag= Tag to use. Will attempt to extract version number from it. ---lib=[,] A comma-separated list of libraries that C files need to - link to. -]] - -local function open_file(name) - return io.open(dir.path(fs.current_dir(), name), "r") -end - -local function get_url(rockspec) - local file, temp_dir, err_code, err_file, err_temp_dir = fetch.fetch_sources(rockspec, false) - if err_code == "source.dir" then - file, temp_dir = err_file, err_temp_dir - elseif not file then - util.warning("Could not fetch sources - "..temp_dir) - return false - end - util.printout("File successfully downloaded. Making checksum and checking base dir...") - if fetch.is_basic_protocol(rockspec.source.protocol) then - rockspec.source.md5 = fs.get_md5(file) - end - local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, rockspec.source.url) - return true, found_dir or inferred_dir, temp_dir -end - -local function configure_lua_version(rockspec, luaver) - if luaver == "5.1" then - table.insert(rockspec.dependencies, "lua ~> 5.1") - elseif luaver == "5.2" then - table.insert(rockspec.dependencies, "lua ~> 5.2") - elseif luaver == "5.3" then - table.insert(rockspec.dependencies, "lua ~> 5.3") - elseif luaver == "5.1,5.2" then - table.insert(rockspec.dependencies, "lua >= 5.1, < 5.3") - elseif luaver == "5.2,5.3" then - table.insert(rockspec.dependencies, "lua >= 5.2, < 5.4") - elseif luaver == "5.1,5.2,5.3" then - table.insert(rockspec.dependencies, "lua >= 5.1, < 5.4") - else - util.warning("Please specify supported Lua version with --lua-version=. "..util.see_help("write_rockspec")) - end -end - -local function detect_description() - local fd = open_file("README.md") or open_file("README") - if not fd then return end - local data = fd:read("*a") - fd:close() - local paragraph = data:match("\n\n([^%[].-)\n\n") - if not paragraph then paragraph = data:match("\n\n(.*)") end - local summary, detailed - if paragraph then - detailed = paragraph - - if #paragraph < 80 then - summary = paragraph:gsub("\n", "") - else - summary = paragraph:gsub("\n", " "):match("([^.]*%.) ") - end - end - return summary, detailed -end - -local function detect_mit_license(data) - local strip_copyright = (data:gsub("Copyright [^\n]*\n", "")) - local sum = 0 - for i = 1, #strip_copyright do - local num = string.byte(strip_copyright:sub(i,i)) - if num > 32 and num <= 128 then - sum = sum + num - end - end - return sum == 78656 -end - -local simple_scm_protocols = { - git = true, ["git+http"] = true, ["git+https"] = true, - hg = true, ["hg+http"] = true, ["hg+https"] = true -} - -local function detect_url_from_command(program, args, directory) - local command = fs.Q(cfg.variables[program:upper()]).. " "..args - local pipe = io.popen(fs.command_at(directory, fs.quiet_stderr(command))) - if not pipe then return nil end - local url = pipe:read("*a"):match("^([^\r\n]+)") - pipe:close() - if not url then return nil end - if not util.starts_with(url, program.."://") then - url = program.."+"..url - end - - if simple_scm_protocols[dir.split_url(url)] then - return url - end -end - -local function detect_scm_url(directory) - return detect_url_from_command("git", "config --get remote.origin.url", directory) or - detect_url_from_command("hg", "paths default", directory) -end - -local function show_license(rockspec) - local fd = open_file("COPYING") or open_file("LICENSE") or open_file("MIT-LICENSE.txt") - if not fd then return nil end - local data = fd:read("*a") - fd:close() - local is_mit = detect_mit_license(data) - util.title("License for "..rockspec.package..":") - util.printout(data) - util.printout() - return is_mit -end - -local function get_cmod_name(file) - local fd = open_file(file) - if not fd then return nil end - local data = fd:read("*a") - fd:close() - return (data:match("int%s+luaopen_([a-zA-Z0-9_]+)")) -end - -local luamod_blacklist = { - test = true, - tests = true, -} - -local function fill_as_builtin(rockspec, libs) - rockspec.build.type = "builtin" - rockspec.build.modules = {} - local prefix = "" - - for _, parent in ipairs({"src", "lua"}) do - if fs.is_dir(parent) then - fs.change_dir(parent) - prefix = parent.."/" - break - end - end - - local incdirs, libdirs - if libs then - incdirs, libdirs = {}, {} - for _, lib in ipairs(libs) do - local upper = lib:upper() - incdirs[#incdirs+1] = "$("..upper.."_INCDIR)" - libdirs[#libdirs+1] = "$("..upper.."_LIBDIR)" - end - end - - for _, file in ipairs(fs.find()) do - local luamod = file:match("(.*)%.lua$") - if luamod and not luamod_blacklist[luamod] then - rockspec.build.modules[path.path_to_module(file)] = prefix..file - else - local cmod = file:match("(.*)%.c$") - if cmod then - local modname = get_cmod_name(file) or path.path_to_module(file:gsub("%.c$", ".lua")) - rockspec.build.modules[modname] = { - sources = prefix..file, - libraries = libs, - incdirs = incdirs, - libdirs = libdirs, - } - end - end - end - - for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do - if fs.is_dir(directory) then - if not rockspec.build.copy_directories then - rockspec.build.copy_directories = {} - end - table.insert(rockspec.build.copy_directories, directory) - end - end - - if prefix ~= "" then - fs.pop_dir() - end -end - -local function rockspec_cleanup(rockspec) - rockspec.source.file = nil - rockspec.source.protocol = nil - rockspec.variables = nil - rockspec.name = nil - rockspec.format_is_at_least = nil -end - -function write_rockspec.command(flags, name, version, url_or_dir) - if not name then - url_or_dir = "." - elseif not version then - url_or_dir = name - name = nil - elseif not url_or_dir then - url_or_dir = version - version = nil - end - - if flags["tag"] then - if not version then - version = flags["tag"]:gsub("^v", "") - end - end - - local protocol, pathname = dir.split_url(url_or_dir) - if protocol == "file" then - if pathname == "." then - name = name or dir.base_name(fs.current_dir()) - end - elseif fetch.is_basic_protocol(protocol) then - local filename = dir.base_name(url_or_dir) - local newname, newversion = filename:match("(.*)-([^-]+)") - if newname then - name = name or newname - version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "") - end - else - name = name or dir.base_name(url_or_dir):gsub("%.[^.]+$", "") - end - - if not name then - return nil, "Could not infer rock name. "..util.see_help("write_rockspec") - end - version = version or "scm" - - local filename = flags["output"] or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec") - - local rockspec = { - rockspec_format = flags["rockspec-format"], - package = name, - name = name:lower(), - version = version.."-1", - source = { - url = "*** please add URL for source tarball, zip or repository here ***", - tag = flags["tag"], - }, - description = { - summary = flags["summary"] or "*** please specify description summary ***", - detailed = flags["detailed"] or "*** please enter a detailed description ***", - homepage = flags["homepage"] or "*** please enter a project homepage ***", - license = flags["license"] or "*** please specify a license ***", - }, - dependencies = {}, - build = {}, - } - path.configure_paths(rockspec) - rockspec.source.protocol = protocol - rockspec.format_is_at_least = deps.format_is_at_least - - configure_lua_version(rockspec, flags["lua-version"]) - - local local_dir = url_or_dir - - if url_or_dir:match("://") then - rockspec.source.url = url_or_dir - rockspec.source.file = dir.base_name(url_or_dir) - rockspec.source.dir = "dummy" - if not fetch.is_basic_protocol(rockspec.source.protocol) then - if version ~= "scm" then - rockspec.source.tag = flags["tag"] or "v" .. version - end - end - rockspec.source.dir = nil - local ok, base_dir, temp_dir = get_url(rockspec) - if ok then - if base_dir ~= dir.base_name(url_or_dir) then - rockspec.source.dir = base_dir - end - end - if base_dir then - local_dir = dir.path(temp_dir, base_dir) - else - local_dir = nil - end - else - rockspec.source.url = detect_scm_url(local_dir) or rockspec.source.url - end - - if not local_dir then - local_dir = "." - end - - if not flags["homepage"] then - local url_protocol, url_path = dir.split_url(rockspec.source.url) - - if simple_scm_protocols[url_protocol] then - for _, domain in ipairs({"github.com", "bitbucket.org", "gitlab.com"}) do - if util.starts_with(url_path, domain) then - rockspec.description.homepage = "https://"..url_path:gsub("%.git$", "") - break - end - end - end - end - - local libs = nil - if flags["lib"] then - libs = {} - rockspec.external_dependencies = {} - for lib in flags["lib"]:gmatch("([^,]+)") do - table.insert(libs, lib) - rockspec.external_dependencies[lib:upper()] = { - library = lib - } - end - end - - local ok, err = fs.change_dir(local_dir) - if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end - - if (not flags["summary"]) or (not flags["detailed"]) then - local summary, detailed = detect_description() - rockspec.description.summary = flags["summary"] or summary - rockspec.description.detailed = flags["detailed"] or detailed - end - - local is_mit = show_license(rockspec) - - if is_mit and not flags["license"] then - rockspec.description.license = "MIT" - end - - fill_as_builtin(rockspec, libs) - - rockspec_cleanup(rockspec) - - persist.save_from_table(filename, rockspec, type_check.rockspec_order) - - util.printout() - util.printout("Wrote template at "..filename.." -- you should now edit and finish it.") - util.printout() - - return true -end - -return write_rockspec -- cgit v1.2.3-55-g6feb